Repository: developerjet/JetChat Branch: master Commit: 859faaa91272 Files: 2279 Total size: 18.0 MB Directory structure: gitextract_i1lra9d3/ ├── .gitignore ├── JetChat/ │ ├── FY-IMChat/ │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── ChatView/ │ │ │ │ ├── Contents.json │ │ │ │ ├── MessageVideoDownload.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── MessageVideoPlay.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ReceiverImageNodeBorder.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ic_avatar_placeholder.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ic_group_placeholder.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ic_msg_forward_n.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ic_msg_forward_s.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_receiver_background_highlight.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_receiver_background_normal.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_receiver_background_reversed.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_sender_background_highlight.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_sender_background_normal.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_sender_background_reversed.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_voice_receiver_normal.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_voice_receiver_playing_1.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_voice_receiver_playing_2.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_voice_receiver_playing_3.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_voice_sender_normal.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_voice_sender_playing_1.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_voice_sender_playing_2.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── message_voice_sender_playing_3.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── play_btn_normal.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── play_btn_pressed.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── player_back_button.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── player_suspend_button.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Common/ │ │ │ │ ├── Contents.json │ │ │ │ ├── ic_list_selection.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── ic_placeholder.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.launchimage/ │ │ │ │ └── Contents.json │ │ │ ├── Mine/ │ │ │ │ ├── Contents.json │ │ │ │ └── icon_arrow_right.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Moments/ │ │ │ │ ├── Contents.json │ │ │ │ ├── ic_album_reflash.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ic_comment_normal.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ic_comment_selected.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ic_star_normal.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── ic_star_selected.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── nav_camera_black.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── nav_camera_white.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Nav/ │ │ │ │ ├── Contents.json │ │ │ │ ├── icon_more_add.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── nav_back_black.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── nav_back_white.imageset/ │ │ │ │ └── Contents.json │ │ │ └── TabBar/ │ │ │ ├── Contents.json │ │ │ ├── ic_tabbar01_normal.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ic_tabbar01_selected.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ic_tabbar02_normal.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ic_tabbar02_selected.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ic_tabbar03_normal.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ic_tabbar03_selected.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ic_tabbar04_normal.imageset/ │ │ │ │ └── Contents.json │ │ │ └── ic_tabbar04_selected.imageset/ │ │ │ └── Contents.json │ │ ├── Classes/ │ │ │ ├── AppDelegate/ │ │ │ │ ├── AppDelegate+Utils.swift │ │ │ │ ├── AppDelegate+Wcdb.swift │ │ │ │ └── AppDelegate.swift │ │ │ ├── Base/ │ │ │ │ ├── FYBaseIGListViewController.swift │ │ │ │ ├── FYBaseNavigationController.swift │ │ │ │ ├── FYBaseTabBarController.swift │ │ │ │ ├── FYBaseViewController.swift │ │ │ │ ├── Model/ │ │ │ │ │ └── FYCellDataConfig.swift │ │ │ │ └── ViewModel/ │ │ │ │ └── BaseViewModel.swift │ │ │ ├── Common/ │ │ │ │ └── AppCommon.swift │ │ │ ├── Extensions/ │ │ │ │ ├── Array/ │ │ │ │ │ └── Array+Extension.swift │ │ │ │ ├── Dictionary/ │ │ │ │ │ └── Dictionary+Exted.swift │ │ │ │ ├── Notification/ │ │ │ │ │ └── Notification+Name.swift │ │ │ │ ├── RxSwift/ │ │ │ │ │ ├── JFButton+Rx.swift │ │ │ │ │ ├── MJRefresh+Rx.swift │ │ │ │ │ └── Observable+Operators.swift │ │ │ │ ├── SnapKit/ │ │ │ │ │ ├── ConstraintArray+Extensions.swift │ │ │ │ │ └── ConstraintArrayDSL.swift │ │ │ │ ├── Strings/ │ │ │ │ │ ├── String+Date.swift │ │ │ │ │ └── String+Extension.swift │ │ │ │ ├── UIColor/ │ │ │ │ │ └── UIColor+Extension.swift │ │ │ │ ├── UIKit/ │ │ │ │ │ ├── UIAlert+Extension.swift │ │ │ │ │ ├── UIButton+Extension.swift │ │ │ │ │ ├── UIFont+PingFang.swift │ │ │ │ │ ├── UIImage+Extension.swift │ │ │ │ │ ├── UIImageView+Kingfisher.swift │ │ │ │ │ ├── UILabel+Extension.swift │ │ │ │ │ ├── UINavBarItem+Extension.swift │ │ │ │ │ └── UITableView+Extension.swift │ │ │ │ └── UIView/ │ │ │ │ ├── UIView+Extensions.swift │ │ │ │ └── UIViewController+Extend.swift │ │ │ ├── Libraries/ │ │ │ │ ├── ActionSheet/ │ │ │ │ │ ├── FYActionSheet.swift │ │ │ │ │ └── FYActionSheetCell.swift │ │ │ │ ├── HUDProgress/ │ │ │ │ │ ├── HUDAssets.bundle/ │ │ │ │ │ │ └── Info.plist │ │ │ │ │ └── MBConfiguredHUD.swift │ │ │ │ ├── NavPopup/ │ │ │ │ │ ├── Cell/ │ │ │ │ │ │ └── FYNavDropMenuCell.swift │ │ │ │ │ └── FYNavPopuListMenu.swift │ │ │ │ └── Refreshing/ │ │ │ │ └── FYMomentsHeaderRefresh.swift │ │ │ ├── MainModule/ │ │ │ │ ├── ChatRoom/ │ │ │ │ │ ├── Controller/ │ │ │ │ │ │ ├── FYChatBaseViewController.swift │ │ │ │ │ │ ├── FYChatRoomListViewController.swift │ │ │ │ │ │ └── FYMessageForwardViewController.swift │ │ │ │ │ ├── Model/ │ │ │ │ │ │ ├── FYMessageBaseModel.swift │ │ │ │ │ │ ├── FYMessageChatModel.swift │ │ │ │ │ │ └── FYMessageItem.swift │ │ │ │ │ ├── View/ │ │ │ │ │ │ ├── FYImageMessageCell.swift │ │ │ │ │ │ ├── FYMessageBaseCell.swift │ │ │ │ │ │ ├── FYTextMessageCell.swift │ │ │ │ │ │ └── FYVideoMessageCell.swift │ │ │ │ │ └── ViewModel/ │ │ │ │ │ └── FYMessageViewModel.swift │ │ │ │ ├── Contacts/ │ │ │ │ │ ├── Controller/ │ │ │ │ │ │ ├── FYContactsInfoViewController.swift │ │ │ │ │ │ ├── FYContactsListViewController.swift │ │ │ │ │ │ └── FYEditChatInfoViewController.swift │ │ │ │ │ └── View/ │ │ │ │ │ ├── FYContactsInfoView.swift │ │ │ │ │ └── FYContactsTableViewCell.swift │ │ │ │ ├── Conversation/ │ │ │ │ │ ├── Controller/ │ │ │ │ │ │ └── FYSesstionListViewController.swift │ │ │ │ │ └── View/ │ │ │ │ │ └── FYConversationCell.swift │ │ │ │ ├── Mine/ │ │ │ │ │ ├── Controller/ │ │ │ │ │ │ ├── FYMineViewController.swift │ │ │ │ │ │ ├── FYSettingViewController.swift │ │ │ │ │ │ └── FYThemeSelectionListVC.swift │ │ │ │ │ └── View/ │ │ │ │ │ └── FYFastGridListView.swift │ │ │ │ ├── Moments/ │ │ │ │ │ ├── FYMomentsViewController.swift │ │ │ │ │ ├── JSONData/ │ │ │ │ │ │ ├── moments1.json │ │ │ │ │ │ └── moments2.json │ │ │ │ │ ├── Model/ │ │ │ │ │ │ ├── FYCommentInfo.swift │ │ │ │ │ │ ├── FYMoUserInfo.swift │ │ │ │ │ │ └── FYMomentInfo.swift │ │ │ │ │ ├── Sections/ │ │ │ │ │ │ └── FYMomentBindingSection.swift │ │ │ │ │ └── View/ │ │ │ │ │ ├── Cell/ │ │ │ │ │ │ ├── CommentContentCell.swift │ │ │ │ │ │ ├── CommentContentView.swift │ │ │ │ │ │ ├── CommentInputView.swift │ │ │ │ │ │ ├── CommentThumbView.swift │ │ │ │ │ │ ├── MomentBottomCell.swift │ │ │ │ │ │ ├── MomentCommentCell.swift │ │ │ │ │ │ ├── MomentHeaderCell.swift │ │ │ │ │ │ ├── MomentHeaderImageCell.swift │ │ │ │ │ │ ├── MomentLocationCell.swift │ │ │ │ │ │ └── MomentTopCell.swift │ │ │ │ │ ├── FYMomentNavBar.swift │ │ │ │ │ ├── MomentLabel/ │ │ │ │ │ │ ├── FYLabel.swift │ │ │ │ │ │ └── FYLabelType.swift │ │ │ │ │ ├── NineImageView.swift │ │ │ │ │ ├── OperateMenuView.swift │ │ │ │ │ └── TextView/ │ │ │ │ │ └── FYTextView.swift │ │ │ │ └── QrScan/ │ │ │ │ └── ScanQRCodeViewController.swift │ │ │ ├── Resource/ │ │ │ │ ├── Languages/ │ │ │ │ │ ├── en.lproj/ │ │ │ │ │ │ ├── InfoPlist.strings │ │ │ │ │ │ └── Localizable.strings │ │ │ │ │ └── zh-Hans.lproj/ │ │ │ │ │ ├── InfoPlist.strings │ │ │ │ │ ├── LWLocalizations.strings │ │ │ │ │ └── Localizable.strings │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── R.generated.swift │ │ │ ├── Thirdparty/ │ │ │ │ ├── BottomPopupController/ │ │ │ │ │ ├── BottomPopupDismissAnimator.swift │ │ │ │ │ ├── BottomPopupDismissInteractionController.swift │ │ │ │ │ ├── BottomPopupNavigationController.swift │ │ │ │ │ ├── BottomPopupPresentAnimator.swift │ │ │ │ │ ├── BottomPopupPresentationController.swift │ │ │ │ │ ├── BottomPopupTransitionHandler.swift │ │ │ │ │ ├── BottomPopupUtils.swift │ │ │ │ │ ├── BottomPopupViewController.swift │ │ │ │ │ └── CSBottomPopupNavigationController.swift │ │ │ │ ├── NavigationHandy/ │ │ │ │ │ ├── NSObject+BinAdd.h │ │ │ │ │ ├── NSObject+BinAdd.m │ │ │ │ │ ├── UINavigationController+Extensions.h │ │ │ │ │ └── UINavigationController+Extensions.m │ │ │ │ ├── RxActivityIndicator/ │ │ │ │ │ └── ActivityIndicator.swift │ │ │ │ └── RxErrorTracker/ │ │ │ │ └── ErrorTracker.swift │ │ │ └── Utilites/ │ │ │ ├── DataBase/ │ │ │ │ ├── WCDBManager/ │ │ │ │ │ └── DBQuery/ │ │ │ │ │ ├── FYDBQueryHelper.swift │ │ │ │ │ └── FYMessageUserModel.swift │ │ │ │ ├── WCDataBaseManager.swift │ │ │ │ └── WCDataBaseTable.swift │ │ │ ├── FPSLabel/ │ │ │ │ ├── FPSLabel.swift │ │ │ │ ├── WeakProxy.h │ │ │ │ └── WeakProxy.m │ │ │ ├── FileSizeManager/ │ │ │ │ ├── FYFileSizeManager.swift │ │ │ │ └── FYUserDefaultManager.swift │ │ │ ├── Helpers/ │ │ │ │ ├── CountDownHandy.swift │ │ │ │ └── LanguageManager.swift │ │ │ ├── KeyboardCore/ │ │ │ │ ├── Cell/ │ │ │ │ │ ├── ChatAppleEmojiCell.swift │ │ │ │ │ ├── ChatKeyboardFlowLayout.swift │ │ │ │ │ └── ChatMoreMenuCell.swift │ │ │ │ ├── ChatEmojiListView.swift │ │ │ │ ├── ChatGrowingTextView.swift │ │ │ │ ├── ChatKeyboard+Extensions.swift │ │ │ │ ├── ChatKeyboardView.swift │ │ │ │ ├── ChatMoreMenuView.swift │ │ │ │ ├── Helper/ │ │ │ │ │ ├── ChatEmotionAttachment.swift │ │ │ │ │ ├── ChatEmotionHelper.swift │ │ │ │ │ └── ChatFindEmotion.swift │ │ │ │ ├── Model/ │ │ │ │ │ ├── ChatEmoticon.swift │ │ │ │ │ └── ChatMoreMnueConfig.swift │ │ │ │ └── Resource/ │ │ │ │ ├── Emoji/ │ │ │ │ │ └── Emoticons.bundle/ │ │ │ │ │ └── com.apple.emoji/ │ │ │ │ │ └── info.plist │ │ │ │ └── Emotion/ │ │ │ │ └── Expression.plist │ │ │ └── Theme/ │ │ │ ├── FYDarkTheme.swift │ │ │ ├── FYLightTheme.swift │ │ │ ├── FYThemeCenter.swift │ │ │ └── FYThemeColors.swift │ │ ├── FY-IMChat.entitlements │ │ ├── FYObjcBridge.h │ │ └── Info.plist │ ├── FY-IMChat.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ └── FY-IMChat.xcscheme │ ├── FY-IMChat.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ ├── FY-IMChatTests/ │ │ ├── FY_IMChatTests.swift │ │ └── Info.plist │ ├── FY-IMChatUITests/ │ │ ├── FY_IMChatUITests.swift │ │ └── Info.plist │ ├── JetChatWidget/ │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── WidgetBackground.colorset/ │ │ │ │ └── Contents.json │ │ │ └── icon_widget_bg.imageset/ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── JetChatWidget.intentdefinition │ │ └── JetChatWidget.swift │ ├── JetChatWidgetExtension.entitlements │ ├── Podfile │ └── Pods/ │ ├── Alamofire/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Source/ │ │ ├── AFError.swift │ │ ├── Alamofire.swift │ │ ├── AlamofireExtended.swift │ │ ├── AuthenticationInterceptor.swift │ │ ├── CachedResponseHandler.swift │ │ ├── Combine.swift │ │ ├── Concurrency.swift │ │ ├── DispatchQueue+Alamofire.swift │ │ ├── EventMonitor.swift │ │ ├── HTTPHeaders.swift │ │ ├── HTTPMethod.swift │ │ ├── MultipartFormData.swift │ │ ├── MultipartUpload.swift │ │ ├── NetworkReachabilityManager.swift │ │ ├── Notifications.swift │ │ ├── OperationQueue+Alamofire.swift │ │ ├── ParameterEncoder.swift │ │ ├── ParameterEncoding.swift │ │ ├── Protected.swift │ │ ├── RedirectHandler.swift │ │ ├── Request.swift │ │ ├── RequestInterceptor.swift │ │ ├── RequestTaskMap.swift │ │ ├── Response.swift │ │ ├── ResponseSerialization.swift │ │ ├── Result+Alamofire.swift │ │ ├── RetryPolicy.swift │ │ ├── ServerTrustEvaluation.swift │ │ ├── Session.swift │ │ ├── SessionDelegate.swift │ │ ├── StringEncoding+Alamofire.swift │ │ ├── URLConvertible+URLRequestConvertible.swift │ │ ├── URLEncodedFormEncoder.swift │ │ ├── URLRequest+Alamofire.swift │ │ ├── URLSessionConfiguration+Alamofire.swift │ │ └── Validation.swift │ ├── Alamofire.xcodeproj/ │ │ └── project.pbxproj │ ├── FDFullscreenPopGesture/ │ │ ├── FDFullscreenPopGesture/ │ │ │ ├── UINavigationController+FDFullscreenPopGesture.h │ │ │ └── UINavigationController+FDFullscreenPopGesture.m │ │ ├── LICENSE │ │ └── README.md │ ├── FDFullscreenPopGesture.xcodeproj/ │ │ └── project.pbxproj │ ├── HandyJSON/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Source/ │ │ ├── AnyExtensions.swift │ │ ├── BuiltInBasicType.swift │ │ ├── BuiltInBridgeType.swift │ │ ├── CBridge.swift │ │ ├── Configuration.swift │ │ ├── ContextDescriptorType.swift │ │ ├── CustomDateFormatTransform.swift │ │ ├── DataTransform.swift │ │ ├── DateFormatterTransform.swift │ │ ├── DateTransform.swift │ │ ├── Deserializer.swift │ │ ├── EnumTransform.swift │ │ ├── EnumType.swift │ │ ├── Export.swift │ │ ├── ExtendCustomBasicType.swift │ │ ├── ExtendCustomModelType.swift │ │ ├── FieldDescriptor.swift │ │ ├── HandyJSON.h │ │ ├── HelpingMapper.swift │ │ ├── HexColorTransform.swift │ │ ├── ISO8601DateTransform.swift │ │ ├── Logger.swift │ │ ├── MangledName.swift │ │ ├── Measuable.swift │ │ ├── Metadata.swift │ │ ├── NSDecimalNumberTransform.swift │ │ ├── OtherExtension.swift │ │ ├── PointerType.swift │ │ ├── Properties.swift │ │ ├── PropertyInfo.swift │ │ ├── ReflectionHelper.swift │ │ ├── Serializer.swift │ │ ├── TransformOf.swift │ │ ├── TransformType.swift │ │ ├── Transformable.swift │ │ └── URLTransform.swift │ ├── HandyJSON.xcodeproj/ │ │ └── project.pbxproj │ ├── IGListDiffKit/ │ │ ├── LICENSE.md │ │ ├── README.md │ │ └── Source/ │ │ └── IGListDiffKit/ │ │ ├── IGListAssert.h │ │ ├── IGListBatchUpdateData.h │ │ ├── IGListBatchUpdateData.mm │ │ ├── IGListCompatibility.h │ │ ├── IGListDiff.h │ │ ├── IGListDiff.mm │ │ ├── IGListDiffKit.h │ │ ├── IGListDiffable.h │ │ ├── IGListExperiments.h │ │ ├── IGListIndexPathResult.h │ │ ├── IGListIndexPathResult.m │ │ ├── IGListIndexSetResult.h │ │ ├── IGListIndexSetResult.m │ │ ├── IGListMacros.h │ │ ├── IGListMoveIndex.h │ │ ├── IGListMoveIndex.m │ │ ├── IGListMoveIndexPath.h │ │ ├── IGListMoveIndexPath.m │ │ ├── Internal/ │ │ │ ├── IGListIndexPathResultInternal.h │ │ │ ├── IGListIndexSetResultInternal.h │ │ │ ├── IGListMoveIndexInternal.h │ │ │ └── IGListMoveIndexPathInternal.h │ │ ├── NSNumber+IGListDiffable.h │ │ ├── NSNumber+IGListDiffable.m │ │ ├── NSString+IGListDiffable.h │ │ └── NSString+IGListDiffable.m │ ├── IGListDiffKit.xcodeproj/ │ │ └── project.pbxproj │ ├── IGListKit/ │ │ ├── LICENSE.md │ │ ├── README.md │ │ └── Source/ │ │ ├── IGListDiffKit/ │ │ │ └── Internal/ │ │ │ ├── IGListIndexPathResultInternal.h │ │ │ ├── IGListIndexSetResultInternal.h │ │ │ ├── IGListMoveIndexInternal.h │ │ │ └── IGListMoveIndexPathInternal.h │ │ └── IGListKit/ │ │ ├── IGListAdapter.h │ │ ├── IGListAdapter.m │ │ ├── IGListAdapterDataSource.h │ │ ├── IGListAdapterDelegate.h │ │ ├── IGListAdapterMoveDelegate.h │ │ ├── IGListAdapterPerformanceDelegate.h │ │ ├── IGListAdapterUpdateListener.h │ │ ├── IGListAdapterUpdater.h │ │ ├── IGListAdapterUpdater.m │ │ ├── IGListAdapterUpdaterDelegate.h │ │ ├── IGListBatchContext.h │ │ ├── IGListBindable.h │ │ ├── IGListBindingSectionController.h │ │ ├── IGListBindingSectionController.m │ │ ├── IGListBindingSectionControllerDataSource.h │ │ ├── IGListBindingSectionControllerSelectionDelegate.h │ │ ├── IGListCollectionContext.h │ │ ├── IGListCollectionScrollingTraits.h │ │ ├── IGListCollectionView.h │ │ ├── IGListCollectionView.m │ │ ├── IGListCollectionViewDelegateLayout.h │ │ ├── IGListCollectionViewLayout.h │ │ ├── IGListCollectionViewLayout.mm │ │ ├── IGListCollectionViewLayoutCompatible.h │ │ ├── IGListDisplayDelegate.h │ │ ├── IGListGenericSectionController.h │ │ ├── IGListGenericSectionController.m │ │ ├── IGListKit.h │ │ ├── IGListReloadDataUpdater.h │ │ ├── IGListReloadDataUpdater.m │ │ ├── IGListScrollDelegate.h │ │ ├── IGListSectionController.h │ │ ├── IGListSectionController.m │ │ ├── IGListSingleSectionController.h │ │ ├── IGListSingleSectionController.m │ │ ├── IGListSupplementaryViewSource.h │ │ ├── IGListTransitionDelegate.h │ │ ├── IGListUpdatingDelegate.h │ │ ├── IGListWorkingRangeDelegate.h │ │ └── Internal/ │ │ ├── IGListAdapter+DebugDescription.h │ │ ├── IGListAdapter+DebugDescription.m │ │ ├── IGListAdapter+UICollectionView.h │ │ ├── IGListAdapter+UICollectionView.m │ │ ├── IGListAdapterInternal.h │ │ ├── IGListAdapterProxy.h │ │ ├── IGListAdapterProxy.m │ │ ├── IGListAdapterUpdater+DebugDescription.h │ │ ├── IGListAdapterUpdater+DebugDescription.m │ │ ├── IGListAdapterUpdaterInternal.h │ │ ├── IGListArrayUtilsInternal.h │ │ ├── IGListBatchUpdateData+DebugDescription.h │ │ ├── IGListBatchUpdateData+DebugDescription.m │ │ ├── IGListBatchUpdateState.h │ │ ├── IGListBatchUpdates.h │ │ ├── IGListBatchUpdates.m │ │ ├── IGListBindingSectionController+DebugDescription.h │ │ ├── IGListBindingSectionController+DebugDescription.m │ │ ├── IGListCollectionViewLayoutInternal.h │ │ ├── IGListDebugger.h │ │ ├── IGListDebugger.m │ │ ├── IGListDebuggingUtilities.h │ │ ├── IGListDebuggingUtilities.m │ │ ├── IGListDisplayHandler.h │ │ ├── IGListDisplayHandler.m │ │ ├── IGListReloadIndexPath.h │ │ ├── IGListReloadIndexPath.m │ │ ├── IGListSectionControllerInternal.h │ │ ├── IGListSectionMap+DebugDescription.h │ │ ├── IGListSectionMap+DebugDescription.m │ │ ├── IGListSectionMap.h │ │ ├── IGListSectionMap.m │ │ ├── IGListWorkingRangeHandler.h │ │ ├── IGListWorkingRangeHandler.mm │ │ ├── UICollectionView+DebugDescription.h │ │ ├── UICollectionView+DebugDescription.m │ │ ├── UICollectionView+IGListBatchUpdateData.h │ │ ├── UICollectionView+IGListBatchUpdateData.m │ │ ├── UICollectionViewLayout+InteractiveReordering.h │ │ ├── UICollectionViewLayout+InteractiveReordering.m │ │ ├── UIScrollView+IGListKit.h │ │ └── UIScrollView+IGListKit.m │ ├── IGListKit.xcodeproj/ │ │ └── project.pbxproj │ ├── IQKeyboardManagerSwift/ │ │ ├── IQKeyboardManagerSwift/ │ │ │ ├── Categories/ │ │ │ │ ├── IQNSArray+Sort.swift │ │ │ │ ├── IQUIScrollView+Additions.swift │ │ │ │ ├── IQUITextFieldView+Additions.swift │ │ │ │ ├── IQUIView+Hierarchy.swift │ │ │ │ └── IQUIViewController+Additions.swift │ │ │ ├── Constants/ │ │ │ │ ├── IQKeyboardManagerConstants.swift │ │ │ │ └── IQKeyboardManagerConstantsInternal.swift │ │ │ ├── IQKeyboardManager+Debug.swift │ │ │ ├── IQKeyboardManager+Internal.swift │ │ │ ├── IQKeyboardManager+OrientationNotification.swift │ │ │ ├── IQKeyboardManager+Position.swift │ │ │ ├── IQKeyboardManager+Toolbar.swift │ │ │ ├── IQKeyboardManager+UIKeyboardNotification.swift │ │ │ ├── IQKeyboardManager+UITextFieldViewNotification.swift │ │ │ ├── IQKeyboardManager.swift │ │ │ ├── IQKeyboardReturnKeyHandler.swift │ │ │ ├── IQTextView/ │ │ │ │ └── IQTextView.swift │ │ │ └── IQToolbar/ │ │ │ ├── IQBarButtonItem.swift │ │ │ ├── IQInvocation.swift │ │ │ ├── IQPreviousNextView.swift │ │ │ ├── IQTitleBarButtonItem.swift │ │ │ ├── IQToolbar.swift │ │ │ └── IQUIView+IQKeyboardToolbar.swift │ │ ├── LICENSE.md │ │ └── README.md │ ├── IQKeyboardManagerSwift.xcodeproj/ │ │ └── project.pbxproj │ ├── Kingfisher/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Sources/ │ │ ├── Cache/ │ │ │ ├── CacheSerializer.swift │ │ │ ├── DiskStorage.swift │ │ │ ├── FormatIndicatedCacheSerializer.swift │ │ │ ├── ImageCache.swift │ │ │ ├── MemoryStorage.swift │ │ │ └── Storage.swift │ │ ├── Extensions/ │ │ │ ├── ImageView+Kingfisher.swift │ │ │ ├── NSButton+Kingfisher.swift │ │ │ ├── NSTextAttachment+Kingfisher.swift │ │ │ ├── TVMonogramView+Kingfisher.swift │ │ │ ├── UIButton+Kingfisher.swift │ │ │ └── WKInterfaceImage+Kingfisher.swift │ │ ├── General/ │ │ │ ├── ImageSource/ │ │ │ │ ├── AVAssetImageDataProvider.swift │ │ │ │ ├── ImageDataProvider.swift │ │ │ │ ├── Resource.swift │ │ │ │ └── Source.swift │ │ │ ├── KF.swift │ │ │ ├── KFOptionsSetter.swift │ │ │ ├── Kingfisher.swift │ │ │ ├── KingfisherError.swift │ │ │ ├── KingfisherManager.swift │ │ │ └── KingfisherOptionsInfo.swift │ │ ├── Image/ │ │ │ ├── Filter.swift │ │ │ ├── GIFAnimatedImage.swift │ │ │ ├── GraphicsContext.swift │ │ │ ├── Image.swift │ │ │ ├── ImageDrawing.swift │ │ │ ├── ImageFormat.swift │ │ │ ├── ImageProcessor.swift │ │ │ ├── ImageProgressive.swift │ │ │ ├── ImageTransition.swift │ │ │ └── Placeholder.swift │ │ ├── Networking/ │ │ │ ├── AuthenticationChallengeResponsable.swift │ │ │ ├── ImageDataProcessor.swift │ │ │ ├── ImageDownloader.swift │ │ │ ├── ImageDownloaderDelegate.swift │ │ │ ├── ImageModifier.swift │ │ │ ├── ImagePrefetcher.swift │ │ │ ├── RedirectHandler.swift │ │ │ ├── RequestModifier.swift │ │ │ ├── RetryStrategy.swift │ │ │ ├── SessionDataTask.swift │ │ │ └── SessionDelegate.swift │ │ ├── SwiftUI/ │ │ │ ├── ImageBinder.swift │ │ │ ├── KFImage.swift │ │ │ └── KFImageOptions.swift │ │ ├── Utility/ │ │ │ ├── Box.swift │ │ │ ├── CallbackQueue.swift │ │ │ ├── Delegate.swift │ │ │ ├── ExtensionHelpers.swift │ │ │ ├── Result.swift │ │ │ ├── Runtime.swift │ │ │ ├── SizeExtensions.swift │ │ │ └── String+MD5.swift │ │ └── Views/ │ │ ├── AnimatedImageView.swift │ │ └── Indicator.swift │ ├── Kingfisher.xcodeproj/ │ │ └── project.pbxproj │ ├── Localize-Swift/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Sources/ │ │ ├── Localize.swift │ │ ├── Localize_Swift.h │ │ ├── String+LocalizeBundle.swift │ │ ├── String+LocalizeTableName.swift │ │ ├── String+LocalizedBundleTableName.swift │ │ └── UI/ │ │ └── IBDesignable+Localize.swift │ ├── Localize-Swift.xcodeproj/ │ │ └── project.pbxproj │ ├── LookinServer/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Src/ │ │ ├── Server/ │ │ │ ├── Category/ │ │ │ │ ├── CALayer+LookinServer.h │ │ │ │ ├── CALayer+LookinServer.m │ │ │ │ ├── NSObject+LookinServer.h │ │ │ │ ├── NSObject+LookinServer.m │ │ │ │ ├── UIBlurEffect+LookinServer.h │ │ │ │ ├── UIBlurEffect+LookinServer.m │ │ │ │ ├── UIColor+LookinServer.h │ │ │ │ ├── UIColor+LookinServer.m │ │ │ │ ├── UIGestureRecognizer+LookinServer.h │ │ │ │ ├── UIGestureRecognizer+LookinServer.m │ │ │ │ ├── UIImage+LookinServer.h │ │ │ │ ├── UIImage+LookinServer.m │ │ │ │ ├── UIImageView+LookinServer.h │ │ │ │ ├── UIImageView+LookinServer.m │ │ │ │ ├── UILabel+LookinServer.h │ │ │ │ ├── UILabel+LookinServer.m │ │ │ │ ├── UITableView+LookinServer.h │ │ │ │ ├── UITableView+LookinServer.m │ │ │ │ ├── UITextField+LookinServer.h │ │ │ │ ├── UITextField+LookinServer.m │ │ │ │ ├── UITextView+LookinServer.h │ │ │ │ ├── UITextView+LookinServer.m │ │ │ │ ├── UIView+LookinServer.h │ │ │ │ ├── UIView+LookinServer.m │ │ │ │ ├── UIViewController+LookinServer.h │ │ │ │ ├── UIViewController+LookinServer.m │ │ │ │ ├── UIVisualEffectView+LookinServer.h │ │ │ │ └── UIVisualEffectView+LookinServer.m │ │ │ ├── Connection/ │ │ │ │ ├── LKS_ConnectionManager.h │ │ │ │ ├── LKS_ConnectionManager.m │ │ │ │ ├── LKS_RequestHandler.h │ │ │ │ ├── LKS_RequestHandler.m │ │ │ │ └── RequestHandler/ │ │ │ │ ├── LKS_AttrModificationHandler.h │ │ │ │ ├── LKS_AttrModificationHandler.m │ │ │ │ ├── LKS_AttrModificationPatchHandler.h │ │ │ │ ├── LKS_AttrModificationPatchHandler.m │ │ │ │ ├── LKS_HierarchyDetailsHandler.h │ │ │ │ └── LKS_HierarchyDetailsHandler.m │ │ │ ├── Inspect/ │ │ │ │ ├── LKS_LocalInspectManager.h │ │ │ │ ├── LKS_LocalInspectManager.m │ │ │ │ ├── LKS_LocalInspectPanelLabelView.h │ │ │ │ ├── LKS_LocalInspectPanelLabelView.m │ │ │ │ ├── LKS_LocalInspectViewController.h │ │ │ │ └── LKS_LocalInspectViewController.m │ │ │ ├── LookinServer.h │ │ │ ├── Others/ │ │ │ │ ├── LKS_AttrGroupsMaker.h │ │ │ │ ├── LKS_AttrGroupsMaker.m │ │ │ │ ├── LKS_EventHandlerMaker.h │ │ │ │ ├── LKS_EventHandlerMaker.m │ │ │ │ ├── LKS_ExportManager.h │ │ │ │ ├── LKS_ExportManager.m │ │ │ │ ├── LKS_Helper.h │ │ │ │ ├── LKS_Helper.m │ │ │ │ ├── LKS_HierarchyDisplayItemsMaker.h │ │ │ │ ├── LKS_HierarchyDisplayItemsMaker.m │ │ │ │ ├── LKS_MethodTraceManager.h │ │ │ │ ├── LKS_MethodTraceManager.m │ │ │ │ ├── LKS_ObjectRegistry.h │ │ │ │ ├── LKS_ObjectRegistry.m │ │ │ │ ├── LKS_TraceManager.h │ │ │ │ ├── LKS_TraceManager.m │ │ │ │ └── LookinServerDefines.h │ │ │ └── Perspective/ │ │ │ ├── LKS_PerspectiveDataSource.h │ │ │ ├── LKS_PerspectiveDataSource.m │ │ │ ├── LKS_PerspectiveHierarchyCell.h │ │ │ ├── LKS_PerspectiveHierarchyCell.m │ │ │ ├── LKS_PerspectiveHierarchyView.h │ │ │ ├── LKS_PerspectiveHierarchyView.m │ │ │ ├── LKS_PerspectiveItemLayer.h │ │ │ ├── LKS_PerspectiveItemLayer.m │ │ │ ├── LKS_PerspectiveLayer.h │ │ │ ├── LKS_PerspectiveLayer.m │ │ │ ├── LKS_PerspectiveManager.h │ │ │ ├── LKS_PerspectiveManager.m │ │ │ ├── LKS_PerspectiveToolbarButtons.h │ │ │ ├── LKS_PerspectiveToolbarButtons.m │ │ │ ├── LKS_PerspectiveViewController.h │ │ │ └── LKS_PerspectiveViewController.m │ │ └── Shared/ │ │ ├── Category/ │ │ │ ├── CALayer+Lookin.h │ │ │ ├── CALayer+Lookin.m │ │ │ ├── NSArray+Lookin.h │ │ │ ├── NSArray+Lookin.m │ │ │ ├── NSObject+Lookin.h │ │ │ ├── NSObject+Lookin.m │ │ │ ├── NSSet+Lookin.h │ │ │ ├── NSSet+Lookin.m │ │ │ ├── NSString+Lookin.h │ │ │ └── NSString+Lookin.m │ │ ├── LookinAppInfo.h │ │ ├── LookinAppInfo.m │ │ ├── LookinAttrIdentifiers.h │ │ ├── LookinAttrIdentifiers.m │ │ ├── LookinAttrType.h │ │ ├── LookinAttribute.h │ │ ├── LookinAttribute.m │ │ ├── LookinAttributeModification.h │ │ ├── LookinAttributeModification.m │ │ ├── LookinAttributesGroup.h │ │ ├── LookinAttributesGroup.m │ │ ├── LookinAttributesSection.h │ │ ├── LookinAttributesSection.m │ │ ├── LookinAutoLayoutConstraint.h │ │ ├── LookinAutoLayoutConstraint.m │ │ ├── LookinCodingValueType.h │ │ ├── LookinConnectionAttachment.h │ │ ├── LookinConnectionAttachment.m │ │ ├── LookinConnectionResponseAttachment.h │ │ ├── LookinConnectionResponseAttachment.m │ │ ├── LookinDashboardBlueprint.h │ │ ├── LookinDashboardBlueprint.m │ │ ├── LookinDefines.h │ │ ├── LookinDisplayItem.h │ │ ├── LookinDisplayItem.m │ │ ├── LookinDisplayItemDetail.h │ │ ├── LookinDisplayItemDetail.m │ │ ├── LookinEventHandler.h │ │ ├── LookinEventHandler.m │ │ ├── LookinHierarchyFile.h │ │ ├── LookinHierarchyFile.m │ │ ├── LookinHierarchyInfo.h │ │ ├── LookinHierarchyInfo.m │ │ ├── LookinIvarTrace.h │ │ ├── LookinIvarTrace.m │ │ ├── LookinMethodTraceRecord.h │ │ ├── LookinMethodTraceRecord.m │ │ ├── LookinObject.h │ │ ├── LookinObject.m │ │ ├── LookinScreenshotFetchManager.h │ │ ├── LookinStaticAsyncUpdateTask.h │ │ ├── LookinStaticAsyncUpdateTask.m │ │ ├── LookinTuple.h │ │ ├── LookinTuple.m │ │ ├── LookinWeakContainer.h │ │ ├── LookinWeakContainer.m │ │ ├── Message/ │ │ │ ├── LookinMsgAttribute.h │ │ │ ├── LookinMsgAttribute.m │ │ │ ├── LookinMsgTargetAction.h │ │ │ └── LookinMsgTargetAction.m │ │ └── Peertalk/ │ │ ├── Lookin_PTChannel.h │ │ ├── Lookin_PTChannel.m │ │ ├── Lookin_PTPrivate.h │ │ ├── Lookin_PTProtocol.h │ │ ├── Lookin_PTProtocol.m │ │ ├── Lookin_PTUSBHub.h │ │ ├── Lookin_PTUSBHub.m │ │ └── Peertalk.h │ ├── LookinServer.xcodeproj/ │ │ └── project.pbxproj │ ├── MBProgressHUD/ │ │ ├── LICENSE │ │ ├── MBProgressHUD.h │ │ ├── MBProgressHUD.m │ │ └── README.mdown │ ├── MBProgressHUD.xcodeproj/ │ │ └── project.pbxproj │ ├── MJRefresh/ │ │ ├── LICENSE │ │ ├── MJRefresh/ │ │ │ ├── Base/ │ │ │ │ ├── MJRefreshAutoFooter.h │ │ │ │ ├── MJRefreshAutoFooter.m │ │ │ │ ├── MJRefreshBackFooter.h │ │ │ │ ├── MJRefreshBackFooter.m │ │ │ │ ├── MJRefreshComponent.h │ │ │ │ ├── MJRefreshComponent.m │ │ │ │ ├── MJRefreshFooter.h │ │ │ │ ├── MJRefreshFooter.m │ │ │ │ ├── MJRefreshHeader.h │ │ │ │ ├── MJRefreshHeader.m │ │ │ │ ├── MJRefreshTrailer.h │ │ │ │ └── MJRefreshTrailer.m │ │ │ ├── Custom/ │ │ │ │ ├── Footer/ │ │ │ │ │ ├── Auto/ │ │ │ │ │ │ ├── MJRefreshAutoGifFooter.h │ │ │ │ │ │ ├── MJRefreshAutoGifFooter.m │ │ │ │ │ │ ├── MJRefreshAutoNormalFooter.h │ │ │ │ │ │ ├── MJRefreshAutoNormalFooter.m │ │ │ │ │ │ ├── MJRefreshAutoStateFooter.h │ │ │ │ │ │ └── MJRefreshAutoStateFooter.m │ │ │ │ │ └── Back/ │ │ │ │ │ ├── MJRefreshBackGifFooter.h │ │ │ │ │ ├── MJRefreshBackGifFooter.m │ │ │ │ │ ├── MJRefreshBackNormalFooter.h │ │ │ │ │ ├── MJRefreshBackNormalFooter.m │ │ │ │ │ ├── MJRefreshBackStateFooter.h │ │ │ │ │ └── MJRefreshBackStateFooter.m │ │ │ │ ├── Header/ │ │ │ │ │ ├── MJRefreshGifHeader.h │ │ │ │ │ ├── MJRefreshGifHeader.m │ │ │ │ │ ├── MJRefreshNormalHeader.h │ │ │ │ │ ├── MJRefreshNormalHeader.m │ │ │ │ │ ├── MJRefreshStateHeader.h │ │ │ │ │ └── MJRefreshStateHeader.m │ │ │ │ └── Trailer/ │ │ │ │ ├── MJRefreshNormalTrailer.h │ │ │ │ ├── MJRefreshNormalTrailer.m │ │ │ │ ├── MJRefreshStateTrailer.h │ │ │ │ └── MJRefreshStateTrailer.m │ │ │ ├── MJRefresh.bundle/ │ │ │ │ ├── en.lproj/ │ │ │ │ │ └── Localizable.strings │ │ │ │ ├── ko.lproj/ │ │ │ │ │ └── Localizable.strings │ │ │ │ ├── ru.lproj/ │ │ │ │ │ └── Localizable.strings │ │ │ │ ├── uk.lproj/ │ │ │ │ │ └── Localizable.strings │ │ │ │ ├── zh-Hans.lproj/ │ │ │ │ │ └── Localizable.strings │ │ │ │ └── zh-Hant.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── MJRefresh.h │ │ │ ├── MJRefreshConfig.h │ │ │ ├── MJRefreshConfig.m │ │ │ ├── MJRefreshConst.h │ │ │ ├── MJRefreshConst.m │ │ │ ├── NSBundle+MJRefresh.h │ │ │ ├── NSBundle+MJRefresh.m │ │ │ ├── UICollectionViewLayout+MJRefresh.h │ │ │ ├── UICollectionViewLayout+MJRefresh.m │ │ │ ├── UIScrollView+MJExtension.h │ │ │ ├── UIScrollView+MJExtension.m │ │ │ ├── UIScrollView+MJRefresh.h │ │ │ ├── UIScrollView+MJRefresh.m │ │ │ ├── UIView+MJExtension.h │ │ │ └── UIView+MJExtension.m │ │ └── README.md │ ├── MJRefresh.xcodeproj/ │ │ └── project.pbxproj │ ├── Moya/ │ │ ├── License.md │ │ ├── Readme.md │ │ └── Sources/ │ │ ├── Moya/ │ │ │ ├── AnyEncodable.swift │ │ │ ├── Atomic.swift │ │ │ ├── Cancellable.swift │ │ │ ├── Endpoint.swift │ │ │ ├── Image.swift │ │ │ ├── Moya+Alamofire.swift │ │ │ ├── MoyaError.swift │ │ │ ├── MoyaProvider+Defaults.swift │ │ │ ├── MoyaProvider+Internal.swift │ │ │ ├── MoyaProvider.swift │ │ │ ├── MultiTarget.swift │ │ │ ├── MultipartFormData.swift │ │ │ ├── Plugin.swift │ │ │ ├── Plugins/ │ │ │ │ ├── AccessTokenPlugin.swift │ │ │ │ ├── CredentialsPlugin.swift │ │ │ │ ├── NetworkActivityPlugin.swift │ │ │ │ └── NetworkLoggerPlugin.swift │ │ │ ├── RequestTypeWrapper.swift │ │ │ ├── Response.swift │ │ │ ├── TargetType.swift │ │ │ ├── Task.swift │ │ │ ├── URL+Moya.swift │ │ │ ├── URLRequest+Encoding.swift │ │ │ └── ValidationType.swift │ │ └── RxMoya/ │ │ ├── MoyaProvider+Rx.swift │ │ ├── Observable+Response.swift │ │ └── Single+Response.swift │ ├── Moya.xcodeproj/ │ │ └── project.pbxproj │ ├── NSObject+Rx/ │ │ ├── HasDisposeBag.swift │ │ ├── LICENSE │ │ ├── NSObject+Rx.swift │ │ └── Readme.md │ ├── NSObject+Rx.xcodeproj/ │ │ └── project.pbxproj │ ├── Pods.xcodeproj/ │ │ └── project.pbxproj │ ├── R.swift/ │ │ ├── License │ │ └── rswift │ ├── R.swift.Library/ │ │ ├── Library/ │ │ │ ├── Core/ │ │ │ │ ├── ColorResource.swift │ │ │ │ ├── FileResource.swift │ │ │ │ ├── FontResource.swift │ │ │ │ ├── Identifier.swift │ │ │ │ ├── ImageResource.swift │ │ │ │ ├── NibResource.swift │ │ │ │ ├── ReuseIdentifierProtocol.swift │ │ │ │ ├── StoryboardResource.swift │ │ │ │ ├── StoryboardSegueIdentifierProtocol.swift │ │ │ │ ├── StoryboardViewControllerResource.swift │ │ │ │ ├── StringResource.swift │ │ │ │ └── Validatable.swift │ │ │ ├── Foundation/ │ │ │ │ ├── Bundle+FileResource.swift │ │ │ │ └── Data+FileResource.swift │ │ │ └── UIKit/ │ │ │ ├── NibResource+UIKit.swift │ │ │ ├── StoryboardResourceWithInitialController+UIKit.swift │ │ │ ├── TypedStoryboardSegueInfo+UIStoryboardSegue.swift │ │ │ ├── UICollectionView+ReuseIdentifierProtocol.swift │ │ │ ├── UIColor+ColorResource.swift │ │ │ ├── UIFont+FontResource.swift │ │ │ ├── UIImage+ImageResource.swift │ │ │ ├── UINib+NibResource.swift │ │ │ ├── UIStoryboard+StoryboardResource.swift │ │ │ ├── UIStoryboard+StoryboardViewControllerResource.swift │ │ │ ├── UITableView+ReuseIdentifierProtocol.swift │ │ │ ├── UIViewController+NibResource.swift │ │ │ └── UIViewController+StoryboardSegueIdentifierProtocol.swift │ │ ├── License │ │ └── Readme.md │ ├── R.swift.Library.xcodeproj/ │ │ └── project.pbxproj │ ├── R.swift.xcodeproj/ │ │ └── project.pbxproj │ ├── ReachabilitySwift/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Sources/ │ │ └── Reachability.swift │ ├── ReachabilitySwift.xcodeproj/ │ │ └── project.pbxproj │ ├── RxCocoa/ │ │ ├── LICENSE.md │ │ ├── Platform/ │ │ │ ├── DataStructures/ │ │ │ │ ├── Bag.swift │ │ │ │ ├── InfiniteSequence.swift │ │ │ │ ├── PriorityQueue.swift │ │ │ │ └── Queue.swift │ │ │ ├── DispatchQueue+Extensions.swift │ │ │ ├── Platform.Darwin.swift │ │ │ ├── Platform.Linux.swift │ │ │ └── RecursiveLock.swift │ │ ├── README.md │ │ └── RxCocoa/ │ │ ├── Common/ │ │ │ ├── ControlTarget.swift │ │ │ ├── DelegateProxy.swift │ │ │ ├── DelegateProxyType.swift │ │ │ ├── Infallible+Bind.swift │ │ │ ├── Observable+Bind.swift │ │ │ ├── RxCocoaObjCRuntimeError+Extensions.swift │ │ │ ├── RxTarget.swift │ │ │ ├── SectionedViewDataSourceType.swift │ │ │ └── TextInput.swift │ │ ├── Foundation/ │ │ │ ├── KVORepresentable+CoreGraphics.swift │ │ │ ├── KVORepresentable+Swift.swift │ │ │ ├── KVORepresentable.swift │ │ │ ├── NSObject+Rx+KVORepresentable.swift │ │ │ ├── NSObject+Rx+RawRepresentable.swift │ │ │ ├── NSObject+Rx.swift │ │ │ ├── NotificationCenter+Rx.swift │ │ │ └── URLSession+Rx.swift │ │ ├── Runtime/ │ │ │ ├── _RX.m │ │ │ ├── _RXDelegateProxy.m │ │ │ ├── _RXKVOObserver.m │ │ │ ├── _RXObjCRuntime.m │ │ │ └── include/ │ │ │ ├── RxCocoaRuntime.h │ │ │ ├── _RX.h │ │ │ ├── _RXDelegateProxy.h │ │ │ ├── _RXKVOObserver.h │ │ │ └── _RXObjCRuntime.h │ │ ├── RxCocoa.h │ │ ├── RxCocoa.swift │ │ ├── Traits/ │ │ │ ├── ControlEvent.swift │ │ │ ├── ControlProperty.swift │ │ │ ├── Driver/ │ │ │ │ ├── BehaviorRelay+Driver.swift │ │ │ │ ├── ControlEvent+Driver.swift │ │ │ │ ├── ControlProperty+Driver.swift │ │ │ │ ├── Driver+Subscription.swift │ │ │ │ ├── Driver.swift │ │ │ │ └── ObservableConvertibleType+Driver.swift │ │ │ ├── SharedSequence/ │ │ │ │ ├── ObservableConvertibleType+SharedSequence.swift │ │ │ │ ├── SchedulerType+SharedSequence.swift │ │ │ │ ├── SharedSequence+Concurrency.swift │ │ │ │ ├── SharedSequence+Operators+arity.swift │ │ │ │ ├── SharedSequence+Operators.swift │ │ │ │ └── SharedSequence.swift │ │ │ └── Signal/ │ │ │ ├── ControlEvent+Signal.swift │ │ │ ├── ObservableConvertibleType+Signal.swift │ │ │ ├── PublishRelay+Signal.swift │ │ │ ├── Signal+Subscription.swift │ │ │ └── Signal.swift │ │ ├── iOS/ │ │ │ ├── DataSources/ │ │ │ │ ├── RxCollectionViewReactiveArrayDataSource.swift │ │ │ │ ├── RxPickerViewAdapter.swift │ │ │ │ └── RxTableViewReactiveArrayDataSource.swift │ │ │ ├── Events/ │ │ │ │ └── ItemEvents.swift │ │ │ ├── NSTextStorage+Rx.swift │ │ │ ├── Protocols/ │ │ │ │ ├── RxCollectionViewDataSourceType.swift │ │ │ │ ├── RxPickerViewDataSourceType.swift │ │ │ │ └── RxTableViewDataSourceType.swift │ │ │ ├── Proxies/ │ │ │ │ ├── RxCollectionViewDataSourcePrefetchingProxy.swift │ │ │ │ ├── RxCollectionViewDataSourceProxy.swift │ │ │ │ ├── RxCollectionViewDelegateProxy.swift │ │ │ │ ├── RxNavigationControllerDelegateProxy.swift │ │ │ │ ├── RxPickerViewDataSourceProxy.swift │ │ │ │ ├── RxPickerViewDelegateProxy.swift │ │ │ │ ├── RxScrollViewDelegateProxy.swift │ │ │ │ ├── RxSearchBarDelegateProxy.swift │ │ │ │ ├── RxSearchControllerDelegateProxy.swift │ │ │ │ ├── RxTabBarControllerDelegateProxy.swift │ │ │ │ ├── RxTabBarDelegateProxy.swift │ │ │ │ ├── RxTableViewDataSourcePrefetchingProxy.swift │ │ │ │ ├── RxTableViewDataSourceProxy.swift │ │ │ │ ├── RxTableViewDelegateProxy.swift │ │ │ │ ├── RxTextStorageDelegateProxy.swift │ │ │ │ ├── RxTextViewDelegateProxy.swift │ │ │ │ └── RxWKNavigationDelegateProxy.swift │ │ │ ├── UIActivityIndicatorView+Rx.swift │ │ │ ├── UIApplication+Rx.swift │ │ │ ├── UIBarButtonItem+Rx.swift │ │ │ ├── UIButton+Rx.swift │ │ │ ├── UICollectionView+Rx.swift │ │ │ ├── UIControl+Rx.swift │ │ │ ├── UIDatePicker+Rx.swift │ │ │ ├── UIGestureRecognizer+Rx.swift │ │ │ ├── UINavigationController+Rx.swift │ │ │ ├── UIPickerView+Rx.swift │ │ │ ├── UIRefreshControl+Rx.swift │ │ │ ├── UIScrollView+Rx.swift │ │ │ ├── UISearchBar+Rx.swift │ │ │ ├── UISearchController+Rx.swift │ │ │ ├── UISegmentedControl+Rx.swift │ │ │ ├── UISlider+Rx.swift │ │ │ ├── UIStepper+Rx.swift │ │ │ ├── UISwitch+Rx.swift │ │ │ ├── UITabBar+Rx.swift │ │ │ ├── UITabBarController+Rx.swift │ │ │ ├── UITableView+Rx.swift │ │ │ ├── UITextField+Rx.swift │ │ │ ├── UITextView+Rx.swift │ │ │ └── WKWebView+Rx.swift │ │ └── macOS/ │ │ ├── NSButton+Rx.swift │ │ ├── NSControl+Rx.swift │ │ ├── NSSlider+Rx.swift │ │ ├── NSTextField+Rx.swift │ │ ├── NSTextView+Rx.swift │ │ └── NSView+Rx.swift │ ├── RxCocoa.xcodeproj/ │ │ └── project.pbxproj │ ├── RxRelay/ │ │ ├── LICENSE.md │ │ ├── README.md │ │ └── RxRelay/ │ │ ├── BehaviorRelay.swift │ │ ├── Observable+Bind.swift │ │ ├── PublishRelay.swift │ │ ├── ReplayRelay.swift │ │ └── Utils.swift │ ├── RxRelay.xcodeproj/ │ │ └── project.pbxproj │ ├── RxSwift/ │ │ ├── LICENSE.md │ │ ├── Platform/ │ │ │ ├── AtomicInt.swift │ │ │ ├── DataStructures/ │ │ │ │ ├── Bag.swift │ │ │ │ ├── InfiniteSequence.swift │ │ │ │ ├── PriorityQueue.swift │ │ │ │ └── Queue.swift │ │ │ ├── DispatchQueue+Extensions.swift │ │ │ ├── Platform.Darwin.swift │ │ │ ├── Platform.Linux.swift │ │ │ └── RecursiveLock.swift │ │ ├── README.md │ │ └── RxSwift/ │ │ ├── AnyObserver.swift │ │ ├── Binder.swift │ │ ├── Cancelable.swift │ │ ├── Concurrency/ │ │ │ ├── AsyncLock.swift │ │ │ ├── Lock.swift │ │ │ ├── LockOwnerType.swift │ │ │ ├── SynchronizedDisposeType.swift │ │ │ ├── SynchronizedOnType.swift │ │ │ └── SynchronizedUnsubscribeType.swift │ │ ├── ConnectableObservableType.swift │ │ ├── Date+Dispatch.swift │ │ ├── Disposable.swift │ │ ├── Disposables/ │ │ │ ├── AnonymousDisposable.swift │ │ │ ├── BinaryDisposable.swift │ │ │ ├── BooleanDisposable.swift │ │ │ ├── CompositeDisposable.swift │ │ │ ├── Disposables.swift │ │ │ ├── DisposeBag.swift │ │ │ ├── DisposeBase.swift │ │ │ ├── NopDisposable.swift │ │ │ ├── RefCountDisposable.swift │ │ │ ├── ScheduledDisposable.swift │ │ │ ├── SerialDisposable.swift │ │ │ ├── SingleAssignmentDisposable.swift │ │ │ └── SubscriptionDisposable.swift │ │ ├── Errors.swift │ │ ├── Event.swift │ │ ├── Extensions/ │ │ │ └── Bag+Rx.swift │ │ ├── GroupedObservable.swift │ │ ├── ImmediateSchedulerType.swift │ │ ├── Observable+Concurrency.swift │ │ ├── Observable.swift │ │ ├── ObservableConvertibleType.swift │ │ ├── ObservableType+Extensions.swift │ │ ├── ObservableType.swift │ │ ├── Observables/ │ │ │ ├── AddRef.swift │ │ │ ├── Amb.swift │ │ │ ├── AsMaybe.swift │ │ │ ├── AsSingle.swift │ │ │ ├── Buffer.swift │ │ │ ├── Catch.swift │ │ │ ├── CombineLatest+Collection.swift │ │ │ ├── CombineLatest+arity.swift │ │ │ ├── CombineLatest.swift │ │ │ ├── CompactMap.swift │ │ │ ├── Concat.swift │ │ │ ├── Create.swift │ │ │ ├── Debounce.swift │ │ │ ├── Debug.swift │ │ │ ├── Decode.swift │ │ │ ├── DefaultIfEmpty.swift │ │ │ ├── Deferred.swift │ │ │ ├── Delay.swift │ │ │ ├── DelaySubscription.swift │ │ │ ├── Dematerialize.swift │ │ │ ├── DistinctUntilChanged.swift │ │ │ ├── Do.swift │ │ │ ├── ElementAt.swift │ │ │ ├── Empty.swift │ │ │ ├── Enumerated.swift │ │ │ ├── Error.swift │ │ │ ├── Filter.swift │ │ │ ├── First.swift │ │ │ ├── Generate.swift │ │ │ ├── GroupBy.swift │ │ │ ├── Just.swift │ │ │ ├── Map.swift │ │ │ ├── Materialize.swift │ │ │ ├── Merge.swift │ │ │ ├── Multicast.swift │ │ │ ├── Never.swift │ │ │ ├── ObserveOn.swift │ │ │ ├── Optional.swift │ │ │ ├── Producer.swift │ │ │ ├── Range.swift │ │ │ ├── Reduce.swift │ │ │ ├── Repeat.swift │ │ │ ├── RetryWhen.swift │ │ │ ├── Sample.swift │ │ │ ├── Scan.swift │ │ │ ├── Sequence.swift │ │ │ ├── ShareReplayScope.swift │ │ │ ├── SingleAsync.swift │ │ │ ├── Sink.swift │ │ │ ├── Skip.swift │ │ │ ├── SkipUntil.swift │ │ │ ├── SkipWhile.swift │ │ │ ├── StartWith.swift │ │ │ ├── SubscribeOn.swift │ │ │ ├── Switch.swift │ │ │ ├── SwitchIfEmpty.swift │ │ │ ├── Take.swift │ │ │ ├── TakeLast.swift │ │ │ ├── TakeWithPredicate.swift │ │ │ ├── Throttle.swift │ │ │ ├── Timeout.swift │ │ │ ├── Timer.swift │ │ │ ├── ToArray.swift │ │ │ ├── Using.swift │ │ │ ├── Window.swift │ │ │ ├── WithLatestFrom.swift │ │ │ ├── WithUnretained.swift │ │ │ ├── Zip+Collection.swift │ │ │ ├── Zip+arity.swift │ │ │ └── Zip.swift │ │ ├── ObserverType.swift │ │ ├── Observers/ │ │ │ ├── AnonymousObserver.swift │ │ │ ├── ObserverBase.swift │ │ │ └── TailRecursiveSink.swift │ │ ├── Reactive.swift │ │ ├── Rx.swift │ │ ├── RxMutableBox.swift │ │ ├── SchedulerType.swift │ │ ├── Schedulers/ │ │ │ ├── ConcurrentDispatchQueueScheduler.swift │ │ │ ├── ConcurrentMainScheduler.swift │ │ │ ├── CurrentThreadScheduler.swift │ │ │ ├── HistoricalScheduler.swift │ │ │ ├── HistoricalSchedulerTimeConverter.swift │ │ │ ├── Internal/ │ │ │ │ ├── DispatchQueueConfiguration.swift │ │ │ │ ├── InvocableScheduledItem.swift │ │ │ │ ├── InvocableType.swift │ │ │ │ ├── ScheduledItem.swift │ │ │ │ └── ScheduledItemType.swift │ │ │ ├── MainScheduler.swift │ │ │ ├── OperationQueueScheduler.swift │ │ │ ├── RecursiveScheduler.swift │ │ │ ├── SchedulerServices+Emulation.swift │ │ │ ├── SerialDispatchQueueScheduler.swift │ │ │ ├── VirtualTimeConverterType.swift │ │ │ └── VirtualTimeScheduler.swift │ │ ├── Subjects/ │ │ │ ├── AsyncSubject.swift │ │ │ ├── BehaviorSubject.swift │ │ │ ├── PublishSubject.swift │ │ │ ├── ReplaySubject.swift │ │ │ └── SubjectType.swift │ │ ├── SwiftSupport/ │ │ │ └── SwiftSupport.swift │ │ └── Traits/ │ │ ├── Infallible/ │ │ │ ├── Infallible+CombineLatest+arity.swift │ │ │ ├── Infallible+Concurrency.swift │ │ │ ├── Infallible+Create.swift │ │ │ ├── Infallible+Operators.swift │ │ │ ├── Infallible+Zip+arity.swift │ │ │ ├── Infallible.swift │ │ │ └── ObservableConvertibleType+Infallible.swift │ │ └── PrimitiveSequence/ │ │ ├── Completable+AndThen.swift │ │ ├── Completable.swift │ │ ├── Maybe.swift │ │ ├── ObservableType+PrimitiveSequence.swift │ │ ├── PrimitiveSequence+Concurrency.swift │ │ ├── PrimitiveSequence+Zip+arity.swift │ │ ├── PrimitiveSequence.swift │ │ └── Single.swift │ ├── RxSwift.xcodeproj/ │ │ └── project.pbxproj │ ├── RxTheme/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Sources/ │ │ ├── Extensions/ │ │ │ ├── CALayer+Theme.swift │ │ │ ├── CAShapeLayer+Theme.swift │ │ │ ├── UIActivityIndicatorView+Theme.swift │ │ │ ├── UIBarButtonItem+Theme.swift │ │ │ ├── UIButton+Theme.swift │ │ │ ├── UILabel+Theme.swift │ │ │ ├── UINavigationBar+Theme.swift │ │ │ ├── UIPageControl+Theme.swift │ │ │ ├── UIProgressView+Theme.swift │ │ │ ├── UISearchBar+Theme.swift │ │ │ ├── UISegmentedControl+Theme.swift │ │ │ ├── UISlider+Theme.swift │ │ │ ├── UISwitch+Theme.swift │ │ │ ├── UITAbleViewCell+Theme.swift │ │ │ ├── UITabBar+Theme.swift │ │ │ ├── UITableView+Theme.swift │ │ │ ├── UITextField+Theme.swift │ │ │ ├── UITextView+Theme.swift │ │ │ ├── UIToolbar+Theme.swift │ │ │ └── UIView+Theme.swift │ │ ├── ThemeAttribute.swift │ │ ├── ThemeProvider.swift │ │ ├── ThemeProxy.swift │ │ └── ThemeService.swift │ ├── RxTheme.xcodeproj/ │ │ └── project.pbxproj │ ├── SDWebImage/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SDWebImage/ │ │ │ ├── Core/ │ │ │ │ ├── NSButton+WebCache.h │ │ │ │ ├── NSButton+WebCache.m │ │ │ │ ├── NSData+ImageContentType.h │ │ │ │ ├── NSData+ImageContentType.m │ │ │ │ ├── NSImage+Compatibility.h │ │ │ │ ├── NSImage+Compatibility.m │ │ │ │ ├── SDAnimatedImage.h │ │ │ │ ├── SDAnimatedImage.m │ │ │ │ ├── SDAnimatedImagePlayer.h │ │ │ │ ├── SDAnimatedImagePlayer.m │ │ │ │ ├── SDAnimatedImageRep.h │ │ │ │ ├── SDAnimatedImageRep.m │ │ │ │ ├── SDAnimatedImageView+WebCache.h │ │ │ │ ├── SDAnimatedImageView+WebCache.m │ │ │ │ ├── SDAnimatedImageView.h │ │ │ │ ├── SDAnimatedImageView.m │ │ │ │ ├── SDDiskCache.h │ │ │ │ ├── SDDiskCache.m │ │ │ │ ├── SDGraphicsImageRenderer.h │ │ │ │ ├── SDGraphicsImageRenderer.m │ │ │ │ ├── SDImageAPNGCoder.h │ │ │ │ ├── SDImageAPNGCoder.m │ │ │ │ ├── SDImageAWebPCoder.h │ │ │ │ ├── SDImageAWebPCoder.m │ │ │ │ ├── SDImageCache.h │ │ │ │ ├── SDImageCache.m │ │ │ │ ├── SDImageCacheConfig.h │ │ │ │ ├── SDImageCacheConfig.m │ │ │ │ ├── SDImageCacheDefine.h │ │ │ │ ├── SDImageCacheDefine.m │ │ │ │ ├── SDImageCachesManager.h │ │ │ │ ├── SDImageCachesManager.m │ │ │ │ ├── SDImageCoder.h │ │ │ │ ├── SDImageCoder.m │ │ │ │ ├── SDImageCoderHelper.h │ │ │ │ ├── SDImageCoderHelper.m │ │ │ │ ├── SDImageCodersManager.h │ │ │ │ ├── SDImageCodersManager.m │ │ │ │ ├── SDImageFrame.h │ │ │ │ ├── SDImageFrame.m │ │ │ │ ├── SDImageGIFCoder.h │ │ │ │ ├── SDImageGIFCoder.m │ │ │ │ ├── SDImageGraphics.h │ │ │ │ ├── SDImageGraphics.m │ │ │ │ ├── SDImageHEICCoder.h │ │ │ │ ├── SDImageHEICCoder.m │ │ │ │ ├── SDImageIOAnimatedCoder.h │ │ │ │ ├── SDImageIOAnimatedCoder.m │ │ │ │ ├── SDImageIOCoder.h │ │ │ │ ├── SDImageIOCoder.m │ │ │ │ ├── SDImageLoader.h │ │ │ │ ├── SDImageLoader.m │ │ │ │ ├── SDImageLoadersManager.h │ │ │ │ ├── SDImageLoadersManager.m │ │ │ │ ├── SDImageTransformer.h │ │ │ │ ├── SDImageTransformer.m │ │ │ │ ├── SDMemoryCache.h │ │ │ │ ├── SDMemoryCache.m │ │ │ │ ├── SDWebImageCacheKeyFilter.h │ │ │ │ ├── SDWebImageCacheKeyFilter.m │ │ │ │ ├── SDWebImageCacheSerializer.h │ │ │ │ ├── SDWebImageCacheSerializer.m │ │ │ │ ├── SDWebImageCompat.h │ │ │ │ ├── SDWebImageCompat.m │ │ │ │ ├── SDWebImageDefine.h │ │ │ │ ├── SDWebImageDefine.m │ │ │ │ ├── SDWebImageDownloader.h │ │ │ │ ├── SDWebImageDownloader.m │ │ │ │ ├── SDWebImageDownloaderConfig.h │ │ │ │ ├── SDWebImageDownloaderConfig.m │ │ │ │ ├── SDWebImageDownloaderDecryptor.h │ │ │ │ ├── SDWebImageDownloaderDecryptor.m │ │ │ │ ├── SDWebImageDownloaderOperation.h │ │ │ │ ├── SDWebImageDownloaderOperation.m │ │ │ │ ├── SDWebImageDownloaderRequestModifier.h │ │ │ │ ├── SDWebImageDownloaderRequestModifier.m │ │ │ │ ├── SDWebImageDownloaderResponseModifier.h │ │ │ │ ├── SDWebImageDownloaderResponseModifier.m │ │ │ │ ├── SDWebImageError.h │ │ │ │ ├── SDWebImageError.m │ │ │ │ ├── SDWebImageIndicator.h │ │ │ │ ├── SDWebImageIndicator.m │ │ │ │ ├── SDWebImageManager.h │ │ │ │ ├── SDWebImageManager.m │ │ │ │ ├── SDWebImageOperation.h │ │ │ │ ├── SDWebImageOperation.m │ │ │ │ ├── SDWebImageOptionsProcessor.h │ │ │ │ ├── SDWebImageOptionsProcessor.m │ │ │ │ ├── SDWebImagePrefetcher.h │ │ │ │ ├── SDWebImagePrefetcher.m │ │ │ │ ├── SDWebImageTransition.h │ │ │ │ ├── SDWebImageTransition.m │ │ │ │ ├── UIButton+WebCache.h │ │ │ │ ├── UIButton+WebCache.m │ │ │ │ ├── UIImage+ExtendedCacheData.h │ │ │ │ ├── UIImage+ExtendedCacheData.m │ │ │ │ ├── UIImage+ForceDecode.h │ │ │ │ ├── UIImage+ForceDecode.m │ │ │ │ ├── UIImage+GIF.h │ │ │ │ ├── UIImage+GIF.m │ │ │ │ ├── UIImage+MemoryCacheCost.h │ │ │ │ ├── UIImage+MemoryCacheCost.m │ │ │ │ ├── UIImage+Metadata.h │ │ │ │ ├── UIImage+Metadata.m │ │ │ │ ├── UIImage+MultiFormat.h │ │ │ │ ├── UIImage+MultiFormat.m │ │ │ │ ├── UIImage+Transform.h │ │ │ │ ├── UIImage+Transform.m │ │ │ │ ├── UIImageView+HighlightedWebCache.h │ │ │ │ ├── UIImageView+HighlightedWebCache.m │ │ │ │ ├── UIImageView+WebCache.h │ │ │ │ ├── UIImageView+WebCache.m │ │ │ │ ├── UIView+WebCache.h │ │ │ │ ├── UIView+WebCache.m │ │ │ │ ├── UIView+WebCacheOperation.h │ │ │ │ └── UIView+WebCacheOperation.m │ │ │ └── Private/ │ │ │ ├── NSBezierPath+SDRoundedCorners.h │ │ │ ├── NSBezierPath+SDRoundedCorners.m │ │ │ ├── SDAssociatedObject.h │ │ │ ├── SDAssociatedObject.m │ │ │ ├── SDAsyncBlockOperation.h │ │ │ ├── SDAsyncBlockOperation.m │ │ │ ├── SDDeviceHelper.h │ │ │ ├── SDDeviceHelper.m │ │ │ ├── SDDisplayLink.h │ │ │ ├── SDDisplayLink.m │ │ │ ├── SDFileAttributeHelper.h │ │ │ ├── SDFileAttributeHelper.m │ │ │ ├── SDImageAssetManager.h │ │ │ ├── SDImageAssetManager.m │ │ │ ├── SDImageCachesManagerOperation.h │ │ │ ├── SDImageCachesManagerOperation.m │ │ │ ├── SDImageIOAnimatedCoderInternal.h │ │ │ ├── SDInternalMacros.h │ │ │ ├── SDInternalMacros.m │ │ │ ├── SDWeakProxy.h │ │ │ ├── SDWeakProxy.m │ │ │ ├── SDWebImageTransitionInternal.h │ │ │ ├── SDmetamacros.h │ │ │ ├── UIColor+SDHexString.h │ │ │ └── UIColor+SDHexString.m │ │ └── WebImage/ │ │ └── SDWebImage.h │ ├── SDWebImage.xcodeproj/ │ │ └── project.pbxproj │ ├── SQLiteRepairKit/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── repair/ │ │ ├── SQLiteRepairKit.h │ │ ├── sqliterk.c │ │ ├── sqliterk.h │ │ ├── sqliterk_api.c │ │ ├── sqliterk_btree.c │ │ ├── sqliterk_btree.h │ │ ├── sqliterk_column.c │ │ ├── sqliterk_column.h │ │ ├── sqliterk_crypto.c │ │ ├── sqliterk_crypto.h │ │ ├── sqliterk_os.c │ │ ├── sqliterk_os.h │ │ ├── sqliterk_output.cpp │ │ ├── sqliterk_pager.c │ │ ├── sqliterk_pager.h │ │ ├── sqliterk_util.c │ │ ├── sqliterk_util.h │ │ ├── sqliterk_values.c │ │ └── sqliterk_values.h │ ├── SQLiteRepairKit.xcodeproj/ │ │ └── project.pbxproj │ ├── SnapKit/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Sources/ │ │ ├── Constraint.swift │ │ ├── ConstraintAttributes.swift │ │ ├── ConstraintConfig.swift │ │ ├── ConstraintConstantTarget.swift │ │ ├── ConstraintDSL.swift │ │ ├── ConstraintDescription.swift │ │ ├── ConstraintDirectionalInsetTarget.swift │ │ ├── ConstraintDirectionalInsets.swift │ │ ├── ConstraintInsetTarget.swift │ │ ├── ConstraintInsets.swift │ │ ├── ConstraintItem.swift │ │ ├── ConstraintLayoutGuide+Extensions.swift │ │ ├── ConstraintLayoutGuide.swift │ │ ├── ConstraintLayoutGuideDSL.swift │ │ ├── ConstraintLayoutSupport.swift │ │ ├── ConstraintLayoutSupportDSL.swift │ │ ├── ConstraintMaker.swift │ │ ├── ConstraintMakerEditable.swift │ │ ├── ConstraintMakerExtendable.swift │ │ ├── ConstraintMakerFinalizable.swift │ │ ├── ConstraintMakerPrioritizable.swift │ │ ├── ConstraintMakerRelatable+Extensions.swift │ │ ├── ConstraintMakerRelatable.swift │ │ ├── ConstraintMultiplierTarget.swift │ │ ├── ConstraintOffsetTarget.swift │ │ ├── ConstraintPriority.swift │ │ ├── ConstraintPriorityTarget.swift │ │ ├── ConstraintRelatableTarget.swift │ │ ├── ConstraintRelation.swift │ │ ├── ConstraintView+Extensions.swift │ │ ├── ConstraintView.swift │ │ ├── ConstraintViewDSL.swift │ │ ├── Debugging.swift │ │ ├── LayoutConstraint.swift │ │ ├── LayoutConstraintItem.swift │ │ ├── Typealiases.swift │ │ └── UILayoutSupport+Extensions.swift │ ├── SnapKit.xcodeproj/ │ │ └── project.pbxproj │ ├── SwiftDate/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Sources/ │ │ └── SwiftDate/ │ │ ├── Date/ │ │ │ ├── Date+Compare.swift │ │ │ ├── Date+Components.swift │ │ │ ├── Date+Create.swift │ │ │ ├── Date+Math.swift │ │ │ └── Date.swift │ │ ├── DateInRegion/ │ │ │ ├── DateInRegion+Compare.swift │ │ │ ├── DateInRegion+Components.swift │ │ │ ├── DateInRegion+Create.swift │ │ │ ├── DateInRegion+Math.swift │ │ │ ├── DateInRegion.swift │ │ │ └── Region.swift │ │ ├── DateRepresentable.swift │ │ ├── Formatters/ │ │ │ ├── DotNetParserFormatter.swift │ │ │ ├── Formatter+Protocols.swift │ │ │ ├── ISOFormatter.swift │ │ │ ├── ISOParser.swift │ │ │ └── RelativeFormatter/ │ │ │ ├── RelativeFormatter+Style.swift │ │ │ ├── RelativeFormatter.swift │ │ │ ├── RelativeFormatterLanguage.swift │ │ │ └── langs/ │ │ │ ├── af.json │ │ │ ├── am.json │ │ │ ├── ar.json │ │ │ ├── ar_AE.json │ │ │ ├── as.json │ │ │ ├── be.json │ │ │ ├── bg.json │ │ │ ├── bn.json │ │ │ ├── br.json │ │ │ ├── bs-Cyrl.json │ │ │ ├── bs.json │ │ │ ├── ca.json │ │ │ ├── cs.json │ │ │ ├── cy.json │ │ │ ├── da.json │ │ │ ├── de.json │ │ │ ├── dsb.json │ │ │ ├── dz.json │ │ │ ├── ee.json │ │ │ ├── el.json │ │ │ ├── en.json │ │ │ ├── es.json │ │ │ ├── es_AR.json │ │ │ ├── es_MX.json │ │ │ ├── es_PY.json │ │ │ ├── es_US.json │ │ │ ├── et.json │ │ │ ├── eu.json │ │ │ ├── fa.json │ │ │ ├── fi.json │ │ │ ├── fil.json │ │ │ ├── fo.json │ │ │ ├── fr.json │ │ │ ├── fr_CA.json │ │ │ ├── fur.json │ │ │ ├── fy.json │ │ │ ├── ga.json │ │ │ ├── gd.json │ │ │ ├── gl.json │ │ │ ├── gu.json │ │ │ ├── he.json │ │ │ ├── hi.json │ │ │ ├── hr.json │ │ │ ├── hsb.json │ │ │ ├── hu.json │ │ │ ├── hy.json │ │ │ ├── id.json │ │ │ ├── is.json │ │ │ ├── it.json │ │ │ ├── ja.json │ │ │ ├── jgo.json │ │ │ ├── ka.json │ │ │ ├── kea.json │ │ │ ├── kk.json │ │ │ ├── kl.json │ │ │ ├── km.json │ │ │ ├── kn.json │ │ │ ├── ko.json │ │ │ ├── kok.json │ │ │ ├── ksh.json │ │ │ ├── ky.json │ │ │ ├── lb.json │ │ │ ├── lkt.json │ │ │ ├── lo.json │ │ │ ├── lt.json │ │ │ ├── lv.json │ │ │ ├── mk.json │ │ │ ├── ml.json │ │ │ ├── mn.json │ │ │ ├── mr.json │ │ │ ├── ms.json │ │ │ ├── mt.json │ │ │ ├── my.json │ │ │ ├── mzn.json │ │ │ ├── nb.json │ │ │ ├── ne.json │ │ │ ├── nl.json │ │ │ ├── nn.json │ │ │ ├── or.json │ │ │ ├── pa.json │ │ │ ├── pl.json │ │ │ ├── ps.json │ │ │ ├── pt.json │ │ │ ├── ro.json │ │ │ ├── ru.json │ │ │ ├── sah.json │ │ │ ├── sd.json │ │ │ ├── se.json │ │ │ ├── se_FI.json │ │ │ ├── si.json │ │ │ ├── sk.json │ │ │ ├── sl.json │ │ │ ├── sq.json │ │ │ ├── sr.json │ │ │ ├── sr_Latn.json │ │ │ ├── sv.json │ │ │ ├── sw.json │ │ │ ├── ta.json │ │ │ ├── te.json │ │ │ ├── th.json │ │ │ ├── ti.json │ │ │ ├── tk.json │ │ │ ├── to.json │ │ │ ├── tr.json │ │ │ ├── ug.json │ │ │ ├── uk.json │ │ │ ├── ur.json │ │ │ ├── ur_IN.json │ │ │ ├── uz.json │ │ │ ├── uz_Cyrl.json │ │ │ ├── vi.json │ │ │ ├── wae.json │ │ │ ├── yi.json │ │ │ ├── yue_Hans.json │ │ │ ├── yue_Hant.json │ │ │ ├── zh.json │ │ │ ├── zh_Hans.json │ │ │ ├── zh_Hans_HK.json │ │ │ ├── zh_Hans_MO.json │ │ │ ├── zh_Hans_SG.json │ │ │ ├── zh_Hant.json │ │ │ ├── zh_Hant_HK.json │ │ │ └── zu.json │ │ ├── Foundation+Extras/ │ │ │ ├── DateComponents+Extras.swift │ │ │ ├── Int+DateComponents.swift │ │ │ ├── String+Parser.swift │ │ │ └── TimeInterval+Formatter.swift │ │ ├── Supports/ │ │ │ ├── AssociatedValues.swift │ │ │ ├── Calendars.swift │ │ │ ├── Commons.swift │ │ │ ├── Locales.swift │ │ │ ├── TimeStructures.swift │ │ │ └── Zones.swift │ │ ├── SwiftDate.swift │ │ └── TimePeriod/ │ │ ├── Groups/ │ │ │ ├── TimePeriodChain.swift │ │ │ ├── TimePeriodCollection.swift │ │ │ └── TimePeriodGroup.swift │ │ ├── TimePeriod+Support.swift │ │ ├── TimePeriod.swift │ │ └── TimePeriodProtocol.swift │ ├── SwiftDate.xcodeproj/ │ │ └── project.pbxproj │ ├── SwifterSwift/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Sources/ │ │ └── SwifterSwift/ │ │ ├── AppKit/ │ │ │ ├── NSColorExtensions.swift │ │ │ ├── NSImageExtensions.swift │ │ │ └── NSViewExtensions.swift │ │ ├── CoreAnimation/ │ │ │ ├── CAGradientLayerExtensions.swift │ │ │ └── CATransform3DExtensions.swift │ │ ├── CoreGraphics/ │ │ │ ├── CGAffineTransformExtensions.swift │ │ │ ├── CGColorExtensions.swift │ │ │ ├── CGFloatExtensions.swift │ │ │ ├── CGPointExtensions.swift │ │ │ ├── CGRectExtensions.swift │ │ │ ├── CGSizeExtensions.swift │ │ │ └── CGVectorExtensions.swift │ │ ├── CoreLocation/ │ │ │ ├── CLLocationArrayExtensions.swift │ │ │ ├── CLLocationExtensions.swift │ │ │ └── CLVisitExtensions.swift │ │ ├── Dispatch/ │ │ │ └── DispatchQueueExtensions.swift │ │ ├── Foundation/ │ │ │ ├── CalendarExtensions.swift │ │ │ ├── DataExtensions.swift │ │ │ ├── DateExtensions.swift │ │ │ ├── FileManagerExtensions.swift │ │ │ ├── LocaleExtensions.swift │ │ │ ├── NSAttributedStringExtensions.swift │ │ │ ├── NSPredicateExtensions.swift │ │ │ ├── NSRegularExpressionExtensions.swift │ │ │ ├── NotificationCenterExtensions.swift │ │ │ ├── URLExtensions.swift │ │ │ ├── URLRequestExtensions.swift │ │ │ └── UserDefaultsExtensions.swift │ │ ├── MapKit/ │ │ │ ├── MKMapViewExtensions.swift │ │ │ └── MKPolylineExtensions.swift │ │ ├── SceneKit/ │ │ │ ├── SCNBoxExtensions.swift │ │ │ ├── SCNCapsuleExtensions.swift │ │ │ ├── SCNConeExtensions.swift │ │ │ ├── SCNCylinderExtensions.swift │ │ │ ├── SCNGeometryExtensions.swift │ │ │ ├── SCNMaterialExtensions.swift │ │ │ ├── SCNPlaneExtensions.swift │ │ │ ├── SCNShapeExtensions.swift │ │ │ ├── SCNSphereExtensions.swift │ │ │ └── SCNVector3Extensions.swift │ │ ├── Shared/ │ │ │ ├── ColorExtensions.swift │ │ │ └── EdgeInsetsExtensions.swift │ │ ├── SpriteKit/ │ │ │ └── SKNodeExtensions.swift │ │ ├── StoreKit/ │ │ │ └── SKProductExtensions.swift │ │ ├── SwiftStdlib/ │ │ │ ├── ArrayExtensions.swift │ │ │ ├── BidirectionalCollectionExtensions.swift │ │ │ ├── BinaryFloatingPointExtensions.swift │ │ │ ├── BoolExtensions.swift │ │ │ ├── CharacterExtensions.swift │ │ │ ├── CollectionExtensions.swift │ │ │ ├── ComparableExtensions.swift │ │ │ ├── DecodableExtensions.swift │ │ │ ├── Deprecated/ │ │ │ │ └── StdlibDeprecated.swift │ │ │ ├── DictionaryExtensions.swift │ │ │ ├── DoubleExtensions.swift │ │ │ ├── FloatExtensions.swift │ │ │ ├── FloatingPointExtensions.swift │ │ │ ├── IntExtensions.swift │ │ │ ├── KeyedDecodingContainerExtensions.swift │ │ │ ├── MutableCollectionExtensions.swift │ │ │ ├── OptionalExtensions.swift │ │ │ ├── RandomAccessCollectionExtensions.swift │ │ │ ├── RangeReplaceableCollectionExtensions.swift │ │ │ ├── SequenceExtensions.swift │ │ │ ├── SignedIntegerExtensions.swift │ │ │ ├── SignedNumericExtensions.swift │ │ │ ├── StringExtensions.swift │ │ │ └── StringProtocolExtensions.swift │ │ └── UIKit/ │ │ ├── UIActivityExtensions.swift │ │ ├── UIAlertControllerExtensions.swift │ │ ├── UIApplicationExtensions.swift │ │ ├── UIBarButtonItemExtensions.swift │ │ ├── UIBezierPathExtensions.swift │ │ ├── UIButtonExtensions.swift │ │ ├── UICollectionViewExtensions.swift │ │ ├── UIColorExtensions.swift │ │ ├── UIDatePickerExtensions.swift │ │ ├── UIFontExtensions.swift │ │ ├── UIGestureRecognizerExtensions.swift │ │ ├── UIImageExtensions.swift │ │ ├── UIImageViewExtensions.swift │ │ ├── UILabelExtensions.swift │ │ ├── UILayoutPriorityExtensions.swift │ │ ├── UINavigationBarExtensions.swift │ │ ├── UINavigationControllerExtensions.swift │ │ ├── UINavigationItemExtensions.swift │ │ ├── UIRefreshControlExtensions.swift │ │ ├── UIScrollViewExtensions.swift │ │ ├── UISearchBarExtensions.swift │ │ ├── UISegmentedControlExtensions.swift │ │ ├── UISliderExtensions.swift │ │ ├── UIStackViewExtensions.swift │ │ ├── UIStoryboardExtensions.swift │ │ ├── UISwitchExtensions.swift │ │ ├── UITabBarExtensions.swift │ │ ├── UITableViewExtensions.swift │ │ ├── UITextFieldExtensions.swift │ │ ├── UITextViewExtensions.swift │ │ ├── UIViewControllerExtensions.swift │ │ ├── UIViewExtensions.swift │ │ └── UIWindowExtensions.swift │ ├── SwifterSwift.xcodeproj/ │ │ └── project.pbxproj │ ├── SwiftyJSON/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── Source/ │ │ └── SwiftyJSON/ │ │ └── SwiftyJSON.swift │ ├── SwiftyJSON.xcodeproj/ │ │ └── project.pbxproj │ ├── TZImagePickerController/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── TZImagePickerController/ │ │ └── TZImagePickerController/ │ │ ├── NSBundle+TZImagePicker.h │ │ ├── NSBundle+TZImagePicker.m │ │ ├── TZAssetCell.h │ │ ├── TZAssetCell.m │ │ ├── TZAssetModel.h │ │ ├── TZAssetModel.m │ │ ├── TZAuthLimitedFooterTipView.h │ │ ├── TZAuthLimitedFooterTipView.m │ │ ├── TZGifPhotoPreviewController.h │ │ ├── TZGifPhotoPreviewController.m │ │ ├── TZImageCropManager.h │ │ ├── TZImageCropManager.m │ │ ├── TZImageManager.h │ │ ├── TZImageManager.m │ │ ├── TZImagePickerController.bundle/ │ │ │ ├── ar.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── de.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── en.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── es.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── fr.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── ja.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── ko-KP.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── pt.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── ru.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── vi.lproj/ │ │ │ │ └── Localizable.strings │ │ │ ├── zh-Hans.lproj/ │ │ │ │ └── Localizable.strings │ │ │ └── zh-Hant.lproj/ │ │ │ └── Localizable.strings │ │ ├── TZImagePickerController.h │ │ ├── TZImagePickerController.m │ │ ├── TZImageRequestOperation.h │ │ ├── TZImageRequestOperation.m │ │ ├── TZLocationManager.h │ │ ├── TZLocationManager.m │ │ ├── TZPhotoPickerController.h │ │ ├── TZPhotoPickerController.m │ │ ├── TZPhotoPreviewCell.h │ │ ├── TZPhotoPreviewCell.m │ │ ├── TZPhotoPreviewController.h │ │ ├── TZPhotoPreviewController.m │ │ ├── TZProgressView.h │ │ ├── TZProgressView.m │ │ ├── TZVideoCropController.h │ │ ├── TZVideoCropController.m │ │ ├── TZVideoEditedPreviewController.h │ │ ├── TZVideoEditedPreviewController.m │ │ ├── TZVideoPlayerController.h │ │ ├── TZVideoPlayerController.m │ │ ├── UIView+TZLayout.h │ │ └── UIView+TZLayout.m │ ├── TZImagePickerController.xcodeproj/ │ │ └── project.pbxproj │ ├── Target Support Files/ │ │ ├── Alamofire/ │ │ │ ├── Alamofire-Info.plist │ │ │ ├── Alamofire-dummy.m │ │ │ ├── Alamofire-prefix.pch │ │ │ ├── Alamofire-umbrella.h │ │ │ ├── Alamofire.debug.xcconfig │ │ │ ├── Alamofire.modulemap │ │ │ └── Alamofire.release.xcconfig │ │ ├── FDFullscreenPopGesture/ │ │ │ ├── FDFullscreenPopGesture-Info.plist │ │ │ ├── FDFullscreenPopGesture-dummy.m │ │ │ ├── FDFullscreenPopGesture-prefix.pch │ │ │ ├── FDFullscreenPopGesture-umbrella.h │ │ │ ├── FDFullscreenPopGesture.debug.xcconfig │ │ │ ├── FDFullscreenPopGesture.modulemap │ │ │ └── FDFullscreenPopGesture.release.xcconfig │ │ ├── HandyJSON/ │ │ │ ├── HandyJSON-Info.plist │ │ │ ├── HandyJSON-dummy.m │ │ │ ├── HandyJSON-prefix.pch │ │ │ ├── HandyJSON-umbrella.h │ │ │ ├── HandyJSON.debug.xcconfig │ │ │ ├── HandyJSON.modulemap │ │ │ └── HandyJSON.release.xcconfig │ │ ├── IGListDiffKit/ │ │ │ ├── IGListDiffKit-Info.plist │ │ │ ├── IGListDiffKit-dummy.m │ │ │ ├── IGListDiffKit-prefix.pch │ │ │ ├── IGListDiffKit-umbrella.h │ │ │ ├── IGListDiffKit.debug.xcconfig │ │ │ ├── IGListDiffKit.modulemap │ │ │ └── IGListDiffKit.release.xcconfig │ │ ├── IGListKit/ │ │ │ ├── IGListKit-Info.plist │ │ │ ├── IGListKit-dummy.m │ │ │ ├── IGListKit-prefix.pch │ │ │ ├── IGListKit-umbrella.h │ │ │ ├── IGListKit.debug.xcconfig │ │ │ ├── IGListKit.modulemap │ │ │ └── IGListKit.release.xcconfig │ │ ├── IQKeyboardManagerSwift/ │ │ │ ├── IQKeyboardManagerSwift-Info.plist │ │ │ ├── IQKeyboardManagerSwift-dummy.m │ │ │ ├── IQKeyboardManagerSwift-prefix.pch │ │ │ ├── IQKeyboardManagerSwift-umbrella.h │ │ │ ├── IQKeyboardManagerSwift.debug.xcconfig │ │ │ ├── IQKeyboardManagerSwift.modulemap │ │ │ └── IQKeyboardManagerSwift.release.xcconfig │ │ ├── Kingfisher/ │ │ │ ├── Kingfisher-Info.plist │ │ │ ├── Kingfisher-dummy.m │ │ │ ├── Kingfisher-prefix.pch │ │ │ ├── Kingfisher-umbrella.h │ │ │ ├── Kingfisher.debug.xcconfig │ │ │ ├── Kingfisher.modulemap │ │ │ └── Kingfisher.release.xcconfig │ │ ├── Localize-Swift/ │ │ │ ├── Localize-Swift-Info.plist │ │ │ ├── Localize-Swift-dummy.m │ │ │ ├── Localize-Swift-prefix.pch │ │ │ ├── Localize-Swift-umbrella.h │ │ │ ├── Localize-Swift.debug.xcconfig │ │ │ ├── Localize-Swift.modulemap │ │ │ └── Localize-Swift.release.xcconfig │ │ ├── LookinServer/ │ │ │ ├── LookinServer-Info.plist │ │ │ ├── LookinServer-dummy.m │ │ │ ├── LookinServer-prefix.pch │ │ │ ├── LookinServer-umbrella.h │ │ │ ├── LookinServer.debug.xcconfig │ │ │ ├── LookinServer.modulemap │ │ │ └── LookinServer.release.xcconfig │ │ ├── MBProgressHUD/ │ │ │ ├── MBProgressHUD-Info.plist │ │ │ ├── MBProgressHUD-dummy.m │ │ │ ├── MBProgressHUD-prefix.pch │ │ │ ├── MBProgressHUD-umbrella.h │ │ │ ├── MBProgressHUD.debug.xcconfig │ │ │ ├── MBProgressHUD.modulemap │ │ │ └── MBProgressHUD.release.xcconfig │ │ ├── MJRefresh/ │ │ │ ├── MJRefresh-Info.plist │ │ │ ├── MJRefresh-dummy.m │ │ │ ├── MJRefresh-prefix.pch │ │ │ ├── MJRefresh-umbrella.h │ │ │ ├── MJRefresh.debug.xcconfig │ │ │ ├── MJRefresh.modulemap │ │ │ └── MJRefresh.release.xcconfig │ │ ├── Moya/ │ │ │ ├── Moya-Info.plist │ │ │ ├── Moya-dummy.m │ │ │ ├── Moya-prefix.pch │ │ │ ├── Moya-umbrella.h │ │ │ ├── Moya.debug.xcconfig │ │ │ ├── Moya.modulemap │ │ │ └── Moya.release.xcconfig │ │ ├── NSObject+Rx/ │ │ │ ├── NSObject+Rx-Info.plist │ │ │ ├── NSObject+Rx-dummy.m │ │ │ ├── NSObject+Rx-prefix.pch │ │ │ ├── NSObject+Rx-umbrella.h │ │ │ ├── NSObject+Rx.debug.xcconfig │ │ │ ├── NSObject+Rx.modulemap │ │ │ └── NSObject+Rx.release.xcconfig │ │ ├── Pods-FY-IMChat/ │ │ │ ├── Pods-FY-IMChat-Info.plist │ │ │ ├── Pods-FY-IMChat-acknowledgements.markdown │ │ │ ├── Pods-FY-IMChat-acknowledgements.plist │ │ │ ├── Pods-FY-IMChat-dummy.m │ │ │ ├── Pods-FY-IMChat-frameworks.sh │ │ │ ├── Pods-FY-IMChat-umbrella.h │ │ │ ├── Pods-FY-IMChat.debug.xcconfig │ │ │ ├── Pods-FY-IMChat.modulemap │ │ │ └── Pods-FY-IMChat.release.xcconfig │ │ ├── R.swift/ │ │ │ ├── R.swift.debug.xcconfig │ │ │ └── R.swift.release.xcconfig │ │ ├── R.swift.Library/ │ │ │ ├── R.swift.Library-Info.plist │ │ │ ├── R.swift.Library-dummy.m │ │ │ ├── R.swift.Library-prefix.pch │ │ │ ├── R.swift.Library-umbrella.h │ │ │ ├── R.swift.Library.debug.xcconfig │ │ │ ├── R.swift.Library.modulemap │ │ │ └── R.swift.Library.release.xcconfig │ │ ├── ReachabilitySwift/ │ │ │ ├── ReachabilitySwift-Info.plist │ │ │ ├── ReachabilitySwift-dummy.m │ │ │ ├── ReachabilitySwift-prefix.pch │ │ │ ├── ReachabilitySwift-umbrella.h │ │ │ ├── ReachabilitySwift.debug.xcconfig │ │ │ ├── ReachabilitySwift.modulemap │ │ │ └── ReachabilitySwift.release.xcconfig │ │ ├── RxCocoa/ │ │ │ ├── RxCocoa-Info.plist │ │ │ ├── RxCocoa-dummy.m │ │ │ ├── RxCocoa-prefix.pch │ │ │ ├── RxCocoa-umbrella.h │ │ │ ├── RxCocoa.debug.xcconfig │ │ │ ├── RxCocoa.modulemap │ │ │ └── RxCocoa.release.xcconfig │ │ ├── RxRelay/ │ │ │ ├── RxRelay-Info.plist │ │ │ ├── RxRelay-dummy.m │ │ │ ├── RxRelay-prefix.pch │ │ │ ├── RxRelay-umbrella.h │ │ │ ├── RxRelay.debug.xcconfig │ │ │ ├── RxRelay.modulemap │ │ │ └── RxRelay.release.xcconfig │ │ ├── RxSwift/ │ │ │ ├── RxSwift-Info.plist │ │ │ ├── RxSwift-dummy.m │ │ │ ├── RxSwift-prefix.pch │ │ │ ├── RxSwift-umbrella.h │ │ │ ├── RxSwift.debug.xcconfig │ │ │ ├── RxSwift.modulemap │ │ │ └── RxSwift.release.xcconfig │ │ ├── RxTheme/ │ │ │ ├── RxTheme-Info.plist │ │ │ ├── RxTheme-dummy.m │ │ │ ├── RxTheme-prefix.pch │ │ │ ├── RxTheme-umbrella.h │ │ │ ├── RxTheme.debug.xcconfig │ │ │ ├── RxTheme.modulemap │ │ │ └── RxTheme.release.xcconfig │ │ ├── SDWebImage/ │ │ │ ├── SDWebImage-Info.plist │ │ │ ├── SDWebImage-dummy.m │ │ │ ├── SDWebImage-prefix.pch │ │ │ ├── SDWebImage-umbrella.h │ │ │ ├── SDWebImage.debug.xcconfig │ │ │ ├── SDWebImage.modulemap │ │ │ └── SDWebImage.release.xcconfig │ │ ├── SQLiteRepairKit/ │ │ │ ├── SQLiteRepairKit-Info.plist │ │ │ ├── SQLiteRepairKit-dummy.m │ │ │ ├── SQLiteRepairKit-prefix.pch │ │ │ ├── SQLiteRepairKit-umbrella.h │ │ │ ├── SQLiteRepairKit.debug.xcconfig │ │ │ ├── SQLiteRepairKit.modulemap │ │ │ └── SQLiteRepairKit.release.xcconfig │ │ ├── SnapKit/ │ │ │ ├── SnapKit-Info.plist │ │ │ ├── SnapKit-dummy.m │ │ │ ├── SnapKit-prefix.pch │ │ │ ├── SnapKit-umbrella.h │ │ │ ├── SnapKit.debug.xcconfig │ │ │ ├── SnapKit.modulemap │ │ │ └── SnapKit.release.xcconfig │ │ ├── SwiftDate/ │ │ │ ├── SwiftDate-Info.plist │ │ │ ├── SwiftDate-dummy.m │ │ │ ├── SwiftDate-prefix.pch │ │ │ ├── SwiftDate-umbrella.h │ │ │ ├── SwiftDate.debug.xcconfig │ │ │ ├── SwiftDate.modulemap │ │ │ └── SwiftDate.release.xcconfig │ │ ├── SwifterSwift/ │ │ │ ├── SwifterSwift-Info.plist │ │ │ ├── SwifterSwift-dummy.m │ │ │ ├── SwifterSwift-prefix.pch │ │ │ ├── SwifterSwift-umbrella.h │ │ │ ├── SwifterSwift.debug.xcconfig │ │ │ ├── SwifterSwift.modulemap │ │ │ └── SwifterSwift.release.xcconfig │ │ ├── SwiftyJSON/ │ │ │ ├── SwiftyJSON-Info.plist │ │ │ ├── SwiftyJSON-dummy.m │ │ │ ├── SwiftyJSON-prefix.pch │ │ │ ├── SwiftyJSON-umbrella.h │ │ │ ├── SwiftyJSON.debug.xcconfig │ │ │ ├── SwiftyJSON.modulemap │ │ │ └── SwiftyJSON.release.xcconfig │ │ ├── TZImagePickerController/ │ │ │ ├── TZImagePickerController-Info.plist │ │ │ ├── TZImagePickerController-dummy.m │ │ │ ├── TZImagePickerController-prefix.pch │ │ │ ├── TZImagePickerController-umbrella.h │ │ │ ├── TZImagePickerController.debug.xcconfig │ │ │ ├── TZImagePickerController.modulemap │ │ │ └── TZImagePickerController.release.xcconfig │ │ ├── UITableView+FDTemplateLayoutCell/ │ │ │ ├── UITableView+FDTemplateLayoutCell-Info.plist │ │ │ ├── UITableView+FDTemplateLayoutCell-dummy.m │ │ │ ├── UITableView+FDTemplateLayoutCell-prefix.pch │ │ │ ├── UITableView+FDTemplateLayoutCell-umbrella.h │ │ │ ├── UITableView+FDTemplateLayoutCell.debug.xcconfig │ │ │ ├── UITableView+FDTemplateLayoutCell.modulemap │ │ │ └── UITableView+FDTemplateLayoutCell.release.xcconfig │ │ ├── UIView+FDCollapsibleConstraints/ │ │ │ ├── UIView+FDCollapsibleConstraints-Info.plist │ │ │ ├── UIView+FDCollapsibleConstraints-dummy.m │ │ │ ├── UIView+FDCollapsibleConstraints-prefix.pch │ │ │ ├── UIView+FDCollapsibleConstraints-umbrella.h │ │ │ ├── UIView+FDCollapsibleConstraints.debug.xcconfig │ │ │ ├── UIView+FDCollapsibleConstraints.modulemap │ │ │ └── UIView+FDCollapsibleConstraints.release.xcconfig │ │ ├── WCDB.swift/ │ │ │ ├── WCDB.swift-Info.plist │ │ │ ├── WCDB.swift-dummy.m │ │ │ ├── WCDB.swift-prefix.pch │ │ │ ├── WCDB.swift-umbrella.h │ │ │ ├── WCDB.swift.debug.xcconfig │ │ │ ├── WCDB.swift.modulemap │ │ │ └── WCDB.swift.release.xcconfig │ │ ├── WCDBOptimizedSQLCipher/ │ │ │ ├── WCDBOptimizedSQLCipher-Info.plist │ │ │ ├── WCDBOptimizedSQLCipher-dummy.m │ │ │ ├── WCDBOptimizedSQLCipher-prefix.pch │ │ │ ├── WCDBOptimizedSQLCipher-umbrella.h │ │ │ ├── WCDBOptimizedSQLCipher.debug.xcconfig │ │ │ ├── WCDBOptimizedSQLCipher.modulemap │ │ │ └── WCDBOptimizedSQLCipher.release.xcconfig │ │ ├── YBImageBrowser/ │ │ │ ├── YBImageBrowser-Info.plist │ │ │ ├── YBImageBrowser-dummy.m │ │ │ ├── YBImageBrowser-prefix.pch │ │ │ ├── YBImageBrowser-umbrella.h │ │ │ ├── YBImageBrowser.debug.xcconfig │ │ │ ├── YBImageBrowser.modulemap │ │ │ └── YBImageBrowser.release.xcconfig │ │ ├── YYImage/ │ │ │ ├── YYImage-Info.plist │ │ │ ├── YYImage-dummy.m │ │ │ ├── YYImage-prefix.pch │ │ │ ├── YYImage-umbrella.h │ │ │ ├── YYImage.debug.xcconfig │ │ │ ├── YYImage.modulemap │ │ │ └── YYImage.release.xcconfig │ │ └── YYText/ │ │ ├── YYText-Info.plist │ │ ├── YYText-dummy.m │ │ ├── YYText-prefix.pch │ │ ├── YYText-umbrella.h │ │ ├── YYText.debug.xcconfig │ │ ├── YYText.modulemap │ │ └── YYText.release.xcconfig │ ├── UITableView+FDTemplateLayoutCell/ │ │ ├── Classes/ │ │ │ ├── UITableView+FDIndexPathHeightCache.h │ │ │ ├── UITableView+FDIndexPathHeightCache.m │ │ │ ├── UITableView+FDKeyedHeightCache.h │ │ │ ├── UITableView+FDKeyedHeightCache.m │ │ │ ├── UITableView+FDTemplateLayoutCell.h │ │ │ ├── UITableView+FDTemplateLayoutCell.m │ │ │ ├── UITableView+FDTemplateLayoutCellDebug.h │ │ │ └── UITableView+FDTemplateLayoutCellDebug.m │ │ ├── LICENSE │ │ └── README.md │ ├── UITableView+FDTemplateLayoutCell.xcodeproj/ │ │ └── project.pbxproj │ ├── UIView+FDCollapsibleConstraints/ │ │ ├── Classes/ │ │ │ ├── UIView+FDCollapsibleConstraints.h │ │ │ └── UIView+FDCollapsibleConstraints.m │ │ ├── LICENSE │ │ └── README.md │ ├── UIView+FDCollapsibleConstraints.xcodeproj/ │ │ └── project.pbxproj │ ├── WCDB.swift/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── swift/ │ │ └── source/ │ │ ├── abstract/ │ │ │ ├── Column.swift │ │ │ ├── ColumnDef.swift │ │ │ ├── ColumnIndex.swift │ │ │ ├── ColumnResult.swift │ │ │ ├── ColumnType.swift │ │ │ ├── Conflict.swift │ │ │ ├── Convertible.swift │ │ │ ├── Describable.swift │ │ │ ├── Expression.swift │ │ │ ├── ForeignKey.swift │ │ │ ├── FundamentalValue.swift │ │ │ ├── Handle.swift │ │ │ ├── HandleStatement.swift │ │ │ ├── JoinClause.swift │ │ │ ├── LiteralValue.swift │ │ │ ├── ModuleArgument.swift │ │ │ ├── Operable.swift │ │ │ ├── Order.swift │ │ │ ├── OrderTerm.swift │ │ │ ├── Pragma.swift │ │ │ ├── Statement.swift │ │ │ ├── StatementAlterTable.swift │ │ │ ├── StatementAttach.swift │ │ │ ├── StatementCreateIndex.swift │ │ │ ├── StatementCreateTable.swift │ │ │ ├── StatementCreateVirtualTable.swift │ │ │ ├── StatementDelete.swift │ │ │ ├── StatementDetach.swift │ │ │ ├── StatementDropIndex.swift │ │ │ ├── StatementDropTable.swift │ │ │ ├── StatementExplain.swift │ │ │ ├── StatementInsert.swift │ │ │ ├── StatementPragma.swift │ │ │ ├── StatementReindex.swift │ │ │ ├── StatementRelease.swift │ │ │ ├── StatementRollback.swift │ │ │ ├── StatementSavepoint.swift │ │ │ ├── StatementSelect.swift │ │ │ ├── StatementTransaction.swift │ │ │ ├── StatementUpdate.swift │ │ │ ├── StatementVacuum.swift │ │ │ ├── Subquery.swift │ │ │ ├── TableConstraint.swift │ │ │ ├── Tokenize.swift │ │ │ └── Tracer.swift │ │ ├── builtin/ │ │ │ ├── CodableType.swift │ │ │ ├── CommonStatement.swift │ │ │ └── Master.swift │ │ ├── core/ │ │ │ ├── base/ │ │ │ │ ├── Config.swift │ │ │ │ ├── Core.swift │ │ │ │ ├── CoreStatement.swift │ │ │ │ ├── Database.swift │ │ │ │ ├── HandlePool.swift │ │ │ │ ├── RecyclableCore.swift │ │ │ │ └── Transaction.swift │ │ │ ├── binding/ │ │ │ │ ├── ColumnConstraintBinding.swift │ │ │ │ ├── IndexBinding.swift │ │ │ │ ├── Property.swift │ │ │ │ ├── Redirectable.swift │ │ │ │ ├── TableBinding.swift │ │ │ │ ├── TableConstraintBinding.swift │ │ │ │ └── VirtualTableBinding.swift │ │ │ ├── codable/ │ │ │ │ ├── CodingTableKey.swift │ │ │ │ ├── ColumnCodable.swift │ │ │ │ ├── ColumnTypeDecoder.swift │ │ │ │ ├── TableCodable.swift │ │ │ │ ├── TableDecoder.swift │ │ │ │ └── TableEncoder.swift │ │ │ ├── fts/ │ │ │ │ └── WCDBTokenize.swift │ │ │ └── interface/ │ │ │ ├── ChainCall.swift │ │ │ ├── Declare.swift │ │ │ ├── Delete.swift │ │ │ ├── Insert.swift │ │ │ ├── Interface.swift │ │ │ ├── MultiSelect.swift │ │ │ ├── RowSelect.swift │ │ │ ├── Select.swift │ │ │ ├── Selectable.swift │ │ │ ├── Table.swift │ │ │ ├── TableInterface.swift │ │ │ └── Update.swift │ │ └── util/ │ │ ├── Atomic.swift │ │ ├── ConcurrentList.swift │ │ ├── Convenience.swift │ │ ├── Error.swift │ │ ├── File.swift │ │ ├── Lock.swift │ │ ├── Optional.swift │ │ ├── RWLock.swift │ │ ├── Recyclable.swift │ │ ├── SQLite-Bridging.c │ │ ├── SQLite-Bridging.h │ │ ├── SQLite-Bridging.swift │ │ ├── SteadyClock.swift │ │ ├── Tagged.swift │ │ ├── ThreadLocal.swift │ │ ├── TimedQueue.swift │ │ └── WCDB-Bridging.h │ ├── WCDB.swift.xcodeproj/ │ │ └── project.pbxproj │ ├── WCDBOptimizedSQLCipher/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── ext/ │ │ │ ├── fts3/ │ │ │ │ ├── fts3.c │ │ │ │ ├── fts3.h │ │ │ │ ├── fts3Int.h │ │ │ │ ├── fts3_aux.c │ │ │ │ ├── fts3_expr.c │ │ │ │ ├── fts3_hash.c │ │ │ │ ├── fts3_hash.h │ │ │ │ ├── fts3_icu.c │ │ │ │ ├── fts3_porter.c │ │ │ │ ├── fts3_snippet.c │ │ │ │ ├── fts3_tokenize_vtab.c │ │ │ │ ├── fts3_tokenizer.c │ │ │ │ ├── fts3_tokenizer.h │ │ │ │ ├── fts3_tokenizer1.c │ │ │ │ ├── fts3_unicode.c │ │ │ │ ├── fts3_unicode2.c │ │ │ │ └── fts3_write.c │ │ │ ├── icu/ │ │ │ │ ├── icu.c │ │ │ │ └── sqliteicu.h │ │ │ ├── rbu/ │ │ │ │ ├── sqlite3rbu.c │ │ │ │ └── sqlite3rbu.h │ │ │ ├── rtree/ │ │ │ │ ├── rtree.c │ │ │ │ ├── rtree.h │ │ │ │ └── sqlite3rtree.h │ │ │ └── userauth/ │ │ │ └── sqlite3userauth.h │ │ ├── fts5.c │ │ ├── fts5.h │ │ ├── keywordhash.h │ │ ├── opcodes.c │ │ ├── opcodes.h │ │ ├── parse.c │ │ ├── parse.h │ │ ├── sqlite3.h │ │ └── src/ │ │ ├── alter.c │ │ ├── analyze.c │ │ ├── attach.c │ │ ├── auth.c │ │ ├── backup.c │ │ ├── bitvec.c │ │ ├── btmutex.c │ │ ├── btree.c │ │ ├── btree.h │ │ ├── btreeInt.h │ │ ├── build.c │ │ ├── callback.c │ │ ├── complete.c │ │ ├── crypto.c │ │ ├── crypto.h │ │ ├── crypto_cc.c │ │ ├── crypto_impl.c │ │ ├── crypto_libtomcrypt.c │ │ ├── ctime.c │ │ ├── date.c │ │ ├── dbstat.c │ │ ├── delete.c │ │ ├── expr.c │ │ ├── fault.c │ │ ├── fkey.c │ │ ├── func.c │ │ ├── global.c │ │ ├── hash.c │ │ ├── hash.h │ │ ├── hwtime.h │ │ ├── insert.c │ │ ├── legacy.c │ │ ├── loadext.c │ │ ├── main.c │ │ ├── malloc.c │ │ ├── mem0.c │ │ ├── mem1.c │ │ ├── mem2.c │ │ ├── mem3.c │ │ ├── mem5.c │ │ ├── memjournal.c │ │ ├── msvc.h │ │ ├── mutex.c │ │ ├── mutex.h │ │ ├── mutex_noop.c │ │ ├── mutex_unix.c │ │ ├── mutex_wcdb.c │ │ ├── mutex_wcdb.h │ │ ├── notify.c │ │ ├── os.c │ │ ├── os.h │ │ ├── os_common.h │ │ ├── os_setup.h │ │ ├── os_unix.c │ │ ├── os_wcdb.c │ │ ├── os_wcdb.h │ │ ├── pager.c │ │ ├── pager.h │ │ ├── pcache.c │ │ ├── pcache.h │ │ ├── pcache1.c │ │ ├── pragma.c │ │ ├── pragma.h │ │ ├── prepare.c │ │ ├── printf.c │ │ ├── queue.c │ │ ├── queue.h │ │ ├── random.c │ │ ├── resolve.c │ │ ├── rowset.c │ │ ├── select.c │ │ ├── sqlcipher.h │ │ ├── sqlite3ext.h │ │ ├── sqliteInt.h │ │ ├── sqliteLimit.h │ │ ├── status.c │ │ ├── table.c │ │ ├── threads.c │ │ ├── tokenize.c │ │ ├── treeview.c │ │ ├── trigger.c │ │ ├── update.c │ │ ├── utf.c │ │ ├── util.c │ │ ├── vacuum.c │ │ ├── vdbe.c │ │ ├── vdbe.h │ │ ├── vdbeInt.h │ │ ├── vdbeapi.c │ │ ├── vdbeaux.c │ │ ├── vdbeblob.c │ │ ├── vdbemem.c │ │ ├── vdbesort.c │ │ ├── vdbetrace.c │ │ ├── vtab.c │ │ ├── vxworks.h │ │ ├── wal.c │ │ ├── wal.h │ │ ├── walker.c │ │ ├── where.c │ │ ├── whereInt.h │ │ ├── wherecode.c │ │ └── whereexpr.c │ ├── WCDBOptimizedSQLCipher.xcodeproj/ │ │ └── project.pbxproj │ ├── YBImageBrowser/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── Video/ │ │ │ ├── YBIBVideoActionBar.h │ │ │ ├── YBIBVideoActionBar.m │ │ │ ├── YBIBVideoCell+Internal.h │ │ │ ├── YBIBVideoCell.h │ │ │ ├── YBIBVideoCell.m │ │ │ ├── YBIBVideoData+Internal.h │ │ │ ├── YBIBVideoData.h │ │ │ ├── YBIBVideoData.m │ │ │ ├── YBIBVideoTopBar.h │ │ │ ├── YBIBVideoTopBar.m │ │ │ ├── YBIBVideoView.h │ │ │ └── YBIBVideoView.m │ │ └── YBImageBrowser/ │ │ ├── AuxiliaryView/ │ │ │ ├── YBIBAuxiliaryViewHandler.h │ │ │ ├── YBIBAuxiliaryViewHandler.m │ │ │ ├── YBIBLoadingView.h │ │ │ ├── YBIBLoadingView.m │ │ │ ├── YBIBToastView.h │ │ │ └── YBIBToastView.m │ │ ├── Base/ │ │ │ ├── NSObject+YBImageBrowser.h │ │ │ ├── NSObject+YBImageBrowser.m │ │ │ ├── YBIBAnimatedTransition.h │ │ │ ├── YBIBAnimatedTransition.m │ │ │ ├── YBIBCollectionView.h │ │ │ ├── YBIBCollectionView.m │ │ │ ├── YBIBCollectionViewLayout.h │ │ │ ├── YBIBCollectionViewLayout.m │ │ │ ├── YBIBContainerView.h │ │ │ ├── YBIBContainerView.m │ │ │ ├── YBIBDataMediator.h │ │ │ ├── YBIBDataMediator.m │ │ │ ├── YBIBScreenRotationHandler.h │ │ │ ├── YBIBScreenRotationHandler.m │ │ │ └── YBImageBrowser+Internal.h │ │ ├── Helper/ │ │ │ ├── YBIBCopywriter.h │ │ │ ├── YBIBCopywriter.m │ │ │ ├── YBIBIconManager.h │ │ │ ├── YBIBIconManager.m │ │ │ ├── YBIBPhotoAlbumManager.h │ │ │ ├── YBIBPhotoAlbumManager.m │ │ │ ├── YBIBSentinel.h │ │ │ ├── YBIBSentinel.m │ │ │ ├── YBIBUtilities.h │ │ │ └── YBIBUtilities.m │ │ ├── Image/ │ │ │ ├── YBIBImageCache+Internal.h │ │ │ ├── YBIBImageCache.h │ │ │ ├── YBIBImageCache.m │ │ │ ├── YBIBImageCell+Internal.h │ │ │ ├── YBIBImageCell.h │ │ │ ├── YBIBImageCell.m │ │ │ ├── YBIBImageData+Internal.h │ │ │ ├── YBIBImageData.h │ │ │ ├── YBIBImageData.m │ │ │ ├── YBIBImageLayout.h │ │ │ ├── YBIBImageLayout.m │ │ │ ├── YBIBImageScrollView.h │ │ │ ├── YBIBImageScrollView.m │ │ │ ├── YBIBInteractionProfile.h │ │ │ ├── YBIBInteractionProfile.m │ │ │ ├── YBImage.h │ │ │ └── YBImage.m │ │ ├── Protocol/ │ │ │ ├── YBIBCellProtocol.h │ │ │ ├── YBIBDataProtocol.h │ │ │ ├── YBIBGetBaseInfoProtocol.h │ │ │ ├── YBIBOperateBrowserProtocol.h │ │ │ ├── YBIBOrientationReceiveProtocol.h │ │ │ ├── YBImageBrowserDataSource.h │ │ │ └── YBImageBrowserDelegate.h │ │ ├── ToolView/ │ │ │ ├── YBIBSheetView.h │ │ │ ├── YBIBSheetView.m │ │ │ ├── YBIBToolViewHandler.h │ │ │ ├── YBIBToolViewHandler.m │ │ │ ├── YBIBTopView.h │ │ │ └── YBIBTopView.m │ │ ├── WebImageMediator/ │ │ │ ├── YBIBDefaultWebImageMediator.h │ │ │ ├── YBIBDefaultWebImageMediator.m │ │ │ └── YBIBWebImageMediator.h │ │ ├── YBImageBrowser.h │ │ └── YBImageBrowser.m │ ├── YBImageBrowser.xcodeproj/ │ │ └── project.pbxproj │ ├── YYImage/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── YYImage/ │ │ ├── YYAnimatedImageView.h │ │ ├── YYAnimatedImageView.m │ │ ├── YYFrameImage.h │ │ ├── YYFrameImage.m │ │ ├── YYImage.h │ │ ├── YYImage.m │ │ ├── YYImageCoder.h │ │ ├── YYImageCoder.m │ │ ├── YYSpriteSheetImage.h │ │ └── YYSpriteSheetImage.m │ ├── YYImage.xcodeproj/ │ │ └── project.pbxproj │ ├── YYText/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── YYText/ │ │ ├── Component/ │ │ │ ├── YYTextContainerView.h │ │ │ ├── YYTextContainerView.m │ │ │ ├── YYTextDebugOption.h │ │ │ ├── YYTextDebugOption.m │ │ │ ├── YYTextEffectWindow.h │ │ │ ├── YYTextEffectWindow.m │ │ │ ├── YYTextInput.h │ │ │ ├── YYTextInput.m │ │ │ ├── YYTextKeyboardManager.h │ │ │ ├── YYTextKeyboardManager.m │ │ │ ├── YYTextLayout.h │ │ │ ├── YYTextLayout.m │ │ │ ├── YYTextLine.h │ │ │ ├── YYTextLine.m │ │ │ ├── YYTextMagnifier.h │ │ │ ├── YYTextMagnifier.m │ │ │ ├── YYTextSelectionView.h │ │ │ └── YYTextSelectionView.m │ │ ├── String/ │ │ │ ├── YYTextArchiver.h │ │ │ ├── YYTextArchiver.m │ │ │ ├── YYTextAttribute.h │ │ │ ├── YYTextAttribute.m │ │ │ ├── YYTextParser.h │ │ │ ├── YYTextParser.m │ │ │ ├── YYTextRubyAnnotation.h │ │ │ ├── YYTextRubyAnnotation.m │ │ │ ├── YYTextRunDelegate.h │ │ │ └── YYTextRunDelegate.m │ │ ├── Utility/ │ │ │ ├── NSAttributedString+YYText.h │ │ │ ├── NSAttributedString+YYText.m │ │ │ ├── NSParagraphStyle+YYText.h │ │ │ ├── NSParagraphStyle+YYText.m │ │ │ ├── UIPasteboard+YYText.h │ │ │ ├── UIPasteboard+YYText.m │ │ │ ├── UIView+YYText.h │ │ │ ├── UIView+YYText.m │ │ │ ├── YYTextAsyncLayer.h │ │ │ ├── YYTextAsyncLayer.m │ │ │ ├── YYTextTransaction.h │ │ │ ├── YYTextTransaction.m │ │ │ ├── YYTextUtilities.h │ │ │ ├── YYTextUtilities.m │ │ │ ├── YYTextWeakProxy.h │ │ │ └── YYTextWeakProxy.m │ │ ├── YYLabel.h │ │ ├── YYLabel.m │ │ ├── YYText.h │ │ ├── YYTextView.h │ │ └── YYTextView.m │ └── YYText.xcodeproj/ │ └── project.pbxproj ├── LICENSE └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint ## Obj-C/Swift specific *.hmap *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved .build/ # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # Pods/ # # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-Notification@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-Notification@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-Small.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-Small@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-Small@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-Small-40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-Small-40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-60@3x.png", "scale" : "3x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-60@2xstore_1024pt.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/MessageVideoDownload.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "MessageVideoDownload@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "MessageVideoDownload@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/MessageVideoPlay.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "MessageVideoPlay@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "MessageVideoPlay@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/ReceiverImageNodeBorder.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ReceiverImageNodeBorder@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ReceiverImageNodeBorder@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/ic_avatar_placeholder.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "ic_avatar_placeholder.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_avatar_placeholder@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_avatar_placeholder@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/ic_group_placeholder.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "ic_group_placeholder.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_group_placeholder@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_group_placeholder@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/ic_msg_forward_n.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_msg_forward_n@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_msg_forward_n@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/ic_msg_forward_s.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_msg_forward_s@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_msg_forward_s@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_receiver_background_highlight.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_receiver_background_highlight@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_receiver_background_highlight@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_receiver_background_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_receiver_background_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_receiver_background_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_receiver_background_reversed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_receiver_background_reversed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_receiver_background_reversed@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_sender_background_highlight.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_sender_background_highlight@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_sender_background_highlight@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_sender_background_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_sender_background_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_sender_background_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_sender_background_reversed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_sender_background_reversed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_sender_background_reversed@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_voice_receiver_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_voice_receiver_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_voice_receiver_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_voice_receiver_playing_1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_voice_receiver_playing_1@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_voice_receiver_playing_1@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_voice_receiver_playing_2.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_voice_receiver_playing_2@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_voice_receiver_playing_2@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_voice_receiver_playing_3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_voice_receiver_playing_3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_voice_receiver_playing_3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_voice_sender_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_voice_sender_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_voice_sender_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_voice_sender_playing_1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_voice_sender_playing_1@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_voice_sender_playing_1@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_voice_sender_playing_2.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_voice_sender_playing_2@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_voice_sender_playing_2@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/message_voice_sender_playing_3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "message_voice_sender_playing_3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "message_voice_sender_playing_3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/play_btn_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "play_btn_normal.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/play_btn_pressed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "play_btn_pressed.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/player_back_button.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "player_back_button@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "player_back_button@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/ChatView/player_suspend_button.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "player_suspend_button@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "player_suspend_button@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Common/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Common/ic_list_selection.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "ic_list_selection@2x.png", "idiom" : "universal", "scale" : "2x" }, { "filename" : "ic_list_selection@3x.png", "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Common/ic_placeholder.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "ic_placeholder@2x.png", "idiom" : "universal", "scale" : "2x" }, { "filename" : "ic_placeholder@3x.png", "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "2436h", "filename" : "LaunchImage(1125*2436).png", "minimum-system-version" : "11.0", "orientation" : "portrait", "scale" : "3x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "736h", "filename" : "LaunchImage(1242*2208).png", "minimum-system-version" : "8.0", "orientation" : "portrait", "scale" : "3x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "667h", "filename" : "LaunchImage(750*1334).png", "minimum-system-version" : "8.0", "orientation" : "portrait", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "filename" : "LaunchImage(640*960)-1.png", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "retina4", "filename" : "LaunchImage(640*1136)-1.png", "minimum-system-version" : "7.0", "orientation" : "portrait", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "filename" : "LaunchImage(768*1024)-1.png", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "filename" : "LaunchImage(768*1024)@2x-1.png", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "filename" : "Launch(320*480).png", "extent" : "full-screen", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "iphone", "filename" : "LaunchImage(640*960).png", "extent" : "full-screen", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "filename" : "LaunchImage(640*1136).png", "extent" : "full-screen", "subtype" : "retina4", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Mine/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Mine/icon_arrow_right.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "icon_arrow_right@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "icon_arrow_right@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Moments/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Moments/ic_album_reflash.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "ic_album_reflash@2x.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Moments/ic_comment_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "ic_comment_normal@2x.png", "idiom" : "universal", "scale" : "2x" }, { "filename" : "ic_comment_normal@3x.png", "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Moments/ic_comment_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "ic_comment_selected@2x.png", "idiom" : "universal", "scale" : "2x" }, { "filename" : "ic_comment_selected@3x.png", "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Moments/ic_star_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "ic_star_normal@2x.png", "idiom" : "universal", "scale" : "2x" }, { "filename" : "ic_star_normal@3x.png", "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Moments/ic_star_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "ic_star_selected@2x.png", "idiom" : "universal", "scale" : "2x" }, { "filename" : "ic_star_selected@3x.png", "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Moments/nav_camera_black.imageset/Contents.json ================================================ { "images" : [ { "filename" : "nav_camera_black.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Moments/nav_camera_white.imageset/Contents.json ================================================ { "images" : [ { "filename" : "nav_camera_white.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Nav/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Nav/icon_more_add.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "icon_more_add@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "icon_more_add@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Nav/nav_back_black.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "nav_back_black@2x.png", "idiom" : "universal", "scale" : "2x" }, { "filename" : "nav_back_black@3x.png", "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/Nav/nav_back_white.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "nav_back_white@2x.png", "idiom" : "universal", "scale" : "2x" }, { "filename" : "nav_back_white@3x.png", "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/TabBar/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/TabBar/ic_tabbar01_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_tabbar01_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_tabbar01_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/TabBar/ic_tabbar01_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_tabbar01_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_tabbar01_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/TabBar/ic_tabbar02_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_tabbar02_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_tabbar02_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/TabBar/ic_tabbar02_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_tabbar02_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_tabbar02_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/TabBar/ic_tabbar03_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_tabbar03_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_tabbar03_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/TabBar/ic_tabbar03_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_tabbar03_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_tabbar03_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/TabBar/ic_tabbar04_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_tabbar04_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_tabbar04_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Assets.xcassets/TabBar/ic_tabbar04_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ic_tabbar04_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ic_tabbar04_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: JetChat/FY-IMChat/Classes/AppDelegate/AppDelegate+Utils.swift ================================================ // // AppDelegate+Utils.swift // FY-JetChat // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019 Jett. All rights reserved. // import Foundation import Reachability import IQKeyboardManagerSwift import SnapKit import Localize_Swift import RxSwift import RxCocoa import NSObject_Rx import SwifterSwift import RxTheme import WidgetKit extension AppDelegate { // MARK: - NetworkStatusListener // 开始网络连接状态监听 func networkStatusListener() { // 1.设置网络状态消息监听 // 2.获得网络Reachability对象 NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: reachability) do { // 3.开启网络状态消息监听 try reachability.startNotifier() }catch { print("could not start reachability notifier") } } @objc func reachabilityChanged(note: NSNotification) { // 4.准备获取网络连接信息 let curReachability = note.object as! Reachability // 5.判断网络连接状态A switch curReachability.connection { case .wifi: printLog("Reachable via WiFi") case .cellular: printLog("Reachable via Cellular") case .none: printLog("Network not reachable") showReachability("当前网络已断开".rLocalized()) case .unavailable: printLog("Network not unavailable") showReachability("当前网络已断开".rLocalized()) } } func showReachability(_ message: String) { MBHUD.showImageError(message) } // MARK: - AppearanceSetting func appearanceSetting() { // iOS 11 及其以上系统运行 if #available(iOS 11, *) { UIScrollView.appearance().contentInsetAdjustmentBehavior = .never UITableView.appearance().contentInsetAdjustmentBehavior = .never UICollectionView.appearance().contentInsetAdjustmentBehavior = .never } } // MARK: - 设置窗口根控制器 func setupViewController() { window = UIWindow(frame: UIScreen.main.bounds) window?.backgroundColor = UIColor.white let tabBar = FYBaseTabBarController() AppDelegate.app.window?.rootViewController = tabBar AppDelegate.app.window?.makeKeyAndVisible() } // MARK: - 键盘管理 func keyboardManager() { //开启键盘监听 IQKeyboardManager.shared.enable = true //控制点击背景是否收起键盘 IQKeyboardManager.shared.shouldResignOnTouchOutside = true //控制键盘上的工具条文字颜色是否用户自定义 IQKeyboardManager.shared.shouldToolbarUsesTextFieldTintColor = true //IQKeyboardManager.sharedManager().shouldToolbarUsesTextFieldTintColor = true //将右边Done改成完成 //IQKeyboardManager.shared.toolbarDoneBarButtonItemText = "完成" // 控制是否显示键盘上的工具条 IQKeyboardManager.shared.enableAutoToolbar = true //最新版的设置键盘的returnKey的关键字 ,可以点击键盘上的next键,自动跳转到下一个输入框,最后一个输入框点击完成,自动收起键盘 IQKeyboardManager.shared.toolbarManageBehaviour = .byPosition } func configTheme() { // 上次所选主题 if (lastThemeMode == .system) { if #available(iOS 13.0, *) { // iOS13可跟随系统 if UITraitCollection.current.userInterfaceStyle == .dark { print("System Dark mode") themeService.switch(.dark) }else { print("System Light mode") themeService.switch(.light) } } }else { // 自行选择的 switch lastThemeMode { case .light: themeService.switch(.light) default: themeService.switch(.dark) } } } // 刷新Widget数据 func reloadWidgetData() { if #available(iOS 14.0, *) { WidgetCenter.shared.reloadAllTimelines() WidgetCenter.shared.reloadTimelines(ofKind: "JetChatWidget") } else { // Fallback on earlier versions } } // FPS func setupFPSStatus() { #if DEBUG UIApplication.shared.keyWindow?.addSubview(fpsLabel) #endif } func appInitializes() { configTheme() keyboardManager() appearanceSetting() networkStatusListener() LanguageManager.manager.initConfig() // 刷新Widget组件数据 reloadWidgetData() } } extension AppDelegate { /// 获取window当前导航控制器 public var currentViewController: UIViewController? { get { guard UIApplication.shared.keyWindow?.rootViewController != nil else { return nil } if let nav = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController { return nav } if let tab = UIApplication.shared.keyWindow?.rootViewController as? UITabBarController { return tab } if let rootVC = UIApplication.shared.keyWindow?.rootViewController { return rootVC } return nil } } } ================================================ FILE: JetChat/FY-IMChat/Classes/AppDelegate/AppDelegate+Wcdb.swift ================================================ // // AppDelegate+Wcdb.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/9. // Copyright © 2019 Jett. All rights reserved. // import Foundation import WCDBSwift extension AppDelegate { /// 创建数据库-表 func createWcdbTable() { WCDataBaseManager.shared.createTable(table: kTABLE.chat, of: FYMessageChatModel.self) WCDataBaseManager.shared.createTable(table: kTABLE.message, of: FYMessageItem.self) WCDataBaseManager.shared.createTable(table: kTABLE.session, of: FYMessageItem.self) } } ================================================ FILE: JetChat/FY-IMChat/Classes/AppDelegate/AppDelegate.swift ================================================ // // AppDelegate.swift // FY-JetChat // // Created by iOS.Jet on 2019/2/27. // Copyright © 2019 Jett. All rights reserved. // import UIKit import Reachability import WCDBSwift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { /// 单利 static let app: AppDelegate = (UIApplication.shared.delegate as? AppDelegate)! let reachability = try! Reachability.init() var window: UIWindow? var lastThemeMode: FYThemeMode { return FYThemeCenter.shared.currentTheme } /// fps public lazy var fpsLabel: FPSLabel = { let label = FPSLabel(frame: CGRect.init(x: kScreenW - 80, y: (kScreenH - 30)/2, width: 70, height: 30)) label.backgroundColor = .red label.textColor = .white return label }() /// 毛玻璃 fileprivate lazy var visualEffectView: UIVisualEffectView = { let blurEffect = UIBlurEffect.init(style: lastThemeMode == .dark ? .dark : .light) let vi = UIVisualEffectView(effect: blurEffect) vi.frame = UIScreen.main.bounds vi.alpha = 0 return vi }() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // app initialize appInitializes() // init rootViewController setupViewController() // create db table createWcdbTable() // FPS setupFPSStatus() return true } // 快要进入前台 func applicationWillResignActive(_ application: UIApplication) { reloadWidgetData() } // 已经退到后台 func applicationDidEnterBackground(_ application: UIApplication) { reloadWidgetData() } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } // 已经进入到前台 func applicationDidBecomeActive(_ application: UIApplication) { reloadWidgetData() } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } // 接收Widget点击交互 func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { if url.absoluteURL.absoluteString.contains("chatId") { let urlString = url.absoluteURL.absoluteString let chatObjc = urlString.components(separatedBy:"=") if let chatId = chatObjc.last { printLog("Widget safe chatId === \(chatId)") openWidgetChat(chatId: chatId.int ?? -1) } } return true } // MARK: - Widget private func openWidgetChat(chatId: Int) { guard chatId > 0 else { return } let chatModel = FYDBQueryHelper.shared.qureyFromChatId(chatId) let chatVc = FYChatBaseViewController(chatModel: chatModel) if let tabbar = AppDelegate.app.window?.rootViewController as? UITabBarController { if let nav = tabbar.viewControllers?[tabbar.selectedIndex] as? UINavigationController { nav.pushViewController(chatVc, animated: false) } } } // MARK: - VisualEffect private func addWindowVisualEffect() { UIApplication.shared.keyWindow?.addSubview(self.visualEffectView) UIView.animate(withDuration: 0.5) { self.visualEffectView.alpha = 1 } } private func removeWindowVisualEffect() { if !visualEffectView.isEqual(nil) { UIView.animate(withDuration: 0.25) { self.visualEffectView.alpha = 0 } completion: { finished in self.visualEffectView.removeFromSuperview() } } } deinit { // 关闭网络状态消息监听 reachability.stopNotifier() // 移除网络状态消息通知 NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Base/FYBaseIGListViewController.swift ================================================ // // FYBaseIGListViewController.swift // FY-JetChat // // Created by Jett on 2022/4/28. // Copyright © 2022 Jett. All rights reserved. // import UIKit import IGListKit import IGListDiffKit class FYBaseIGListViewController: FYBaseViewController { // MARK: - lazy var var objects: [ListDiffable] = [ListDiffable]() /// 朋友圈-列表 lazy var collectionView: UICollectionView = { let flowLayout = UICollectionViewFlowLayout() let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: flowLayout) if #available(iOS 11.0, *) { collectionView.contentInsetAdjustmentBehavior = .never } else { automaticallyAdjustsScrollViewInsets = false } collectionView.backgroundColor = .clear return collectionView }() lazy var adapter: ListAdapter = { let adapter = ListAdapter(updater: ListAdapterUpdater(), viewController: self) return adapter }() // MARK: - life cycle override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.navigationController?.setNavigationBarHidden(true, animated: animated) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.view.endEditing(true) } override func viewDidLoad() { super.viewDidLoad() view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } self.fd_prefersNavigationBarHidden = true self.automaticallyAdjustsScrollViewInsets = false self.modalPresentationCapturesStatusBarAppearance = false view.addSubview(collectionView) adapter.collectionView = collectionView adapter.dataSource = self makeUI() createViewModel() bindViewModel() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() collectionView.frame = view.bounds } override func touchesBegan(_ touches: Set, with event: UIEvent?) { view.endEditing(true) UIApplication.shared.keyWindow?.endEditing(true) } } // MARK: - extension FYBaseIGListViewController : ListAdapterDataSource { func objects(for listAdapter: ListAdapter) -> [ListDiffable] { return objects } func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController { return ListSectionController() } func emptyView(for listAdapter: ListAdapter) -> UIView? { // 无数据时collectionView的展示 return nil } } extension Notification.Name { struct list { /// 发布通知 static let publish = Notification.Name("list-publish") /// 删除通知 static let delete = Notification.Name("list-delete") /// 定位通知 static let location = Notification.Name("list-location") /// collectionview的评论列表定位到当前通知 static let contentOffset = Notification.Name("list-contentOffset") /// 跳转通知 static let push = Notification.Name("list-push") /// 打开URL通知 static let openURL = Notification.Name("list-openURL") } } extension NSObject { /// 返回类名 static var fy_className: String { get { let a = NSStringFromClass(self) let className = a.split(separator: ".").last return String(className!) } } } public extension UITableView { /// 获取当前`cell`实例 func cell(_ cellClass: T.Type, reuseIdentifier: String? = nil, fromNib: Bool = false) -> T where T : UITableViewCell { let identifier = reuseIdentifier ?? cellClass.fy_className var cell = dequeueReusableCell(withIdentifier: identifier) as? T if cell == nil { if fromNib { cell = Bundle.main.loadNibNamed(cellClass.fy_className, owner: self , options: nil)?.last as? T }else { cell = T(style: .default, reuseIdentifier: identifier) } } return cell! } } public extension UICollectionView { /// 获取当前`cell`实例 func cell(_ cellClass: T.Type, indexPath: IndexPath, reuseIdentifier: String? = nil, fromNib: Bool = false) -> T where T : UICollectionViewCell { let identifier = reuseIdentifier ?? cellClass.fy_className if fromNib { register(UINib(nibName: cellClass.fy_className, bundle: nil), forCellWithReuseIdentifier: identifier) }else { register(cellClass, forCellWithReuseIdentifier: identifier) } return dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! T } } ================================================ FILE: JetChat/FY-IMChat/Classes/Base/FYBaseNavigationController.swift ================================================ // // FYBaseNavigationController.swift // FY-JetChat // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019 Jett. All rights reserved. // import UIKit import RxTheme import RxSwift import RxCocoa class FYBaseNavigationController: UINavigationController { override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } private var titleTextAttributes: [NSAttributedString.Key : NSObject] { return [NSAttributedString.Key.font:UIFont.PingFangMedium(17), NSAttributedString.Key.foregroundColor:UIColor.white] } private lazy var backButton: UIButton = { let button = UIButton(type: .custom) button.frame = CGRect(x: 0, y: 0, width: 40, height: 40) button.theme.buttonImage(from: themed { $0.nav_back_image }, for: .normal) button.imageEdgeInsets = UIEdgeInsets(top: 0, left:-20, bottom: 0, right: 0) button.addTarget(self, action: #selector(pop), for: .touchUpInside) return button }() // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() didInitialize() } // 初始化导航栏 func didInitialize() { self.navigationBar.shadowImage = UIImage() settingNavBarStyle() // 设置代理 delegate = self self.navigationBar.isTranslucent = false self.interactivePopGestureRecognizer?.delegate = self self.automaticallyAdjustsScrollViewInsets = false; } private func addThemeChangedNoti() { NotificationCenter.default.addObserver(self, selector: #selector(settingNavBarStyle), name: .kTraitCollectionDidChange, object: nil) } // MARK: - Notification @objc private func settingNavBarStyle() { themeService.typeStream.subscribe ({ theme in if #available(iOS 13, *) { let appearance = UINavigationBarAppearance() // 重置导航栏背景颜色和阴影 appearance.configureWithOpaqueBackground() appearance.shadowImage = UIImage() appearance.shadowColor = nil appearance.titleTextAttributes = self.titleTextAttributes switch theme.event.element { case .light: // 设置背景色 appearance.theme.backgroundColor = themed { $0.FYColor_Nav_BackgroundColor } self.navigationBar.standardAppearance = appearance self.navigationBar.scrollEdgeAppearance = appearance default: // 设置背景色 appearance.theme.backgroundColor = themed { $0.FYColor_Nav_BackgroundColor } self.navigationBar.standardAppearance = appearance self.navigationBar.scrollEdgeAppearance = appearance } } else { // 导航栏背景颜色 self.navigationBar.theme.backgroundColor = themed { $0.FYColor_Nav_BackgroundColor } } self.navigationBar.theme.barTintColor = themed { $0.FYColor_Nav_BackgroundColor } self.navigationBar.titleTextAttributes = self.titleTextAttributes self.navigationBar.tintColor = .Color_White_FFFFFF }).disposed(by: rx.disposeBag) } } // MARK: - UINavigationControllerDelegate extension FYBaseNavigationController: UINavigationControllerDelegate, UIGestureRecognizerDelegate { override func pushViewController(_ viewController: UIViewController, animated: Bool) { if self.viewControllers.count > 0 { // 隐藏tabBar viewController.hidesBottomBarWhenPushed = true let backItem = UIBarButtonItem(customView: self.backButton) viewController.navigationItem.leftBarButtonItem = backItem // 手势可用 self.interactivePopGestureRecognizer?.isEnabled = true } super.pushViewController(viewController, animated: animated) } /// 返回&出栈 @objc private func pop() { self.popViewController(animated: true) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Base/FYBaseTabBarController.swift ================================================ // // FYBaseTabBarController.swift // FY-JetChat // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019 Jett. All rights reserved. // import UIKit class FYBaseTabBarController: UITabBarController { // MARK: - life cycle private var indexFlag: Int = 0 private lazy var impactGenerator: UIImpactFeedbackGenerator = { let imp = UIImpactFeedbackGenerator(style: .medium) return imp }() override func viewDidLoad() { super.viewDidLoad() didInitialize() createChildVc() } // MARK: - initialize private func didInitialize() { let tabBar = UITabBar.appearance() tabBar.isTranslucent = false if #available(iOS 13, *) { let normalAttr = [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 10), NSAttributedString.Key.foregroundColor : UIColor.tabBarTitleNormalColor()] let selectedAttr = [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 10), NSAttributedString.Key.foregroundColor : UIColor.tabBarTitleSelectColor()] let appearance = UITabBarAppearance() let par = NSMutableParagraphStyle.init() par.alignment = .center let noraml = appearance.stackedLayoutAppearance.normal noraml.titleTextAttributes = normalAttr let selected = appearance.stackedLayoutAppearance.selected selected.titleTextAttributes = selectedAttr // 官方文档写的是 重置背景和阴影为透明 appearance.configureWithTransparentBackground() appearance.configureWithOpaqueBackground() // 设置 self.tabBar.standardAppearance = appearance }else { let normalAttr = [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 13), NSAttributedString.Key.foregroundColor : UIColor.tabBarTitleNormalColor()] let selectedAttr = [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 13), NSAttributedString.Key.foregroundColor : UIColor.tabBarTitleSelectColor()] let tabBarItem = UITabBarItem.appearance() tabBarItem.setTitleTextAttributes(normalAttr, for: .normal) tabBarItem.setTitleTextAttributes(selectedAttr, for: .selected) } // 设置背景颜色 self.tabBar.theme.backgroundColor = themed{ $0.FYColor_Tab_BackgroundColor } } private func createChildVc() { let vc1 = FYSesstionListViewController() addChildViewController(vc1, title: "会话".rLocalized(), image: R.image.ic_tabbar01_normal()!, selectedImage: R.image.ic_tabbar01_selected()!) let vc2 = FYChatRoomListViewController() addChildViewController(vc2, title: "群组".rLocalized(), image: R.image.ic_tabbar02_normal()!, selectedImage: R.image.ic_tabbar02_selected()!) let vc3 = FYContactsListViewController() addChildViewController(vc3, title: "好友".rLocalized(), image: R.image.ic_tabbar03_normal()!, selectedImage: R.image.ic_tabbar03_selected()!) let vc4 = FYMineViewController() addChildViewController(vc4, title: "我".rLocalized(), image: R.image.ic_tabbar04_normal()!, selectedImage: R.image.ic_tabbar04_selected()!) } /// 为tababr点击添加动画 /// - Parameters: /// - tabbar: tabbar /// - item: tabbar菜单 override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { if let index = tabBar.items?.firstIndex(of: item) { if indexFlag != index { animationSelectedIndex(index: index) } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } extension FYBaseTabBarController { fileprivate func addChildViewController(_ vc: UIViewController?, title: String, image: UIImage, selectedImage: UIImage) { if let rootVC = vc { // configure rootVC.title = title rootVC.tabBarItem.image = UIImage.imageWithRenderingMode(image) rootVC.tabBarItem.selectedImage = UIImage.imageWithRenderingMode(selectedImage) // nav let nav = FYBaseNavigationController(rootViewController: rootVC) addChild(nav) } } /// 实现点击选中缩放动画 private func animationSelectedIndex(index: Int) { var btnImageViews: [UIView] = [] for tabBarButton in self.tabBar.subviews { if tabBarButton.isKind(of: NSClassFromString("UITabBarButton")!) { for imageView in tabBarButton.subviews { if imageView.isKind(of: NSClassFromString("UITabBarSwappableImageView")!) { btnImageViews.append(imageView) } } } } // 缩放动画 let pulse = CABasicAnimation(keyPath: "transform.scale") pulse.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) pulse.duration = 0.15 pulse.repeatCount = 1 pulse.autoreverses = true pulse.fromValue = NSNumber(value: 0.7) pulse.toValue = NSNumber(value: 1.1) btnImageViews[index].layer.add(pulse, forKey: nil) indexFlag = index impactGenerator.impactOccurred() } } ================================================ FILE: JetChat/FY-IMChat/Classes/Base/FYBaseViewController.swift ================================================ // // FYBaseViewController.swift // FY-JetChat // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019 Jett. All rights reserved. // 业务基类 import UIKit import HandyJSON import RxSwift import RxCocoa import Moya class FYBaseViewController: UIViewController { override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } let headerRefreshTrigger = PublishSubject() let footerRefreshTrigger = PublishSubject() let loadingTrigger = PublishSubject() let isHeaderLoading = BehaviorRelay(value: false) let isFooterLoading = BehaviorRelay(value: false) let isLoading = BehaviorRelay(value: false) /// 设置导航栏底线 var hideNavShadowImage: Bool = false { didSet { if hideNavShadowImage == true { self.navigationController?.navigationBar.shadowImage = UIImage() } } } /// 基类-普通列表 lazy var plainTabView: UITableView = { let frame = CGRect(x: 0, y: 0, width: kScreenW, height: self.view.height) let table = UITableView(frame: frame, style: .plain) table.backgroundColor = .clear table.separatorColor = .clear table.delegate = self as? UITableViewDelegate table.dataSource = self as? UITableViewDataSource table.showsVerticalScrollIndicator = false table.showsHorizontalScrollIndicator = false table.tableFooterView = UIView() if #available(iOS 11.0, *) { table.contentInsetAdjustmentBehavior = UIScrollView.ContentInsetAdjustmentBehavior.never } else { // Fallback on earlier versions } return table }() /// 基类-分组列表 lazy var groupTabView: UITableView = { let frame = CGRect(x: 0, y: 0, width: kScreenW, height: self.view.height) let table = UITableView(frame: frame, style: .grouped) table.backgroundColor = .clear table.separatorColor = .clear table.delegate = self as? UITableViewDelegate table.dataSource = self as? UITableViewDataSource table.showsVerticalScrollIndicator = false table.showsHorizontalScrollIndicator = false table.tableFooterView = UIView() if #available(iOS 11.0, *) { table.contentInsetAdjustmentBehavior = UIScrollView.ContentInsetAdjustmentBehavior.never } else { // Fallback on earlier versions } return table }() // 解决push时界面卡住问题 override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if self.navigationController?.viewControllers.first == self { self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false }else { self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.view.endEditing(true) } override func viewDidLoad() { super.viewDidLoad() view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V2 } self.fd_prefersNavigationBarHidden = false self.automaticallyAdjustsScrollViewInsets = false self.modalPresentationCapturesStatusBarAppearance = false makeUI() createViewModel() bindViewModel() } override func touchesBegan(_ touches: Set, with event: UIEvent?) { view.endEditing(true) UIApplication.shared.keyWindow?.endEditing(true) } // 系统主题变化 override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) if #available(iOS 13.0, *) { guard let hasUserInterfaceStyleChanged = previousTraitCollection?.hasDifferentColorAppearance(comparedTo: previousTraitCollection) else { return } DispatchQueue.main.async { self.userInterfaceStyleListener(isChanged: hasUserInterfaceStyleChanged) } } else { // Fallback on earlier versions } } // MARK: - 提供子类重写 open func makeUI() { } open func createViewModel() { } open func bindViewModel() { } } // MARK: - Action extension FYBaseViewController { /// 基类方法 - 返回上一个控制器 @objc func popLastVC() { if ((self.navigationController?.viewControllers) != nil) { self.navigationController?.popViewController(animated: true) } } /// 基类方法 - 返回根控制器 @objc func popRootVC() { if ((self.navigationController?.viewControllers) != nil) { self.navigationController?.popToRootViewController(animated: true) } } } // MARK: - UserInterfaceStyleListener extension FYBaseViewController { /// 监听当前系统模式 func userInterfaceStyleListener(isChanged: Bool) { let lastThemeMode = FYThemeCenter.shared.currentTheme if (lastThemeMode != .system) { return } if #available(iOS 13.0, *) { if UITraitCollection.current.userInterfaceStyle == .dark { print("System Dark mode") themeService.switch(.dark) }else { print("System Light mode") themeService.switch(.light) } // 发出系统主题模式改变通知 NotificationCenter.default.post(name: .kTraitCollectionDidChange, object: nil) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Base/Model/FYCellDataConfig.swift ================================================ // // FYCellDataConfig.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/2. // Copyright © 2019 Jett. All rights reserved. // import HandyJSON class FYCellDataConfig: NSObject { var title: String? var subtitle: String? var image: String? var isShow: Bool = false var targetVC: UIViewController? public init(title: String? = "", subtitle: String? = "", image : String? = "", isShow: Bool = false, targetVC: UIViewController? = nil) { super.init() self.title = title self.subtitle = subtitle self.image = image self.isShow = isShow self.targetVC = targetVC } } ================================================ FILE: JetChat/FY-IMChat/Classes/Base/ViewModel/BaseViewModel.swift ================================================ // // BaseViewModel.swift // FY-JetChat // // Created by Jett on 2019/3/15. // Copyright © 2019 Jett. All rights reserved. // import Foundation import RxSwift import RxCocoa import Moya protocol ViewModelType { associatedtype Input associatedtype Output func transform(input: Input) -> Output } class BaseViewModel: NSObject { var page = 0 var pageSize = 20 let loading = ActivityIndicator() let headerLoading = ActivityIndicator() let footerLoading = ActivityIndicator() let error = ErrorTracker() override init() { super.init() error.asDriver().drive(onNext: { (error) in printLog("ViewModel error:\(error.localizedDescription)") }).disposed(by: rx.disposeBag) } deinit { print("\(type(of: self)):Deinited") } } ================================================ FILE: JetChat/FY-IMChat/Classes/Common/AppCommon.swift ================================================ // // AppCommon.swift // FY-JetChat // // Created by iOS.Jet on 2019/2/27. // Copyright © 2019 Jett. All rights reserved. // import UIKit import Reachability import SnapKit // MARK: - 常用距离 public let kScreen = UIScreen.main.bounds public let kScreenW = UIScreen.main.bounds.size.width public let kScreenH = UIScreen.main.bounds.size.height public let kMargin = kFitScale(at: 10) public let kLineMargin = kFitScale(at: 1) public let isIphoneX = { () -> Bool in var isX = false if #available(iOS 11.0, *) { isX = (UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0) > CGFloat(0.0) } return isX } public let kWindowSafeAreaInset = { () -> UIEdgeInsets in var insets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0) if #available(iOS 11.0, *) { insets = UIApplication.shared.keyWindow?.safeAreaInsets ?? insets } return insets } public let kSafeAreaTop = kWindowSafeAreaInset().top == 0 ? 20 : kWindowSafeAreaInset().top public let kSafeAreaBottom = kWindowSafeAreaInset().bottom public let kStatusH = kWindowSafeAreaInset().top public let kNavigaH = 44 + kStatusH public let kTabBarH = 49 + kSafeAreaBottom // MARK: - 动画执行时间 let kDuration: TimeInterval = 3.0 // MARK: - 偏好设置存储 /// 应用是否首次启动 public let kAppLaunchUserDefaultsKey = "kAppLaunchUserDefaultsKey" /// 应用当前语言设置 public let kAppLanguageUserDefaultsKey = "kAppLanguageUserDefaultsKey" /// 白天夜间模式 public let kThemeSettingUserDefaultKey = "kThemeSettingUserDefaultKey" // MARK: - FileManager Path public let cachesURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] public let libraryURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0] // MARK: - 当前窗口 let kCurrentWindow = UIApplication.shared.keyWindow // MARK: - 自定义打印日志 func printLog(_ message: T, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line){ //文件名、方法、行号、打印信息 #if DEBUG print("方法:\(methodName)\n行号:\(lineNumber)\n打印信息:\(message)"); #endif } // MARK: - 系统信息相关 let infoDictionary = Bundle.main.infoDictionary! /// 程序名称 let appDisplayName = infoDictionary["CFBundleDisplayName"] as! String /// 主程序版本号 let majorVersion = infoDictionary["CFBundleShortVersionString"] as! String /// 设备唯一标识码 public let kDeviceUUID = { () -> String in if let uuid = UIDevice.current.identifierForVendor?.uuidString { return uuid } return "" } // MARK: - 屏幕适配375 | 6s尺寸 func kFitScale(at ratio: CGFloat) -> CGFloat { return (UIScreen.main.bounds.width / 375) * ratio } // MARK: - 朋友圈 let mImageW = (kScreenW - 156) / 3 ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/Array/Array+Extension.swift ================================================ // // NSArray+Extension.swift // EmiotAppCode // // Created by Jett on 2017/5/27. // Copyright © 2017年 Zehuihong. All rights reserved. // import UIKit extension Array { subscript (safe index:Int) -> Element?{ return (0.. String { let data = try? JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions.prettyPrinted) let strJson = NSString(data: data!, encoding: String.Encoding.utf8.rawValue) return strJson! as String } func toData() -> Data { let data = try? JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions.prettyPrinted) return data! } } extension Array where Element: Hashable { // 数组去重处理 var unique:[Element] { var uniq = Set() uniq.reserveCapacity(self.count) return self.filter { return uniq.insert($0).inserted } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/Dictionary/Dictionary+Exted.swift ================================================ // // Dictionary+EXTension.swift // SZBookMall // // Created by fisland on 2017/11/20. // Copyright © 2017年 Zehuihong. All rights reserved. // import Foundation extension Dictionary { static func += ( left: inout Dictionary, right: Dictionary) { for (k, v) in right { left.updateValue(v, forKey: k) } } func toJSONString() -> String { let data = try? JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions.prettyPrinted) let jsonString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue) return jsonString! as String } func toData() -> Data { let data = try? JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions.prettyPrinted) return data! } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/Notification/Notification+Name.swift ================================================ // // Notification+Name.swift // FY-JetChat // // Created by iOS.Jet on 2019/10/10. // Copyright © 2019 Jett. All rights reserved. // 通知便捷使用 import Foundation // MARK: - NSNotificationName extension NSNotification.Name { /// 主题更改 public static let kThemeDidChanged = Notification.Name("kThemeDidChanged") /// 网络状态监听 public static let kReachabilityChanged = Notification.Name(rawValue:"ReachabilityChangedNotification") /// 刷新消息列表 public static let kNeedRefreshSesstionList = Notification.Name(rawValue:"kNeedRefreshSesstionList") /// 退出群聊 public static let kNeedRefreshChatInfoList = Notification.Name(rawValue:"kNeedRefreshChatInfoList") /// 系统主题模式改变 public static let kTraitCollectionDidChange = Notification.Name(rawValue:"kTraitCollectionDidChange") } // MARK: - NotificationCenter public typealias NotiCenter = NotificationCenter extension NotificationCenter { /// 发送通知内容 static func postNoti(_ name: NSNotification.Name, object: Any? = nil) { self.default.post(name: name, object: nil) } /// 发送通知传递实体内容 static func postNotiWithUserInfo(_ name: NSNotification.Name, object: Any? = nil, userInfo: [AnyHashable : Any] = [AnyHashable : Any]()) { self.default.post(name: name, object: nil, userInfo: userInfo) } func observe(name: NSNotification.Name?, object: Any?, queue: OperationQueue?, using block: @escaping (Notification) ->()) -> NotificationToken { let token = addObserver(forName: name, object: object, queue: queue, using: block) return NotificationToken(notificationCenter: self, token: token) } class func addObserve(target: Any, action: Selector, name: NSNotification.Name?, object: Any? = nil) { self.default.addObserver(target, selector: action, name: name, object: object) } } final class NotificationToken: NSObject { let notificcationCenter: NotificationCenter let token: Any init(notificationCenter: NotificationCenter = .default, token: Any) { self.notificcationCenter = notificationCenter self.token = token } deinit { notificcationCenter.removeObserver(token) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/RxSwift/JFButton+Rx.swift ================================================ // // JFButton+Rx.swift // MK-MChain // // Created by Jett on 2019/5/30. // Copyright © 2019 miku. All rights reserved. // import Foundation import Kingfisher import RxCocoa import RxSwift private var KJFButtonIndicator = "KJFButtonIndicator" private var KJFButtonCurrentText = "KJFButtonCurrentText" extension Reactive where Base: UIButton { //是否秀菊花 public var isShowIndicator: Binder{ return Binder(self.base, binding: { btn, active in if active{ objc_setAssociatedObject(btn, &KJFButtonCurrentText, btn.currentTitle, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) btn.setTitle("", for: .normal) btn.whiteIndicator.startAnimating() btn.isUserInteractionEnabled = false } else{ btn.whiteIndicator.stopAnimating() if let title = objc_getAssociatedObject(btn, &KJFButtonCurrentText) as? String{ btn.setTitle(title, for: .normal) } btn.isUserInteractionEnabled = true } }) } } public extension UIButton { var whiteIndicator : UIActivityIndicatorView{ get { var indicator = objc_getAssociatedObject(self, &KJFButtonIndicator) as? UIActivityIndicatorView if indicator == nil { indicator = UIActivityIndicatorView(style: .white) indicator!.center = CGPoint(x: self.bounds.width / 2, y: self.bounds.height / 2) self.addSubview(indicator!) //indicator.startAnimating() } self.whiteIndicator = indicator! //c重新设置中心点 indicator!.center = CGPoint(x: self.bounds.width / 2, y: self.bounds.height / 2) return indicator! } set { objc_setAssociatedObject(self, &KJFButtonIndicator, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/RxSwift/MJRefresh+Rx.swift ================================================ // // MJRefresh+Rx.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/13. // Copyright © 2019 Jett. All rights reserved. // import Foundation import MJRefresh import RxSwift import RxCocoa //对MJRefreshComponent增加rx扩展 extension Reactive where Base: MJRefreshComponent { /// 正在刷新事件 var refreshing: ControlEvent { let source: Observable = Observable.create { [weak control = self.base] observer in if let control = control { control.refreshingBlock = { observer.on(.next(())) } } return Disposables.create() } return ControlEvent(events: source) } /// 正在刷新 var isRefreshing: Binder { return Binder(base) { refresh, isRefresh in if isRefresh { refresh.beginRefreshing() } } } /// 停止刷新 var endRefreshing: Binder { return Binder(base) { refresh, isEnd in if isEnd { refresh.endRefreshing() } } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/RxSwift/Observable+Operators.swift ================================================ // // Observable+Operators.swift // Cake Builder // // Created by Khoren Markosyan on 10/19/16. // Copyright © 2016 Khoren Markosyan. All rights reserved. // import Foundation import RxSwift import RxCocoa protocol OptionalType { associatedtype Wrapped var value: Wrapped? { get } } extension Optional: OptionalType { var value: Wrapped? { return self } } extension Observable where Element: OptionalType { func filterNil() -> Observable { return flatMap { (element) -> Observable in if let value = element.value { return .just(value) } else { return .empty() } } } func filterNilKeepOptional() -> Observable { return self.filter { (element) -> Bool in return element.value != nil } } func replaceNil(with nilValue: Element.Wrapped) -> Observable { return flatMap { (element) -> Observable in if let value = element.value { return .just(value) } else { return .just(nilValue) } } } } protocol BooleanType { var boolValue: Bool { get } } extension Bool: BooleanType { var boolValue: Bool { return self } } // Maps true to false and vice versa extension Observable where Element: BooleanType { func not() -> Observable { return self.map { input in return !input.boolValue } } } extension Observable where Element: Equatable { func ignore(value: Element) -> Observable { return filter { (selfE) -> Bool in return value != selfE } } } extension ObservableType where Element == Bool { /// Boolean not operator public func not() -> Observable { return self.map(!) } } extension SharedSequenceConvertibleType { func mapToVoid() -> SharedSequence { return map { _ in } } } extension ObservableType { func asDriverOnErrorJustComplete() -> Driver { return asDriver { error in assertionFailure("Error \(error)") return Driver.empty() } } func mapToVoid() -> Observable { return map { _ in } } } //https://gist.github.com/brocoo/aaabf12c6c2b13d292f43c971ab91dfa extension Reactive where Base: UIScrollView { public var reachedBottom: Observable { let scrollView = self.base as UIScrollView return self.contentOffset.flatMap { [weak scrollView] (contentOffset) -> Observable in guard let scrollView = scrollView else { return Observable.empty() } let visibleHeight = scrollView.frame.height - self.base.contentInset.top - scrollView.contentInset.bottom let y = contentOffset.y + scrollView.contentInset.top let threshold = max(0.0, scrollView.contentSize.height - visibleHeight) return (y > threshold) ? Observable.just(()) : Observable.empty() } } } // Two way binding operator between control property and variable, that's all it takes { infix operator <-> : DefaultPrecedence func nonMarkedText(_ textInput: UITextInput) -> String? { let start = textInput.beginningOfDocument let end = textInput.endOfDocument guard let rangeAll = textInput.textRange(from: start, to: end), let text = textInput.text(in: rangeAll) else { return nil } guard let markedTextRange = textInput.markedTextRange else { return text } guard let startRange = textInput.textRange(from: start, to: markedTextRange.start), let endRange = textInput.textRange(from: markedTextRange.end, to: end) else { return text } return (textInput.text(in: startRange) ?? "") + (textInput.text(in: endRange) ?? "") } func <-> (textInput: TextInput, variable: BehaviorRelay) -> Disposable { let bindToUIDisposable = variable.asObservable() .bind(to: textInput.text) let bindToVariable = textInput.text .subscribe(onNext: { [weak base = textInput.base] value in guard let base = base else { return } let nonMarkedTextValue = nonMarkedText(base) /** In some cases `textInput.textRangeFromPosition(start, toPosition: end)` will return nil even though the underlying value is not nil. This appears to be an Apple bug. If it's not, and we are doing something wrong, please let us know. The can be reproed easily if replace bottom code with if nonMarkedTextValue != variable.value { variable.value = nonMarkedTextValue ?? "" } and you hit "Done" button on keyboard. */ if let nonMarkedTextValue = nonMarkedTextValue, nonMarkedTextValue != variable.value { variable.accept(nonMarkedTextValue) } }, onCompleted: { bindToUIDisposable.dispose() }) return Disposables.create(bindToUIDisposable, bindToVariable) } func <-> (property: ControlProperty, variable: BehaviorRelay) -> Disposable { if T.self == String.self { #if DEBUG fatalError("It is ok to delete this message, but this is here to warn that you are maybe trying to bind to some `rx.text` property directly to variable.\n" + "That will usually work ok, but for some languages that use IME, that simplistic method could cause unexpected issues because it will return intermediate results while text is being inputed.\n" + "REMEDY: Just use `textField <-> variable` instead of `textField.rx.text <-> variable`.\n" + "Find out more here: https://github.com/ReactiveX/RxSwift/issues/649\n" ) #endif } let bindToUIDisposable = variable.asObservable() .bind(to: property) let bindToVariable = property .subscribe(onNext: { value in variable.accept(value) }, onCompleted: { bindToUIDisposable.dispose() }) return Disposables.create(bindToUIDisposable, bindToVariable) } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/SnapKit/ConstraintArray+Extensions.swift ================================================ import SnapKit extension String { func size(withFont font: UIFont) -> CGSize { let attributes = [NSAttributedString.Key.font: font] return (self as NSString).size(withAttributes: attributes) } } extension Array where Element: ConstraintView { public var snp: ConstraintArrayDSL { return ConstraintArrayDSL(array: self) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/SnapKit/ConstraintArrayDSL.swift ================================================ import SnapKit public struct ConstraintArrayDSL { @discardableResult public func prepareConstraints(_ closure: (_ make: ConstraintMaker) -> Void) -> [Constraint] { var constraints = Array() for view in self.array { constraints.append(contentsOf: view.snp.prepareConstraints(closure)) } return constraints } public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) { for view in self.array { view.snp.makeConstraints(closure) } } public func remakeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) { for view in self.array { view.snp.remakeConstraints(closure) } } public func updateConstraints(_ closure: (_ make: ConstraintMaker) -> Void) { for view in self.array { view.snp.updateConstraints(closure) } } public func removeConstraints() { for view in self.array { view.snp.removeConstraints() } } /// distribute with fixed spacing /// /// - Parameters: /// - axisType: which axis to distribute items along /// - fixedSpacing: the spacing between each item /// - leadSpacing: the spacing before the first item and the container /// - tailSpacing: the spacing after the last item and the container public func distributeViewsAlong(axisType:NSLayoutConstraint.Axis, fixedSpacing:CGFloat, leadSpacing:CGFloat = 0, tailSpacing:CGFloat = 0) { guard self.array.count > 1, let tempSuperView = commonSuperviewOfViews() else { return } if axisType == .horizontal { var prev : ConstraintView? for (i, v) in self.array.enumerated() { v.snp.makeConstraints({ (make) in if prev != nil { make.width.equalTo(prev!) make.left.equalTo((prev?.snp.right)!).offset(fixedSpacing) if (i == self.array.count - 1) {//last one make.right.equalTo(tempSuperView).offset(-tailSpacing); } }else { make.left.equalTo(tempSuperView).offset(leadSpacing); } }) prev = v; } }else { var prev : ConstraintView? for (i, v) in self.array.enumerated() { v.snp.makeConstraints({ (make) in if prev != nil { make.height.equalTo(prev!) make.top.equalTo((prev?.snp.bottom)!).offset(fixedSpacing) if (i == self.array.count - 1) {//last one make.bottom.equalTo(tempSuperView).offset(-tailSpacing); } }else { make.top.equalTo(tempSuperView).offset(leadSpacing); } }) prev = v; } } } /// distribute with fixed item size /// /// - Parameters: /// - axisType: which axis to distribute items along /// - fixedItemLength: the fixed length of each item /// - leadSpacing: the spacing before the first item and the container /// - tailSpacing: the spacing after the last item and the container public func distributeViewsAlong(axisType:NSLayoutConstraint.Axis, fixedItemLength:CGFloat, leadSpacing:CGFloat = 0, tailSpacing:CGFloat = 0) { guard self.array.count > 1, let tempSuperView = commonSuperviewOfViews() else { return } if axisType == .horizontal { var prev : ConstraintView? for (i, v) in self.array.enumerated() { v.snp.makeConstraints({ (make) in make.width.equalTo(fixedItemLength) if prev != nil { if (i == self.array.count - 1) {//last one make.right.equalTo(tempSuperView).offset(-tailSpacing); }else { let offset = (CGFloat(1) - (CGFloat(i) / CGFloat(self.array.count - 1))) * (fixedItemLength + leadSpacing) - CGFloat(i) * tailSpacing / CGFloat(self.array.count - 1) make.right .equalTo(tempSuperView) .multipliedBy(CGFloat(i) / CGFloat(self.array.count - 1)) .offset(offset) } }else { make.left.equalTo(tempSuperView).offset(leadSpacing); } }) prev = v; } }else { var prev : ConstraintView? for (i, v) in self.array.enumerated() { v.snp.makeConstraints({ (make) in make.height.equalTo(fixedItemLength) if prev != nil { if (i == self.array.count - 1) {//last one make.bottom.equalTo(tempSuperView).offset(-tailSpacing); }else { let offset = (CGFloat(1) - (CGFloat(i) / CGFloat(self.array.count - 1))) * (fixedItemLength + leadSpacing) - CGFloat(i) * tailSpacing / CGFloat(self.array.count - 1) make.bottom .equalTo(tempSuperView) .multipliedBy(CGFloat(i) / CGFloat(self.array.count-1)) .offset(offset) } }else { make.top.equalTo(tempSuperView).offset(leadSpacing); } }) prev = v; } } } public func distributeSudokuViews(fixedItemWidth: CGFloat, fixedItemHeight: CGFloat, warpCount: Int, edgeInset: UIEdgeInsets = UIEdgeInsets.zero) { guard self.array.count > 1, warpCount >= 1, let tempSuperView = commonSuperviewOfViews() else { return } let rowCount = self.array.count % warpCount == 0 ? self.array.count / warpCount : self.array.count / warpCount + 1; let columnCount = warpCount for (i,v) in self.array.enumerated() { let currentRow = i / warpCount let currentColumn = i % warpCount v.snp.makeConstraints({ (make) in make.width.equalTo(fixedItemWidth) make.height.equalTo(fixedItemHeight) if currentRow == 0 {//fisrt row make.top.equalTo(tempSuperView).offset(edgeInset.top) } if currentRow == rowCount - 1 {//last row make.bottom.equalTo(tempSuperView).offset(-edgeInset.bottom) } if currentRow != 0 && currentRow != rowCount - 1 {//other row let offset = (CGFloat(1) - CGFloat(currentRow) / CGFloat(rowCount - 1)) * (fixedItemHeight + edgeInset.top) - CGFloat(currentRow) * edgeInset.bottom / CGFloat(rowCount - 1) make.bottom .equalTo(tempSuperView) .multipliedBy(CGFloat(currentRow) / CGFloat(rowCount - 1)) .offset(offset); } if currentColumn == 0 {//first col make.left.equalTo(tempSuperView).offset(edgeInset.left) } if currentColumn == columnCount - 1 {//last col make.right.equalTo(tempSuperView).offset(-edgeInset.right) } if currentColumn != 0 && currentColumn != columnCount - 1 {//other col let offset = (CGFloat(1) - CGFloat(currentColumn) / CGFloat(columnCount - 1)) * (fixedItemWidth + edgeInset.left) - CGFloat(currentColumn) * edgeInset.right / CGFloat(columnCount - 1) make.right .equalTo(tempSuperView) .multipliedBy(CGFloat(currentColumn) / CGFloat(columnCount - 1)) .offset(offset); } }) } } public func distributeSudokuViews(fixedLineSpacing: CGFloat, fixedInteritemSpacing: CGFloat, warpCount: Int, edgeInset: UIEdgeInsets = UIEdgeInsets.zero) { guard self.array.count > 1, warpCount >= 1, let tempSuperView = commonSuperviewOfViews() else { return } let columnCount = warpCount let rowCount = self.array.count % warpCount == 0 ? self.array.count / warpCount : self.array.count / warpCount + 1; var prev : ConstraintView? for (i,v) in self.array.enumerated() { let currentRow = i / warpCount let currentColumn = i % warpCount v.snp.makeConstraints({ (make) in if prev != nil { make.width.height.equalTo(prev!) } if currentRow == 0 {//fisrt row make.top.equalTo(tempSuperView).offset(edgeInset.top) } if currentRow == rowCount - 1 {//last row if currentRow != 0 && i - columnCount >= 0 { make.top.equalTo(self.array[i-columnCount].snp.bottom).offset(fixedLineSpacing) } make.bottom.equalTo(tempSuperView).offset(-edgeInset.bottom) } if currentRow != 0 && currentRow != rowCount - 1 {//other row make.top.equalTo(self.array[i-columnCount].snp.bottom).offset(fixedLineSpacing); } if currentColumn == 0 {//first col make.left.equalTo(tempSuperView).offset(edgeInset.left) } if currentColumn == warpCount - 1 {//last col if currentColumn != 0 { make.left.equalTo(prev!.snp.right).offset(fixedInteritemSpacing) } make.right.equalTo(tempSuperView).offset(-edgeInset.right) } if currentColumn != 0 && currentColumn != warpCount - 1 {//other col make.left.equalTo(prev!.snp.right).offset(fixedInteritemSpacing); } }) prev = v } } public var target: AnyObject? { return self.array as AnyObject } internal let array: Array internal init(array: Array) { self.array = array } } private extension ConstraintArrayDSL { func commonSuperviewOfViews() -> ConstraintView? { var commonSuperview : ConstraintView? var previousView : ConstraintView? for view in self.array { if previousView != nil { commonSuperview = view.closestCommonSuperview(commonSuperview) }else { commonSuperview = view } previousView = view } return commonSuperview } } private extension ConstraintView { func closestCommonSuperview(_ view : ConstraintView?) -> ConstraintView? { var closestCommonSuperview: ConstraintView? var secondViewSuperview: ConstraintView? = view while closestCommonSuperview == nil && secondViewSuperview != nil { var firstViewSuperview: ConstraintView? = self while closestCommonSuperview == nil && firstViewSuperview != nil { if secondViewSuperview == firstViewSuperview { closestCommonSuperview = secondViewSuperview } firstViewSuperview = firstViewSuperview?.superview } secondViewSuperview = secondViewSuperview?.superview } return closestCommonSuperview } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/Strings/String+Date.swift ================================================ // // String+Date.swift // FY-JetChat // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019 Jett. All rights reserved. // import UIKit import Foundation import SwiftDate extension String { /// 通用时间 HH:mm MM-dd /// /// - Returns: 本地时间 func commonDateString() -> String? { let region = Region.current let update = self.toISODate(region: region) let date = update?.date.convertTo(region: region) let now = date?.toFormat("HH:mm MM-dd", locale: region.locale) return now } /// 日期时间 yyyy-MM-dd HH:mm:ss /// /// - Returns: 本地时间 func allDateString() -> String? { let region = Region.current let update = self.toISODate(region: region) let date = update?.date.convertTo(region: region) let now = date?.toFormat("yyyy-MM-dd HH:mm:ss", locale: region.locale) return now } /// 日期 yyyy-MM-dd /// /// - Returns: 本地时间 func dateDayString() -> String? { let region = Region.current let update = self.toISODate(region: region) let date = update?.date.convertTo(region: region) let now = date?.toFormat("yyyy-MM-dd", locale: region.locale) return now } /// 日期 yyyy.MM.dd /// /// - Returns: 本地时间 func dotDateString() -> String? { let region = Region.current let update = self.toISODate(region: region) let date = update?.date.convertTo(region: region) let now = date?.toFormat("yyyy.MM.dd", locale: region.locale) return now } /** 时间戳转为时间 - returns: 时间字符串 */ func timeStampToString() -> String { let string = NSString(string: self) let timeSta: TimeInterval = string.doubleValue / 1000.0 let dfmatter = DateFormatter() dfmatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let date = Date(timeIntervalSince1970: timeSta) return dfmatter.string(from: date) } /** 时间戳转为NSDate - returns: NSDate */ func timeStampToDate() -> Date { let string = NSString(string: self) let timeSta: TimeInterval = string.doubleValue let date = Date(timeIntervalSince1970: timeSta) return date } /** 时间转为时间戳 - returns: 时间戳字符串 */ func stringToTimeStamp() -> String { let dfmatter = DateFormatter() dfmatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let date = dfmatter.date(from: self) let dateStamp: TimeInterval = date!.timeIntervalSince1970 let dateSt:Int = Int(dateStamp) return String(dateSt) } /** 时间戳处理(当前时间比较) - returns: 对比时间 */ func compareCurrentTime() -> String { let string = NSString(string: self) let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let timeDate = formatter.date(from: string as String) let currentDate = Date() let timeInterval: TimeInterval = currentDate.timeIntervalSince(timeDate!) var temp: Double = 0 var result = String() if (timeInterval/60 < 1) { result = "刚刚" }else if ((timeInterval/60)<60){ temp = timeInterval/60 result = String(format:"%ld分钟前", Int(temp)) }else if ((timeInterval/60/60)<24){ temp = timeInterval/60/60 result = String(format:"%ld小时前", Int(temp)) }else if ((timeInterval/60/60/24)<30){ temp = timeInterval/60/60/24 result = String(format:"%ld天前", Int(temp)) }else if ((timeInterval/60/60/24/30)<12){ temp = timeInterval/60/60/24/30 result = String(format:"%ld月前", Int(temp)) }else{ temp = timeInterval/60/60/24/30/12; result = String(format:"%ld年前", Int(temp)) } return result } /** 传入cell文本内容,解析成元素为昵称的数组 - returns: 昵称数组 */ func checkAtUserNickname() -> [String]? { do { let pattern = "@\\S*" let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) let results = regex.matches(in: self, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.length)) var resultStrings = [String]() for result in results { resultStrings.append(String((self as NSString).substring(with: result.range))) } return resultStrings } catch { return nil } } func validNumber() -> Bool{ do { let pattern = "^[0-9]*$" let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) let results = regex.matches(in: self, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.length)) return results.count > 0 }catch { return false } } /// 获取文本的最大高度 func getTextMaxHeigh(_ font: UIFont, width:CGFloat) -> CGFloat { let normalText = NSString.init(string: self) let size = CGSize(width: width, height: 1000) let dic: [NSAttributedString.Key: Any] = [NSAttributedString.Key(rawValue: NSAttributedString.Key.font.rawValue): font] let stringSize = normalText.boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: dic, context:nil).size return stringSize.height } /// 获取文本的最大宽度 func getTextMaxWidth(_ font: UIFont, height: CGFloat) -> CGFloat { let normalText = NSString.init(string: self) let size = CGSize(width: 1000, height: height) let dic:[NSAttributedString.Key: Any] = [NSAttributedString.Key(rawValue: NSAttributedString.Key.font.rawValue): font] let stringSize = normalText.boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: dic, context:nil).size return stringSize.width } } // MARK: - 当前时间比较 extension String { func detailDate24Week(time: Double, format: String = "yyyy-MM-dd HH:mm:ss") -> String{ let formatter = DateFormatter() formatter.dateStyle = DateFormatter.Style.medium formatter.timeStyle = DateFormatter.Style.short formatter.dateFormat = format let timeInterval = TimeInterval(time / 1000.0) //获取到Date let confromTimeDate = Date.init(timeIntervalSince1970: timeInterval) var calender = NSCalendar.current let unitFlags = Set([.year,.month,.day,.hour,.minute]) let comp = calender.dateComponents(unitFlags, from: Date()) _ = String(comp.year!) _ = String(comp.day!) let comp2 = calender.dateComponents(unitFlags, from: confromTimeDate) _ = String(comp2.year!) _ = String(comp2.month!) _ = String(comp2.day!) var hour = String(comp2.hour!) var minute = String(comp2.minute!) // 设置区域 calender.locale = Locale.init(identifier: "zh_CN") // 设置时区 /* 设置时区,设置为 GMT+8,即北京时间(+8) */ calender.timeZone = TimeZone.init(abbreviation: "EST")! calender.timeZone = TimeZone.init(secondsFromGMT: +28800)! // 设定每周的第一天从星期几开始 /* 1 代表星期日开始,2 代表星期一开始,以此类推。默认值是 1 */ calender.firstWeekday = 1 //ordinalityOfUnit let timeWeek = calender.component(.weekOfYear, from: confromTimeDate) let systimeWeek = calender.component(.weekOfYear, from: Date.init()) if calender.isDateInToday(confromTimeDate) { if hour.doubleValue < 10 { hour = "0\(hour)" } if minute.doubleValue < 10 { minute = "0\(minute)" } return "今天".rLocalized() + " \(hour):\(minute)" }else if calender.isDateInYesterday(confromTimeDate) { if hour.doubleValue < 10 { hour = "0\(hour)" } if minute.doubleValue < 10 { minute = "0\(minute)" } return "昨天".rLocalized() + " \(hour):\(minute)" }else if timeWeek == systimeWeek { let weeks = ["星期日".rLocalized(), "星期一".rLocalized(), "星期二".rLocalized(), "星期三".rLocalized(), "星期四".rLocalized(), "星期五".rLocalized(), "星期六".rLocalized()] let i = calender.ordinality(of: .weekday, in: .weekOfYear, for: confromTimeDate) //此处一定要记得减1 return weeks[i!-1] }else { return self.getCurrentTime(timeNum: time, format: "MM-dd") } } //MARK: - 时间格式转换"YYYY-MM-dd HH:mm:ss" func getCurrentTime(timeNum:Double , format:String) -> String { let formatter = DateFormatter() formatter.dateStyle = DateFormatter.Style.medium formatter.timeStyle = DateFormatter.Style.short formatter.dateFormat = format let timeInterval = TimeInterval(timeNum / 1000.0) let confromTimesp = NSDate(timeIntervalSince1970:timeInterval) let confromTimespStr = formatter.string(from: confromTimesp as Date) return confromTimespStr } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/Strings/String+Extension.swift ================================================ // // String+Extension.swift // String+Extension // // Created by TAN on 2017/10/18. // Copyright © 2019 Jett. All rights reserved. // import Foundation import CommonCrypto import UIKit extension String { /// 截取第一个到第任意位置 /// /// - Parameter end: 结束的位值 /// - Returns: 截取后的字符串 public func stringCut(end: Int) -> String{ if !(end <= count) { return self } let sInde = index(startIndex, offsetBy: end) return String(self[.. String { if !(star < count) { return "截取超出范围" } let sRang = index(startIndex, offsetBy: star).. String { if !(last < count) { return "截取超出范围" } let sRang = index(endIndex, offsetBy: -last).. String { if !(locat < count) { return "操作超出范围" } let str1 = stringCut(end: locat) let str2 = stringCutToEnd(star: locat) return str1 + content + str2 } /// JSON字符串转字典 /// /// - Parameter jsonString: json字符串 /// - Returns: 转成后的字典 func getDictionaryFromJSONString(_ jsonString: String) ->NSDictionary { let jsonData:Data = jsonString.data(using: .utf8)! let dict = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) if dict != nil { return dict as! NSDictionary } return NSDictionary() } /// JSON字符串转数组 /// /// - Parameter jsonString: json字符串 /// - Returns: 转成后的数组 func getArrayFromJSONString(jsonString:String) ->NSArray{ let jsonData:Data = jsonString.data(using: .utf8)! let array = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) if array != nil { return array as! NSArray } return array as! NSArray } /// 字典转成JSON字符串 /// /// - Parameter dictionary: 字典 /// - Returns: 转成后的字符串 func getJSONStringFromDictionary(_ dictionary: NSDictionary) -> String { if (!JSONSerialization.isValidJSONObject(dictionary)) { print("无法解析出JSONString") return "" } let data : NSData! = try? JSONSerialization.data(withJSONObject: dictionary, options: []) as NSData let JSONString = NSString(data:data as Data, encoding: String.Encoding.utf8.rawValue) return JSONString! as String } /// 数组转JSON字符串 /// /// - Parameter array: 数组 /// - Returns: 转成后的字符串 func getJSONStringFromArray(array:NSArray) -> String { if (!JSONSerialization.isValidJSONObject(array)) { print("无法解析出JSONString") return "" } let data : Data = try! JSONSerialization.data(withJSONObject: array, options: []) as Data let JSONString = NSString(data:data as Data,encoding: String.Encoding.utf8.rawValue) return JSONString! as String } /// 计算字符串的尺寸 /// /// - Parameters: /// - text: 字符串 /// - rectSize: 容器的尺寸 /// - fontSize: 字体 /// - Returns: 尺寸 /// public func getStringSize(rectSize: CGSize, fontSize: CGFloat) -> CGSize { let str: NSString = self as NSString let rect = str.boundingRect(with: rectSize, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: fontSize)], context: nil) return CGSize(width: ceil(rect.width), height: ceil(rect.height)) } public func getStringSize(fontSize: CGFloat) -> CGSize { return self.getStringSize(rectSize: CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT)), fontSize: fontSize) } /// 输入字符串 输出数组 /// e.g "qwert" -> ["q","w","e","r","t"] /// - Returns: ["q","w","e","r","t"] public func stringToArr() -> [String] { let num = count if !(num > 0) { return [""] } var arr: [String] = [] for i in 0.. "cdef" /// - Parameters: /// - start: 开始位置 3 /// - end: 结束位置 6 /// - Returns: 截取后的字符串 "cdef" public func startToEnd(start: Int,end: Int) -> String { if !(end < count) || start > end { return "取值范围错误" } var tempStr: String = "" for i in start...end { let temp: String = self[self.index(self.startIndex, offsetBy: i - 1)].description tempStr += temp } return tempStr } /// 字符串修改部分为密文 /// /// - Parameters: /// - start: 开始位置 /// - end: 结束为止 /// - Returns: 修改后的字符串 func stringAddSecret(start: Int, end: Int) -> String{ if !(end < count) || start > end { return "取值范围错误" } let startI = self.index(self.startIndex, offsetBy: start) let offset = count - start - end let endI = self.index(startI, offsetBy: offset) let secret = String.init(repeating: "*", count: offset) let string = self.replacingCharacters(in: startI.. String { let url = self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) return url! } /// 字符URL格式化,防止被转义 /// /// - Returns: 格式化的 url public func stringEncod() -> String { let url = self.stringEncoding() let result = url.removingPercentEncoding ?? "" return result } /// 是否包含字符串 public func containsIgnoringCase(find: String) -> Bool{ return self.range(of: find, options: .caseInsensitive) != nil } /// 去除字符串中空格 public func trimming() -> String { return self.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: CharacterSet.urlPathAllowed) } /// MD5加密1 public func md51() -> String { let str = self.cString(using: String.Encoding.utf8) let strLen = CUnsignedInt(self.lengthOfBytes(using: String.Encoding.utf8)) let digestLen = Int(CC_MD5_DIGEST_LENGTH) let result = UnsafeMutablePointer.allocate(capacity: 16) //16位加密 CC_MD5(str!, strLen, result) let hash = NSMutableString() for i in 0 ..< digestLen { hash.appendFormat("%02x", result[i]) } free(result) return String(format: hash as String) } /// 限制设置为整数 /// /// - Returns: 只返回整数的字符串 func getDigits() -> String { return String(self.filter { if let value = $0.int, value >= 0 && value <= 9 { return true } return false }) } } extension String { /// MD5加密 var ios_md5 : String{ let str = self.cString(using: String.Encoding.utf8) let strLen = CC_LONG(self.lengthOfBytes(using: String.Encoding.utf8)) let digestLen = Int(CC_MD5_DIGEST_LENGTH) let result = UnsafeMutablePointer.allocate(capacity: digestLen) CC_MD5(str!, strLen, result) let hash = NSMutableString() for i in 0 ..< digestLen { hash.appendFormat("%02x", result[i]) } result.deinitialize(count: 16) return String(format: hash as String) } } extension NSString{ ///修改字符串中数字样式 @objc public func attributeNumber(_ fontsize :CGFloat,color:UIColor,hcolor:UIColor,B:Bool)-> NSMutableAttributedString{ return (self as String).attributeNumber(fontsize, color: color, hcolor: hcolor, B: B) } } extension String { /// 删除字符串中Unicode.Cc/Cf字符,类似于\0这种 public func stringByRemovingControlCharacters() -> String { let controlChars = CharacterSet.controlCharacters var range = self.rangeOfCharacter(from: controlChars) var mutable = self while let removeRange = range { mutable.removeSubrange(removeRange) range = mutable.rangeOfCharacter(from: controlChars) } return mutable } /// 修改字符串中数字样式,将其加粗,变黑,加大4个字号,同时修改行间距 /// /// - Parameters: /// - fontsize: 非数字字号 /// - color: 非数字颜色 /// - lineSpace: 行间距 /// - Returns: 修改完成的AttributedString public func attributeNumber(BoldFontSize fontsize:CGFloat, color:UIColor,lineSpace:CGFloat?)->NSMutableAttributedString{ let AttributedStr = NSMutableAttributedString(string: self, attributes: [.font: UIFont.systemFont(ofSize: fontsize), .foregroundColor: color]) for i in 0 ..< self.count { let char = self.utf8[self.index(self.startIndex, offsetBy: i)] if (char > 47 && char < 58) { AttributedStr.addAttribute(.foregroundColor, value: UIColor(red: 33 / 255.0, green: 34 / 255.0, blue: 35 / 255.0, alpha: 1), range: NSRange(location: i, length: 1)) AttributedStr.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: fontsize + 4), range: NSRange(location: i, length: 1)) } } if let space = lineSpace { let paragraphStyleT = NSMutableParagraphStyle() paragraphStyleT.lineSpacing = space AttributedStr.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyleT, range: NSMakeRange(0,self.count)) } return AttributedStr } /// 给字符串中数字变样式 /// /// - Parameters: /// - fontsize: 字体大小 /// - color: 非数字颜色 /// - hcolor: 数字颜色 /// - B: 是否加粗变大 /// - Returns: 修改完成字符串 public func attributeNumber(_ fontsize :CGFloat,color:UIColor,hcolor:UIColor,B:Bool)-> NSMutableAttributedString{ let AttributedStr = NSMutableAttributedString(string: self, attributes: [.font: UIFont.systemFont(ofSize: fontsize), .foregroundColor: color]) for i in 0 ..< self.count { let char = self.utf8[self.index(self.startIndex, offsetBy: i)] if (char > 47 && char < 58) { AttributedStr.addAttribute(.foregroundColor, value: hcolor, range: NSRange(location: i, length: 1)) if B { AttributedStr.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: fontsize + 2), range: NSRange(location: i, length: 1)) } } } return AttributedStr } /// 字符串修改行间距和字间距 /// /// - Parameters: /// - lineSpace: 行间距 /// - wordSpace: 字间距 /// - Returns: 修改完成字符串 public func attributeChangeSpace(lineSpace:CGFloat, wordSpace:CGFloat) -> NSMutableAttributedString { let attributedString = NSMutableAttributedString.init(string: self, attributes: [NSAttributedString.Key.kern:wordSpace]) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = lineSpace attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: .init(location: 0, length: self.count)) return attributedString } /// 指定文本内容字体变色 /// /// - Parameters: /// - fontSize: 字体大小 /// - allColor:的字体颜色 /// - changedColor: 需要改变的颜色 /// - normalContent: 不设置变色的文本 /// - richContent: 需设置变色的文本 /// - Returns: 返回富文本 public func attributeSpecified(_ font :UIFont, originalColor: UIColor, changedColor: UIColor, normalContent: String, richContent: String)-> NSMutableAttributedString{ guard normalContent.length > 0, richContent.length > 0 else { return NSMutableAttributedString.init() } // 所有文本内容 let allContent = normalContent let attributedStr = NSMutableAttributedString(string: allContent) let range1: NSRange = NSRange(location: (attributedStr.string as NSString).range(of: normalContent).location, length: (attributedStr.string as NSString).range(of: normalContent).length) let range2: NSRange = NSRange(location: (attributedStr.string as NSString).range(of: richContent).location, length: (attributedStr.string as NSString).range(of: richContent).length) attributedStr.addAttributes([NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor : originalColor], range: range1) attributedStr.addAttributes([NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor : changedColor], range: range2) return attributedStr; } /// 替换手机号中间四位 /// /// - Returns: 替换后的值 func replacePhone() -> String { let start = self.index(self.startIndex, offsetBy: 3) let end = self.index(self.startIndex, offsetBy: 7) let range = Range(uncheckedBounds: (lower: start, upper: end)) return self.replacingCharacters(in: range, with: "****") } } extension String { public var length: Int { ///更改成其他的影响含有emoji协议的签名 return self.utf16.count } public var doubleValue: Double { return (self as NSString).doubleValue } public var intValue: Int32 { return (self as NSString).intValue } public var floatValue: Float { return (self as NSString).floatValue } public var integerValue: Int { return (self as NSString).integerValue } public var longLongValue: Int64 { return (self as NSString).longLongValue } public var boolValue: Bool { return (self as NSString).boolValue } } public protocol URLConvertibleProtocol { var URLValue: URL? { get } var URLStringValue: String { get } } extension String: URLConvertibleProtocol { ///String转换成URL public var URLValue: URL? { if let URL = URL(string: self) { return URL } let set = CharacterSet() .union(.urlHostAllowed) .union(.urlPathAllowed) .union(.urlQueryAllowed) .union(.urlFragmentAllowed) return self.addingPercentEncoding(withAllowedCharacters: set).flatMap { URL(string: $0) } } public var URLStringValue: String { return self } } extension String{ /** 将当前字符串拼接到cache目录后面 */ public func cacheDir() -> String{ let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last! return (path as NSString).appendingPathComponent((self as NSString).lastPathComponent) } /** 将当前字符串拼接到doc目录后面 */ public func docDir() -> String{ let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first! return (path as NSString).appendingPathComponent((self as NSString).lastPathComponent) } /** 将当前字符串拼接到tmp目录后面 */ public func tmpDir() -> String{ let path = NSTemporaryDirectory() as NSString return path.appendingPathComponent((self as NSString).lastPathComponent) } } extension String{ ///判断String是否存在汉字 public func isIncludeChineseIn() -> Bool { for (_, value) in self.enumerated() { if ("\u{4E00}" <= value && value <= "\u{9FA5}") { return true } } return false } } // MARK: - 通用正则处理 extension String { // MARK: - 字符输入长度限制 func trimAll(_ trim: String, rangeCount: Int) -> Bool { if (trim.lengthOfBytes(using: String.Encoding.utf8) == rangeCount) { printLog("输入限制为%d个字符") return false }else { return true } } // MARK: 用户名正则表达式 func validateUserName() -> Bool { let phoneRegex = try? NSRegularExpression(pattern: "^[A-Za-z0-9]{3,20}$", options: NSRegularExpression.Options.caseInsensitive) return phoneRegex?.firstMatch(in: self, options: [], range: NSMakeRange(0, self.length)) != nil } // MARK: 手机号正则表达式 func validateMobile() -> Bool { let phoneRegex = try? NSRegularExpression(pattern: "^1(3[0-9]|5[0-35-9]|8[02345-9]|70|77)\\d{8}$", options: NSRegularExpression.Options.caseInsensitive) return phoneRegex?.firstMatch(in: self, options: [], range: NSMakeRange(0, self.length)) != nil } // MARK: - 图形验证码正则表达式 func validatePicture() -> Bool { let pictureCodeRegex: String = "^(?![0-9]+$)(?![a-zA-Z]+$)[a-zA-Z0-9]{4}" let pictureCodeTest = NSPredicate(format: "SELF MATCHES", pictureCodeRegex) return pictureCodeTest.evaluate(with: self) } // MARK: 邮箱正则表达式 func validateEmail() -> Bool { let emailRegex = try? NSRegularExpression(pattern: "[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?", options: NSRegularExpression.Options.caseInsensitive) return emailRegex?.firstMatch(in: self, options: [], range: NSMakeRange(0, self.length)) != nil } // MARK: 密码正则表达式(6-16位密码且包含英文字母和数字组合,不能使用特殊字符) func validatePassword() -> Bool { let passwordRegex: String = "^(?![0-9]+$)(?![a-zA-Z]+$)[a-zA-Z0-9]{8,16}" let passwordTest = NSPredicate(format: "SELF MATCHES %@", passwordRegex) return passwordTest.evaluate(with: self) } // MARK: 邮政编码正则表达式(中国邮政编码为6位数字) func validateZipCode() -> Bool {// [1-9]\d{5}(?!\d)$ let zipCodeRegex: String = "[1-9]\\d{5}(?!\\d)$" let zipCodeTest = NSPredicate(format: "SELF MATCHES %@", zipCodeRegex) return zipCodeTest.evaluate(with: self) } // MARK: 纯数字正则表达式 func validateShouldNum() -> Bool { if self.length <= 0 { return false } let shouldNumRegex: String = "[0-9]*" let shouldNumTest = NSPredicate(format: "SELF MATCHES %@", shouldNumRegex) return shouldNumTest.evaluate(with: self) } // MARK: 身份证正则表达式 func validIDCardNumber() -> Bool{ do { let pattern = "(^[0-9]{15}$)|([0-9]{17}([0-9]|X)$)" let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) let results = regex.matches(in: self, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.length)) return results.count > 0 }catch { return false } } } // MARK: - 字符复制 extension String { /// 字符串复制 public func stringGeneral() { guard self.length > 0 else { printLog("复制内容不能为空") return } UIPasteboard.general.string = self MBConfiguredHUD.showSuccess("复制成功") } } extension String { /// 拓展方法 var utf8Encoded: Data { return data(using: .utf8)! } func urlEncoded() -> String { let encodeUrlString = self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) return encodeUrlString ?? "" } } extension String { static func randomCode(length:Int) -> String { let base = pow(10, length - 1) as NSDecimalNumber let max = (pow(10, length) - (base as Decimal) - 1) as NSDecimalNumber let code = String(Int(arc4random_uniform(UInt32(max.intValue))) + base.intValue) return code } } extension String { /// 是否包含空/空格字符 var isBlank: Bool { return allSatisfy({ $0.isWhitespace }) } } extension Optional where Wrapped == String { var isBlank: Bool { return self?.isBlank ?? true } } extension String { /// 获取文字的宽高 空字符串高度为字体高度 /// /// - Parameters: /// - maxWidth: 空间的最大宽度 /// - font: 文字字体 /// - Returns: 返回计算好的size func textSize(_ maxWidth: CGFloat, font: UIFont) -> CGSize { let constraint = CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude) let rect = self.boundingRect(with: constraint, options: ([.usesLineFragmentOrigin]), attributes: [NSAttributedString.Key.font: font], context: nil) return CGSize(width: ceil(rect.width), height: ceil(rect.height)) } /// 获取文字的每一行字符串 空字符串为空数组 /// /// - Parameters: /// - maxWidth: 空间的最大宽度 /// - font: 文字字体 /// - Returns: 返回计算好的行字符串 func textLines(_ maxWidth: CGFloat, font: UIFont) -> [String] { let myFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil) let attStr = NSMutableAttributedString(string: self) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineBreakMode = .byCharWrapping attStr.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: attStr.length)) attStr.addAttribute(NSAttributedString.Key(kCTFontAttributeName as String), value: myFont, range: NSRange(location: 0, length: attStr.length)) let frameSetter = CTFramesetterCreateWithAttributedString(attStr) let path = CGMutablePath() path.addRect(CGRect(x: 0, y: 0, width:maxWidth, height: 100000), transform: .identity) let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(CFIndex(0), CFIndex(0)), path, nil) let lines = CTFrameGetLines(frame) as? [AnyHashable] var linesArray: [String] = [] for line in lines ?? [] { let lineRange = CTLineGetStringRange(line as! CTLine) let range = NSRange(location: lineRange.location, length: lineRange.length) let lineString = (self as NSString).substring(with: range) CFAttributedStringSetAttribute(attStr, lineRange, kCTKernAttributeName, (NSNumber(value: 0.0))) linesArray.append(lineString) } return linesArray } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIColor/UIColor+Extension.swift ================================================ // // UIColor+Extend.swift // UIColor // // Created by TAN on 2018/8/30. // Copyright © 2018年 iOS. All rights reserved. // UIColor使用拓展 import UIKit extension UIColor { /// 16进制转化Color /// /// - Parameter hex: 16进制 /// - Returns: Color class func colorWithHexStr(_ hex: String) -> UIColor { var cString = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased() if (cString.hasSuffix("#")) { let index = cString.index(cString.startIndex, offsetBy: 1) //cString = cString.substring(from: index) cString = String(cString[index...]) // Swift 4 } if (cString.count != 6) { return UIColor.red } let rIndex = cString.index(cString.startIndex, offsetBy: 2) //let rString = cString.substring(to: rIndex) let rString = String(cString[.. UIColor { var cString:String = hex.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines).uppercased() if (cString.hasPrefix("#")) { cString = (cString as NSString).substring(from: 1) } if (cString.count != 6) { return UIColor.gray } let rString = (cString as NSString).substring(to: 2) let gString = ((cString as NSString).substring(from: 2) as NSString).substring(to: 2) let bString = ((cString as NSString).substring(from: 4) as NSString).substring(to: 2) var r:CUnsignedInt = 0, g:CUnsignedInt = 0, b:CUnsignedInt = 0; Scanner(string: rString).scanHexInt32(&r) Scanner(string: gString).scanHexInt32(&g) Scanner(string: bString).scanHexInt32(&b) return UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: alpha) } // MARK: - RGB的颜色设置 class func RGB(r: CGFloat, g: CGFloat, b: CGFloat) -> UIColor { return UIColor(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: 1.0) } class func RGBA(r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) -> UIColor { return UIColor(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: a/1.0) } // MARK: - 随机颜色 /// 随机颜色 /// /// - Returns: 随机颜色 class func randomColor() -> UIColor { let red = CGFloat(arc4random_uniform(256)) let green = CGFloat(arc4random_uniform(256)) let blue = CGFloat(arc4random_uniform(256)) return RGB(r: red, g: green, b: blue) } // MARK: - 应用控件主题颜色 /// 1FB922 /// /// - Returns: 1FB922 class func appThemeHexColor() ->UIColor { return colorWithHexStr("1FB922") } // MARK: - HUD的背景颜色 class func hudBackgroundColor() -> UIColor { return UIColor.black.withAlphaComponent(0.7) } // MARK: - tabBar标题未选中颜色 class func tabBarTitleNormalColor() -> UIColor { return colorWithHexStr("AAAAAA") } // MARK: - tabBar标题已选中颜色 class func tabBarTitleSelectColor() -> UIColor { return colorWithHexStr("1FB922") } } extension UIColor { convenience init?(netHex: Int) { self.init(red:(netHex >> 16) & 0xff, green:(netHex >> 8) & 0xff, blue:netHex & 0xff) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIKit/UIAlert+Extension.swift ================================================ // // EasyAlertView.swift // FY-JetChat // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019 Jett. All rights reserved. // import UIKit class EasyAlertView: NSObject { override init() { } /// 点击不响应提示框 class func sureActionAlert(title: String, message: String, vc: UIViewController) { let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) let sureAction = UIAlertAction(title: "确定".rLocalized(), style: .default) { (UIAlertAction) in } alertVC.addAction(sureAction) vc.present(alertVC, animated: true, completion: nil) } /// 默认带确认&取消事件响应的提示框 class func confirmAlert(title: String, message: String, vc: UIViewController, source: @escaping () -> Void) { let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) let confirmAction = UIAlertAction(title: "确定".rLocalized(), style: .default) { (UIAlertAction) in source() } let cancleAction = UIAlertAction(title: "取消".rLocalized(), style: .default, handler: nil) alertVC.addAction(cancleAction) alertVC.addAction(confirmAction) vc.present(alertVC, animated: true, completion: nil) } class func defaultConfirm(title: String, message: String, vc: UIViewController, source: @escaping () -> Void) { let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) let confirmAction = UIAlertAction(title: "确认".rLocalized(), style: .default) { (UIAlertAction) in source() } alertVC.addAction(confirmAction) vc.present(alertVC, animated: true, completion: nil) } /// 默认只带确认事件响应的提示框 class func sheetAction(title: String, message: String, vc: UIViewController, source: @escaping () -> Void) { let alertVC = UIAlertController(title: title, message: message, preferredStyle: .actionSheet) let confirmAction = UIAlertAction(title: "确定".rLocalized(), style: .default) { (UIAlertAction) in source() } let cancleAction = UIAlertAction(title: "取消".rLocalized(), style: .default, handler: nil) alertVC.addAction(cancleAction) alertVC.addAction(confirmAction) vc.present(alertVC, animated: true, completion: nil) } /// 完全自定义样式的Alert class func customAlert(title: String, message: String, confirm: String, cancel: String, vc: UIViewController, confirmBlock: @escaping () -> Void, cancelBlock: @escaping () -> Void) { let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) let confirmAction = UIAlertAction(title: confirm, style: .default) { (UIAlertAction) in confirmBlock() } let cancelAction = UIAlertAction(title: cancel, style: .default) { (UIAlertAction) in cancelBlock() } alertVC.addAction(confirmAction) alertVC.addAction(cancelAction) vc.present(alertVC, animated: true, completion: nil) } class func customConfirm(title: String, message: String, confirm: String, vc: UIViewController, source: @escaping () -> Void) { let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) let confirmAction = UIAlertAction(title: confirm, style: .default) { (UIAlertAction) in source() } alertVC.addAction(confirmAction) vc.present(alertVC, animated: true, completion: nil) } /// 带文本输入的提示框 func textFiledAlert(title: String, message: String, placeholder: String, vc: UIViewController, source: @escaping (_ text: String) -> Void) { let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) alertVC.addTextField { (textFiled: UITextField) in textFiled.placeholder = placeholder } let cancelAction = UIAlertAction(title: "确定".rLocalized(), style: .default, handler: nil) let confirmAction = UIAlertAction(title: "取消".rLocalized(), style: .default) { (UIAlertAction) in let login = alertVC.textFields![0] source(login.text!) printLog("输入的是:\(String(describing: login.text))") } alertVC.addAction(cancelAction) alertVC.addAction(confirmAction) vc.present(alertVC, animated: true, completion: nil) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIKit/UIButton+Extension.swift ================================================ // // UIButton+Extension.swift // FY-JetChat // // Created by iOS.Jet on 2019/10/11. // Copyright © 2019 Jett. All rights reserved. // import Foundation import RxSwift import RxCocoa import RxTheme /// 按钮图片和文字对齐方式 enum ButtonStyle { ///图片在左,文字在右,整体居中 case `default` ///图片在左,文字在右,整体居中 case left ///图片在右,文字在左,整体居中。 case right ///图片在上,文字在下,整体居中 case top ///图片在下,文字在上,整体居中 case bottom ///图片居中,文字在图片下面。 case centerTop ///图片居中,文字在图片上面 case centerBottom ///图片居中,文字在上距离按钮顶部。 case centerUp ///图片居中,文字在按钮下边。 case centerDown ///图片在右,文字在左,距离按钮两边边距 case rightLeft ///图片在左,文字在右,距离按钮两边边距 case leftRight } // MARK: - Button EdgeInsets extension UIButton { /// 按钮图片和文字的排版 /// - Parameter style: 样式 /// - Parameter padding: 间距大小 func setImageButtonStyle(_ style: ButtonStyle, padding: CGFloat){ if imageView?.image != nil && titleLabel?.text != nil { //先还原 titleEdgeInsets = .zero imageEdgeInsets = .zero let imageRect: CGRect = imageView!.frame let titleRect: CGRect = titleLabel!.frame let totalHeight: CGFloat = (imageRect.size.height) + padding + (titleRect.size.height) let selfWidth = frame.size.width let selfHeight = frame.size.height switch style { case .left: if padding != 0 { titleEdgeInsets = UIEdgeInsets(top: 0, left: padding / 2, bottom: 0, right: -padding / 2) imageEdgeInsets = UIEdgeInsets(top: 0, left: -padding / 2, bottom: 0, right: padding / 2) } break case .right: //图片在右,文字在左 titleEdgeInsets = UIEdgeInsets(top: 0, left: -((imageRect.size.width) + padding / 2), bottom: 0, right: ((imageRect.size.width) + padding / 2)) imageEdgeInsets = UIEdgeInsets(top: 0, left: ((titleRect.size.width) + padding / 2), bottom: 0, right: -((titleRect.size.width) + padding / 2)) break case .top: //图片在上,文字在下 titleEdgeInsets = UIEdgeInsets(top: ((selfHeight - totalHeight) / 2 + imageRect.size.height + padding - titleRect.origin.y), left: (selfWidth/2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2, bottom: -((selfHeight - totalHeight) / 2 + imageRect.size.height + padding - titleRect.origin.y), right: -(selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2) imageEdgeInsets = UIEdgeInsets(top: ((selfHeight - totalHeight) / 2 - imageRect.origin.y), left: (selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2), bottom: -((selfHeight - totalHeight)/2 - imageRect.origin.y), right: -(selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2)) break case .bottom: //图片在下,文字在上。 titleEdgeInsets = UIEdgeInsets(top: ((selfHeight - totalHeight) / 2 - titleRect.origin.y), left: (selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2, bottom: -((selfHeight - totalHeight) / 2 - titleRect.origin.y), right: -(selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2) imageEdgeInsets = UIEdgeInsets(top: ((selfHeight - totalHeight) / 2 + titleRect.size.height + padding - imageRect.origin.y), left: (selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2), bottom: -((selfHeight - totalHeight) / 2 + titleRect.size.height + padding - imageRect.origin.y), right: -(selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2)) break case .centerTop: titleEdgeInsets = UIEdgeInsets(top: -(titleRect.origin.y - padding), left: (selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2, bottom: (titleRect.origin.y - padding), right: -(selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2) imageEdgeInsets = UIEdgeInsets(top: 0, left: (selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2), bottom: 0, right: -(selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2)) break case .centerBottom: titleEdgeInsets = UIEdgeInsets(top: (selfHeight - padding - titleRect.origin.y - titleRect.size.height), left: (selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2, bottom: -(selfHeight - padding - titleRect.origin.y - titleRect.size.height), right: -(selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2) imageEdgeInsets = UIEdgeInsets(top: 0, left: (selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2), bottom: 0, right: -(selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2)) break case .centerUp: titleEdgeInsets = UIEdgeInsets(top: -(titleRect.origin.y + titleRect.size.height - imageRect.origin.y + padding), left: (selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2, bottom: (titleRect.origin.y + titleRect.size.height - imageRect.origin.y + padding), right: -(selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2) imageEdgeInsets = UIEdgeInsets(top: 0, left: (selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2), bottom: 0, right: -(selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2)) break case .centerDown: titleEdgeInsets = UIEdgeInsets(top: (imageRect.origin.y + imageRect.size.height - titleRect.origin.y + padding), left: (selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2, bottom: -(imageRect.origin.y + imageRect.size.height - titleRect.origin.y + padding), right: -(selfWidth / 2 - titleRect.origin.x - titleRect.size.width / 2) - (selfWidth - titleRect.size.width) / 2) imageEdgeInsets = UIEdgeInsets(top: 0, left: (selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2), bottom: 0, right: -(selfWidth / 2 - imageRect.origin.x - imageRect.size.width / 2)) break case .rightLeft: //图片在右,文字在左,距离按钮两边边距 titleEdgeInsets = UIEdgeInsets(top: 0, left: -(titleRect.origin.x - padding), bottom: 0, right: (titleRect.origin.x - padding)) imageEdgeInsets = UIEdgeInsets(top: 0, left: (selfWidth - padding - imageRect.origin.x - imageRect.size.width), bottom: 0, right: -(selfWidth - padding - imageRect.origin.x - imageRect.size.width)) break case .leftRight: //图片在左,文字在右,距离按钮两边边距 titleEdgeInsets = UIEdgeInsets(top: 0, left: (selfWidth - padding - titleRect.origin.x - titleRect.size.width), bottom: 0, right: -(selfWidth - padding - titleRect.origin.x - titleRect.size.width)) imageEdgeInsets = UIEdgeInsets(top: 0, left: -(imageRect.origin.x - padding), bottom: 0, right: (imageRect.origin.x - padding)) break default: titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) break } }else { titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) } } } // MARK: - RxSwift tap extension UIButton { func rxTapClosure(_ time: DispatchTimeInterval = .microseconds(100), callback:(@escaping() -> ())) { self.rx.tap.debounce(time, scheduler: MainScheduler.instance).bind { callback() }.disposed(by: rx.disposeBag) } } // MARK: - Theme public extension ThemeProxy where Base: UIButton { func buttonImage(from newValue: ThemeAttribute, for state: UIControl.State) { base.setImage(newValue.value, for: state) let disposable = newValue.stream .take(until: base.rx.deallocating) .observe(on: MainScheduler.instance) .bind(to: base.rx.buttnImage(for: state)) hold(disposable, for: "buttnImage.forState.\(state.rawValue)") } } extension Reactive where Base: UIButton { /// Bindable sink for `buttnImage` property func buttnImage(for state: UIControl.State) -> Binder { return Binder(self.base) { view, attr in view.setImage(attr, for: state) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIKit/UIFont+PingFang.swift ================================================ // // UIFont+PingFang.swift // FY-JetChat // // Created by Jett on 2019/3/6. // Copyright © 2019 Jett. All rights reserved. // import Foundation import UIKit extension UIFont { /// 苹方-简 常规体 PingFangSC-Regular static func PingFangRegular(_ size:CGFloat) -> UIFont { return UIFont(name: "PingFangSC-Regular", size: size) ?? UIFont.systemFont(ofSize:size) } /// 苹方-简 中黑体 PingFangSC-Medium static func PingFangMedium(_ size:CGFloat) -> UIFont { return UIFont(name: "PingFangSC-Medium", size: size) ?? UIFont.systemFont(ofSize:size) } /// 苹方-简 中粗体 PingFangSC-Semibold static func PingFangSemibold(_ size:CGFloat) -> UIFont { return UIFont(name: "PingFangSC-Semibold", size: size) ?? UIFont.systemFont(ofSize:size) } /// 苹方-特粗体 PingFang-SC-Heavy static func PingFangHeavy(_ size:CGFloat) -> UIFont { return UIFont(name: "PingFang-SC-Heavy", size: size) ?? UIFont.systemFont(ofSize:size) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIKit/UIImage+Extension.swift ================================================ // // LWUIImage+Extension.swift // FY-JetChat // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019 Jett. All rights reserved. // import Foundation extension UIImage { /// 颜色转化为图片 class func imageWithColor(_ color: UIColor) -> UIImage { let rect = CGRect(x: 0, y: 0, width: 1, height: 1) UIGraphicsBeginImageContext(rect.size) let context = UIGraphicsGetCurrentContext() context?.setFillColor(color.cgColor) context?.fill(rect) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image! } /// 图片去锯齿 func setAntialiasedImage(_ size: CGSize, _ scale: CGFloat) -> UIImage { UIGraphicsBeginImageContextWithOptions(size, false, 0.0) self.draw(in: CGRect(x: 1, y: 1, width: size.width-2, height: size.height-2)) let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return image } /// 图片置灰 func setAshPlacingImage(_ sourceImage: UIImage) -> UIImage { UIGraphicsBeginImageContext(self.size) let colorSpace = CGColorSpaceCreateDeviceGray() let context = CGContext(data: nil , width: Int(self.size.width), height: Int(self.size.height),bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.none.rawValue) context?.draw(sourceImage.cgImage!, in: CGRect.init(x: 0, y: 0, width: sourceImage.size.width, height: sourceImage.size.height)) let cgImage = context!.makeImage() let grayImage = UIImage.init(cgImage: cgImage!) return grayImage } /// 截屏使用 func screenShotWithoutMask(shotView: UIView) -> UIImage { UIGraphicsBeginImageContextWithOptions(shotView.frame.size, true, 0.0) shotView.drawHierarchy(in: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: kScreenW, height: kScreenH)), afterScreenUpdates: false) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image! } /// 图片点九处理 /// - Parameter sscale: 比例 func stretchableImage(centerStretchScale sscale:CGFloat) -> UIImage { let top = self.size.height - 8; let left = self.size.width / 2 - 2; let right = self.size.width / 2 + 2; let bottom = self.size.height - 4; return self.resizableImage(withCapInsets: UIEdgeInsets.init(top: top, left: left, bottom: bottom, right: right), resizingMode: .stretch) } /// 返回不同颜色的新图片 func changeColor(color: UIColor) -> UIImage? { UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale); let context = UIGraphicsGetCurrentContext(); context?.translateBy(x: 0, y: self.size.height) context?.scaleBy(x: 1.0, y: -1.0) context?.setBlendMode(.normal) let rect = CGRect.init(x: 0, y: 0, width: self.size.width, height: self.size.height) context?.clip(to: rect, mask: self.cgImage!) context?.setFillColor(color.cgColor) context?.fill(rect) let newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage } /// 返回不被拉伸的图片 class func imageWithRenderingMode(_ originalImage: UIImage) -> UIImage { return originalImage.withRenderingMode(.alwaysOriginal) } /** 生成高清二维码 */ class func createQRForString(_ qrString: String) -> UIImage { if qrString.isEmpty == true{ return UIImage() } // 将字符串转换为二进制 let data = qrString.data(using: String.Encoding.utf8, allowLossyConversion: false) let filter = CIFilter(name: "CIQRCodeGenerator")! filter.setValue(data, forKey: "inputMessage") filter.setValue("H", forKey: "inputCorrectionLevel") let qrCIImage = filter.outputImage let colorFilter = CIFilter(name: "CIFalseColor")! colorFilter.setDefaults() colorFilter.setValue(qrCIImage, forKey: "inputImage") colorFilter.setValue(CIColor(red: 0, green: 0, blue: 0), forKey: "inputColor0") colorFilter.setValue(CIColor(red: 1, green: 1, blue: 1), forKey: "inputColor1") let outImage = colorFilter.outputImage let scale = 172 / outImage!.extent.size.width; let transform = CGAffineTransform(scaleX: scale, y: scale) let transformImage = colorFilter.outputImage!.transformed(by: transform) let image = UIImage(ciImage: transformImage) return image } /// 拉伸按钮背景图片 class func resizableImage(_ image: UIImage) -> UIImage { let w: CGFloat = image.size.width * 0.5 let h: CGFloat = image.size.height * 0.5 return image.resizableImage(withCapInsets: UIEdgeInsets(top: w, left: h, bottom: w, right: h)) } } // 扩展 UIImage 的 init 方法,获得渐变效果 public extension UIImage { convenience init?(gradientColors:[UIColor], size: CGSize) { UIGraphicsBeginImageContextWithOptions(size, true, 0) let context = UIGraphicsGetCurrentContext() let colorSpace = CGColorSpaceCreateDeviceRGB() let colors = gradientColors.map {(color: UIColor) -> AnyObject? in return color.cgColor as AnyObject } as NSArray let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: nil) // 第二个参数是起始位置,第三个参数是终止位置 context!.drawLinearGradient(gradient!, start: CGPoint(x: 0, y: 0), end: CGPoint(x: size.width, y: 0), options: CGGradientDrawingOptions(rawValue: 0)) self.init(cgImage: (UIGraphicsGetImageFromCurrentImageContext()?.cgImage)!) UIGraphicsEndImageContext() } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIKit/UIImageView+Kingfisher.swift ================================================ // // UIImageView+Kingfisher.swift // MK-MChain // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019 miku. All rights reserved. // import UIKit import Foundation import Kingfisher import RxTheme import RxCocoa import RxSwift enum ImageResult { case success(UIImage) case failure(String) } extension UIImageView { /// 设置网络图片 /// /// - Parameter url: 网络图片资源url func setImageWithURL(_ url: String) { guard let imageURL = url.URLValue else { return } kf.setImage(with: imageURL) } /// 设置网络图片及占位图 /// /// - Parameters: /// - url: 网络图片资源url /// - placeholder: 占位图片 func setImageWithURL(_ url: String, placeholder: UIImage = UIImage()) { guard let imageURL = url.URLValue else { self.setPlaceholderImage(placeholder) return } kf.setImage(with: imageURL, placeholder: placeholder) } /// 设置网络图片及占位图 /// /// - Parameters: /// - url: 网络图片资源url /// - placeholder: 占位图片名称 func setImageWithURL(_ url: String, placeholder: String) { guard let imageURL = url.URLValue else { self.setPlaceholderImageNamed(placeholder) return } kf.setImage(with: imageURL, placeholder: UIImage(named: placeholder)) } func setPlaceholderImage(_ image: UIImage) { DispatchQueue.main.async { self.image = image } } func setPlaceholderImageNamed(_ named: String) { DispatchQueue.main.async { if let placeholder = UIImage(named: named) { self.image = placeholder } } } /// 下载网络图片资源 /// /// - Parameters: /// - url: 网络图片资源url /// - placeholder: 占位图片名称 /// - callback: 下载完成回调 func downloadImageWithURL(_ url: String, placeholder: UIImage = UIImage(), callback:(@escaping (ImageResult) -> ())) { guard let imageURL = URL(string: url) else { return } kf.setImage(with: imageURL, options: [.memoryCacheExpiration(.expired)]) { result in switch result { case .success(let value): printLog("Task done for: \(value.source.url?.absoluteString ?? "")") callback(ImageResult.success(try! result.get().image)) case .failure(let error): printLog("Job failed: \(error.localizedDescription)") callback(ImageResult.failure("Job failed: \(error.localizedDescription)")) } } } } // MARK: - Theme public extension ThemeProxy where Base: UIImageView { /// (set only) bind a stream to backgroundColor var image: ThemeAttribute { get { fatalError("set only") } set { base.image = newValue.value let disposable = newValue.stream .take(until: base.rx.deallocating) .observe(on: MainScheduler.instance) .bind(to: base.rx.image) hold(disposable, for: "backgroundColor") } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIKit/UILabel+Extension.swift ================================================ // // UIlabel+Extension.swift // FY-JetChat // // Created by Jett on 2019/3/26. // Copyright © 2019 Jett. All rights reserved. // import UIKit class PaddingLabel: UILabel { private struct AssociatedKeys { static var padding = UIEdgeInsets() } public var padding: UIEdgeInsets? { get { return objc_getAssociatedObject(self, &AssociatedKeys.padding) as? UIEdgeInsets } set { if let newValue = newValue { objc_setAssociatedObject(self, &AssociatedKeys.padding, newValue as UIEdgeInsets?, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } override open func draw(_ rect: CGRect) { if let insets = padding { self.drawText(in: rect.inset(by: insets)) } else { self.drawText(in: rect) } } override open var intrinsicContentSize: CGSize { guard let text = self.text else { return super.intrinsicContentSize } var contentSize = super.intrinsicContentSize var textWidth: CGFloat = frame.size.width var insetsHeight: CGFloat = 0.0 var insetsWidth: CGFloat = 0.0 if let insets = padding { insetsWidth += insets.left + insets.right insetsHeight += insets.top + insets.bottom textWidth -= insetsWidth } let newSize = text.boundingRect(with: CGSize(width: textWidth, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: self.font!], context: nil) contentSize.height = ceil(newSize.size.height) + insetsHeight contentSize.width = ceil(newSize.size.width) + insetsWidth return contentSize } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIKit/UINavBarItem+Extension.swift ================================================ // // NavBarItem+Extension.swift // SwiftStudy // // Created by iOS.Jet on 2019/2/20. // Copyright © 2019 Jett. All rights reserved. // import Foundation import UIKit /// 便捷方法拓展 extension UIBarButtonItem { /// 设置导航栏侧边内容(只设置图片) /// /// - Parameters: /// - image: 普通状态下图片 /// - action: 添加的点击事件 /// - Returns: 自定义设置导航栏侧边内容 class func setNavItemImage(_ image: UIImage, target: Any?, action: Selector) -> UIBarButtonItem { let customButton = UIButton(type: .custom) customButton.setImage(image, for: .normal) customButton.setTitleColor(.appThemeHexColor(), for: .normal) customButton.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 14) customButton.sizeToFit() customButton.addTarget(target, action: action, for: .touchUpInside) return UIBarButtonItem(customView: customButton) } /// 设置导航栏侧边内容(设置普通&高亮图片) /// /// - Parameters: /// - image: 普通状态下图片 /// - hightImage: 高亮状态下图片 /// - action: 添加的点击事件 /// - Returns: 自定义设置导航栏侧边内容 class func setNavItemHightImage(_ image: UIImage, hightImage: UIImage, target: Any?, action: Selector) -> UIBarButtonItem { let customButton = UIButton(type: .custom) customButton.setImage(image, for: .normal) customButton.setImage(hightImage, for: .highlighted) customButton.setTitleColor(.appThemeHexColor(), for: .normal) customButton.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 14) customButton.sizeToFit() customButton.addTarget(target, action: action, for: .touchUpInside) return UIBarButtonItem(customView: customButton) } /// 设置导航栏侧边内容(设置普通&高亮图片) /// /// - Parameters: /// - image: 普通状态下图片 /// - selectedImage: 选中状态下图片 /// - action: 添加的点击事件 /// - Returns: 自定义设置导航栏侧边内容 class func setNavItemSelectedImage(_ image: UIImage, selectedImage: UIImage, target: Any?, action: Selector) -> UIBarButtonItem { let customButton = UIButton(type: .custom) customButton.setImage(image, for: .normal) customButton.setImage(selectedImage, for: .selected) customButton.setTitleColor(.appThemeHexColor(), for: .normal) customButton.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 14) customButton.sizeToFit() customButton.addTarget(target, action: action, for: .touchUpInside) return UIBarButtonItem(customView: customButton) } /// 设置导航栏侧边内容(只设置文字) /// /// - Parameters: /// - title: 普通状态下文字 /// - selector: 添加的点击事件 /// - Returns: 自定义设置导航栏侧边内容 class func setNavItemTitle(_ title: String, target: Any?, selector: Selector) -> UIBarButtonItem { let customButton = UIButton(type: .custom) customButton.setTitle(title, for: .normal) customButton.setTitleColor(.appThemeHexColor(), for: .normal) customButton.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 14) customButton.sizeToFit() customButton.addTarget(target, action: selector, for: .touchUpInside) return UIBarButtonItem(customView: customButton) } /// 设置导航栏侧边内容(只设置文字) /// /// - Parameters: /// - title: 普通状态下文字 /// - color: 普通状态下文字颜色 /// - action: 添加的点击事件 /// - Returns: 自定义设置导航栏侧边内容 class func setTitleWithColor(_ title: String, color: UIColor, target: Any?, selector: Selector) -> UIBarButtonItem { let customButton = UIButton(type: .custom) customButton.setTitle(title, for: .normal) customButton.setTitleColor(color, for: .normal) customButton.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 14) customButton.sizeToFit() customButton.addTarget(target, action: selector, for: .touchUpInside) return UIBarButtonItem(customView: customButton) } /// 设置导航栏侧边内容(设置图片+文字) /// /// - Parameters: /// - image: 普通状态下图片 /// - title: 普通状态下文字 /// - action: 添加的点击事件 /// - Returns: 自定义设置导航栏侧边内容 class func setNavItemImageOrTitle(_ image: UIImage, title: String, target: Any, action: Selector) -> UIBarButtonItem { let customButton = UIButton(type: .custom) customButton.setImage(image, for: .normal) customButton.setTitle(title, for: .normal) customButton.setTitleColor(.appThemeHexColor(), for: .normal) customButton.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 14) customButton.sizeToFit() customButton.addTarget(target, action: action, for: .touchUpInside) return UIBarButtonItem(customView: customButton) } /// 设置导航栏侧边内容(设置高亮图片+文字) /// /// - Parameters: /// - hightImage: 高亮状态下图片 /// - title: 普通状态下文字 /// - action: 添加的点击事件 /// - Returns: 自定义设置导航栏侧边内容 class func setNavItemHightImageOrTitle(_ hightImage: UIImage, title: String, target: Any?, action: Selector) -> UIBarButtonItem { let customButton = UIButton(type: .custom) customButton.setTitle(title, for: .normal) customButton.setImage(hightImage, for: .highlighted) customButton.setTitleColor(.appThemeHexColor(), for: .normal) customButton.titleLabel?.font = UIFont(name: "PingFangSC-Regular", size: 14) customButton.sizeToFit() customButton.addTarget(target, action: action, for: .touchUpInside) return UIBarButtonItem(customView: customButton) } /// 设置导航栏侧边内容(自定义按钮) /// /// - Parameters: /// - button: 自定义按钮 /// - action: 添加的点击事件 /// - Returns: 自定义设置导航栏侧边内容 class func setNavItemCustomView(_ button : UIButton, target: Any?, action: Selector) -> UIBarButtonItem { button.sizeToFit() button.addTarget(target, action: action, for: .touchUpInside) return UIBarButtonItem(customView: button) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIKit/UITableView+Extension.swift ================================================ // // UITableView+EmptyData.swift // FY-JetChat // // Created by iOS.Jet on 2019/6/12. // Copyright © 2019 Jett. All rights reserved. // import Foundation public extension UITableView { func scrollToFirst(at scrollPosition: UITableView.ScrollPosition, animated: Bool) { guard numberOfSections > 0 else { return } guard numberOfRows(inSection: 0) > 0 else { return } let indexPath = IndexPath(item: 0, section: 0) scrollToRow(at: indexPath, at: scrollPosition, animated: animated) } func scrollToLast(at scrollPosition: UITableView.ScrollPosition, animated: Bool) { guard numberOfSections > 0 else { return } let lastSection = numberOfSections - 1 guard numberOfRows(inSection: 0) > 0 else { return } let lastIndexPath = IndexPath(item: numberOfRows(inSection: lastSection)-1, section: lastSection) scrollToRow(at: lastIndexPath, at: scrollPosition, animated: animated) } } public extension UITableView { // MARK: - Cell register and reuse /** Register cell nib - parameter aClass: class */ func fy_registerCellNib(_ aClass: T.Type) { let name = String(describing: aClass) let nib = UINib(nibName: name, bundle: nil) self.register(nib, forCellReuseIdentifier: name) } /** Register cell class - parameter aClass: class */ func fy_registerCellClass(_ aClass: T.Type) { let name = String(describing: aClass) self.register(aClass, forCellReuseIdentifier: name) } /** Reusable Cell - parameter aClass: class - returns: cell */ func fy_dequeueReusableCell(_ aClass: T.Type) -> T! { let name = String(describing: aClass) guard let cell = dequeueReusableCell(withIdentifier: name) as? T else { fatalError("\(name) is not registed") } return cell } // MARK: - HeaderFooter register and reuse /** Register cell nib - parameter aClass: class */ func fy_registerHeaderFooterNib(_ aClass: T.Type) { let name = String(describing: aClass) let nib = UINib(nibName: name, bundle: nil) self.register(nib, forHeaderFooterViewReuseIdentifier: name) } /** Register cell class - parameter aClass: class */ func fy_registerHeaderFooterClass(_ aClass: T.Type) { let name = String(describing: aClass) self.register(aClass, forHeaderFooterViewReuseIdentifier: name) } /** Reusable Cell - parameter aClass: class - returns: cell */ func fy_dequeueReusableHeaderFooter(_ aClass: T.Type) -> T! { let name = String(describing: aClass) guard let cell = dequeueReusableHeaderFooterView(withIdentifier: name) as? T else { fatalError("\(name) is not registed") } return cell } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIView/UIView+Extensions.swift ================================================ // // UIView+Extend.swift // UIView拓展 // // Created by Mac on 2018/8/16. // Copyright © 2018年 iOS. All rights reserved. // import UIKit import SnapKit import RxTheme import RxSwift extension UIView { /// x public var x : CGFloat { get { return self.frame.origin.x } set (x) { var frame = self.frame frame.origin.x = x self.frame = frame } } /// y public var y : CGFloat { get { return self.frame.origin.y } set (y) { var frame = self.frame frame.origin.y = y self.frame = frame } } /// maxX public var maxX : CGFloat { get { return self.frame.maxX } set(maxX) { self.frame.origin.x = maxX - self.frame.size.width } } /// maxY public var maxY : CGFloat { get { return self.frame.maxY } set(maxY) { self.frame.origin.y = maxY - self.frame.size.height } } /// width public var width : CGFloat { get { return self.frame.size.width } set (width) { var frame = self.frame frame.size.width = width self.frame = frame } } /// height public var height : CGFloat { get { return self.frame.size.height } set (height) { var frame = self.frame frame.size.height = height self.frame = frame } } /// centerX public var centerX : CGFloat { get { return self.center.x } set (centerX) { var center = self.center center.x = centerX self.center = center } } /// centerY public var centerY : CGFloat { get { return self.center.y } set (centerY) { var center = self.center center.y = centerY self.center = center } } /// size public var size : CGSize { get { return self.frame.size } set (size) { var newSize = self.frame.size newSize = CGSize(width: size.width, height: size.height) self.frame.size = newSize } } /// origin public var origin : CGPoint { get { return self.frame.origin } set (origin) { var newOrigin = self.frame.origin newOrigin = CGPoint(x: origin.x, y: origin.y) self.frame.origin = newOrigin } } /// cornerRadius public var radius: CGFloat { get { return self.layer.cornerRadius; } set (radius){ self.layer.cornerRadius = radius guard self.layer.masksToBounds else { return } self.layer.masksToBounds = true } } /// borderWidth public var borderWidth: CGFloat { get { return self.layer.borderWidth } set (borderWidth){ self.layer.borderWidth = borderWidth guard self.layer.masksToBounds else { return } self.layer.masksToBounds = true } } /// 指定方向设置圆角 /// /// - Parameters: /// - size: 圆角大小 /// - type: 圆角方向 public func makeLayerRadius(_ size: CGSize?, type: UIRectCorner) { guard let size = size else { return } let bezierPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: type, cornerRadii: size) let maskLayer = CAShapeLayer() maskLayer.frame = self.bounds maskLayer.path = bezierPath.cgPath self.layer.mask = maskLayer } /// 设置view的阴影 /// /// - Parameters: /// - radius: 阴影的模糊半径 /// - color: 阴影颜色 /// - size: 阴影的偏移量 public func makeLayerShadow(_ size: CGSize?, radius: CGFloat, color: UIColor, opacity: Float) { guard let size = size else { return } self.layer.shadowColor = color.cgColor self.layer.shadowOffset = size self.layer.shadowRadius = radius self.layer.shadowOpacity = opacity //阴影透明度(默认为1 | 0为不显示) } /// 设置view的阴影 带圆角 /// /// - Parameters: /// - size: 大小 默认CGSize(width: 0, height: 3) /// - radius: 阴影的模糊半径 默认 4 /// - color: 阴影颜色 默认 黑色 0.5 /// - opacity: 阴影 渲染 默认1 /// - corner: 圆角 默认18 public func makeLayerShadowCorner(size: CGSize = CGSize(width: 0, height: 3), radius: CGFloat = 4, color: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5), opacity: Float = 1, corner: CGFloat = 18) { self.layer.shadowColor = color.cgColor self.layer.shadowOffset = size self.layer.shadowRadius = radius self.layer.shadowOpacity = opacity self.layer.cornerRadius = corner } /// 设置view的阴影 带圆角 背景色 /// /// - Parameters: /// - size: 大小 默认CGSize(width: 0, height: 2) /// - radius: 阴影的模糊半径 默认 3 /// - color: 阴影颜色 默认 黑色 0.3 /// - opacity: 阴影 渲染 默认1 /// - corner: 圆角 默认8 /// - bgColor: 背景色 public func setShadowCornerBgColor(size: CGSize = CGSize(width: 0, height: 2), radius: CGFloat = 3, color: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3), opacity: Float = 1, corner: CGFloat = 8, bgColor: UIColor) { self.layer.cornerRadius = corner self.layer.shadowColor = color.cgColor self.layer.shadowRadius = radius self.layer.shadowOffset = size self.layer.shadowOpacity = opacity self.layer.backgroundColor = bgColor.cgColor } /// 设置view的渐变颜色背景 /// /// - Parameter colors: 渐变色颜色数组 public func makeGradientLayer(_ colors: [Any]?) { guard let colors = colors else { return } let gradientLayer = CAGradientLayer() let sizeWidth = UIScreen.main.bounds.size.width gradientLayer.frame = CGRect(x: 0, y: 0, width: sizeWidth, height: self.height) gradientLayer.startPoint = CGPoint(x: -0.03, y: -0.1) gradientLayer.endPoint = CGPoint(x: 0.96, y: 0.96) gradientLayer.locations = [0, 1.0] //设置渐变的主颜色 gradientLayer.colors = colors //将gradientLayer作为子layer添加到主layer上 self.layer.addSublayer(gradientLayer) self.layer.masksToBounds = true } /// 设置view默认的渐变颜色(3E9EF7 ~ 185DFF) public func makeDefaultGradient() { let colors = [UIColor.colorWithHexStr("3E9EF7").cgColor, UIColor.colorWithHexStr("185DFF").cgColor] let gradientLayer = CAGradientLayer() let sizeWidth = UIScreen.main.bounds.size.width gradientLayer.frame = CGRect(x: 0, y: 0, width: sizeWidth, height: self.height) gradientLayer.startPoint = CGPoint(x: 0, y: 0) gradientLayer.endPoint = CGPoint(x: 0.93, y: 0.92) gradientLayer.locations = [0, 1.0] //设置渐变的主颜色 gradientLayer.colors = colors //将gradientLayer作为子layer添加到主layer上 self.layer.addSublayer(gradientLayer) self.layer.masksToBounds = true } /// 设置边距宽度和颜色 func makeLayerBoards(at width: CGFloat, color: UIColor) { self.layer.borderWidth = width self.layer.borderColor = color.cgColor } } extension UIView { class func fromNib() -> T { return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T } } protocol ViewChainable {} extension ViewChainable where Self: UIView { @discardableResult func config(_ config: (Self) -> Void) -> Self { config(self) return self } } extension UIView: ViewChainable { func adhere(toSuperView: UIView) -> Self { toSuperView.addSubview(self) return self } @discardableResult func layout(snapKitMaker: (ConstraintMaker) -> Void) -> Self { self.snp.makeConstraints { (make) in snapKitMaker(make) } return self } } extension UIView { func cropView(with rect: CGRect) -> UIImage? { UIGraphicsBeginImageContextWithOptions(CGSize(width: rect.size.width, height: rect.size.height), _: false, _: 0.0) //currentView 当前的view 创建一个基于位图的图形上下文并指定大小为 if let context = UIGraphicsGetCurrentContext() { layer.render(in: context) } //renderInContext呈现接受者及其子范围到指定的上下文 let viewImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext() //返回一个基于当前图形上下文的图片 UIGraphicsEndImageContext() //移除栈顶的基于当前位图的图形上下文 // UIImageWriteToSavedPhotosAlbum(viewImage, nil, nil, nil);//然后将该图片保存到图片图 return viewImage } } // MARK: - Reactive Tap extension UIView { func tapClosure(callback:(@escaping() -> ())) { let tapGesture = UITapGestureRecognizer() self.isUserInteractionEnabled = true self.addGestureRecognizer(tapGesture) // 绑定方式实现 tapGesture.rx.event.bind { _ in callback() }.disposed(by: rx.disposeBag) } } // MARK: - ThemeProxy public extension ThemeProxy where Base: UITextField { /// (set only) bind a stream to borderColor var placeholderColor: ThemeAttribute { get { fatalError("set only") } set { base.setPlaceHolderTextColor(newValue.value ?? UIColor.lightGray) } } } @available(iOS 13.0, *) public extension ThemeProxy where Base: UIBarAppearance { /// (set only) bind a stream to backgroundColor var backgroundColor: ThemeAttribute { get { fatalError("set only") } set { base.backgroundColor = newValue.value ?? UIColor.gray } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Extensions/UIView/UIViewController+Extend.swift ================================================ // // UIViewController+Extend.swift // FY-JetChat // // Created by iOS.Jet on 2019/8/18. // Copyright © 2019 Jett. All rights reserved. // import Foundation extension UIViewController { /// Configuration back @objc func back(_ animated: Bool = true) { navigationController?.popViewController(animated: animated) } // Get currentController class func currentViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? { if let navigationController = controller as? UINavigationController { return currentViewController(controller: navigationController.visibleViewController) } if let tabBarController = controller as? UITabBarController { if let selected = tabBarController.selectedViewController { return currentViewController(controller: selected) } } if let presented = controller?.presentedViewController { return currentViewController(controller: presented) } return controller } } ================================================ FILE: JetChat/FY-IMChat/Classes/Libraries/ActionSheet/FYActionSheet.swift ================================================ // // FYActionSheet.swift // FYActionSheet // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019年 Jett. All rights reserved. // import Foundation import UIKit class FYActionSheet: BottomPopupViewController { // MARK: - Setter var textFont: UIFont? { didSet { guard let font = textFont else { return } titleFont = font tableView.reloadData() } } var cancelTextFont: UIFont? { didSet { guard let font = textFont else { return } cancelBtn.titleLabel?.font = font } } var textColor: UIColor? var cancelTextColor: UIColor? { didSet { guard let titleColor = textColor else { return } cancelBtn.setTitleColor(titleColor, for: .normal) } } // MARK: - Private private let bottomSpace: CGFloat = 6 private let cancelHeight: CGFloat = 44 private let bottomSafeHeight: CGFloat = 34 // MARK: - lazy var public var handler: ((_ index: Int)->Void)? private var titleFont: UIFont? = nil private var dataSource: [String] = [] private var isShowCancel: Bool = true private var cellHeight: CGFloat = 55 private var tableHeight: CGFloat { return CGFloat(dataSource.count) * cellHeight } private lazy var cancelBtn: UIButton = { let button = UIButton(type: .custom) button.frame = CGRect(x: 0, y: bottomSpace, width: kScreenW, height: cancelHeight) button.setTitle("取消".rLocalized(), for: .normal) button.titleLabel?.font = .PingFangRegular(14) button.theme.titleColor(from: themed { $0.FYColor_Main_TextColor_V2 }, for: .normal) button.addTarget(self, action: #selector(dissAction), for: .touchUpInside) button.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V12 } return button }() private lazy var footerBtnView: UIView = { let height = bottomSpace + cancelHeight let footerView = UIView(frame: CGRect(x: 0, y: 0, width: kScreenW, height: height)) footerView.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V2 } footerView.addSubview(cancelBtn) return footerView }() private lazy var tableView: UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: kScreenW, height: tableHeight)) tableView.delegate = self tableView.bounces = false tableView.isScrollEnabled = false tableView.dataSource = self tableView.separatorStyle = .none tableView.tableFooterView = UIView() tableView.estimatedSectionHeaderHeight = 0 tableView.estimatedSectionFooterHeight = 0 tableView.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V2 } tableView.register(cellWithClass: FYActionSheetCell.self) return tableView }() // MARK: - Life cycle required public init(isShowCancel: Bool = false, actionTitles: [String]) { self.dataSource = actionTitles self.isShowCancel = isShowCancel super.init(nibName: nil, bundle: nil) buildUI() } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func buildUI() { view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V2 } if isShowCancel { tableView.tableFooterView = footerBtnView } view.addSubview(tableView) tableView.snp.makeConstraints { (make) in make.top.equalToSuperview() make.left.right.equalToSuperview() make.bottom.equalToSuperview() } tableView.reloadData() } // Bottom popup attribute variables // You can override the desired variable to change appearance override var popupHeight: CGFloat { makePopHeight() } override var popupTopCornerRadius: CGFloat { return 8 } override var popupPresentDuration: Double { return 0.25 } override var popupDismissDuration: Double { return 0.25 } override var popupShouldDismissInteractivelty: Bool { return false } override var popupDimmingViewAlpha: CGFloat { return BottomPopupConstants.kDimmingViewDefaultAlphaValue } private func makePopHeight() -> CGFloat { if isShowCancel { return tableHeight + footerBtnView.height + bottomSafeHeight }else { return tableHeight + bottomSafeHeight } } // MARK: - Action @objc func dissAction() { dismiss(animated: true, completion: nil) } } // MARK: - UITableViewDataSource && UITableViewDelegate extension FYActionSheet: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withClass: FYActionSheetCell.self) if dataSource.count > indexPath.row { cell.textColor = textColor cell.titleFont = titleFont cell.title = dataSource[safe: indexPath.row] if indexPath.row == dataSource.count - 1 { cell.hideLine = true } } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return cellHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if dataSource.count > indexPath.row { handler?(indexPath.row) } dissAction() } } ================================================ FILE: JetChat/FY-IMChat/Classes/Libraries/ActionSheet/FYActionSheetCell.swift ================================================ // // FYActionSheetCell.swift // PGActionSheet // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019年 Jett. All rights reserved. // import UIKit class FYActionSheetCell: UITableViewCell { // MARK: - setter && getter var title: String? { didSet { titleLabel.text = title ?? "" } } var titleFont: UIFont? { didSet { guard let font = titleFont else { return } titleLabel.font = font } } var textColor: UIColor? { didSet { guard let color = textColor else { return } titleLabel.textColor = color } } var hideLine: Bool? { didSet { guard let isHidden = hideLine else { return } lineView.isHidden = isHidden } } private lazy var titleLabel: UILabel = { let label = UILabel() label.textAlignment = .center label.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } label.font = .PingFangRegular(14) return label }() private lazy var lineView: UIView = { let view = UIView() view.theme.backgroundColor = themed { $0.FYColor_BorderColor_V1 } return view }() // MARK: - life cycle override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.selectionStyle = .none theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V12 } contentView.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V12 } buildUI() } private func buildUI() { contentView.addSubview(titleLabel) contentView.addSubview(lineView) titleLabel.snp.makeConstraints { (make) in make.left.equalToSuperview().offset(13) make.right.equalToSuperview().offset(-13) make.centerY.equalToSuperview() } lineView.snp.makeConstraints { (make) in make.left.right.equalToSuperview() make.bottom.equalToSuperview() make.height.equalTo(0.5) } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: JetChat/FY-IMChat/Classes/Libraries/HUDProgress/MBConfiguredHUD.swift ================================================ // // MBConfiguredHUD.swift // FY-JetChat // // Created by iOS.Jet on 2019/2/28. // Copyright © 2019 Jett. All rights reserved. // import Foundation import MBProgressHUD import UIKit typealias MBHUD = MBConfiguredHUD /// 弹框显示时间 fileprivate let kAfterDelay: TimeInterval = 1.5 class MBConfiguredHUD: NSObject { fileprivate static let kRegularFont = UIFont.PingFangRegular(14) /// 获取用于显示提示框的view class func hudWindow() -> UIWindow { var window = UIApplication.shared.keyWindow if window?.windowLevel != UIWindow.Level.normal { let windowArray = UIApplication.shared.windows for newWindow in windowArray { if newWindow.windowLevel == UIWindow.Level.normal { window = newWindow break } } } return window! } // MARK: - 具体样式设置 /// 普通的菊花HUD(需要手动隐藏) class func show() { DispatchQueue.main.async { let window = hudWindow() MBProgressHUD.hide(for: window, animated: false) // create let hud = MBProgressHUD.showAdded(to: window, animated: true) hud.mode = .indeterminate hud.label.textColor = .white hud.label.font = self.kRegularFont hud.contentColor = .white //文字和菊花颜色 hud.bezelView.style = .solidColor UIActivityIndicatorView.appearance(whenContainedInInstancesOf: [MBProgressHUD.self]).color = .white //设置菊花颜色为白色 hud.bezelView.color = .hudBackgroundColor() hud.isUserInteractionEnabled = false hud.removeFromSuperViewOnHide = true } } /// 带文字的菊花HUD(需手动隐藏) class func showStatus(_ status: String, view: UIView = hudWindow()) { DispatchQueue.main.async { let window = view MBProgressHUD.hide(for: window, animated: false) // create let hud = MBProgressHUD.showAdded(to: window, animated: true) hud.label.text = status hud.mode = .indeterminate hud.label.textColor = .white hud.label.font = self.kRegularFont hud.animationType = .zoom hud.contentColor = .white //文字和菊花颜色 hud.bezelView.style = .solidColor UIActivityIndicatorView.appearance(whenContainedInInstancesOf: [MBProgressHUD.self]).color = .white hud.isUserInteractionEnabled = false hud.bezelView.color = .hudBackgroundColor() hud.removeFromSuperViewOnHide = true } } /// 普通文本提示HUD(自动隐藏) class func showMessage(_ message: String) { DispatchQueue.main.async { let window = hudWindow() MBProgressHUD.hide(for: window, animated: false) // create let hud = MBProgressHUD.showAdded(to: window, animated: true) hud.mode = .text hud.label.text = message hud.label.textColor = .white hud.label.font = self.kRegularFont hud.contentColor = .white //文字和菊花颜色 hud.bezelView.style = .solidColor hud.margin = 5 hud.animationType = .zoom hud.isUserInteractionEnabled = false hud.bezelView.color = .hudBackgroundColor() //hud.bezelView.cornerRadius = 4 hud.removeFromSuperViewOnHide = true hud.hide(animated: true, afterDelay: kAfterDelay) } } /// 长类型文本提示HUD(自动隐藏) class func showLongMessage(_ message: String) { DispatchQueue.main.async { let window = hudWindow() MBProgressHUD.hide(for: window, animated: false) // create let hud = MBProgressHUD.showAdded(to: window, animated: true) hud.mode = .text hud.label.text = message hud.label.numberOfLines = 0 hud.label.textColor = .white hud.label.font = self.kRegularFont hud.contentColor = .white //文字和菊花颜色 hud.bezelView.style = .solidColor hud.margin = 5 hud.animationType = .zoom hud.isUserInteractionEnabled = false hud.bezelView.color = .hudBackgroundColor() //hud.bezelView.cornerRadius = 4 hud.removeFromSuperViewOnHide = true hud.hide(animated: true, afterDelay: kAfterDelay) } } /// 成功提示HUD + 图片 class func showSuccess(_ success: String) { DispatchQueue.main.async { let window = hudWindow() MBProgressHUD.hide(for: window, animated: false) // create let hud = MBProgressHUD.showAdded(to: window, animated: true) let imageView = UIImageView(image: UIImage(named: "HUDAssets.bundle/icon_hud_success")) hud.mode = .customView hud.label.text = success hud.label.textColor = .white hud.label.font = self.kRegularFont hud.contentColor = .white //文字和菊花颜色 hud.bezelView.layer.masksToBounds = false; hud.bezelView.style = .solidColor hud.animationType = .zoom hud.customView = imageView //hud.minSize = CGSize(width: 231, height: 123) //弹框大小设置 hud.isUserInteractionEnabled = false hud.bezelView.color = .hudBackgroundColor() hud.removeFromSuperViewOnHide = true hud.hide(animated: true, afterDelay: kAfterDelay) } } /// 失败提示HUD + 图片 class func showFailure(_ success: String) { DispatchQueue.main.async { let window = hudWindow() MBProgressHUD.hide(for: window, animated: false) // create let hud = MBProgressHUD.showAdded(to: window, animated: true) let imageView = UIImageView(image: UIImage(named: "HUDAssets.bundle/icon_hud_failure")) hud.mode = .customView hud.label.text = success hud.label.textColor = .white hud.label.font = self.kRegularFont hud.contentColor = .white //文字和菊花颜色 hud.animationType = .zoom hud.bezelView.layer.masksToBounds = false; hud.bezelView.style = .solidColor hud.customView = imageView //hud.minSize = CGSize(width: 231, height: 123) //弹框大小设置 hud.isUserInteractionEnabled = false hud.bezelView.color = .hudBackgroundColor() hud.removeFromSuperViewOnHide = true hud.hide(animated: true, afterDelay: kAfterDelay) } } /// 错误提示HUD + 图片 class func showImageError(_ error: String) { DispatchQueue.main.async { let window = hudWindow() MBProgressHUD.hide(for: window, animated: false) // create let hud = MBProgressHUD.showAdded(to: window, animated: true) let imageView = UIImageView(image: UIImage(named: "HUDAssets.bundle/icon_hud_error")) hud.mode = .customView hud.label.text = error hud.label.textColor = .white hud.label.font = self.kRegularFont hud.contentColor = .white //文字和菊花颜色 hud.animationType = .zoom hud.bezelView.layer.masksToBounds = false; hud.bezelView.style = .solidColor hud.customView = imageView hud.isUserInteractionEnabled = false hud.bezelView.color = .hudBackgroundColor() hud.removeFromSuperViewOnHide = true hud.hide(animated: true, afterDelay: kAfterDelay) } } /// HUD立即消失 class func hide(_ view: UIView = hudWindow()) { DispatchQueue.main.async { view.isUserInteractionEnabled = true MBProgressHUD.hide(for: view, animated: true) } } /// HUD指定时间消失 class func hideWithDelay(_ view: UIView = hudWindow(), delay: TimeInterval = kAfterDelay) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay) { view.isUserInteractionEnabled = true MBProgressHUD.hide(for: view, animated: true) } } } extension MBConfiguredHUD { /// 指定父视图提示HUD class func showMessageInView(_ message: String, view: UIView) { DispatchQueue.main.async { MBProgressHUD.hide(for: view, animated: true) // create let hud = MBProgressHUD.showAdded(to: view, animated: true) hud.mode = .text hud.label.text = message hud.label.textColor = .white hud.label.font = self.kRegularFont hud.contentColor = .white //文字和菊花颜色 hud.bezelView.style = .solidColor hud.margin = 5 hud.animationType = .zoom hud.isUserInteractionEnabled = false hud.bezelView.color = .hudBackgroundColor() //hud.bezelView.cornerRadius = 4 hud.removeFromSuperViewOnHide = true hud.hide(animated: true, afterDelay: kAfterDelay) } } /// 指定父视图成功提示HUD class func showSuccessInView(_ success: String, view: UIView) { MBConfiguredHUD.hide() DispatchQueue.main.async { MBProgressHUD.hide(for: view, animated: true) // create let hud = MBProgressHUD.showAdded(to: view, animated: true) let imageView = UIImageView(image: UIImage(named: "HUDAssets.bundle/icon_hud_success")) hud.mode = .customView hud.label.text = success hud.label.textColor = .white hud.label.font = self.kRegularFont hud.contentColor = .white //文字和菊花颜色 hud.animationType = .zoom hud.bezelView.layer.masksToBounds = false; hud.bezelView.style = .solidColor hud.customView = imageView //hud.minSize = CGSize(width: 231, height: 123) //弹框大小设置 hud.isUserInteractionEnabled = true hud.bezelView.color = .hudBackgroundColor() hud.removeFromSuperViewOnHide = true hud.hide(animated: true, afterDelay: kAfterDelay) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Libraries/NavPopup/Cell/FYNavDropMenuCell.swift ================================================ // // FYNavDropMenuCell.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/6. // Copyright © 2019 Jett. All rights reserved. // import UIKit class FYNavDropMenuCell: UITableViewCell { // MARK: - setter && getter var model: FYCellDataConfig? { didSet { guard let model = model else { return } titleLabel.text = model.title leftImageView.image = UIImage(named: model.image!) lineView.isHidden = !model.isShow if model.image.isBlank { titleLabel.snp.remakeConstraints { (make) in make.centerY.equalToSuperview() make.centerX.equalToSuperview() } } } } private lazy var titleLabel: UILabel = { let label = UILabel() label.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } label.font = UIFont.PingFangRegular(15) return label }() private lazy var leftImageView: UIImageView = { let imageView = UIImageView() return imageView }() private lazy var lineView: UIView = { let view = UIView() view.theme.backgroundColor = themed { $0.FYColor_BorderColor_V1 } return view }() // MARK: - life cycle override func awakeFromNib() { super.awakeFromNib() // Initialization code self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V12 } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.makeUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func makeUI() { self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V12 } contentView.addSubview(leftImageView) contentView.addSubview(titleLabel) contentView.addSubview(lineView) leftImageView.snp.makeConstraints { (make) in make.centerY.equalToSuperview() make.left.equalToSuperview().offset(16) make.width.equalTo(25) make.height.equalTo(25) } titleLabel.snp.makeConstraints { (make) in make.centerY.equalToSuperview() make.left.equalTo(leftImageView.snp.right).offset(5) } lineView.snp.makeConstraints { (make) in make.left.bottom.right.equalToSuperview() make.height.equalTo(0.6) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Libraries/NavPopup/FYNavPopuListMenu.swift ================================================ // // FYNavPopuListMenu.swift // FYNavPopuListMenu // // Created by iOS.Jet on 2019/2/20. // Copyright © 2019 Jett. All rights reserved. // import UIKit /// 声明代理方法 protocol FYPopListMenuDelegate: Any { func menu(_ model: FYCellDataConfig, didSelectRowAt index: Int) } extension FYPopListMenuDelegate { func menu(_ model: FYCellDataConfig, didSelectRowAt index: Int) { } } class FYNavPopuListMenu: UIView { let kMaxCount: CGFloat = 6 let kCellHeight: CGFloat = 50 let triangleHeight: CGFloat = 12 var tableH: CGFloat = 0 let tableW: CGFloat = 140 let xSpace: CGFloat = 15 var ySpace: CGFloat = kNavigaH + 1 var didClosedClosure : (()->Void)? var didSelectedClosure : ((Int, FYCellDataConfig)->Void)? var dataSource: [FYCellDataConfig] = [] /// 设置代理 var delegate: FYPopListMenuDelegate? /// 尖角 var triangleView: UIView? var triangleFrame: CGRect? // MARK: - var lazy private lazy var tapGesture: UITapGestureRecognizer = { let tap = UITapGestureRecognizer.init(target: self, action: #selector(dismiss)) tap.numberOfTapsRequired = 1 return tap }() private lazy var menuMaskView: UIView = { let maskView = UIView.init() maskView.isUserInteractionEnabled = true maskView.addGestureRecognizer(tapGesture) maskView.backgroundColor = UIColor.clear maskView.alpha = 0 return maskView }() private lazy var tableView: UITableView = { let table = UITableView.init() table.delegate = self table.dataSource = self table.cornerRadius = 5 table.estimatedRowHeight = 0 table.rowHeight = kCellHeight table.separatorStyle = .none table.tableFooterView = UIView() table.showsVerticalScrollIndicator = false table.separatorInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10) table.isScrollEnabled = dataSource.count > Int(kMaxCount) table.register(FYNavDropMenuCell.self, forCellReuseIdentifier: "kPopMenuCellIdentifier") return table }() private lazy var contentView: UIView = { let content = UIView.init() content.backgroundColor = UIColor.clear content.makeLayerShadowCorner() content.alpha = 0 return content }() // MARK: - Life cycle convenience init(dataSource: [FYCellDataConfig], ySpace: CGFloat = kNavigaH + 1) { self.init() self.ySpace = ySpace self.dataSource = dataSource makeUI() } override init(frame: CGRect) { super.init(frame: frame) makeUI() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) makeUI() } func makeUI() { self.frame = UIScreen.main.bounds self.backgroundColor = UIColor.clear guard dataSource.count > 0 else { return } self.menuMaskView.frame = self.bounds self.addSubview(menuMaskView) tableH = dataSource.count > Int(kMaxCount) ? kMaxCount : CGFloat(dataSource.count) * kCellHeight contentView.frame = CGRect(x: kScreenW - tableW - xSpace, y: ySpace, width: tableW, height: tableH + triangleHeight) addSubview(contentView) triangleFrame = CGRect(x: tableW - 25, y: 0, width: 16, height: triangleHeight) drawTriangleView() tableView.frame = CGRect(x: 0, y: triangleHeight, width: tableW, height: tableH) contentView.addSubview(tableView) } // 绘制三角形尖角 func drawTriangleView() { if triangleView == nil { triangleView = UIView() triangleView?.alpha = 0.0 triangleView?.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V12 } contentView.addSubview(triangleView!) } triangleFrame = CGRect(x: self.triangleFrame!.minX, y: triangleFrame!.minY, width: triangleFrame!.width, height: triangleFrame!.height) triangleView?.frame = triangleFrame! let shaperLayer = CAShapeLayer() let path = CGMutablePath() path.move(to: CGPoint(x: triangleFrame!.width * 0.5, y: 0), transform: .identity) path.addLine(to: CGPoint(x: 0, y: triangleFrame!.height), transform: .identity) path.addLine(to: CGPoint(x: triangleFrame!.width, y: triangleFrame!.height), transform: .identity) shaperLayer.path = path triangleView?.layer.mask = shaperLayer } } // MARK: - Action extension FYNavPopuListMenu { func show() { self.contentView.alpha = 1.0 self.menuMaskView.alpha = 1.0 self.triangleView?.alpha = 1.0 UIApplication.shared.keyWindow?.addSubview(self) let animation = CAKeyframeAnimation(keyPath: "transform") animation.duration = CFTimeInterval(0.2) //动画时间 var values = [AnyHashable]() values.append(NSValue(caTransform3D: CATransform3DMakeScale(0.5, 0.5, 1.0))) values.append(NSValue(caTransform3D: CATransform3DMakeScale(0.95, 0.95, 1.0))) values.append(NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0))) animation.values = values self.contentView.layer.add(animation, forKey: nil) } @objc func dismiss(_ isAnimate: Bool = true) { // 关闭 self.didClosedClosure?() self.contentView.layer.removeAllAnimations() UIView.animate(withDuration: 0.25, animations: { [unowned self] in self.triangleView?.alpha = 0 self.menuMaskView.alpha = 0 self.contentView.alpha = 0 self.contentView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) }) { (finished) -> Void in self.removeFromSuperview() } } } // MARK: - UITableViewDataSource && Delegate extension FYNavPopuListMenu: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "kPopMenuCellIdentifier", for: indexPath) as! FYNavDropMenuCell if let model = dataSource[safe: indexPath.row] { cell.model = model } return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let model = dataSource[safe: indexPath.row] { delegate?.menu(model, didSelectRowAt: indexPath.row) // block if didSelectedClosure != nil { didSelectedClosure?(indexPath.row, model) } } dismiss() } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return kCellHeight } } ================================================ FILE: JetChat/FY-IMChat/Classes/Libraries/Refreshing/FYMomentsHeaderRefresh.swift ================================================ // // FYMomentsHeaderRefresh.swift // FY-JetChat // // Created by Jett on 2022/4/28. // Copyright © 2022 Jett. All rights reserved. // import UIKit import MJRefresh fileprivate let kHeaderHeight: CGFloat = 60 class FYMomentsHeaderRefresh: MJRefreshHeader { private lazy var rotateImageView: UIImageView = { let imageView = UIImageView(image: R.image.ic_album_reflash()) return imageView }() // MARK: - prepare override func prepare() { super.prepare() self.ignoredScrollViewContentInsetTop = -60 self.mj_h = kHeaderHeight self.addSubview(rotateImageView) self.mj_y = -self.mj_h - self.ignoredScrollViewContentInsetTop; } override func placeSubviews() { super.placeSubviews() self.rotateImageView.frame = CGRect(x: 30, y: 30, width: 30, height: 30) } // MARK: - ScrollViewPanStateDidChange override func scrollViewPanStateDidChange(_ change: [AnyHashable : Any]?) { super.scrollViewPanStateDidChange(change) self.mj_y = -self.mj_h - self.ignoredScrollViewContentInsetTop; let scrollViewOffsetY: CGFloat = self.scrollView?.mj_offsetY ?? 0 let pullingY: CGFloat = abs(scrollViewOffsetY + 64 + self.ignoredScrollViewContentInsetTop) if (pullingY >= kHeaderHeight) { let marginY: CGFloat = -kHeaderHeight - (pullingY - kHeaderHeight) - self.ignoredScrollViewContentInsetTop; self.mj_y = marginY ; } UIView.animate(withDuration: 1.35) { self.rotateImageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2).concatenating(self.rotateImageView.transform) } } // MARK: - MJRefreshState override var state: MJRefreshState { didSet { switch state { case .idle: self.rotateImageView.isHidden = true case .pulling: self.rotateImageView.isHidden = false case .refreshing: self.rotateImageView.isHidden = false default: break } } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/Controller/FYChatBaseViewController.swift ================================================ // // FYChatBaseViewController.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/13. // Copyright © 2019 Jett. All rights reserved. // import UIKit import IQKeyboardManagerSwift import YBImageBrowser import RxSwift public enum ChatType: Int { case singleChat = 1 case groupedChat = 2 } private let kTextMessageCellIdentifier = "kTextMessageCellIdentifier" private let kImageMessageCellIdentifier = "kImageMessageCellIdentifier" private let kVideoMessageCellIdentifier = "kVideoMessageCellIdentifier" class FYChatBaseViewController: FYBaseViewController { /// 角标数记录 private var badge: Int = 0 // 键盘收起/弹出 private var isBecome: Bool = false private var isSended: Bool = true private var isTimered: Bool = false private let keyboardLastY: CGFloat = 301 private let kToolBarLastH: CGFloat = 52 private var kToolBarLastY: CGFloat = 551 private var lastMaxY: CGFloat = 0.0 // MARK: - var lazy /// 聊天类型(默认单聊) var chatModel: FYMessageChatModel? = FYMessageChatModel() /// 消息转发 var isForward: Bool = false var forwardData: FYMessageItem? var timer: Timer? var viewModel: FYMessageViewModel? var dataSource: [FYMessageItem] = [] private lazy var keyboardView: ChatKeyboardView = { let toolBarY = kScreenH - kNavigaH - kToolBarLastH - kSafeAreaBottom let view = ChatKeyboardView(frame: CGRect(x: 0, y: toolBarY, width: kScreenW, height: kToolBarLastH)) view.delegate = self return view }() // MARK: - life cycle override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) IQKeyboardManager.shared.enable = false IQKeyboardManager.shared.enableAutoToolbar = false } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // 禁止侧滑返回手势 navigationController?.interactivePopGestureRecognizer?.isEnabled = false navigationController?.fd_fullscreenPopGestureRecognizer.isEnabled = false } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) IQKeyboardManager.shared.enable = true IQKeyboardManager.shared.enableAutoToolbar = true // stop timer stopChatTimer() } override func viewDidLoad() { super.viewDidLoad() view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V2 } configNavBar() loadCacheData() } /// 构造聊天会话 /// - Parameter chatModel: 聊天实体(分单聊、群聊) /// - Parameter isForward: 是否转发消息 convenience init(chatModel: FYMessageChatModel, isForward: Bool = false) { self.init() self.chatModel = chatModel self.isForward = isForward self.viewModel = FYMessageViewModel(chatModel: chatModel) } private func configNavBar() { if chatModel?.nickName.isBlank == false { navigationItem.title = chatModel?.nickName }else { navigationItem.title = chatModel?.name } if chatModel?.chatType == 2 { let rightBarButtonItem = UIBarButtonItem(title: "退出群".rLocalized(), style: .plain, target: self, action: #selector(exitGroupChat)) navigationItem.rightBarButtonItem = rightBarButtonItem } } override func makeUI() { super.makeUI() let height = kScreenH - (kToolBarLastH + kSafeAreaBottom + kNavigaH) plainTabView.frame = CGRect(x: 0, y: 0, width: kScreenW, height: height) plainTabView.dataSource = self plainTabView.delegate = self plainTabView.separatorStyle = .none plainTabView.estimatedRowHeight = 100 plainTabView.tableFooterView = UIView() plainTabView.showsVerticalScrollIndicator = true registerChatCell() view.addSubview(plainTabView) // 添加聊天键盘 view.addSubview(keyboardView) viewModel?.tableView = plainTabView } override func bindViewModel() { super.bindViewModel() let refresh = Observable.of(Observable.never(), loadingTrigger).merge() let input = FYMessageViewModel.Input(makeMessage: refresh, makeBrowser: refresh) let output = viewModel?.transform(input: input) // message output?.message.asDriver(onErrorJustReturn: FYMessageItem()) .drive(onNext: { [weak self] (msgItem) in guard let self = self else { return } guard msgItem.msgType != nil else { return } self.reloadChatData(msgItem) }).disposed(by: rx.disposeBag) // browser output?.browser.asDriver(onErrorJustReturn: []) .drive(onNext: { [weak self] (objects) in guard let self = self else { return } guard objects.count > 0 else { return } self.browserWithData(objects, index: (self.viewModel?.imageIndex.value)!) }).disposed(by: rx.disposeBag) // forward if isForward { if let messageItem = forwardData { reloadChatData(messageItem) } } } private func startChatTimer() { if chatModel?.chatType == 2 && !self.isTimered { timer = Timer(timeInterval: 10, target: self, selector: #selector(makeGroupAutoSend), userInfo: nil, repeats: true) RunLoop.main.add(timer!, forMode: .common) timer?.fire() // 启动定时器 isTimered = true } } private func saveWidgetData(item: FYMessageItem) { if let object = item.toJSON() { FYUserDefaultManager.saveWidgetObject(object) } AppDelegate.app.reloadWidgetData() } private func stopChatTimer() { if isTimered { timer?.invalidate() } } private func registerChatCell() { plainTabView.register(FYTextMessageCell.self, forCellReuseIdentifier: kTextMessageCellIdentifier) plainTabView.register(FYImageMessageCell.self, forCellReuseIdentifier: kImageMessageCellIdentifier) plainTabView.register(FYVideoMessageCell.self, forCellReuseIdentifier: kVideoMessageCellIdentifier) } /// 滚到底部 private func scrollToBottom(_ animated: Bool = true) { plainTabView.scrollToLast(at: .bottom, animated: animated) } override func touchesEnded(_ touches: Set, with event: UIEvent?) { let myTouch = touches.first! as UITouch let myLocation = myTouch.location(in: self.view) if myLocation.y < keyboardLastY { NotificationCenter.default.post(name: .kChatTextKeyboardNeedHide, object: nil) } } /// 退出群聊 @objc private func exitGroupChat() { EasyAlertView.customAlert(title: "确定退出当前群组吗?".rLocalized(), message: "", confirm: "确定".rLocalized(), cancel: "取消".rLocalized(), vc: self, confirmBlock: { if let uid = self.chatModel?.uid { self.stopChatTimer() FYDBQueryHelper.shared.deleteFromChatWithId(uid) FYDBQueryHelper.shared.deleteFromMesssageWithId(uid) // 退出群 NotificationCenter.default.post(name: .kNeedRefreshChatInfoList, object: nil) let message = String(format: "你已退出%@群聊", self.chatModel?.name ?? "") MBHUD.showMessage(message) self.navigationController?.popViewController() } }, cancelBlock: { }) } deinit { stopChatTimer() } } // MARK: - ChatKeyboardViewDelegate extension FYChatBaseViewController: ChatKeyboardViewDelegate { func keyboard(_ keyboard: ChatKeyboardView, DidFinish content: String) { makeChatMessage(.text, content: content) } func keyboard(_ keyboard: ChatKeyboardView, DidBecome isBecome: Bool) { self.isSended = true self.isBecome = isBecome } func keyboard(_ keyboard: ChatKeyboardView, DidMoreMenu type: ChatMoreMenuType) { openImagePicker(type) } func keyboard(_ keyboard: ChatKeyboardView, DidObserver offsetY: CGFloat) { restChatKeyboardSafeTop(offsetY) } private func restChatKeyboardSafeTop(_ offsetY: CGFloat) { if (dataSource.count >= 1) { let lastIndex = IndexPath(row: dataSource.count - 1, section: 0) let rect = plainTabView.rectForRow(at: lastIndex) if rect.width > 0 { lastMaxY = rect.origin.y + rect.size.height if (lastMaxY <= plainTabView.height) { if (lastMaxY >= offsetY) { plainTabView.y = offsetY - lastMaxY }else { plainTabView.y = 0 } }else { plainTabView.y += offsetY - plainTabView.maxY; } }else { printLog("========😁😁😁😁😁😁😁😁") } } } private func makeChatMessage(_ type: ChatDataType, content: String = "") { viewModel?.browserType.accept(.none) viewModel?.messageType.accept(type) if type == .text { viewModel?.content.accept(content) } loadingTrigger.onNext(()) startChatTimer() } /// 模拟群组聊天自动发送消息 @objc private func makeGroupAutoSend() { viewModel?.browserType.accept(.none) viewModel?.messageType.accept(.autoSend) loadingTrigger.onNext(()) } private func reloadChatData(_ msgItem: FYMessageItem) { isSended = true dataSource.append(msgItem) viewModel?.dataSource.accept(dataSource) DispatchQueue.main.async { self.plainTabView.reloadData { self.scrollToBottom() self.beginCacheMessage(msgItem) self.restChatKeyboardSafeTop(self.keyboardView.y) } } saveWidgetData(item: msgItem) } } // MARK: - MoreMenu Manager extension FYChatBaseViewController: TZImagePickerControllerDelegate { private func openImagePicker(_ type: ChatMoreMenuType) { let imagePicker = TZImagePickerController(maxImagesCount: 1, columnNumber: 5, delegate: self) imagePicker?.didFinishPickingPhotosHandle = {(images: [UIImage]?, assets:[Any]?, isSelectOriginalPhoto: Bool) in printLog(images) if (type == .album) { self.makeChatMessage(.image) }else if (type == .video) { self.makeChatMessage(.video) } } switch type { case .album, .video: present(imagePicker!, animated: true, completion: nil) default: break } } } // MARK: - DataBase extension FYChatBaseViewController { func beginCacheMessage(_ object: FYMessageItem) { FYDBQueryHelper.shared.insertFromMessage(object) // 更新角标 self.badge += 1 if let uid = self.chatModel?.uid { self.chatModel?.unReadCount = self.badge FYDBQueryHelper.shared.updateFromChatModel(self.chatModel!, uid: uid) // 通知刷新会话列表 NotificationCenter.default.post(name: .kNeedRefreshSesstionList, object: nil) } } func loadCacheData(_ toBottom: Bool = true) { if let chatId = chatModel?.uid { dataSource = FYDBQueryHelper.shared.qureyFromMessagesWithChatId(chatId) viewModel?.dataSource.accept(dataSource) } plainTabView.reloadData() if (toBottom) { scrollToBottom(toBottom) } } } // MARK: - UITableViewDataSource extension FYChatBaseViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if let model = dataSource[safe: indexPath.row] { if model.msgType == 1 { let textCell = tableView.dequeueReusableCell(withIdentifier: kTextMessageCellIdentifier) as! FYTextMessageCell textCell.delegate = self configureCellModel(cell: textCell, at: indexPath) return textCell }else if model.msgType == 2 { let imageCell = tableView.dequeueReusableCell(withIdentifier: kImageMessageCellIdentifier) as! FYImageMessageCell configureCellModel(cell: imageCell, at: indexPath) imageCell.delegate = self return imageCell }else if model.msgType == 3 { let videoCell = tableView.dequeueReusableCell(withIdentifier: kVideoMessageCellIdentifier) as! FYVideoMessageCell configureCellModel(cell: videoCell, at: indexPath) videoCell.delegate = self return videoCell } } return UITableViewCell() } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if let model = dataSource[safe: indexPath.row] { if model.msgType == 1 { return plainTabView.fd_heightForCell(withIdentifier: kTextMessageCellIdentifier, cacheBy: indexPath) { [weak self] (cell) in if let textCell = cell as? FYTextMessageCell { self?.configureCellModel(cell: textCell, at: indexPath) } } }else if model.msgType == 2 { return plainTabView.fd_heightForCell(withIdentifier: kImageMessageCellIdentifier, cacheBy: indexPath) { [weak self] (cell) in if let imageCell = cell as? FYImageMessageCell { self?.configureCellModel(cell: imageCell, at: indexPath) } } }else if model.msgType == 3 { return plainTabView.fd_heightForCell(withIdentifier: kVideoMessageCellIdentifier, cacheBy: indexPath) { [weak self] (cell) in if let videoCell = cell as? FYVideoMessageCell { self?.configureCellModel(cell: videoCell, at: indexPath) } } } } return 0.01 } func configureCellModel(cell: UITableViewCell, at indexPath: IndexPath) { if let textCell = cell as? FYTextMessageCell { textCell.model = self.dataSource[safe: indexPath.row] }else if let imageCell = cell as? FYImageMessageCell { imageCell.model = self.dataSource[safe: indexPath.row] }else if let viodeCell = cell as? FYVideoMessageCell { viodeCell.model = self.dataSource[safe: indexPath.row] } } } // MARK: - FYMessageBaseCellDelegate extension FYChatBaseViewController: FYMessageBaseCellDelegate { func cell(_ cell: FYMessageBaseCell, didMenu style: MenuShowStyle, model: FYMessageItem) { if style == .delete { if let row = plainTabView.indexPath(for: cell)?.item { dataSource.remove(at: row) if let messageId = model.messageId { FYDBQueryHelper.shared.deleteFromMessageId(messageId) } loadCacheData(false) // 通知刷新会话列表 NotificationCenter.default.post(name: .kNeedRefreshSesstionList, object: nil) } }else if style == .copy { if let message = model.message, message.length > 0 { message.stringGeneral() } }else if (style == .share) { messageForward(model) } } func cell(_ cell: FYMessageBaseCell, didTapAvatarAt model: FYMessageItem) { if model.sendType == 0 { return } let userModel = FYDBQueryHelper.shared.qureyFromChatId(model.chatId!) let infoVC = FYContactsInfoViewController() infoVC.chatModel = userModel navigationController?.pushViewController(infoVC) } func cell(_ cell: FYMessageBaseCell, didTapPictureAt model: FYMessageItem) { if let row = plainTabView.indexPath(for: cell)?.item { if model.msgType == 2 { viewModel?.messageType.accept(.none) viewModel?.imageIndex.accept(row) viewModel?.browserType.accept(.image) loadingTrigger.onNext(()) } } } func cell(_ cell: FYMessageBaseCell, didTapVideoAt model: FYMessageItem) { if let row = plainTabView.indexPath(for: cell)?.item { if model.msgType == 3 { viewModel?.messageType.accept(.none) viewModel?.imageIndex.accept(row) viewModel?.browserType.accept(.video) loadingTrigger.onNext(()) } } } /// 消息转发 private func messageForward(_ message: FYMessageItem) { if let chatType = message.chatType { let forwardVC = FYMessageForwardViewController() if chatType == 1 { forwardVC.forwardStyle = .friend }else { forwardVC.forwardStyle = .grouped } forwardVC.messageItem = message self.navigationController?.pushViewController(forwardVC) } } } // MARK: - UITableViewDelegate extension FYChatBaseViewController: UITableViewDelegate, UIScrollViewDelegate { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { if isSended { isSended = false } } func scrollViewDidScroll(_ scrollView: UIScrollView) { if isSended { return } if plainTabView.y <= 0 && isBecome { DispatchQueue.main.async { NotificationCenter.default.post(name: .kChatTextKeyboardNeedHide, object: nil) } } } } // MARK: - Image && Video Browser extension FYChatBaseViewController { func browserWithData(_ dataSource: [AnyObject]?, index: Int = 0) { var browserIndex = 0 let browser = YBImageBrowser() if let imageData = dataSource as? [YBIBImageData] { browserIndex = (viewModel?.browserIndexs.value[index] ?? index) browser.dataSourceArray = imageData }else if let videoData = dataSource as? [YBIBVideoData] { browserIndex = (viewModel?.browserIndexs.value[index] ?? index) browser.dataSourceArray = videoData } browser.currentPage = browserIndex - 1 browser.show() } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/Controller/FYChatRoomListViewController.swift ================================================ // // FYChatRoomListViewController.swift // FY-JetChat // // Created by iOS.Jet on 2019/8/18. // Copyright © 2019 Jett. All rights reserved. // import UIKit import WCDBSwift fileprivate let kGroupedChatCellIdentifier = "kGroupedChatCellIdentifier" class FYChatRoomListViewController: FYBaseViewController { // MARK: - var lazy var dataSource: [FYMessageChatModel] = [] private lazy var deleteButton: UIButton = { let button = UIButton(type: .custom) button.setTitle("删除所有群组".rLocalized(), for: .normal) button.theme.titleColor(from: themed { $0.FYColor_Main_TextColor_V11 }, for: .normal) button.titleLabel?.font = .PingFangRegular(16) button.sizeToFit() button.isHidden = true button.rxTapClosure { [weak self] in self?.showClearAlert() } return button }() // MARK: - life cycle override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) deleteButton.isHidden = dataSource.count > 0 ? false : true } override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "群聊".rLocalized() loadGroupData() registerGroupNoti() } @objc private func loadGroupData() { dataSource = FYDBQueryHelper.shared.qureyFromChatsWithType(2) DispatchQueue.main.async { self.plainTabView.reloadData() self.deleteButton.isHidden = self.dataSource.count > 0 ? false : true } } private func registerGroupNoti() { NotificationCenter.default.addObserver(self, selector: #selector(loadGroupData), name: .kNeedRefreshChatInfoList, object: nil) } override func makeUI() { super.makeUI() navigationItem.leftBarButtonItem = UIBarButtonItem(customView: deleteButton) let rightBarButtonItem = UIBarButtonItem(title: "加入群".rLocalized(), style: .plain, target: self, action: #selector(addGroupData)) navigationItem.rightBarButtonItem = rightBarButtonItem plainTabView.register(FYContactsTableViewCell.self, forCellReuseIdentifier: kGroupedChatCellIdentifier) view.addSubview(plainTabView) plainTabView.snp.makeConstraints { (make) in make.top.left.right.equalTo(self.view) if #available(iOS 11.0, *) { make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom) } else { make.bottom.equalTo(self.view.snp.bottomMargin) } } } @objc private func showClearAlert() { EasyAlertView.customAlert(title: "确定删除全部群组吗?".rLocalized(), message: "删除后,会话记录也将清除".rLocalized(), confirm: "确定".rLocalized(), cancel: "取消".rLocalized(), vc: self, confirmBlock: { self.removerGroupData() }, cancelBlock: { }) } @objc private func addGroupData() { var uid = 10 if let lastUser = dataSource.last { uid = lastUser.uid! + 1 } let chat = FYMessageChatModel() chat.uid = uid chat.name = "好好学习群:\(uid)" chat.avatar = "https://img2.woyaogexing.com/2019/11/10/c133b080fcfd43e1916e74eaea4c631b!400x400.jpeg" chat.isShowName = true chat.chatType = 2 //群聊 dataSource.append(chat) DispatchQueue.main.async { self.plainTabView.reloadData { self.scrollToBottom() FYDBQueryHelper.shared.insertFromChat(chat) self.deleteButton.isHidden = self.dataSource.count > 0 ? false : true } } } @objc private func removerGroupData() { dataSource.removeAll() DispatchQueue.main.async { self.plainTabView.reloadData { self.scrollToBottom() FYDBQueryHelper.shared.deleteFromChatsWithType(2) FYDBQueryHelper.shared.deleteFromMessagesWithType(2) self.deleteButton.isHidden = self.dataSource.count > 0 ? false : true // 刷新会话列表 NotificationCenter.default.post(name: .kNeedRefreshSesstionList, object: nil) } } } /// 滚到底部 private func scrollToBottom(_ animated: Bool = true) { plainTabView.scrollToLast(at: .bottom, animated: animated) } } // MARK: - UITableViewDataSource && Delegate extension FYChatRoomListViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: kGroupedChatCellIdentifier) as! FYContactsTableViewCell if let model = dataSource[safe: indexPath.row] { cell.model = model } return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let model = dataSource[safe: indexPath.row] { let chatModel = FYDBQueryHelper.shared.qureyFromChatId(model.uid!) let chatVC = FYChatBaseViewController(chatModel: chatModel) navigationController?.pushViewController(chatVC, animated: true) // clear clearCurrentBadge(chatModel) } } /// 清空角标 private func clearCurrentBadge(_ group: FYMessageChatModel) { if FYDBQueryHelper.shared.queryFromSesstionsUnReadCount() > 0 { if let uid = group.uid { group.unReadCount = 0 FYDBQueryHelper.shared.updateFromChatModel(group, uid: uid) // 刷新会话列表 NotificationCenter.default.post(name: .kNeedRefreshSesstionList, object: nil) } } } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 82 } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/Controller/FYMessageForwardViewController.swift ================================================ // // FYMessageForwardViewController.swift // FY-JetChat // // Created by iOS.Jet on 2019/12/28. // Copyright © 2019 Jett. All rights reserved. // import UIKit enum ForwardStyle { case friend case grouped } class FYMessageForwardViewController: FYBaseViewController { // MARK: - lazy var /// 转发方式 public var forwardStyle = ForwardStyle.friend public var messageItem: FYMessageItem? private var dataSource: [FYMessageChatModel] = [] private var selectedModel: FYMessageChatModel? private lazy var forwardBtn: UIButton = { let button = UIButton(type: .custom) button.setTitle("发送".rLocalized(), for: .normal) button.setTitleColor(.colorWithHexStr("FFFFFF"), for: .normal) button.sizeToFit() button.isHidden = true button.rxTapClosure { [weak self] in self?.forwardAction() } return button }() // MARK: - life cycle override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "消息转发".rLocalized() loadChatBodyData(forwardStyle) } override func makeUI() { super.makeUI() let rightBarButtonItem = UIBarButtonItem(customView: self.forwardBtn) navigationItem.rightBarButtonItem = rightBarButtonItem plainTabView.register(cellWithClass: FYContactsTableViewCell.self) view.addSubview(plainTabView) plainTabView.snp.makeConstraints { (make) in make.top.left.right.equalTo(self.view) if #available(iOS 11.0, *) { make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom) } else { make.bottom.equalTo(self.view.snp.bottomMargin) } } } private func loadChatBodyData(_ style: ForwardStyle) { if (style == .friend) { dataSource = FYDBQueryHelper.shared.qureyFromChatsWithType(1) }else { dataSource = FYDBQueryHelper.shared.qureyFromChatsWithType(2) } plainTabView.reloadData() } // 开始转发 private func forwardAction() { if let model = selectedModel { MBHUD.showStatus("正在发送...") DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { if let message = self.messageItem { // 须实例化新的对象(避免数据库插入新值失败) let msgItem = FYMessageItem() msgItem.message = message.message msgItem.chatId = model.uid msgItem.sendType = 0 //始终是发送方 msgItem.name = message.name msgItem.avatar = message.avatar msgItem.date = Date().string(withFormat: "yyyy-MM-dd HH:mm:ss") msgItem.msgType = message.msgType msgItem.chatType = message.chatType if (message.msgType == 2) { msgItem.image = message.image }else if (message.msgType == 3) { msgItem.video = message.video msgItem.image = message.image } let chat = FYDBQueryHelper.shared.qureyFromChatId(model.uid!) let chatVC = FYChatBaseViewController(chatModel: chat, isForward: true) chatVC.forwardData = msgItem MBHUD.showSuccess("转发成功") self.navigationController?.pushViewController(chatVC, completion: { self.navigationController?.remove(withName: "FYMessageForwardViewController") }) } } } } } // MARK: - UITableViewDataSource && Delegate extension FYMessageForwardViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withClass: FYContactsTableViewCell.self, for: indexPath) cell.selectedView.isHidden = false if let model = dataSource[safe: indexPath.row] { cell.model = model } return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let model = dataSource[safe: indexPath.row] { selectedModel = model forwardBtn.isHidden = false } } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 82 } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/Model/FYMessageBaseModel.swift ================================================ // // FYMessageBaseModel.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/20. // Copyright © 2019 Jett. All rights reserved. // import UIKit import WCDBSwift import HandyJSON class FYMessageBaseModel: NSObject, HandyJSON { required override init() { } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/Model/FYMessageChatModel.swift ================================================ // // FYMessageChatModel.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/28. // Copyright © 2019 Jett. All rights reserved. // import UIKit import WCDBSwift class FYMessageChatModel: FYMessageBaseModel, TableCodable { /// 主键自增id var identifier: Int? = nil /// 用户id var uid: Int? = nil /// 用户名称 var name: String? = nil /// 用户头像 var avatar: String? = nil /// 是否显示昵称 var isShowName: Bool = false /// 聊天类型:1:单聊;2:群聊 var chatType: Int? = nil /// 未读数 var unReadCount: Int? = nil /// 备注名(昵称) var nickName: String? = nil enum CodingKeys: String, CodingTableKey { typealias Root = FYMessageChatModel static let objectRelationalMapping = TableBinding(CodingKeys.self) case identifier = "id" case uid case name case avatar case isShowName case chatType case unReadCount case nickName //Column constraints for primary key, unique, not null, default value and so on. It is optional. static var columnConstraintBindings: [CodingKeys: ColumnConstraintBinding]? { return [ .identifier: ColumnConstraintBinding(isPrimary: true, isAutoIncrement: true) ] } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/Model/FYMessageItem.swift ================================================ // // FYMessageItem.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/20. // Copyright © 2019 Jett. All rights reserved. // import UIKit import WCDBSwift //消息发送类型,我的还是别人的 enum SendType: Int { case mine = 0 case someone = 1 } class FYMessageItem: FYMessageBaseModel, TableCodable { /// 消息id(主键 - 自增长) var messageId: Int? = nil /// 会话id var chatId: Int? = nil var name: String? = nil var avatar: String? = nil var message: String? = nil var image: String? = nil var video: String? = nil /// 消息发送时间 var date: String? = nil /// 消息类型:文字:1;2:图片 var msgType: Int? = nil /// 消息发送方式:0:我的;1:别人 var sendType: Int? = nil /// 聊天类型:1:单聊;2:群聊;3:视频 var chatType: Int? = nil /// 未读数 var unReadCount: Int? = nil /// 备注名称 var nickName: String? = nil enum CodingKeys: String, CodingTableKey { typealias Root = FYMessageItem static let objectRelationalMapping = TableBinding(CodingKeys.self) case messageId = "id" case chatId case name case avatar case message case date case image case video case sendType case msgType case chatType case unReadCount case nickName //Column constraints for primary key, unique, not null, default value and so on. It is optional. static var columnConstraintBindings: [CodingKeys: ColumnConstraintBinding]? { return [ .messageId: ColumnConstraintBinding(isPrimary: true, isAutoIncrement: true) ] } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/View/FYImageMessageCell.swift ================================================ // // FYImageMessageCell.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/27. // Copyright © 2019 Jett. All rights reserved. // import UIKit class FYImageMessageCell: FYMessageBaseCell { // MARK: - var lazy lazy var pictureView: UIImageView = { let imageView = UIImageView() imageView.backgroundColor = .random return imageView }() lazy var pictureTap: UITapGestureRecognizer = { let tap = UITapGestureRecognizer(target: self, action: #selector(pictureTapAction(_:))) return tap }() // MARK: - life cycle override func awakeFromNib() { super.awakeFromNib() // Initialization code } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) initSubview() setupLabelLongPressGes(cellType: .imageCell) } func initSubview() { dateLabel.snp.remakeConstraints { (make) in make.top.equalToSuperview().offset(17) make.centerX.equalToSuperview() make.height.equalTo(18) } dateGradView.snp.remakeConstraints { (make) in make.top.equalTo(dateLabel).offset(-1) make.left.equalTo(dateLabel).offset(-2) make.bottom.equalTo(dateLabel).offset(1) make.right.equalTo(dateLabel).offset(2) } avatarView.snp.remakeConstraints { (make) in make.width.height.equalTo(40) make.left.equalToSuperview().offset(10) make.top.equalTo(dateGradView.snp.bottom).offset(5) } nameLabel.snp.remakeConstraints { (make) in make.top.equalTo(avatarView) make.left.equalTo(avatarView.snp.right).offset(3) } pictureView.isUserInteractionEnabled = true pictureView.addGestureRecognizer(self.pictureTap) contentView.addSubview(pictureView) pictureView.snp.remakeConstraints { (make) in make.top.equalTo(nameLabel.snp.bottom).offset(5) make.left.equalTo(avatarView.snp.right).offset(5) make.bottom.equalTo(self.contentView).offset(-17) make.width.equalTo(80) make.height.equalTo(120) } activityIndicatorView.snp.remakeConstraints { (make) in make.centerY.equalTo(pictureView) make.centerX.equalToSuperview() make.width.height.equalTo(30) } dateLabel.setContentHuggingPriority(.required, for: .horizontal) pictureView.setContentHuggingPriority(.required, for: .horizontal) } override func layoutMessageCell() { super.layoutMessageCell() guard let msgType = model?.msgType, msgType == 2 else { return } dateLabel.text = model?.date if let avatarURL = model?.avatar { avatarView.setImageWithURL(avatarURL) } if let imageURL = model?.image { pictureView.setImageWithURL(imageURL) } if model?.nickName.isBlank == false { nameLabel.text = model?.nickName }else { nameLabel.text = model?.name } // 重新布局 if let sendType = model?.sendType { setupCellLayout(sendType: sendType) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } // MARK: - Action @objc func pictureTapAction(_ tap: UITapGestureRecognizer) { if let dataModel = self.model { delegate?.cell(self, didTapPictureAt: dataModel) } } } // MARK: - Layout extension FYImageMessageCell { func setupCellLayout(sendType: Int) { if sendType == 0 { //我发送的 avatarView.snp.remakeConstraints { (make) in make.width.height.equalTo(40) make.right.equalToSuperview().offset(-10) make.top.equalTo(dateGradView.snp.bottom).offset(5) } nameLabel.isHidden = true nameLabel.snp.remakeConstraints { (make) in make.top.equalTo(avatarView) make.right.equalTo(avatarView.snp.left).offset(-3) make.height.equalTo(0) } pictureView.snp.remakeConstraints { (make) in make.top.equalTo(nameLabel) make.right.equalTo(avatarView.snp.left).offset(-5) make.bottom.equalTo(self.contentView).offset(-17) make.width.equalTo(80) make.height.equalTo(120) } activityIndicatorView.snp.remakeConstraints { (make) in make.centerY.equalTo(pictureView) make.right.equalTo(pictureView.snp.left) make.width.height.equalTo(30) } // start activityStartAnimating() }else { avatarView.snp.remakeConstraints { (make) in make.width.height.equalTo(40) make.left.equalToSuperview().offset(10) make.top.equalTo(dateGradView.snp.bottom).offset(5) } nameLabel.isHidden = false nameLabel.snp.remakeConstraints { (make) in make.top.equalTo(avatarView) make.left.equalTo(avatarView.snp.right).offset(3) } pictureView.snp.remakeConstraints { (make) in make.top.equalTo(nameLabel.snp.bottom).offset(5) make.left.equalTo(avatarView.snp.right).offset(5) make.bottom.equalTo(self.contentView).offset(-17) make.width.equalTo(80) make.height.equalTo(120) } } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/View/FYMessageBaseCell.swift ================================================ // // FYMessageBaseCell.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/23. // Copyright © 2019 Jett. All rights reserved. // import UIKit enum RootCellType { case textCell case imageCell case viodeCell } enum MenuShowStyle { case share case copy case delete } protocol FYMessageBaseCellDelegate: AnyObject { func cell(_ cell: FYMessageBaseCell, didMenu style: MenuShowStyle, model: FYMessageItem) func cell(_ cell: FYMessageBaseCell, didTapAvatarAt model: FYMessageItem) func cell(_ cell: FYMessageBaseCell, didTapPictureAt model: FYMessageItem) func cell(_ cell: FYMessageBaseCell, didTapVideoAt model: FYMessageItem) } extension FYMessageBaseCellDelegate { func cell(_ cell: FYMessageBaseCell, didMenu style: MenuShowStyle, model: FYMessageItem) {} func cell(_ cell: FYMessageBaseCell, didTapAvatarAt model: FYMessageItem) {} func cell(_ cell: FYMessageBaseCell, didTapPictureAt model: FYMessageItem) {} func cell(_ cell: FYMessageBaseCell, didTapVideoAt model: FYMessageItem) {} } class FYMessageBaseCell: UITableViewCell { var model: FYMessageItem? { didSet { guard let _ = model else { return } layoutMessageCell() } } // MARK: - lazy var weak var delegate: FYMessageBaseCellDelegate? lazy var avatarTap: UITapGestureRecognizer = { let tap = UITapGestureRecognizer(target: self, action: #selector(avatarTapAction(_:))) return tap }() lazy var avatarView: UIImageView = { let imageView = UIImageView(image: R.image.ic_avatar_placeholder()!) imageView.cornerRadius = 7 imageView.addGestureRecognizer(self.avatarTap) imageView.isUserInteractionEnabled = true return imageView }() lazy var bubbleView: UIImageView = { let imageView = UIImageView() return imageView }() lazy var nameLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 15) label.theme.textColor = themed { $0.FYColor_Main_TextColor_V2 } return label }() lazy var dateLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 11) label.textColor = .white return label }() lazy var dateGradView: UIView = { let view = UIView() view.cornerRadius = 5 view.backgroundColor = .RGBA(r: 190, g: 190, b: 190, a: 0.6) return view }() lazy var activityIndicatorView: UIActivityIndicatorView = { let activityView = UIActivityIndicatorView(style: .gray) activityView.backgroundColor = .clear activityView.isHidden = true return activityView }() // MARK: - life cycle override func awakeFromNib() { super.awakeFromNib() // Initialization code selectionStyle = .none } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none backgroundColor = .clear contentView.addSubview(dateGradView) contentView.addSubview(dateLabel) contentView.addSubview(avatarView) contentView.addSubview(bubbleView) contentView.addSubview(nameLabel) contentView.addSubview(activityIndicatorView) } /// 提供子类调用 open func layoutMessageCell() { } /// 执行加载动画 open func activityStartAnimating() { guard let sendType = model?.sendType, sendType == 0 else { return } guard let sendDate = model?.date else { return } let nowDate = Date().string(withFormat: "yyyy-MM-dd HH:mm:ss") if ((nowDate.stringToTimeStamp().doubleValue - sendDate.stringToTimeStamp().doubleValue) <= 1) { self.activityIndicatorView.isHidden = false self.activityIndicatorView.startAnimating() DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) { self.activityIndicatorView.startAnimating() self.activityIndicatorView.isHidden = true } } } /// 提供子类调用 /// - Parameter cellType: cell类型 open func setupLabelLongPressGes(cellType: RootCellType) { var longPressGes = UILongPressGestureRecognizer.init() if (cellType == .textCell) { longPressGes = UILongPressGestureRecognizer(target: self, action: #selector(showMenu1Controller)) }else { longPressGes = UILongPressGestureRecognizer(target: self, action: #selector(showMenu2Controller)) } longPressGes.minimumPressDuration = 1 //longPressGes.numberOfTapsRequired = 1 longPressGes.numberOfTouchesRequired = 1 // 长按有效移动范围(从点击开始,长按移动的允许范围 单位 px longPressGes.allowableMovement = 15 self.addGestureRecognizer(longPressGes) } @objc func showMenu1Controller() { if (UIMenuController.shared.isMenuVisible){ return } let sendType = model?.sendType self.becomeFirstResponder() let menu = UIMenuController.shared let item1 = UIMenuItem(title: "转发".rLocalized(), action: #selector(menuShareAction)) let item2 = UIMenuItem(title: "复制".rLocalized(), action: #selector(menuCopyAction)) let item3 = UIMenuItem(title: "删除".rLocalized(), action: #selector(menuDeleteAction)) menu.menuItems = [item1, item2, item3] // 设置箭头方向 menu.arrowDirection = .default if sendType == 0 { let rect = CGRect(x: 40, y: 40, width: self.width, height: self.height) menu.setTargetRect(rect, in: self) }else { let rect = CGRect(x: -60, y: 60, width: self.width, height: self.height) menu.setTargetRect(rect, in: self) } menu.setMenuVisible(true, animated: true) } @objc func showMenu2Controller() { if (UIMenuController.shared.isMenuVisible){ return } let sendType = model?.sendType self.becomeFirstResponder() let menu = UIMenuController.shared let item1 = UIMenuItem(title: "转发".rLocalized(), action: #selector(menuShareAction)) let item3 = UIMenuItem(title: "删除".rLocalized(), action: #selector(menuDeleteAction)) menu.menuItems = [item1, item3] // 设置箭头方向 menu.arrowDirection = .default if sendType == 0 { let rect = CGRect(x: 40, y: 40, width: self.width, height: self.height) menu.setTargetRect(rect, in: self) }else { let rect = CGRect(x: -60, y: 60, width: self.width, height: self.height) menu.setTargetRect(rect, in: self) } menu.setMenuVisible(true, animated: true) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func touchesBegan(_ touches: Set, with event: UIEvent?) { if #available(iOS 13.0, *) { UIMenuController.shared.hideMenu() } } // MARK: - Action /// 分享 @objc open func menuShareAction() { if let dataModel = self.model { delegate?.cell(self, didMenu: .share, model: dataModel) } } /// 复制 @objc open func menuCopyAction() { if let dataModel = self.model { delegate?.cell(self, didMenu: .copy, model: dataModel) } } /// 删除 @objc open func menuDeleteAction() { if let dataModel = self.model { delegate?.cell(self, didMenu: .delete, model: dataModel) } } /// 点击用户头像 @objc func avatarTapAction(_ tap: UIGestureRecognizer) { if let dataModel = self.model { delegate?.cell(self, didTapAvatarAt: dataModel) } } override var canBecomeFirstResponder: Bool { return true } override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { if [#selector(menuShareAction), #selector(menuCopyAction), #selector(menuDeleteAction)].contains(action) { return true } return false } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/View/FYTextMessageCell.swift ================================================ // // FYTextMessageCell.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/20. // Copyright © 2019 Jett. All rights reserved. // import UIKit import YYText class FYTextMessageCell: FYMessageBaseCell { private let kMaxWidth: CGFloat = kScreenW * 0.55 // MARK: - var lazy lazy var contentLabel: YYLabel = { let label = YYLabel() label.numberOfLines = 0 label.displaysAsynchronously = true; label.clearContentsBeforeAsynchronouslyDisplay = false; label.font = UIFont.systemFont(ofSize: 15) switch themeService.type { case .light: label.textColor = .Color_Black_000000 default: label.textColor = .Color_Gray_5A636D } return label }() // MARK: - life cycle override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) initSubview() setupLabelLongPressGes(cellType: .textCell) } func initSubview() { contentView.addSubview(contentLabel) dateLabel.snp.remakeConstraints { (make) in make.top.equalToSuperview().offset(17) make.centerX.equalToSuperview() make.height.equalTo(18) } dateGradView.snp.remakeConstraints { (make) in make.top.equalTo(dateLabel).offset(-1) make.left.equalTo(dateLabel).offset(-2) make.bottom.equalTo(dateLabel).offset(1) make.right.equalTo(dateLabel).offset(2) } avatarView.snp.remakeConstraints { (make) in make.width.height.equalTo(40) make.left.equalToSuperview().offset(10) make.top.equalTo(dateGradView.snp.bottom).offset(5) } nameLabel.snp.remakeConstraints { (make) in make.top.equalTo(avatarView) make.left.equalTo(avatarView.snp.right).offset(3) } bubbleView.snp.remakeConstraints { (make) in make.top.equalTo(nameLabel.snp.bottom).offset(2) make.bottom.equalTo(contentLabel.snp.bottom).offset(2) make.left.equalTo(avatarView.snp.right) make.width.width.equalTo(kMaxWidth) make.height.equalTo(contentLabel).offset(26) } contentLabel.snp.remakeConstraints { (make) in make.top.equalTo(bubbleView).offset(13); make.left.equalTo(bubbleView).offset(20); make.right.equalTo(bubbleView).offset(-15); } activityIndicatorView.snp.remakeConstraints { (make) in make.centerY.equalTo(bubbleView) make.centerX.equalToSuperview() make.width.height.equalTo(30) } dateLabel.setContentHuggingPriority(.required, for: .horizontal) bubbleView.setContentHuggingPriority(.required, for: .horizontal) } override func layoutMessageCell() { super.layoutMessageCell() guard let msgType = model?.msgType, msgType == 1 else { return } dateLabel.text = model?.date contentLabel.text = model?.message if let imageURL = model?.avatar { avatarView.setImageWithURL(imageURL, placeholder: "ic_avatar_placeholder") } if let nickName = model?.nickName, nickName.length > 0 { nameLabel.text = nickName }else { nameLabel.text = model?.name } // 重新布局 let contentSize = contentLabel.sizeThatFits(CGSize(width: kMaxWidth, height: CGFloat(Float.greatestFiniteMagnitude))) if let sendType = model?.sendType { setupCellLayout(sendType: sendType, size: contentSize) } // 设置泡泡 let bubbleImage = model?.sendType == 0 ? #imageLiteral(resourceName: "message_sender_background_normal") : #imageLiteral(resourceName: "message_receiver_background_normal") bubbleView.image = bubbleImage.stretchableImage(centerStretchScale: 0.65) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // MARK: - Layout extension FYTextMessageCell { func setupCellLayout(sendType: Int, size: CGSize) { let sizeWidth = size.width + 12 let sizeHeight = size.height + 8 contentLabel.preferredMaxLayoutWidth = size.width if sendType == 0 { //我发送的 avatarView.snp.remakeConstraints { (make) in make.width.height.equalTo(40) make.right.equalToSuperview().offset(-10) make.top.equalTo(dateGradView.snp.bottom).offset(5) } nameLabel.isHidden = true nameLabel.snp.remakeConstraints { (make) in make.top.equalTo(avatarView) make.right.equalTo(avatarView.snp.left).offset(-3) make.height.equalTo(0) } contentLabel.snp.remakeConstraints { (make) in make.top.equalTo(nameLabel.snp.bottom).offset(8) make.bottom.equalToSuperview().offset(-15) make.right.equalTo(avatarView.snp.left).offset(-14) make.width.equalTo(sizeWidth) make.height.equalTo(sizeHeight) } let top: CGFloat = contentLabel.text!.containEmoji ? -10 : -12 let bottmom: CGFloat = contentLabel.text!.containEmoji ? 10 : 12 bubbleView.snp.remakeConstraints { (make) in make.right.equalTo(avatarView.snp.left).offset(-2) make.top.equalTo(contentLabel).offset(top) make.bottom.equalTo(contentLabel).offset(bottmom) make.width.equalTo(contentLabel).offset(30) } activityIndicatorView.snp.remakeConstraints { (make) in make.centerY.equalTo(bubbleView) make.right.equalTo(bubbleView.snp.left) make.width.height.equalTo(30) } // start activityStartAnimating() }else { avatarView.snp.remakeConstraints { (make) in make.width.height.equalTo(40) make.left.equalToSuperview().offset(10) make.top.equalTo(dateGradView.snp.bottom).offset(5) } nameLabel.isHidden = false nameLabel.snp.remakeConstraints { (make) in make.top.equalTo(avatarView) make.left.equalTo(avatarView.snp.right).offset(3) } contentLabel.snp.remakeConstraints { (make) in make.top.equalTo(nameLabel.snp.bottom).offset(10) make.left.equalTo(avatarView.snp.right).offset(21) make.bottom.equalToSuperview().offset(-15) make.width.equalTo(sizeWidth) } let top: CGFloat = contentLabel.text!.containEmoji ? -10 : -12 let bottmom: CGFloat = contentLabel.text!.containEmoji ? 10 : 12 bubbleView.snp.remakeConstraints { (make) in make.left.equalTo(avatarView.snp.right).offset(2) make.top.equalTo(contentLabel).offset(top) make.bottom.equalTo(contentLabel).offset(bottmom) make.width.equalTo(contentLabel).offset(30) } } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/View/FYVideoMessageCell.swift ================================================ // // FYVideoMessageCell.swift // FY-JetChat // // Created by fangyuan on 2019/12/22. // Copyright © 2019 Jett. All rights reserved. // import UIKit class FYVideoMessageCell: FYMessageBaseCell { // MARK: - var lazy lazy var videoImageView: UIImageView = { let imageView = UIImageView() imageView.backgroundColor = .random return imageView }() lazy var playImgView: UIImageView = { let image = UIImage(named: "play_btn_normal") let imageView = UIImageView(image: image) return imageView }() lazy var videoTap: UITapGestureRecognizer = { let tap = UITapGestureRecognizer(target: self, action: #selector(videoTapAction(_:))) return tap }() // MARK: - life cycle override func awakeFromNib() { super.awakeFromNib() // Initialization code } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) initSubview() setupLabelLongPressGes(cellType: .viodeCell) } func initSubview() { dateLabel.snp.remakeConstraints { (make) in make.top.equalToSuperview().offset(17) make.centerX.equalToSuperview() make.height.equalTo(18) } dateGradView.snp.remakeConstraints { (make) in make.top.equalTo(dateLabel).offset(-1) make.left.equalTo(dateLabel).offset(-2) make.bottom.equalTo(dateLabel).offset(1) make.right.equalTo(dateLabel).offset(2) } avatarView.snp.remakeConstraints { (make) in make.width.height.equalTo(40) make.left.equalToSuperview().offset(10) make.top.equalTo(dateGradView.snp.bottom).offset(5) } nameLabel.snp.remakeConstraints { (make) in make.top.equalTo(avatarView) make.left.equalTo(avatarView.snp.right).offset(3) } videoImageView.isUserInteractionEnabled = true videoImageView.addGestureRecognizer(self.videoTap) contentView.addSubview(videoImageView) videoImageView.snp.remakeConstraints { (make) in make.top.equalTo(nameLabel.snp.bottom).offset(5) make.left.equalTo(avatarView.snp.right).offset(5) make.bottom.equalTo(self.contentView).offset(-17) make.width.equalTo(100) make.height.equalTo(145) } videoImageView.addSubview(playImgView) videoImageView.bringSubviewToFront(playImgView) playImgView.snp.makeConstraints { (make) in make.center.equalToSuperview() make.size.equalTo(CGSize(width: 40, height: 40)) } activityIndicatorView.snp.remakeConstraints { (make) in make.centerY.equalTo(videoImageView) make.centerX.equalToSuperview() make.width.height.equalTo(30) } dateLabel.setContentHuggingPriority(.required, for: .horizontal) videoImageView.setContentHuggingPriority(.required, for: .horizontal) } override func layoutMessageCell() { super.layoutMessageCell() guard let msgType = model?.msgType, msgType == 3 else { return } dateLabel.text = model?.date if let avatarURL = model?.avatar { avatarView.setImageWithURL(avatarURL) } if let imageURL = model?.image { videoImageView.setImageWithURL(imageURL) } if model?.nickName.isBlank == false { nameLabel.text = model?.nickName }else { nameLabel.text = model?.name } // 重新布局 if let sendType = model?.sendType { setupCellLayout(sendType: sendType) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } // MARK: - Action @objc func videoTapAction(_ tap: UITapGestureRecognizer) { if let videoModel = self.model { delegate?.cell(self, didTapVideoAt: videoModel) } } } // MARK: - Layout extension FYVideoMessageCell { func setupCellLayout(sendType: Int) { if sendType == 0 { //我发送的 avatarView.snp.remakeConstraints { (make) in make.width.height.equalTo(40) make.right.equalToSuperview().offset(-10) make.top.equalTo(dateGradView.snp.bottom).offset(5) } nameLabel.isHidden = true nameLabel.snp.remakeConstraints { (make) in make.top.equalTo(avatarView) make.right.equalTo(avatarView.snp.left).offset(-3) make.height.equalTo(0) } videoImageView.snp.remakeConstraints { (make) in make.top.equalTo(nameLabel) make.right.equalTo(avatarView.snp.left).offset(-5) make.bottom.equalTo(self.contentView).offset(-17) make.width.equalTo(100) make.height.equalTo(145) } activityIndicatorView.snp.remakeConstraints { (make) in make.centerY.equalTo(videoImageView) make.right.equalTo(videoImageView.snp.left) make.width.height.equalTo(30) } activityStartAnimating() }else { avatarView.snp.remakeConstraints { (make) in make.width.height.equalTo(40) make.left.equalToSuperview().offset(10) make.top.equalTo(dateGradView.snp.bottom).offset(5) } nameLabel.isHidden = false nameLabel.snp.remakeConstraints { (make) in make.top.equalTo(avatarView) make.left.equalTo(avatarView.snp.right).offset(3) } videoImageView.snp.remakeConstraints { (make) in make.top.equalTo(nameLabel.snp.bottom).offset(5) make.left.equalTo(avatarView.snp.right).offset(5) make.bottom.equalTo(self.contentView).offset(-17) make.width.equalTo(100) make.height.equalTo(145) } } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/ChatRoom/ViewModel/FYMessageViewModel.swift ================================================ // // FYMessageViewModel.swift // FY-JetChat // // Created by fangyuan on 2020/1/3. // Copyright © 2020 iOS.Jet. All rights reserved. // import UIKit import RxSwift import RxCocoa import HandyJSON import YBImageBrowser public enum ChatDataType { case none case text case image case video case autoSend } public enum BrowserTypeData { case none case image case video } class FYMessageViewModel: BaseViewModel, ViewModelType { /// 消息类型 var messageType = BehaviorRelay(value: .none) /// 浏览图片&视频类型 var browserType = BehaviorRelay(value: .none) /// 文本 var content = BehaviorRelay(value: "") /// 图片索引 var imageIndex = BehaviorRelay(value: 0) var tableView: UITableView? var chatModel: FYMessageChatModel? /// 图片视频浏览 var dataSource = BehaviorRelay<[FYMessageItem]>.init(value: []) var browserIndexs = BehaviorRelay<[Int: Int]>.init(value: [:]) // MARK: - Transform struct Input { let makeMessage: Observable let makeBrowser: Observable } struct Output { let message: BehaviorRelay let browser: BehaviorRelay<[AnyObject]> } func transform(input: FYMessageViewModel.Input) -> FYMessageViewModel.Output { let outMessage = BehaviorRelay(value: FYMessageItem()) let outBrowsers = BehaviorRelay<[AnyObject]>(value: []) // 发送消息 input.makeMessage.flatMapLatest ({ [weak self]() -> Single in guard let self = self else { return Single.never() } if (self.messageType.value == .text) { return self.makeChatTextMessage().trackActivity(self.loading).asSingle() }else if (self.messageType.value == .image) { return self.makeChatImageMessage().trackActivity(self.loading).asSingle() }else if (self.messageType.value == .video) { return self.makeChatVideoMessage().trackActivity(self.loading).asSingle() }else if (self.messageType.value == .autoSend) { return self.makeChatGroupAutoSend().trackActivity(self.loading).asSingle() }else { return Single.never() } }) .asObservable() .subscribe(onNext: { (data) in outMessage.accept(data) }).disposed(by: rx.disposeBag) // 浏览图片 input.makeBrowser.flatMapLatest ({ [weak self]() -> Single<[AnyObject]> in guard let self = self else { return Single.never() } if (self.browserType.value == .image) { return self.makeBrowserImagesData().trackActivity(self.loading).asSingle() }else if (self.browserType.value == .video) { return self.makeBrowserVideosData().trackActivity(self.loading).asSingle() }else { return Single.never() } }) .asObservable() .subscribe(onNext: { (objects) in outBrowsers.accept(objects) }).disposed(by: rx.disposeBag) return Output(message: outMessage, browser: outBrowsers) } // MARK: - init init(chatModel: FYMessageChatModel) { super.init() self.chatModel = chatModel } } // MARK: - Configuration Message extension FYMessageViewModel { /// 文本消息 private func makeChatTextMessage() -> Single { return Single.create { single in let random = arc4random() % 9 let msgItem = FYMessageItem() msgItem.message = self.content.value msgItem.chatId = self.chatModel?.uid if self.chatModel?.chatType == 1 { msgItem.sendType = random % 2 == 0 ? 1 : 0 if self.chatModel?.nickName.isBlank == false { msgItem.name = random % 2 == 0 ? self.chatModel?.nickName : "逆流而上" }else { msgItem.name = random % 2 == 0 ? self.chatModel?.name : "逆流而上" } msgItem.avatar = random % 2 == 0 ? self.chatModel?.avatar : "https://img2.woyaogexing.com/2019/11/27/d1dddb1e1faf4b578f12b28a08b04174!400x400.jpeg" }else { msgItem.sendType = 0 msgItem.name = "逆流而上" msgItem.avatar = "https://img2.woyaogexing.com/2019/11/27/d1dddb1e1faf4b578f12b28a08b04174!400x400.jpeg" } msgItem.date = Date().string(withFormat: "yyyy-MM-dd HH:mm:ss") msgItem.msgType = 1 //文字 msgItem.chatType = self.chatModel?.chatType single(.success(msgItem)) return Disposables.create() } } /// 图片消息 private func makeChatImageMessage() -> Single { return Single.create { single in let random = arc4random() % 9 let msgItem = FYMessageItem() msgItem.chatId = self.chatModel?.uid if self.chatModel?.chatType == 1 { msgItem.sendType = random % 2 == 0 ? 1 : 0 if self.chatModel?.nickName.isBlank == false { msgItem.name = random % 2 == 0 ? self.chatModel?.nickName : "逆流而上" msgItem.nickName = random % 2 == 0 ? self.chatModel?.nickName : "逆流而上" }else { msgItem.name = random % 2 == 0 ? self.chatModel?.name : "逆流而上" } msgItem.avatar = random % 2 == 0 ? self.chatModel?.avatar : "https://img2.woyaogexing.com/2019/11/27/d1dddb1e1faf4b578f12b28a08b04174!400x400.jpeg" }else { msgItem.sendType = 0 msgItem.name = "逆流而上" msgItem.avatar = "https://img2.woyaogexing.com/2019/11/27/d1dddb1e1faf4b578f12b28a08b04174!400x400.jpeg" } msgItem.date = Date().string(withFormat: "yyyy-MM-dd HH:mm:ss") msgItem.image = random % 2 == 0 ? "http://img2.woyaogexing.com/2022/04/27/76d9d570d2004c9ba326587045dede07!360x640.jpeg" : "http://img2.woyaogexing.com/2022/04/27/fcc7c52df7454e1680a6625cc3fea7b0.jpeg" msgItem.msgType = 2 //图片 msgItem.message = "【图片】" msgItem.chatType = self.chatModel?.chatType single(.success(msgItem)) return Disposables.create() } } /// 视频消息 private func makeChatVideoMessage() -> Single { return Single.create { single in let random = arc4random() % 9 let msgItem = FYMessageItem() msgItem.chatId = self.chatModel?.uid if self.chatModel?.chatType == 1 { msgItem.sendType = random % 2 == 0 ? 1 : 0 if self.chatModel?.nickName.isBlank == false { msgItem.name = random % 2 == 0 ? self.chatModel?.nickName : "逆流而上" msgItem.nickName = random % 2 == 0 ? self.chatModel?.nickName : "逆流而上" }else { msgItem.name = random % 2 == 0 ? self.chatModel?.name : "逆流而上" } msgItem.avatar = random % 2 == 0 ? self.chatModel?.avatar : "https://img2.woyaogexing.com/2019/11/27/d1dddb1e1faf4b578f12b28a08b04174!400x400.jpeg" }else { msgItem.sendType = 0 msgItem.name = "逆流而上" msgItem.avatar = "https://img2.woyaogexing.com/2019/11/27/d1dddb1e1faf4b578f12b28a08b04174!400x400.jpeg" } msgItem.date = Date().string(withFormat: "yyyy-MM-dd HH:mm:ss") msgItem.image = random % 2 == 0 ? "http://img2.woyaogexing.com/2022/04/25/e484edc5029c41fda2e0dbf1b4310818.jpeg" : "http://img2.woyaogexing.com/2022/04/28/69f49803d9ea443c84c88907e8e76879.jpeg" msgItem.video = random % 2 == 0 ? "localVideo0.mp4" : "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200ff00000bdkpfpdd2r6fb5kf6m50&line=0.mp4" msgItem.msgType = 3 //视频 msgItem.message = "【视频】" msgItem.chatType = self.chatModel?.chatType single(.success(msgItem)) return Disposables.create() } } /// 自动发送文本信息 private func makeChatAutoSendText() -> Single { return Single.create { single in let random = arc4random() % 20 let msgItem = FYMessageItem() msgItem.message = random % 2 == 0 ? "😬😬😬😬😬😬你觉得今天天气如何呢?" : "周末一起去郊游吧😸😸😸😸😸" msgItem.chatId = self.chatModel?.uid msgItem.sendType = 1 msgItem.name = random % 2 == 0 ? "彩虹天堂🌈" : "惊鸿一面🍎" msgItem.avatar = random % 2 == 0 ? "https://img2.woyaogexing.com/2019/11/23/593796f9c01c43ca8c44b6501a45db90!400x400.jpeg" : "https://img2.woyaogexing.com/2019/11/11/4f3352cc750c4648a1c7e320cf045fbc!400x400.jpeg" msgItem.date = Date().string(withFormat: "yyyy-MM-dd HH:mm:ss") msgItem.msgType = 1 //文字 msgItem.chatType = self.chatModel?.chatType single(.success(msgItem)) return Disposables.create() } } /// 模拟群组群员自动发送消息 private func makeChatGroupAutoSend() -> Single { let random = arc4random() % 3 switch random { case 0: return makeChatImageMessage() case 1: return makeChatVideoMessage() default: return makeChatAutoSendText() } } } // MARK: - Configuration ImageData extension FYMessageViewModel { private func makeBrowserImagesData() -> Single<[AnyObject]> { return Single<[AnyObject]>.create { single in var indexs: [Int: Int] = [:] var images: [YBIBImageData] = [] var imageIndex = 0 for (index, model) in self.dataSource.value.enumerated() { if model.msgType == 2 { let data = YBIBImageData() data.imageURL = URL(string: model.image!) data.projectiveView = self.projectiveViewAtRow(self.imageIndex.value) images.append(data) imageIndex += 1 //图片索引 } indexs[index] = imageIndex } self.browserIndexs.accept(indexs) single(.success(images)) return Disposables.create() } } private func makeBrowserVideosData() -> Single<[AnyObject]> { return Single<[AnyObject]>.create { single in var videos: [YBIBVideoData] = [] var indexs: [Int: Int] = [:] var videoIndex = 0 for (index, model) in self.dataSource.value.enumerated() { if model.msgType == 3 { if (model.video?.hasSuffix(".mp4"))! && (model.video?.hasPrefix("http"))! { //网络视频 let data = YBIBVideoData() UIImageView().downloadImageWithURL(model.image!, callback: { (result) in printLog("thumbImage\(result)") switch result { case .success(let value): data.thumbImage = value case .failure(let error): printLog("Job failed: \(error)") } }) data.videoURL = URL(string: model.video!) data.projectiveView = self.projectiveViewAtRow(self.imageIndex.value) videos.append(data) videoIndex += 1 //图片索引 }else { if let path = Bundle.main.path(forResource: model.video?.deletingPathExtension, ofType:model.video?.pathExtension) { let data = YBIBVideoData() UIImageView().downloadImageWithURL(model.image!, callback: { (result) in printLog("thumbImage\(result)") switch result { case .success(let value): data.thumbImage = value case .failure(let error): printLog("Job failed: \(error)") } }) data.videoURL = URL(fileURLWithPath: path) data.projectiveView = self.projectiveViewAtRow(self.imageIndex.value) videos.append(data) videoIndex += 1 //图片索引 } } } indexs[index] = videoIndex } self.browserIndexs.accept(indexs) single(.success(videos)) return Disposables.create() } } private func projectiveViewAtRow(_ row: Int) -> UIView { guard let table = tableView else { return UIView() } let indexPath = IndexPath(row: row, section: 0) if let imageCell = table.cellForRow(at: indexPath) as? FYImageMessageCell { return imageCell.pictureView }else { let videoCell = table.cellForRow(at: indexPath) as? FYVideoMessageCell return videoCell?.videoImageView ?? UIView() } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Contacts/Controller/FYContactsInfoViewController.swift ================================================ // // FYContactsInfoViewController.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/30. // Copyright © 2019 Jett. All rights reserved. // import UIKit class FYContactsInfoViewController: FYBaseViewController { var chatModel: FYMessageChatModel? { didSet { guard let model = chatModel else { return } headerView.chatModel = model if model.chatType == 1 { dataSource = ["设置备注名".rLocalized()] } plainTabView.reloadData() } } // MARK: - lazy var private var dataSource: [String] = [] private lazy var headerView: FYContactsInfoView = { let view = FYContactsInfoView() view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } view.frame = CGRect(x: 0, y: 0, width: kScreenW, height: 100) return view }() private lazy var footerView: UIView = { let view = UIView(frame: CGRect(x: 0, y: 0, width: kScreenW, height: 100)) view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } view.addSubview(sendButton) sendButton.snp.makeConstraints { (make) in make.left.equalToSuperview().offset(30) make.right.equalToSuperview().offset(-30) make.centerY.equalToSuperview() make.height.equalTo(44) } return view }() private lazy var sendButton: UIButton = { let button = UIButton(type: .custom) button.setTitle("发消息".rLocalized(), for: .normal) button.backgroundColor = .appThemeHexColor() button.radius = 7 button.rxTapClosure { [weak self] in if let model = self?.chatModel { let chatVC = FYChatBaseViewController(chatModel: model) self?.navigationController?.pushViewController(chatVC) } } return button }() // MARK: - life cycle override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "个人信息".rLocalized() view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V10 } } override func makeUI() { super.makeUI() plainTabView.rowHeight = 50 plainTabView.tableHeaderView = headerView plainTabView.tableFooterView = footerView view.addSubview(plainTabView) plainTabView.snp.makeConstraints { (make) in make.top.left.right.equalTo(self.view) if #available(iOS 11.0, *) { make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom) } else { make.bottom.equalTo(self.view.snp.bottomMargin) } } } } // MARK: - UITableViewDataSource && Delegate extension FYContactsInfoViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let kContactsInfoCellIdentifier = "kContactsInfoCellIdentifier" var cell = tableView.dequeueReusableCell(withIdentifier: kContactsInfoCellIdentifier) if cell == nil { cell = UITableViewCell(style: .default, reuseIdentifier: kContactsInfoCellIdentifier) cell?.selectionStyle = .none cell?.textLabel?.text = dataSource[safe: indexPath.row] cell?.textLabel?.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } cell?.accessoryView = createAccessoryView() cell?.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } } return cell! } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { return 10.0 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let model = chatModel { let editVC = FYEditChatInfoViewController() editVC.chatModel = model navigationController?.pushViewController(editVC) } } private func createAccessoryView() -> UIImageView { let arrowView = UIImageView() arrowView.image = UIImage(named: "icon_arrow_right") arrowView.sizeToFit() return arrowView } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Contacts/Controller/FYContactsListViewController.swift ================================================ // // FYContactsListViewController.swift // FY-JetChat // // Created by iOS.Jet on 2019/8/18. // Copyright © 2019 Jett. All rights reserved. // import UIKit private let kContactsCellReuseIdentifier = "kContactsCellReuseIdentifier" class FYContactsListViewController: FYBaseViewController { // MARK: - lazy var var dataSource: [FYMessageChatModel] = [] private lazy var deleteButton: UIButton = { let button = UIButton(type: .custom) button.setTitle("删除所有好友".rLocalized(), for: .normal) button.theme.titleColor(from: themed { $0.FYColor_Main_TextColor_V11 }, for: .normal) button.titleLabel?.font = .PingFangRegular(16) button.sizeToFit() button.isHidden = true button.rxTapClosure { [weak self] in self?.showDeleteAlert() } return button }() // MARK: - life cycle override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) deleteButton.isHidden = dataSource.count > 0 ? false : true } override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "好友".rLocalized() reloadUserData() registerUsersNoti() } @objc private func reloadUserData() { dataSource = FYDBQueryHelper.shared.qureyFromChatsWithType(1) DispatchQueue.main.async { self.plainTabView.reloadData() } } private func registerUsersNoti() { NotificationCenter.default.addObserver(self, selector: #selector(reloadUserData), name: .kNeedRefreshSesstionList, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(reloadUserData), name: .kNeedRefreshChatInfoList, object: nil) } override func makeUI() { super.makeUI() let leftBarButtonItem = UIBarButtonItem(customView: deleteButton) navigationItem.leftBarButtonItem = leftBarButtonItem let rightBarButtonItem = UIBarButtonItem(title: "添加好友".rLocalized(), style: .plain, target: self, action: #selector(addUserData)) navigationItem.rightBarButtonItem = rightBarButtonItem plainTabView.register(FYContactsTableViewCell.self, forCellReuseIdentifier: kContactsCellReuseIdentifier) view.addSubview(plainTabView) plainTabView.snp.makeConstraints { (make) in make.top.left.right.equalTo(self.view) if #available(iOS 11.0, *) { make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom) } else { make.bottom.equalTo(self.view.snp.bottomMargin) } } } @objc private func showDeleteAlert() { EasyAlertView.customAlert(title: "确定删除全部好友吗?".rLocalized(), message: "删除后,会话记录也将清除".rLocalized(), confirm: "确定".rLocalized(), cancel: "取消".rLocalized(), vc: self, confirmBlock: { self.removerUserData() }, cancelBlock: { }) } @objc private func addUserData() { var uid = 10000 if let lastUser = dataSource.last { uid = lastUser.uid! + 1 } let chat = FYMessageChatModel() chat.uid = uid chat.name = "用户名:" + "\(uid)" chat.avatar = "http://img.duoziwang.com/2019/02/04232036664241.jpg" chat.isShowName = true chat.chatType = 1 //单聊 dataSource.append(chat) DispatchQueue.main.async { self.plainTabView.reloadData { self.scrollToBottom() FYDBQueryHelper.shared.insertFromChat(chat) self.deleteButton.isHidden = self.dataSource.count > 0 ? false : true } } } @objc private func removerUserData() { dataSource.removeAll() DispatchQueue.main.async { self.plainTabView.reloadData { self.scrollToBottom() FYDBQueryHelper.shared.deleteFromChatsWithType(1) FYDBQueryHelper.shared.deleteFromMessagesWithType(1) self.deleteButton.isHidden = self.dataSource.count > 0 ? false : true // 刷新会话列表 NotificationCenter.default.post(name: .kNeedRefreshSesstionList, object: nil) } } } /// 滚到底部 private func scrollToBottom(_ animated: Bool = true) { if dataSource.count >= 1 { plainTabView.scrollToRow(at: IndexPath(row: dataSource.count - 1, section: 0), at: .bottom, animated: animated)} } } // MARK: - UITableViewDataSource && Delegate extension FYContactsListViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: kContactsCellReuseIdentifier) as! FYContactsTableViewCell if let model = dataSource[safe: indexPath.row] { cell.model = model cell.didAvatarCallClosure = { [weak self] model in self?.pushChatInfo(model) } } return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let model = dataSource[safe: indexPath.row] { let user = FYDBQueryHelper.shared.qureyFromChatId(model.uid!) let chatVC = FYChatBaseViewController(chatModel: user) navigationController?.pushViewController(chatVC, animated: true) // clear clearCurrentBadge(user) } } /// 清空角标 private func clearCurrentBadge(_ user: FYMessageChatModel) { if FYDBQueryHelper.shared.queryFromSesstionsUnReadCount() > 0 { if let uid = user.uid { user.unReadCount = 0 FYDBQueryHelper.shared.updateFromChatModel(user, uid: uid) // 刷新会话列表 NotificationCenter.default.post(name: .kNeedRefreshSesstionList, object: nil) } } } func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { deleteChatFriend(indexPath.row) } } func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? { return "删除".rLocalized() } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 82 } /// 删除好友 @objc private func deleteChatFriend(_ row: Int) { EasyAlertView.customAlert(title: "确定删除该好友吗?".rLocalized(), message: "", confirm: "确定".rLocalized(), cancel: "取消".rLocalized(), vc: self, confirmBlock: { self.handleDeleteContactsAtRow(row) }, cancelBlock: { }) } func handleDeleteContactsAtRow(_ row: Int) { if let model = dataSource[safe: row] { dataSource.remove(at: row) plainTabView.deleteRows(at: [IndexPath.init(row: row, section: 0)], with: .left) DispatchQueue.main.async { self.plainTabView.reloadData { if let uid = model.uid { FYDBQueryHelper.shared.deleteFromChatWithId(uid) FYDBQueryHelper.shared.deleteFromMesssageWithId(uid) // 刷新会话列表 NotificationCenter.default.post(name: .kNeedRefreshSesstionList, object: nil) } } } } deleteButton.isHidden = dataSource.count > 0 ? false : true } func pushChatInfo(_ model: FYMessageChatModel) { let userModel = FYDBQueryHelper.shared.qureyFromChatId(model.uid!) let infoVC = FYContactsInfoViewController() infoVC.chatModel = userModel navigationController?.pushViewController(infoVC) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Contacts/Controller/FYEditChatInfoViewController.swift ================================================ // // FYEditChatInfoViewController.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/30. // Copyright © 2019 Jett. All rights reserved. // import UIKit import RxSwift class FYEditChatInfoViewController: FYBaseViewController { var chatModel: FYMessageChatModel? { didSet { guard let model = chatModel else { return } if let nickName = model.nickName { if !nickName.isBlank { myTextField.text = nickName } } } } // MARK: - lazy var private lazy var saveButton: UIButton = { let button = UIButton(type: .custom) button.setTitle("保存".rLocalized(), for: .normal) button.theme.titleColor(from: themed { $0.FYColor_Main_TextColor_V11 }, for: .normal) button.titleLabel?.font = .PingFangRegular(16) button.isHidden = true button.sizeToFit() button.rxTapClosure { [weak self] in self?.saveEditing() } return button }() private lazy var myTextField: UITextField = { let textField = UITextField() let leftView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 40)) textField.placeholder = "备注名称不超过12个字".rLocalized() textField.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } textField.leftView = leftView textField.delegate = self textField.leftViewMode = .always textField.theme.placeholderColor = themed { $0.FYColor_Placeholder_Color_V1 } textField.clearButtonMode = .whileEditing textField.font = .PingFangRegular(15) textField.cornerRadius = 5 textField.layer.theme.borderColor = themed { $0.FYColor_BorderColor_V1.cgColor } textField.borderWidth = 1 return textField }() // MARK: - life cycle override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) myTextField.becomeFirstResponder() } override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "修改备注名称".rLocalized() } override func makeUI() { super.makeUI() let rightBarButtonItem = UIBarButtonItem(customView: saveButton) navigationItem.rightBarButtonItem = rightBarButtonItem myTextField.rx.text.orEmpty .map { $0.count == 0 } .share(replay: 1) .bind(to: saveButton.rx.isHidden) .disposed(by: rx.disposeBag) view.addSubview(myTextField) myTextField.snp.makeConstraints { (make) in make.top.equalToSuperview().offset(kSafeAreaTop + 50) make.left.equalToSuperview().offset(15) make.right.equalToSuperview().offset(-15) make.height.equalTo(40) } } } // MARK: - UITextFieldDelegate extension FYEditChatInfoViewController: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if (range.length == 1 && string.count == 0) { return true } if range.location >= 12 { return false } return true } // MARK: - Action private func saveEditing() { myTextField.resignFirstResponder() chatModel?.nickName = myTextField.text ?? "" if let model = chatModel, let uid = model.uid { FYDBQueryHelper.shared.updateFromChatModel(model, uid: uid) let messages = FYDBQueryHelper.shared.qureyFromMessagesWithChatId(uid) for msgItem in messages { if msgItem.sendType == 1 { if let message = FYDBQueryHelper.shared.queryMessageWithMsgId(msgItem.messageId!) { message.nickName = myTextField.text ?? "" FYDBQueryHelper.shared.updateMessageWithMsgId(message: message, messageId: message.messageId!) } } } NotificationCenter.default.post(name: .kNeedRefreshSesstionList, object: nil) NotificationCenter.default.post(name: .kNeedRefreshChatInfoList, object: nil) MBHUD.showStatus("正在保存...".rLocalized()) DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) { MBHUD.hide() self.navigationController?.popToRootViewController(animated: true) } } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Contacts/View/FYContactsInfoView.swift ================================================ // // FYContactsInfoView.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/30. // Copyright © 2019 Jett. All rights reserved. // import UIKit class FYContactsInfoView: UIView { // MARK: - setter var chatModel: FYMessageChatModel? { didSet { guard let model = chatModel else { return } nameLabel.text = model.name if let nickName = model.nickName, nickName.length > 0 { nickLabel.text = "备注名:".rLocalized() + nickName } uidLabel.text = "uid:\(model.uid ?? 1000)" avatarView.setImageWithURL(model.avatar!, placeholder: "ic_avatar_placeholder") } } // MARK: - private lazy var private lazy var avatarView: UIImageView = { let imageView = UIImageView() imageView.backgroundColor = .random imageView.cornerRadius = 5 return imageView }() private lazy var nameLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 14) label.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } return label }() private lazy var nickLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 13) label.theme.textColor = themed { $0.FYColor_Main_TextColor_V2 } return label }() private lazy var uidLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 12) label.theme.textColor = themed { $0.FYColor_Main_TextColor_V2 } return label }() // MARK: - life cycle override init(frame: CGRect) { super.init(frame: frame) makeUI() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) makeUI() } func makeUI() { self.addSubview(avatarView) self.addSubview(nameLabel) self.addSubview(nickLabel) self.addSubview(uidLabel) avatarView.snp.remakeConstraints { (make) in make.width.height.equalTo(60) make.left.equalToSuperview().offset(10) make.centerY.equalToSuperview() } nameLabel.snp.remakeConstraints { (make) in make.top.equalTo(avatarView).offset(2) make.left.equalTo(avatarView.snp.right).offset(10) } nickLabel.snp.remakeConstraints { (make) in make.left.equalTo(nameLabel) make.top.equalTo(nameLabel.snp.bottom).offset(5) } uidLabel.snp.remakeConstraints { (make) in make.bottom.equalTo(avatarView).offset(-2) make.left.equalTo(nameLabel) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Contacts/View/FYContactsTableViewCell.swift ================================================ // // FYContactsTableViewCell.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/28. // Copyright © 2019 Jett. All rights reserved. // import UIKit class FYContactsTableViewCell: UITableViewCell { var model: FYMessageChatModel? { didSet { guard let chatModel = model else { return } if chatModel.nickName.isBlank == false { nameLabel.text = chatModel.nickName }else { nameLabel.text = chatModel.name } uidLabel.text = "uid:\(chatModel.uid ?? 1000)" avatarView.setImageWithURL(chatModel.avatar ?? "", placeholder: R.image.ic_avatar_placeholder()!) } } // MARK: - var lazy var didAvatarCallClosure : ((FYMessageChatModel)->Void)? private lazy var avatarView: UIImageView = { let imageView = UIImageView() imageView.cornerRadius = 7 imageView.tapClosure { [weak self] in self?.avatarAction() } return imageView }() private lazy var nameLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 15) label.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } return label }() private lazy var uidLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 14) label.theme.textColor = themed { $0.FYColor_Main_TextColor_V10 } return label }() lazy var selectedView: UIImageView = { let imageView = UIImageView() imageView.isHidden = true return imageView }() lazy var lineView: UIView = { let v = UIView() v.theme.backgroundColor = themed { $0.FYColor_BorderColor_V2 } return v }() // MARK: - life cycle override func awakeFromNib() { super.awakeFromNib() // Initialization code selectionStyle = .none } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupSubview() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubview() { selectionStyle = .none theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } contentView.addSubview(avatarView) contentView.addSubview(nameLabel) contentView.addSubview(uidLabel) contentView.addSubview(selectedView) contentView.addSubview(lineView) avatarView.snp.makeConstraints { (make) in make.left.equalToSuperview().offset(14) make.centerY.equalToSuperview() make.width.height.equalTo(60) } nameLabel.snp.makeConstraints { (make) in make.left.equalTo(avatarView.snp.right).offset(8) make.top.equalTo(avatarView) make.right.equalToSuperview().offset(-14) } uidLabel.snp.makeConstraints { (make) in make.left.equalTo(nameLabel) make.top.equalTo(nameLabel.snp.bottom).offset(20) make.right.equalToSuperview().offset(-14) } selectedView.snp.makeConstraints { (make) in make.centerY.equalToSuperview() make.right.equalToSuperview().offset(-14) } lineView.snp.makeConstraints { make in make.bottom.right.equalToSuperview() make.left.equalTo(avatarView) make.height.equalTo(0.7) } } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) if selected { selectedView.image = UIImage(named: "ic_msg_forward_s") }else { selectedView.image = UIImage(named: "ic_msg_forward_n") } } // MARK: - Action @objc func avatarAction() { if didAvatarCallClosure != nil { didAvatarCallClosure?(self.model!) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Conversation/Controller/FYSesstionListViewController.swift ================================================ // // FYSesstionListViewController.swift // FY-JetChat // // Created by iOS.Jet on 2019/8/18. // Copyright © 2019 Jett. All rights reserved. // import UIKit private let kSessionsCellReuseIdentifier = "kSessionsCellReuseIdentifier" class FYSesstionListViewController: FYBaseViewController { var dataSource: [FYMessageItem] = [] private lazy var clearButton: UIButton = { let button = UIButton(type: .custom) button.setTitle("全部已读".rLocalized(), for: .normal) button.theme.titleColor(from: themed { $0.FYColor_Main_TextColor_V11 }, for: .normal) button.titleLabel?.font = .PingFangRegular(16) button.isHidden = true button.sizeToFit() button.rxTapClosure { [weak self] in self?.readAllAction() } return button }() private lazy var menuList: [FYCellDataConfig] = { let items = [ FYCellDataConfig(title: "发起单聊".rLocalized(), image: "ic_tabbar01_selected", isShow: true), FYCellDataConfig(title: "发起群聊".rLocalized(), image: "ic_tabbar02_selected", isShow: true), FYCellDataConfig(title: "添加朋友".rLocalized(), image: "ic_tabbar03_selected", isShow: true), FYCellDataConfig(title: "扫一扫".rLocalized(), image: "ic_tabbar04_selected", isShow: false) ] return items }() // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "会话".rLocalized(); reloadSesstionData() registerSessionNoti() } override func makeUI() { super.makeUI() let navLeftItem = UIBarButtonItem(customView: clearButton) navigationItem.leftBarButtonItem = navLeftItem let rightBarButtonItem = UIBarButtonItem(image: R.image.icon_more_add(), style: .done, target: self, action: #selector(showPopMenu)) navigationItem.rightBarButtonItem = rightBarButtonItem setupTableView() } private func registerSessionNoti() { NotificationCenter.default.addObserver(self, selector: #selector(reloadSesstionData), name: .kNeedRefreshSesstionList, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(reloadSesstionData), name: .kNeedRefreshChatInfoList, object: nil) } func setupTableView() { plainTabView.register(FYConversationCell.self, forCellReuseIdentifier: kSessionsCellReuseIdentifier) view.addSubview(plainTabView) plainTabView.snp.makeConstraints { (make) in make.top.left.right.equalTo(self.view) if #available(iOS 11.0, *) { make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom) } else { make.bottom.equalTo(self.view.snp.bottomMargin) } } } @objc func showPopMenu() { let popView = FYNavPopuListMenu(dataSource: menuList) popView.delegate = self popView.show() } @objc func readAllAction() { let lastSesstions = FYDBQueryHelper.shared.qureyFromLastSesstions() if lastSesstions.count > 0 { for item in lastSesstions { let chat = FYDBQueryHelper.shared.qureyFromChatId(item.chatId!) chat.unReadCount = 0 FYDBQueryHelper.shared.updateFromChatModel(chat, uid: chat.uid!) } reloadSesstionData() MBHUD.showSuccess("已清除全部未读消息数".rLocalized()) } } @objc private func reloadSesstionData() { dataSource = FYDBQueryHelper.shared.qureyFromLastSesstions() reloadTableView() } private func showSessionBadge() { var badgeValue: String? = nil let unReadCount = FYDBQueryHelper.shared.queryFromSesstionsUnReadCount() if unReadCount > 0 { badgeValue = unReadCount > 99 ? "99+" : "\(unReadCount)" }else { badgeValue = nil } self.tabBarItem.badgeValue = badgeValue } /// 清空角标 private func clearCurrentBadge(_ user: FYMessageChatModel) { if FYDBQueryHelper.shared.queryFromSesstionsUnReadCount() > 0 { if let uid = user.uid { user.unReadCount = 0 FYDBQueryHelper.shared.updateFromChatModel(user, uid: uid) reloadSesstionData() } } } /// 滚到底部 private func scrollToBottom(_ animated: Bool = true) { if dataSource.count >= 1 { plainTabView.scrollToRow(at: IndexPath(row: dataSource.count - 1, section: 0), at: .bottom, animated: animated)} } /// 刷新会话列表 private func reloadTableView() { DispatchQueue.main.async { self.plainTabView.reloadData() self.showSessionBadge() } } } // MARK: - FYPopListMenuDelegate extension FYSesstionListViewController: FYPopListMenuDelegate { func menu(_ model: FYCellDataConfig, didSelectRowAt index: Int) { if index == 0 || index == 2 { UIViewController.currentViewController()?.tabBarController?.selectedIndex = 2 }else if index == 1 { UIViewController.currentViewController()?.tabBarController?.selectedIndex = 1 }else { let scanVC = ScanQRCodeViewController() navigationController?.pushViewController(scanVC) } } } // MARK: - UITableViewDataSource && Delegate extension FYSesstionListViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: kSessionsCellReuseIdentifier) as! FYConversationCell if let model = dataSource[safe: indexPath.row] { cell.model = model cell.avatarOnClick = { [weak self] in self?.pushChatInfo(model) } } clearButton.isHidden = FYDBQueryHelper.shared.queryFromSesstionsUnReadCount() <= 0 return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let model = dataSource[safe: indexPath.row] { let user = FYDBQueryHelper.shared.qureyFromChatId(model.chatId!) let chatVC = FYChatBaseViewController(chatModel: user) navigationController?.pushViewController(chatVC, animated: true) // clear model.unReadCount = 0 clearCurrentBadge(user) } } func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { handleDeleteSesstionAtRow(indexPath.row) } } func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? { return "删除".rLocalized() } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 82 } /// 删除会话记录 func handleDeleteSesstionAtRow(_ row: Int) { if let model = dataSource[safe: row] { dataSource.remove(at: row) plainTabView.deleteRows(at: [IndexPath.init(row: row, section: 0)], with: .left) if let chatId = model.chatId { // 清除记录 FYDBQueryHelper.shared.deleteFromMesssageWithId(chatId) // 清除角标 let user = FYDBQueryHelper.shared.qureyFromChatId(chatId) clearCurrentBadge(user) reloadSesstionData() } } } func pushChatInfo(_ model: FYMessageItem) { let userModel = FYDBQueryHelper.shared.qureyFromChatId(model.chatId!) let infoVC = FYContactsInfoViewController() infoVC.chatModel = userModel navigationController?.pushViewController(infoVC) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Conversation/View/FYConversationCell.swift ================================================ // // FYConversationCell.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/28. // Copyright © 2019 Jett. All rights reserved. // import UIKit class FYConversationCell: UITableViewCell { var avatarOnClick: (()->Void)? var model: FYMessageItem? { didSet { guard let msgItem = model else { return } if msgItem.nickName.isBlank == false { nameLabel.text = msgItem.nickName }else { nameLabel.text = msgItem.name } messageLabel.text = msgItem.message if let doubleDate = msgItem.date?.stringToTimeStamp().doubleValue { dateLabel.text = msgItem.date?.detailDate24Week(time: doubleDate * 1000) } avatarView.setImageWithURL(model!.avatar!, placeholder: "ic_avatar_placeholder") if let unReadCount = msgItem.unReadCount, unReadCount > 0 { if unReadCount <= 99 { badgeLabel.text = "\(unReadCount)" }else { badgeLabel.text = "99+" } badgeLabel.isHidden = false }else { badgeLabel.isHidden = true badgeLabel.text = nil } } } // MARK: - var lazy private lazy var avatarView: UIImageView = { let imageView = UIImageView() imageView.cornerRadius = 7 imageView.tapClosure { [weak self] in self?.avatarTapAction() } return imageView }() private lazy var nameLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 15) label.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } return label }() private lazy var badgeLabel: UILabel = { let label = UILabel() label.isHidden = true label.textColor = .white label.backgroundColor = .red label.font = .PingFangRegular(9) label.textAlignment = .center label.cornerRadius = 8 return label }() private lazy var messageLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 14) label.theme.textColor = themed { $0.FYColor_Main_TextColor_V10 } return label }() private lazy var dateLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 12) label.textAlignment = .right label.theme.textColor = themed { $0.FYColor_Main_TextColor_V2 } return label }() lazy var lineView: UIView = { let v = UIView() v.theme.backgroundColor = themed { $0.FYColor_BorderColor_V2 } return v }() // MARK: - life cycle override func awakeFromNib() { super.awakeFromNib() // Initialization code selectionStyle = .none } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupSubview() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubview() { selectionStyle = .none self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } contentView.addSubview(avatarView) contentView.addSubview(nameLabel) contentView.addSubview(dateLabel) contentView.addSubview(messageLabel) contentView.addSubview(badgeLabel) contentView.addSubview(lineView) avatarView.snp.makeConstraints { (make) in make.left.equalToSuperview().offset(14) make.centerY.equalToSuperview() make.width.height.equalTo(60) } badgeLabel.snp.makeConstraints { (make) in make.right.equalTo(avatarView).offset(8) make.top.equalTo(avatarView).offset(-8) make.width.height.equalTo(16) } nameLabel.snp.makeConstraints { (make) in make.left.equalTo(avatarView.snp.right).offset(12) make.top.equalTo(avatarView) make.right.equalToSuperview().offset(-14) } dateLabel.snp.makeConstraints { (make) in make.right.equalToSuperview().offset(-14) make.top.equalTo(avatarView) } messageLabel.snp.makeConstraints { (make) in make.left.equalTo(nameLabel) make.top.equalTo(nameLabel.snp.bottom).offset(20) make.right.equalToSuperview().offset(-14) } lineView.snp.makeConstraints { make in make.bottom.right.equalToSuperview() make.left.equalTo(avatarView) make.height.equalTo(0.7) } } // MARK: - Action @objc func avatarTapAction() { if avatarOnClick != nil { avatarOnClick!() } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Mine/Controller/FYMineViewController.swift ================================================ // // FYMineViewController.swift // FY-JetChat // // Created by iOS.Jet on 2019/8/18. // Copyright © 2019 Jett. All rights reserved. // import UIKit import RxSwift import SafariServices class FYMineViewController: FYBaseViewController { // MARK: - life cycle override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "我".rLocalized() view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V10 } } override func makeUI() { super.makeUI() let moments = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: false) .title(text: "朋友圈".rLocalized()) .contentState(state: .normal) .clickClosure({ [weak self] in self?.momentsAction() }).last(isLine: true) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(self.view) make.left.right.equalTo(self.view) make.height.equalTo(50) }) let settings = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: false) .title(text: "设置".rLocalized()) .contentState(state: .normal) .clickClosure({ [weak self] in self?.settingAction() }).last(isLine: false) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(moments.snp.bottom) make.left.right.equalTo(self.view) make.height.equalTo(moments) }) _ = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: false) .title(text: "作者github".rLocalized()) .content(text: "Jett") .contentState(state: .normal) .clickClosure({ [weak self] in self?.openSafariURL(url: "https://github.com/developerjet") }).last(isLine: false) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(settings.snp.bottom).offset(10) make.left.right.equalTo(self.view) make.height.equalTo(settings) }) } // MARK: - Action private func momentsAction() { let momentsVc = FYMomentsViewController() navigationController?.pushViewController(momentsVc) } private func settingAction() { let settingVC = FYSettingViewController() navigationController?.pushViewController(settingVC) } private func openSafariURL(url: String) { if let urlValue = url.URLValue { let safariVC = SFSafariViewController(url: urlValue) present(safariVC, animated: true, completion: nil) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Mine/Controller/FYSettingViewController.swift ================================================ // // FYSettingViewController.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/6. // Copyright © 2019 Jett. All rights reserved. // import UIKit class FYSettingViewController: FYBaseViewController { // MARK: - Private private var languageView: FYFastGridListView! private var versionView: FYFastGridListView! private var cachesView: FYFastGridListView! private var themeView: FYFastGridListView! // MARK: - life cycle override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "设置".rLocalized() view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V10 } } override func makeUI() { super.makeUI() let languageCode = LanguageManager.manager.selectedLanguage languageView = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: false) .title(text: "语言设置".rLocalized()) .content(text: (languageCode?.getLanguageRaw().rLocalized())!) .contentState(state: .highlight) .clickClosure({ [weak self] in self?.showLanguageAction() }).last(isLine: true) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(self.view) make.left.right.equalTo(self.view) make.height.equalTo(50) }) versionView = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: false) .title(text: "版本".rLocalized()) .content(text: "版本号:".rLocalized() + majorVersion) .contentState(state: .normal) .clickClosure({ MBHUD.showImageError("已是最新版本".rLocalized()) }).last(isLine: true) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(languageView.snp.bottom) make.left.right.equalTo(self.view) make.height.equalTo(languageView) }) cachesView = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: false) .title(text: "清除图片缓存".rLocalized()) .content(text: "\(fileCachesSize())") .contentState(state: .normal) .clickClosure({ [weak self] in self?.beginClearCaches() }).last(isLine: true) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(versionView.snp.bottom) make.left.right.equalTo(self.view) make.height.equalTo(versionView) }) var themeTitle: String = "" let lastThemeMode = FYThemeCenter.shared.currentTheme switch lastThemeMode { case .light: themeTitle = "白天模式".rLocalized() case .dark: themeTitle = "黑夜模式".rLocalized() default: themeTitle = "跟随系统".rLocalized() } themeView = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: false) .title(text: "主题模式".rLocalized()) .content(text: themeTitle ) .contentState(state: .normal) .clickClosure({ [weak self] in self?.themeSelection() }).last(isLine: false) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(cachesView.snp.bottom) make.left.right.equalTo(self.view) make.height.equalTo(cachesView) }) } private func fileCachesSize() -> String { return FYFileSizeManager.manager.cacheSize() } // MARK: - Action private func showLanguageAction() { let titles = LanguageManager.manager.currentLanguages let actionSheet = FYActionSheet(isShowCancel: true, actionTitles: titles) self.present(actionSheet, animated: true) actionSheet.handler = { index in if (index == 0) { LanguageManager.manager.setCurrentLanguage(.kChinese) }else { LanguageManager.manager.setCurrentLanguage(.kEnglish) } self.navigationController?.popViewController(animated: false) } } private func beginClearCaches() { MBHUD.show() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) { FYFileSizeManager.manager.clearImageCaches { MBHUD.showSuccess("清除成功".rLocalized()) self.cachesView.contentLabel.text = self.fileCachesSize() } } } private func themeSelection() { let themeVc = FYThemeSelectionListVC() navigationController?.pushViewController(themeVc) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Mine/Controller/FYThemeSelectionListVC.swift ================================================ // // FYThemeSelectionListVC.swift // FY-JetChat // // Created by Jett on 2022/4/30. // Copyright © 2022 Jett. All rights reserved. // import UIKit class FYThemeSelectionListVC: FYBaseViewController { // MARK: - lazy var private var systemThemeView: FYFastGridListView! private var lightThemeView: FYFastGridListView! private var drakThemeView: FYFastGridListView! // MARK: - life cycle override func viewDidLoad() { super.viewDidLoad() self.navigationItem.title = "主题模式".rLocalized() view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V10 } } override func makeUI() { super.makeUI() var isHideSystem: Bool = true var systemHeight: CGFloat = 0 // iOS可跟随系统模式 if #available(iOS 13.0, *) { systemHeight = 50 isHideSystem = false } let lastThemeMode = FYThemeCenter.shared.currentTheme systemThemeView = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: lastThemeMode == .system ? false : true) .title(text: "跟随系统".rLocalized()) .content(text: "选取后,将跟随系统设定模式".rLocalized()) .contentState(state: .normal) .clickClosure({ [weak self] in self?.selectedTheme(mode: .system) }).last(isLine: true) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(self.view) make.left.right.equalTo(self.view) make.height.equalTo(systemHeight) }) lightThemeView = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: lastThemeMode == .light ? false : true) .title(text: "白天模式".rLocalized()) .contentState(state: .highlight) .clickClosure({ [weak self] in self?.selectedTheme(mode: .light) }).last(isLine: true) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(systemThemeView.snp.bottom) make.left.right.equalTo(self.view) make.height.equalTo(50) }) drakThemeView = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: lastThemeMode == .dark ? false : true) .title(text: "黑夜模式".rLocalized()) .contentState(state: .normal) .clickClosure({ [weak self] in self?.selectedTheme(mode: .dark) }).last(isLine: false) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(lightThemeView.snp.bottom) make.left.right.equalTo(self.view) make.height.equalTo(lightThemeView) }) systemThemeView.isHidden = isHideSystem systemThemeView.rightImageView.image = R.image.ic_list_selection() lightThemeView.rightImageView.image = R.image.ic_list_selection() drakThemeView.rightImageView.image = R.image.ic_list_selection() } // MARK: - Action private func selectedTheme(mode: FYThemeMode) { switch mode { case .light: lightThemeView.isHiddenArrow(isHidden: false) drakThemeView.isHiddenArrow(isHidden: true) systemThemeView.isHiddenArrow(isHidden: true) themeService.switch(.light) case .dark: themeService.switch(.dark) drakThemeView.isHiddenArrow(isHidden: false) lightThemeView.isHiddenArrow(isHidden: true) systemThemeView.isHiddenArrow(isHidden: true) default: if #available(iOS 13.0, *) { // iOS13跟随系统 if UITraitCollection.current.userInterfaceStyle == .dark { print("System Dark mode") themeService.switch(.dark) }else { print("System Light mode") themeService.switch(.light) } systemThemeView.isHiddenArrow(isHidden: false) lightThemeView.isHiddenArrow(isHidden: true) drakThemeView.isHiddenArrow(isHidden: true) } } FYThemeCenter.shared.saveSelectionTheme(mode: mode) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Mine/View/FYFastGridListView.swift ================================================ // // FYFastGridListView.swift // FY-JetChat // // Created by iOS.Jet on 2019/8/18. // Copyright © 2019 Jett. All rights reserved. // 快速实现列表栏 import UIKit import RxSwift /** 用例:(Example) let language = FYFastGridListView().config { (view) in view.isHiddenArrow(isHidden: false) .title(text: "语言设置") .content(text: "简体中文") .contentState(state: .highlight) .clickClosure({ [weak self] in }) .last(isLine: true) } .adhere(toSuperView: self.view) .layout(snapKitMaker: { make in make.top.equalTo(self.view) make.left.right.equalTo(self.view) make.height.equalTo(50) }) */ class FYFastGridListView: UIView { fileprivate var didClickClosure : (()->Void)? // MARK: - Lazy var lazy var titleLabel: UILabel = { let label = UILabel() label.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } label.font = UIFont.PingFangRegular(14) return label }() lazy var contentLabel: UILabel = { let label = UILabel() label.theme.textColor = themed { $0.FYColor_Main_TextColor_V2 } label.font = UIFont.PingFangRegular(14) return label }() lazy var rightImageView: UIImageView = { let imageView = UIImageView() imageView.image = R.image.icon_arrow_right() return imageView }() lazy var lineView: UIView = { let view = UIView() view.theme.backgroundColor = themed { $0.FYColor_BorderColor_V2 } return view }() // MARK: - Life cycle override init(frame: CGRect) { super.init(frame: frame) makeUI() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) makeUI() } func makeUI() { self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } self.addSubview(self.titleLabel) self.addSubview(self.contentLabel) self.addSubview(self.rightImageView) self.addSubview(self.lineView) self.titleLabel.snp.makeConstraints { (make) in make.centerY.equalTo(self) make.left.equalTo(self).offset(15) } self.contentLabel.snp.makeConstraints { (make) in make.centerY.equalTo(self) make.right.equalTo(self).offset(-30) } self.rightImageView.snp.makeConstraints { (make) in make.centerY.equalTo(self) make.right.equalTo(self).offset(-15) } self.lineView.snp.makeConstraints { (make) in make.bottom.equalTo(self) make.height.equalTo(0.5) make.left.equalTo(self).offset(14) make.right.equalTo(self) } let tap = UITapGestureRecognizer() tap.rx.event.throttle(.microseconds(100), scheduler: MainScheduler.instance).subscribe {[weak self] (event) in self?.didClickClosure?() }.disposed(by: rx.disposeBag) self.addGestureRecognizer(tap) } } enum CustomContentColorState { case normal case highlight func stateColor() -> UIColor { switch self { case .normal: switch themeService.type { case .light: return .Color_Gray_77808A default: return .Color_Gray_5A636D } case .highlight: return .Color_Blue_1890FF } } } protocol CustomContentProtocal { } extension CustomContentProtocal where Self: FYFastGridListView { @discardableResult func config(_ config:(Self)->Void) -> Self { config(self) return self } } extension FYFastGridListView: CustomContentProtocal { @discardableResult func title(text: String) -> Self { self.titleLabel.text = text return self } @discardableResult func content(text: String) -> Self { self.contentLabel.text = text return self } @discardableResult func titleColor(color: UIColor) -> Self { self.titleLabel.textColor = color return self } @discardableResult func contentColor(color: UIColor) -> Self { self.contentLabel.textColor = color return self } @discardableResult func contentState(state:CustomContentColorState) -> Self { self.contentLabel.textColor = state.stateColor() return self } @discardableResult func clickClosure(_ closure:@escaping ()->Void) -> Self { didClickClosure = closure self.isUserInteractionEnabled = true return self } @discardableResult func isHiddenArrow(isHidden: Bool) -> Self { if (isHidden) { // 隐藏箭头 self.rightImageView.isHidden = true self.contentLabel.snp.updateConstraints{ (make) in make.right.equalTo(self).offset(-15) } }else { // 显示箭头 self.rightImageView.isHidden = false self.contentLabel.snp.updateConstraints { (make) in make.centerY.equalTo(self) make.right.equalTo(self).offset(-30) } self.rightImageView.snp.updateConstraints { (make) in make.centerY.equalTo(self) make.right.equalTo(self).offset(-15) } } self.updateConstraints() return self } // 最后分割线 @discardableResult func last(isLine: Bool) { if (isLine) { self.lineView.isHidden = false }else { self.lineView.isHidden = true } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/FYMomentsViewController.swift ================================================ // // FYMomentsViewController.swift // FY-JetChat // // Created by Jett on 2022/4/28. // Copyright © 2022 Jett. All rights reserved. // import UIKit import IGListKit import IGListDiffKit import MJRefresh class FYMomentsViewController: FYBaseIGListViewController { // MARK: - lazy var fileprivate var contentOffsetY: CGFloat = 0 fileprivate var publish: NSObjectProtocol? fileprivate var location: NSObjectProtocol? fileprivate var delete: NSObjectProtocol? fileprivate var contentOffset: NSObjectProtocol? fileprivate var push: NSObjectProtocol? fileprivate var openURL: NSObjectProtocol? fileprivate var page: Int = 1 private lazy var momentNavBar: FYMomentNavBar = { let nav = FYMomentNavBar(frame: CGRect(x: 0, y: 0, width: kScreenW, height: kNavigaH)) nav.onClick = { [weak self] index in switch index { case 100: self?.navigationController?.popViewController(animated: true) default: break } } return nav }() // MARK: - life cycle override func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController { switch object { case is FYMomentInfo: let section = FYMomentBindingSection() return section default: fatalError() } } override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "朋友圈".rLocalized() addRefreshing() loadData() } override func makeUI() { super.makeUI() adapter.scrollViewDelegate = self view.addSubview(momentNavBar) momentNavBar.snp.makeConstraints { make in make.left.top.right.equalTo(self.view); make.height.equalTo(kNavigaH) } } private func addRefreshing() { collectionView.mj_header = FYMomentsHeaderRefresh(refreshingBlock: {[weak self] in DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self?.collectionView.mj_header?.endRefreshing() } }) collectionView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: {[weak self] in DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self?.loadData("moments2") self?.collectionView.mj_footer?.endRefreshing() } }) } private func loadData(_ resource: String = "moments1") { do { let url = Bundle.main.url(forResource: resource, withExtension: "json")! do { let data = try Data(contentsOf: url) let jsonData: Any = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) if let jsonArray = jsonData as? NSArray { if let modelArray = [FYMomentInfo].deserialize(from: jsonArray) { for info in modelArray { info?.id = Int(arc4random_uniform(255)) self.objects.append(info!) } } adapter.performUpdates(animated: true, completion: nil) } } catch { print("decode failure") } } } } fileprivate extension FYMomentsViewController { func notification() { publish = NotificationCenter.default.addObserver(forName: NSNotification.Name.list.publish, object: nil, queue: OperationQueue.main) {[weak self] (noti) in self?.loadData() } location = NotificationCenter.default.addObserver(forName: NSNotification.Name.list.location, object: nil, queue: OperationQueue.main) { [weak self] (noti) in guard let object = noti.object as? FYMomentInfo else { return } print("click location", object) } delete = NotificationCenter.default.addObserver(forName: Notification.Name.list.delete, object: nil, queue: OperationQueue.main) {[weak self] (noti) in guard let object = noti.object as? FYMomentInfo, let self = self else { return } self.objects.removeAll { (element) -> Bool in guard let ele = element as? FYMomentInfo else { return false } return ele.id == object.id } self.adapter.performUpdates(animated: true, completion: nil) } contentOffset = NotificationCenter.default.addObserver(forName: NSNotification.Name.list.contentOffset, object: nil, queue: OperationQueue.main, using: {[weak self] (noti) in guard let offset = noti.object as? CGFloat, let self = self else { return } if offset < 0 { return } self.collectionView.setContentOffset(CGPoint(x: 0, y: offset), animated: false) }) push = NotificationCenter.default.addObserver(forName: NSNotification.Name.list.push, object: nil, queue: OperationQueue.main, using: {[weak self] (noti) in guard let userId = noti.object as? Int, let self = self else { return } print(userId) }) openURL = NotificationCenter.default.addObserver(forName: NSNotification.Name.list.openURL, object: nil, queue: OperationQueue.main, using: {[weak self] (noti) in guard let url = noti.object as? URL, let self = self else { return } print(url) }) } } // MARK: - extension FYMomentsViewController: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { contentOffsetY = scrollView.contentOffset.y momentNavBar.navBarView.alpha = contentOffsetY / 150 momentNavBar.titleLabel.alpha = contentOffsetY / 150 if contentOffsetY / 150 > 0.6 { momentNavBar.isScrollUp = true } else { momentNavBar.isScrollUp = false } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/JSONData/moments1.json ================================================ [ { "id": 1, "userInfo": { "user_id": 1, "background_url": "https://img-pre.ivsky.com/img/tupian/pre/202108/08/hushui-004.jpg", "avatar_url": "http://img2.woyaogexing.com/2022/03/07/ed37ea279a614c299a34c50cd5bbea4a!400x400.jpeg", "user_name": "Jett2022" } }, { "id": 1, "limage": "http://img2.woyaogexing.com/2021/07/30/f0fb4aa3f74b4674868307b56910834a!400x400.jpeg", "avatar": "http://img2.woyaogexing.com/2022/04/28/58d93adf40b84dcbb8b9979b56a4e6f6!400x400.jpeg", "userName": "夜风已冷~", "location": "北京·天安门", "publicTime": "6小时前", "comments": [ { "avatar_url": "http://img2.woyaogexing.com/2022/04/28/5fd0ec3fd5a440a79bc77708e7c974b0!400x400.jpeg", "comment": "万代大", "person": "欧米茄" }, { "avatar_url": "http://img2.woyaogexing.com/2022/04/26/58348c965d544872a09fd13a3adb1055!400x400.jpeg", "comment": "你在说什么?", "person": "要死啦" }, { "avatar_url": "http://img2.woyaogexing.com/2022/03/25/cadfd26a57b9498d813dbb667c0e0467!400x400.jpeg", "comment": "红哦哦", "person": "爱的心疼" } ] }, { "id": 2, "limage": "", "avatar": "http://p1.ifengimg.com/fck/2017_46/61dc7cab79bf1e8_w500_h500.jpg", "userName": "后知后觉", "content": "对,这话我说过 021-87888822xxa", "location": "西京·未央宫", "publicTime": "1小时前" }, { "id": 3, "limage": "http://img2.woyaogexing.com/2022/04/28/041a8496038c41e89d3a6f3e8f29437b!400x400.jpeg,http://img2.woyaogexing.com/2022/04/28/a5481ff508c44a2282d688b575538a7a!400x400.jpeg,http://img2.woyaogexing.com/2022/04/28/2eab80f3c570464e8cee901aab3f5b04!400x400.jpeg", "avatar": "http://img2.woyaogexing.com/2022/04/28/8c289846c64c4ee98e17db122e4f07c0!400x400.jpeg", "userName": "路飞呀", "content": "伟大的成绩和辛勤劳动是成正比例的,有一分劳动就有一分收获,日积月累,从少到多,奇迹就可以创造出来。http://baidu.com", "location": "东京·大相国寺", "publicTime": "6小时前", "comments": [ { "avatar_url": "http://img2.woyaogexing.com/2022/03/25/a2aa4794c7be4acf8cce2ae3379f3f24!400x400.jpeg", "comment": "cav", "person": "james" }, { "avatar_url": "http://img2.woyaogexing.com/2022/03/25/8134faf1a51d4abcb6b4faa97f14c5e1!400x400.jpeg", "comment": "boston", "person": "irving" }, { "avatar_url": "http://img2.woyaogexing.com/2022/04/28/422f1e900a414686ac4e3e6b5d8b26ae!400x400.jpeg", "comment": "cav", "person": "love" } ] }, { "id": 4, "limage": "http://img2.woyaogexing.com/2022/04/25/6a4dc51ddf364cef888385a2243c9ebf!400x400.jpeg", "avatar": "http://img2.woyaogexing.com/2022/04/28/dbfa227a12f4443a8ba4ea82aff5be9f!400x400.jpeg", "userName": "箴言至心", "content": "哪里有天才,我是把别人喝咖啡的工夫都用在了工作上了。", "location": "北京·故宫", "publicTime": "10小时前" }, { "id": 5, "limage": "https://img2.woyaogexing.com/2021/08/19/96be8e219e544752af1de5d91b9d0e52!400x400.png,http://img2.woyaogexing.com/2022/04/27/c143c859d17b49edb874be731554a403!400x400.jpeg,http://img2.woyaogexing.com/2022/04/25/fa535ae764ff43eab654633711968401!400x400.jpeg", "avatar": "http://img2.woyaogexing.com/2022/04/27/9c89a905f5e846ea9e05d2b490808766!400x400.jpeg", "userName": "友谊的船", "content": "友谊是两颗心真诚相待,而不是一颗心对另一颗心敲打", "location": "", "publicTime": "昨天", "comments": [ { "avatar_url": "http://img2.woyaogexing.com/2021/04/26/04ee2aa30c4a48e889288adbb2f0be54!400x400.jpeg", "comment": "cav", "person": "james" }, { "avatar_url": "http://img2.woyaogexing.com/2021/04/26/148cbcc87dc6429ab3c43c9984707ae9!400x400.jpeg", "comment": "boston", "person": "irving" }, { "avatar_url": "http://img2.woyaogexing.com/2021/07/27/08c15c4a423e4c69bd40ff0650265794!400x400.jpeg", "comment": "cav", "person": "love" } ] }, { "id": 6, "limage": "http://img2.woyaogexing.com/2022/04/26/5a543644c3914b9eae280d2afd936ac5!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/ab179fdf8381437d9b8c26bdebd3d2e7!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/1b1110d87aee44b58b4a389e995654f6!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/8ec9794503304346abd4803dab76389c!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/5db569d16da4497da9ff145687c1a091!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/abde3839d7bc4b1a8df008d26442de27!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/b220efc9b4794a0092f47e01dafba2b5!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/9e9c95954b2242c4a4118006457ac6e5!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/7698cb12589047fb84b9956626d34c05!400x400.jpeg", "avatar": "http://img2.woyaogexing.com/2022/04/25/817ea967fe1645dd8c9f00efc4b9f6e0!400x400.jpeg", "userName": "夏日炎炎", "content": "夏日炎炎,有你才甜呀。", "location": "深圳·白石洲", "publicTime": "2天前" }, { "id": 7, "limage": "http://i7.qhimg.com/t01dc4b7086db70a49a.jpg", "avatar": "http://img2.woyaogexing.com/2022/04/25/f2f5a9ee19d7496596a7a009a4749f6f!400x400.jpeg", "userName": "苏东坡", "content": "明月几时有,老夫想喝酒!!!", "location": "", "publicTime": "10小时前" }, { "id": 8, "limage": "http://img5.artron.net/auction/2012/art501702/d/art5017020027.jpg,http://www.99zihua.com/images/goods/20130502/4238481536fbdad4.jpg,http://n.sinaimg.cn/history/transform/20171009/bWJ7-fymrcpw5253182.jpg,https://pic.rmb.bdstatic.com/1cbba2fabf1554017b9eed38f65715d3.jpeg", "avatar": "http://img2.woyaogexing.com/2022/04/25/f2f5a9ee19d7496596a7a009a4749f6f!400x400.jpeg", "userName": "苏轼", "content": "十年生死两茫茫,不思量,自难忘。千里孤坟,无处话凄凉。纵使相逢应不识,尘满面,鬓如霜。\n夜来幽梦忽还乡,小轩窗,正梳妆。相顾无言,惟有泪千行。料得年年肠断处,明月夜,短松冈。", "location": "苏州·寒山寺", "publicTime": "10小时前" } ] ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/JSONData/moments2.json ================================================ [ { "id": 1, "limage": "http://img2.woyaogexing.com/2022/04/28/a32f8f6c174a4c69af755a14da5ff822!400x400.png", "avatar": "http://img2.woyaogexing.com/2022/04/28/60e043b181144b5e9134c6f32154412b!400x400.png", "userName": "萌宠一族", "location": "北京·大相国寺", "publicTime": "6小时前" }, { "id": 2, "limage": "", "avatar": "http://img2.woyaogexing.com/2022/04/28/940828f2ebf1492497f1f5ac610f3ed5!400x400.jpeg", "userName": "爱的每一天", "content": "对,这话我说过", "location": "西京·未央宫", "publicTime": "1小时前", "comments": [ { "avatar_url": "http://img2.woyaogexing.com/2022/04/25/18db0f9f462644c3843303e5d83aab05!400x400.jpeg", "comment": "cav", "person": "james" }, { "avatar_url": "http://img2.woyaogexing.com/2022/04/25/d1be4715a1dc4d85b11e471a29c462f4!400x400.jpeg", "comment": "boston", "person": "irving" }, { "avatar_url": "http://img2.woyaogexing.com/2022/04/25/0afc130b217245da8f8d7bd4a922b8d7!400x400.jpeg", "comment": "cav", "person": "love" } ] }, { "id": 3, "limage": "http://img2.woyaogexing.com/2022/04/29/8479fe903b9a4938b569f6ee08006d08!400x400.jpeg,http://img2.woyaogexing.com/2022/04/28/86196aa1295a4ebf9254274bdb847408!400x400.jpeg,http://img2.woyaogexing.com/2022/04/28/81ea9c2da0f0426cbcb76a74a80fee71!400x400.jpeg", "avatar": "http://img2.woyaogexing.com/2022/04/25/853289c432004926b7728b435cfc8c66!400x400.jpeg", "userName": "如果当时", "content": "跋涉三年的逝水年华,他始终默默地在原地等她回来,他说,在哪儿走失的,我一定会在哪儿一直等到你回来。那一刻她才知道,也许,正是为着他的不完美,她才更爱他。", "location": "南京·中山陵", "publicTime": "6小时前", "comments": [ { "avatar_url": "http://img2.woyaogexing.com/2021/07/30/2f5ea8a0711840b19abf2f2aefc6b829!400x400.jpeg", "comment": "cav", "person": "james" }, { "avatar_url": "http://img2.woyaogexing.com/2021/07/30/8ad7888a3a7c4c3fb4f8ab28506ca495!400x400.jpeg", "comment": "boston", "person": "irving" }, { "avatar_url": "http://img2.woyaogexing.com/2021/07/30/55ad05a68b4a42f984342f88479660ed!400x400.jpeg", "comment": "cav", "person": "love" } ] }, { "id": 4, "limage": "http://img2.woyaogexing.com/2022/04/27/e43a084ef60b4dab8190f1e8ec6eadfc!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/421ac909604641339171b88089dd8189!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/d2cdd99e2c374d7ba96223ebbc5fa9b9!400x400.jpeg", "avatar": "http://img2.woyaogexing.com/2022/04/27/e2327f3995964bf9b857e840d9311def!400x400.jpeg", "userName": "天道酬勤", "content": "哪里有天才,我是把别人喝咖啡的工夫都用在了工作上了。", "location": "北京·故宫", "publicTime": "10小时前", "comments": [ { "avatar_url": "http://img2.woyaogexing.com/2022/04/25/2d36d0ff34f94a32b2dc6dd498769f87!400x400.jpeg", "comment": "cav", "person": "james" }, { "avatar_url": "http://img2.woyaogexing.com/2022/04/25/f73ebd9c1bc74cd3bed24c7f9c576cb2!400x400.jpeg", "comment": "boston", "person": "irving" }, { "avatar_url": "http://img2.woyaogexing.com/2022/04/28/0bf4f2c245f948f2bb78d419713fda8f!400x400.jpeg", "comment": "cav", "person": "love" } ] }, { "id": 5, "limage": "http://img2.woyaogexing.com/2022/04/27/a53eb0dc04e34d03b4c278faaa06b13e!400x400.jpeg,http://img2.woyaogexing.com/2022/04/27/a2c236610a074d07932b058a67baf633!400x400.jpeg,http://img2.woyaogexing.com/2022/04/27/d559baeadd19415bbfb3067bcb6c40e8!400x400.jpeg,http://img2.woyaogexing.com/2022/04/27/19fc3585283f4c74b463241bbe120ec9!400x400.jpeg,http://img2.woyaogexing.com/2022/04/25/fa535ae764ff43eab654633711968401!400x400.jpeg", "avatar": "http://img2.woyaogexing.com/2021/07/27/c3d34960b12b432497175a792807b78e!400x400.jpeg", "userName": "明天会更好", "content": "友谊是两颗心真诚相待,而不是一颗心对另一颗心敲打", "location": "", "publicTime": "昨天" }, { "id": 6, "limage": "http://img2.woyaogexing.com/2022/04/26/e857e8668e5c41bbaf744b6dabab8e85!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/ab179fdf8381437d9b8c26bdebd3d2e7!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/1b1110d87aee44b58b4a389e995654f6!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/8ec9794503304346abd4803dab76389c!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/5db569d16da4497da9ff145687c1a091!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/abde3839d7bc4b1a8df008d26442de27!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/b220efc9b4794a0092f47e01dafba2b5!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/9e9c95954b2242c4a4118006457ac6e5!400x400.jpeg,http://img2.woyaogexing.com/2022/04/26/7698cb12589047fb84b9956626d34c05!400x400.jpeg", "avatar": "http://img2.woyaogexing.com/2022/04/25/1cbf8d90bb76446db3adf799670fe764!400x400.jpeg", "userName": "星辰大海", "content": "", "location": "海南·三亚", "publicTime": "5天前" }, { "id": 7, "limage": "http://i7.qhimg.com/t01dc4b7086db70a49a.jpg", "avatar": "http://img2.woyaogexing.com/2022/04/25/f2f5a9ee19d7496596a7a009a4749f6f!400x400.jpeg", "userName": "苏东坡", "content": "明月几时有,老夫想喝酒!!!", "location": "", "publicTime": "10小时前" }, { "id": 8, "limage": "http://img5.artron.net/auction/2012/art501702/d/art5017020027.jpg,http://www.99zihua.com/images/goods/20130502/4238481536fbdad4.jpg,http://n.sinaimg.cn/history/transform/20171009/bWJ7-fymrcpw5253182.jpg,https://pic.rmb.bdstatic.com/1cbba2fabf1554017b9eed38f65715d3.jpeg", "avatar": "http://img2.woyaogexing.com/2022/04/25/6b29fbeb53ea4f86ae2363631f9fd3e1!400x400.jpeg", "userName": "苏东坡", "content": "缺月挂疏桐,漏断人初静。时见幽人独往来,缥缈孤鸿影。\n惊起却回头,有恨无人省。拣尽寒枝不肯栖,寂寞沙洲冷。", "location": "苏州·姑苏城", "publicTime": "10小时前" }, { "id": 9, "limage": "http://img2.woyaogexing.com/2021/09/03/9fd125d679984528ae7e7aeab532aad2!400x400.jpeg,http://img2.woyaogexing.com/2021/09/03/fff2a9c00d63463181ff172f1220624c!400x400.jpeg,http://img2.woyaogexing.com/2021/09/03/10c7774f252b47f0a1699cb549d55313!400x400.jpeg", "avatar": "http://img2.woyaogexing.com/2021/09/03/717b15e552284d3e951d2f8ac7e789a7!400x400.jpeg", "userName": "秋天的海", "location": "法国·巴黎", "publicTime": "昨天" }, ] ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/Model/FYCommentInfo.swift ================================================ // // FYFYCommentInfo.swift // FY-JetChat // // Created by Jett on 2022/4/28. // Copyright © 2022 Jett. All rights reserved. // import UIKit import HandyJSON class FYCommentInfo: HandyJSON { var user_id: Int = 0 var person: String = "" var comment: String = "" var avatar_url: String = "" required init() { } } extension FYCommentInfo: Equatable { static func == (lhs: FYCommentInfo, rhs: FYCommentInfo) -> Bool { return (lhs.comment == rhs.comment) && (lhs.user_id == rhs.user_id) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/Model/FYMoUserInfo.swift ================================================ // // FYMoUserInfo.swift // FY-JetChat // // Created by Jett on 2022/4/28. // Copyright © 2022 Jett. All rights reserved. // import UIKit import HandyJSON import IGListDiffKit class FYMoUserInfo: HandyJSON { /// 用户个人id var user_id: Int = 0 /// 顶部背景图片 var background_url: String = "" /// 用户个人头像 var avatar_url: String = "" /// 用户个人昵称 var user_name: String = "" required init() { } } extension FYMoUserInfo: ListDiffable { func diffIdentifier() -> NSObjectProtocol { return user_id as NSObjectProtocol } func isEqual(toDiffableObject object: ListDiffable?) -> Bool { guard self === object else { return true } guard let object = object as? FYMoUserInfo else { return false } return user_id == object.user_id } } extension FYMoUserInfo: Equatable { static func == (lhs: FYMoUserInfo, rhs: FYMoUserInfo) -> Bool { return lhs.isEqual(toDiffableObject: rhs) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/Model/FYMomentInfo.swift ================================================ // // FYMomentInfo.swift // FY-JetChat // // Created by Jett on 2022/4/28. // Copyright © 2022 Jett. All rights reserved. // import UIKit import HandyJSON import IGListDiffKit class FYMomentInfo: HandyJSON { var id: Int = 0 var avatar: String = "" var userName: String = "" var content: String = "" var location: String = "" var publicTime: String = "" /// 图片组 var limage: String = "" /// 个人信息 var userInfo: FYMoUserInfo? /// 评论数据 var comments: [FYCommentInfo] = [] /// 文字是否展开 var isTextExpend: Bool = false required init() { } } extension FYMomentInfo { /// 图片数组 var images: [String] { return limage.split(separator: ",").map({String($0)}) } var cellHeight: CGFloat { var cellHeight: CGFloat = 10 + 20 + 10 if !content.isEmpty { let expendH: CGFloat = isNeedExpend ? 30 : 0 cellHeight += textHeight + expendH } if images.count > 0 { cellHeight += 10 cellHeight += momentPicsHeight(images.count) } return cellHeight } /// 文字是否需要展开 var isNeedExpend: Bool { let lines = content.textLines(MomentHeaderCell.contentW, font: UIFont.systemFont(ofSize: 17)) return lines.count > 3 } var textHeight: CGFloat { let lines = content.textLines(MomentHeaderCell.contentW, font: UIFont.systemFont(ofSize: 17)) return UIFont.systemFont(ofSize: 17).lineHeight * CGFloat((lines.count > 3 && !isTextExpend ? 3 : lines.count)) } func momentPicsHeight(_ picCount: Int) -> CGFloat { let verticalSpace: CGFloat = 5 if picCount == 1 { return mImageW * 2 + verticalSpace } if picCount <= 3 { return mImageW + 2 * verticalSpace } if picCount <= 6 { return 2 * mImageW + 3 * verticalSpace } return 3 * mImageW + 4 * verticalSpace } var thumbsHeight: CGFloat { let verticalSpace: CGFloat = 5 let rows = comments.count / 7 + (comments.count % 7 > 0 ? 1 : 0) let vertical = CGFloat(rows) * verticalSpace * 2 return CGFloat(rows) * CommentThumbView.itemWidth + vertical } var commentHeight: CGFloat { return CGFloat(comments.count * 50) } var contentHeight: CGFloat { return thumbsHeight + commentHeight } } extension FYMomentInfo: ListDiffable { func diffIdentifier() -> NSObjectProtocol { return id as NSObjectProtocol } func isEqual(toDiffableObject object: ListDiffable?) -> Bool { guard self === object else { return true } guard let object = object as? FYMomentInfo else { return false } return id == object.id } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/Sections/FYMomentBindingSection.swift ================================================ // // FYFYMomentBindingSection.swift // FY-JetChat // // Created by Jett on 2022/4/28. // Copyright © 2022 Jett. All rights reserved. // import UIKit import IGListKit import IGListDiffKit enum ViewModelEnum: String { case top, header, image_single, location, bottom, comment } class FYMomentBindingSection: ListBindingSectionController { var momentInfo: FYMomentInfo! override init() { super.init() dataSource = self selectionDelegate = self } override func didUpdate(to object: Any) { guard let obj = object as? FYMomentInfo else { fatalError() } momentInfo = obj super.didUpdate(to: obj) } // MARK: cell func momentTopCell(at index: Int) -> MomentTopCell { guard let cell = collectionContext?.dequeueReusableCell(of: MomentTopCell.self, for: self, at: index) as? MomentTopCell else { fatalError() } cell.bindViewModel(object!) return cell } func momentHeaderCell(at index: Int) -> MomentHeaderCell { guard let cell = collectionContext?.dequeueReusableCell(of: MomentHeaderCell.self, for: self, at: index) as? MomentHeaderCell else { fatalError() } cell.bindViewModel(object!) cell.onClick = {[weak self] idx in self?.toExpend() } return cell } func momentHeaderImageCell(at index: Int) -> MomentHeaderImageCell { guard let cell = collectionContext?.dequeueReusableCell(of: MomentHeaderImageCell.self, for: self, at: index) as? MomentHeaderImageCell else { fatalError() } cell.bindViewModel(object!) cell.onClick = {[weak self] idx in self?.toExpend() } return cell } func momentLocationCell(at index: Int) -> MomentLocationCell { guard let cell = collectionContext?.dequeueReusableCell(of: MomentLocationCell.self, for: self, at: index) as? MomentLocationCell else { fatalError() } cell.bindViewModel(object!) return cell } func momentBottomCell(at index: Int) -> MomentBottomCell { guard let cell = collectionContext?.dequeueReusableCell(of: MomentBottomCell.self, for: self, at: index) as? MomentBottomCell else { fatalError() } cell.bindViewModel(object!) cell.onClick = {[weak self] action in guard let self = self else { return } switch action { case .thumbup: self.toFavor() case .delete: self.toDelete() case .comment(let text): self.toComment(text) case .commentDraft(let text): self.toSaveDraft(text) } } cell.onRelativeRect = {[unowned self] () -> CGRect in let first = self.cellForItem(at: 0).frame let last = self.cellForItem(at: index).frame let rect = CGRect(x: 0, y: first.minY, width: first.width, height: last.maxY-first.minY) return rect } return cell } func momentCommentCell(at index: Int) -> MomentCommentCell { guard let cell = collectionContext?.dequeueReusableCell(of: MomentCommentCell.self, for: self, at: index) as? MomentCommentCell else { fatalError() } cell.bindViewModel(object!) cell.actionDelegate = self return cell } } extension FYMomentBindingSection: ListBindingSectionControllerDataSource, ListBindingSectionControllerSelectionDelegate { func sectionController(_ sectionController: ListBindingSectionController, viewModelsFor object: Any) -> [ListDiffable] { guard let object = object as? FYMomentInfo else { return [] } var results: [ListDiffable] = [] if object.userInfo?.background_url != nil { results.append(ViewModelEnum.top.rawValue as ListDiffable) } if object.images.count == 1 { results.append(ViewModelEnum.image_single.rawValue as ListDiffable) }else { results.append(ViewModelEnum.header.rawValue as ListDiffable) } if !object.location.isEmpty { results.append(ViewModelEnum.location.rawValue as ListDiffable) } if object.userInfo == nil { results.append(ViewModelEnum.bottom.rawValue as ListDiffable) results.append(ViewModelEnum.comment.rawValue as ListDiffable) } return results } func sectionController(_ sectionController: ListBindingSectionController, cellForViewModel viewModel: Any, at index: Int) -> UICollectionViewCell & ListBindable { let viewModel = ViewModelEnum(rawValue: viewModel as! String)! switch viewModel { case .top: return momentTopCell(at: index) case .image_single: return momentHeaderImageCell(at: index) case .header: return momentHeaderCell(at: index) case .location: return momentLocationCell(at: index) case .bottom: return momentBottomCell(at: index) case .comment: return momentCommentCell(at: index) } } func sectionController(_ sectionController: ListBindingSectionController, sizeForViewModel viewModel: Any, at index: Int) -> CGSize { guard let object = object as? FYMomentInfo else { fatalError() } let viewModel = ViewModelEnum(rawValue: viewModel as! String)! let width: CGFloat = collectionContext!.containerSize(for: self).width switch viewModel { case .top: return CGSize(width: width, height: 340) case .header, .image_single: return CGSize(width: width, height: object.cellHeight) case .location: return CGSize(width: width, height: 30) case .bottom: return CGSize(width: width, height: 30) case .comment: var height = object.contentHeight height += object.contentHeight > 0 ? 20 : 10 return CGSize(width: width, height: height) } } func sectionController(_ sectionController: ListBindingSectionController, didSelectItemAt index: Int, viewModel: Any) { } func sectionController(_ sectionController: ListBindingSectionController, didDeselectItemAt index: Int, viewModel: Any) { } func sectionController(_ sectionController: ListBindingSectionController, didHighlightItemAt index: Int, viewModel: Any) { } func sectionController(_ sectionController: ListBindingSectionController, didUnhighlightItemAt index: Int, viewModel: Any) { } } fileprivate extension FYMomentBindingSection { func toExpend() { guard let object = object as? FYMomentInfo else { fatalError() } object.isTextExpend = !object.isTextExpend self.didUpdate(to: object) self.collectionContext?.performBatch(animated: false, updates: { (context) in context.reload(self) }, completion: nil) } func toDelete() { NotificationCenter.default.post(name: NSNotification.Name.list.delete, object: object) } /// 点赞 func toFavor() { guard let object = object as? FYMomentInfo else { fatalError() } self.didUpdate(to: object) self.collectionContext?.performBatch(animated: false, updates: { (context) in context.reload(self) }, completion: nil) } /// 评论 func toComment(_ text: String) { print("comment: \(text)") guard let object = object as? FYMomentInfo else { fatalError() } self.didUpdate(to: object) self.collectionContext?.performBatch(animated: false, updates: { (context) in context.reload(self) }, completion: nil) } func toSaveDraft(_ text: String) { print("comment draft: \(text)") } } extension FYMomentBindingSection: MomentCommentDelegate { func contentDidSelected(_ model: FYCommentInfo, action: CommentContentClickAction) { print(action, model) switch action { case .avatar: print("点击头像") case .title: print("点击title") case .reply: print("回复的标题") case .bg(let isSelf): print("点击背景 \(isSelf)") case .comment(let text): print("评论: \(text)") toComment(text) case .commentDraft(let text): print("评论草稿: \(text)") } } func thumbDidSelected(_ model: FYCommentInfo) { print(model) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/Cell/CommentContentCell.swift ================================================ // // CommentContentCell.swift // JetChat // // Created by Jett on 2020/6/9. // Copyright © 2022 Jett. All rights reserved. // import Foundation class CommentContentCell: UITableViewCell { fileprivate lazy var avatarImageView: UIImageView = { let iv = UIImageView() iv.layer.cornerRadius = 5 iv.layer.masksToBounds = true iv.isUserInteractionEnabled = true let tap = UITapGestureRecognizer(target: self, action: #selector(viewClick(_:))) iv.addGestureRecognizer(tap) return iv }() fileprivate lazy var titleBtn: UIButton = { let btn = UIButton(type: .custom) btn.theme.titleColor(from: themed { $0.FYColor_Main_TextColor_V1 }, for: .normal) btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.contentHorizontalAlignment = .left btn.addTarget(self, action: #selector(click(_:)), for: .touchUpInside) return btn }() fileprivate lazy var contentLabel: FYLabel = { let lb = FYLabel() lb.font = UIFont.systemFont(ofSize: 14) lb.theme.textColor = themed{ $0.FYColor_Main_TextColor_V3 } lb.numberOfLines = 0 return lb }() var comment: FYCommentInfo! { didSet { avatarImageView.setImageWithURL(comment.avatar_url, placeholder: R.image.ic_avatar_placeholder()!) titleBtn.setTitle(comment.person, for: .normal) let reply: String? = "--" if let parent = reply, !parent.isEmpty { contentLabel.text = "回复\(parent):\(comment.comment)" }else { contentLabel.text = comment.comment } } } var onClick: ((CommentContentClickAction)->Void)? var onTextClick: (()->Void)? static func getHeight(_ model: FYCommentInfo) -> CGFloat { let font = UIFont.systemFont(ofSize: 14) let width = kScreenW - 50 - 34 - MomentHeaderCell.padding * 2 let height = 50 + model.comment.textSize(width, font: font).height - font.lineHeight return height } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.selectionStyle = .none self.backgroundColor = .clear addSubview(avatarImageView) addSubview(titleBtn) addSubview(contentLabel) setMultiLabel(contentLabel) avatarImageView.snp.makeConstraints { (make) in make.width.height.equalTo(40) make.leading.equalToSuperview() make.top.equalTo(5) } titleBtn.snp.makeConstraints { (make) in make.leading.equalTo(avatarImageView.snp.trailing).offset(5) make.top.equalTo(avatarImageView) make.height.equalTo(18) make.trailing.lessThanOrEqualToSuperview().offset(-5) } contentLabel.snp.makeConstraints { (make) in make.leading.equalTo(titleBtn) make.trailing.lessThanOrEqualToSuperview().offset(-5) make.top.equalTo(titleBtn.snp.bottom) } } private func setMultiLabel(_ label: FYLabel) { let reply = FYLabelType.custom(pattern: "回复(.+):", start: 2, tender: -1) label.customColor = [reply: .Color_Blue_375793] label.enabledTypes = [.URL, .phone, reply] label.handleNormalTap {[weak self] text in self?.onTextClick?() } label.handleCustomTap(reply) {[weak self] (text) in self?.onClick?(.reply) } label.handleURLTap { (text) in NotificationCenter.default.post(name: NSNotification.Name.list.openURL, object: URL(string: text)) } label.handlePhoneTap { (phone) in UIApplication.shared.openURL(URL(string: "tel://\(phone)")!) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func viewClick(_ ges: UIGestureRecognizer) { onClick?(.avatar) } @objc private func click(_ btn: UIButton) { onClick?(.title) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/Cell/CommentContentView.swift ================================================ // // CommentContentView.swift // JetChat // // Created by Jett on 2020/5/11. // Copyright © 2022 Jett. All rights reserved. // import Foundation enum CommentContentClickAction { /// 点击头像 case avatar /// 点击title case title /// 回复的标题 case reply /// 点击背景 是否是自己的评论 case bg(Bool) /// 评论 case comment(String) /// 草稿 case commentDraft(String) } class CommentContentView: UITableView { var comments = [FYCommentInfo]() { didSet { reloadData() } } weak var actionDelegate: MomentCommentDelegate? fileprivate lazy var commentnputView: CommentInputView = { let inputView = CommentInputView() inputView.delegate = self return inputView }() fileprivate var selectRow: IndexPath? init(frame: CGRect) { super.init(frame: frame, style: .plain) self.delegate = self self.dataSource = self self.backgroundColor = .clear separatorInset = UIEdgeInsets(top: 0, left: 50, bottom: 0, right: 0) } required init?(coder: NSCoder) { fatalError() } func tapContentBg() { guard let indexPath = selectRow else { return } let model = comments[indexPath.item] let isSelf = indexPath.item % 2 == 0 if !isSelf { self.commentnputView.textView.placeholder = "回复\(model.person):" self.commentnputView.show() } actionDelegate?.contentDidSelected(model, action: .bg(isSelf)) } } extension CommentContentView: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return comments.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = cell(CommentContentCell.self) let model = comments[indexPath.item] cell.comment = model cell.onClick = {[weak self] action in self?.actionDelegate?.contentDidSelected(model, action: action) } cell.onTextClick = {[weak self] in self?.selectRow = indexPath self?.tapContentBg() } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let model = comments[indexPath.item] return CommentContentCell.getHeight(model) } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) selectRow = indexPath tapContentBg() } } extension CommentContentView: CommentInputViewDelegate { func onTopChanged(_ top: CGFloat) { guard let indexPath = selectRow, var rect = self.actionDelegate?.commentRect() else { return } var height: CGFloat = 0 for idx in 0...indexPath.item { height += CommentContentCell.getHeight(self.comments[idx]) } rect.origin.y += height rect.size.height = 10 commentnputView.scrollForComment(rect) } func onTextChanged(_ text: String) { print("comment draft: \(text)") guard let indexPath = selectRow else { return } let model = comments[indexPath.item] actionDelegate?.contentDidSelected(model, action: .commentDraft(text)) } func onSend(_ text: String) { guard let indexPath = selectRow, !text.isEmpty else { return } let model = comments[indexPath.item] actionDelegate?.contentDidSelected(model, action: .comment(text)) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/Cell/CommentInputView.swift ================================================ // // CommentInputView.swift // JetChat // // Created by Jett on 2020/5/12. // Copyright © 2022 Jett. All rights reserved. // import Foundation protocol CommentInputViewDelegate: NSObjectProtocol { /// 容器高度变化通知 func onTopChanged(_ top: CGFloat) -> Void /// 输入文本 func onTextChanged(_ text: String) -> Void /// 点击发送 func onSend(_ text: String) -> Void } class CommentInputView: UIView { fileprivate(set) lazy var contentView: UIView = { let v = UIView() v.frame = CGRect(x: 0, y: kScreenH, width: kScreenW, height: contentMinHeight) v.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V13 } let layer = CALayer() layer.backgroundColor = UIColor.lightGray.cgColor layer.frame = CGRect(x: 0, y: 0, width: kScreenW, height: 0.5) v.layer.addSublayer(layer) return v }() /// 容器的最小高度 fileprivate let contentMinHeight: CGFloat = 50 /// 容器最大高度 fileprivate let contentMaxHeight: CGFloat = 120 /// 容器减去输入框的高度 fileprivate let surplusHeight: CGFloat = 15 /// // 记录上一次容器高度 fileprivate var previousCtHeight: CGFloat = 0 /// 记录容器高度 fileprivate var ctHeight: CGFloat = 0 fileprivate(set) var keyboardHeight: CGFloat = 0 /// 容器高度 fileprivate(set) var ctTop: CGFloat = 0 { didSet { delegate?.onTopChanged(ctTop) } } weak var delegate: CommentInputViewDelegate? fileprivate(set) lazy var textView: FYTextView = { let v = FYTextView() v.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V12 } v.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } v.placeholder = "评论".rLocalized() v.lineBreak = false v.returnKeyType = .send v.enablesReturnKeyAutomatically = true v.showsVerticalScrollIndicator = false v.showsHorizontalScrollIndicator = false v.layer.cornerRadius = 5 v.layer.masksToBounds = true v.font = UIFont.systemFont(ofSize: 16) v.frame = CGRect(x: 15, y: surplusHeight/2, width: kScreenW-30, height: contentMinHeight-surplusHeight) return v }() init() { super.init(frame: UIScreen.main.bounds) setup() } func show() { UIApplication.shared.keyWindow?.addSubview(self) textView.becomeFirstResponder() } func dismiss() { textView.resignFirstResponder() removeFromSuperview() } override func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let currentPoint = touches.first?.location(in: superview) else { return } if !contentView.frame.contains(currentPoint) { dismiss() } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } /// 滚动collectionview func scrollForComment(_ rect: CGRect) { if keyboardHeight > 0 { let offset = rect.maxY - ctTop NotificationCenter.default.post(name: NSNotification.Name.list.contentOffset, object: offset) } } } fileprivate extension CommentInputView { func setup() { addSubview(contentView) contentView.addSubview(textView) textView.onKeyAction = {[weak self] action in guard let `self` = self else { return } switch action { case .keyboard(let rect, let duration): self.updateTop(rect: rect, duration: duration) case .change(_): self.updateHeight(self.textView.autoHeight) self.delegate?.onTextChanged(self.textView.text) case .done: self.dismiss() self.delegate?.onSend(self.textView.text) default: break } } } func updateTop(rect: CGRect, duration: Double) { var keyboardH: CGFloat = 0 if rect.origin.y == UIScreen.main.bounds.height { keyboardH = 0 } else { keyboardH = rect.size.height } keyboardHeight = keyboardH // 容器的top var top: CGFloat = 0 if keyboardH > 0 { top = kScreenH - contentView.frame.height - keyboardHeight } else { top = kScreenH } if ctTop == top { return } ctTop = top UIView.animate(withDuration: duration, animations: { self.contentView.frame.origin.y = top }) { finished in if keyboardH == 0 { self.textView.text = nil self.updateHeight(self.textView.autoHeight) self.removeFromSuperview() } } } func updateHeight(_ height: CGFloat) { var ctHeight = height + surplusHeight if ctHeight < contentMinHeight || textView.text.count == 0 { ctHeight = contentMinHeight } if ctHeight > contentMaxHeight { ctHeight = contentMaxHeight } if ctHeight == previousCtHeight { return } previousCtHeight = ctHeight self.ctHeight = ctHeight ctTop = kScreenH - ctHeight - keyboardHeight UIView.animate(withDuration: 0.25) { self.contentView.frame.size.height = ctHeight self.contentView.frame.origin.y = self.ctTop self.textView.frame.size.height = ctHeight - self.surplusHeight } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/Cell/CommentThumbView.swift ================================================ // // CommentThumbView.swift // JetChat // // Created by Jett on 2020/5/11. // Copyright © 2022 Jett. All rights reserved. // import Foundation /// 点赞头像 class CommentThumbView: NineImageView { static let itemWidth: CGFloat = (kScreenW - 30 - 16*2) / 7 var onClick: ((Int)->Void)? override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.cell(NineImageViewCell.self, indexPath: indexPath) cell.imageView.setImageWithURL(images[indexPath.item]) if isRounds { cell.imageView.layer.cornerRadius = 5 cell.imageView.layer.masksToBounds = true } return cell } override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: CommentThumbView.itemWidth, height: CommentThumbView.itemWidth) } override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { print("点赞头像 \(indexPath)") onClick?(indexPath.item) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/Cell/MomentBottomCell.swift ================================================ // // MomentBottomCell.swift // JetChat // // Created by Jett on 2020/4/15. // Copyright © 2022 Jett. All rights reserved. // import Foundation import IGListDiffKit enum MomentBottomAction { /// 删除 case delete /// 点赞/取消 case thumbup /// 评论 case comment(String) /// 草稿 case commentDraft(String) } class MomentBottomCell: UICollectionViewCell { fileprivate lazy var timeLb: UILabel = { let lb = UILabel() lb.sizeToFit() lb.theme.textColor = themed{ $0.FYColor_Main_TextColor_V4 } lb.font = UIFont.systemFont(ofSize: 13) return lb }() fileprivate lazy var deleteBtn: UIButton = { let btn = UIButton(type: .custom) btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.setTitleColor(.blue, for: .normal) btn.theme.titleColor(from: themed { $0.FYColor_Main_TextColor_V12 }, for: .normal) btn.setTitle("删除".rLocalized(), for: .normal) btn.addTarget(self, action: #selector(click(_:)), for: .touchUpInside) btn.tag = 1 return btn }() fileprivate lazy var moreBtn: UIButton = { let btn = UIButton(type: .custom) btn.frame = CGRect(x: kScreenW-MomentHeaderCell.padding-30, y: 5, width: 30, height: 20) btn.titleLabel?.font = UIFont.systemFont(ofSize: 20) btn.theme.titleColor(from: themed { $0.FYColor_Main_TextColor_V2 }, for: .normal) btn.setTitle("··", for: .normal) btn.backgroundColor = .groupTableViewBackground btn.layer.cornerRadius = 2 btn.layer.masksToBounds = true btn.addTarget(self, action: #selector(click(_:)), for: .touchUpInside) return btn }() var onClick: ((MomentBottomAction)->Void)? /// section的顶部cell,即头像部分 var onRelativeRect: (()->CGRect)? override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } fileprivate lazy var commentnputView: CommentInputView = { let inputView = CommentInputView() inputView.delegate = self return inputView }() var viewModel: FYMomentInfo? } fileprivate extension MomentBottomCell { func setup() { self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } addSubview(timeLb) addSubview(deleteBtn) addSubview(moreBtn) timeLb.translatesAutoresizingMaskIntoConstraints = false addConstraint(NSLayoutConstraint(item: timeLb, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: MomentHeaderCell.contentLeft)) addConstraint(NSLayoutConstraint(item: timeLb, attribute: .centerY, relatedBy: .equal, toItem: moreBtn, attribute: .centerY, multiplier: 1, constant: 0)) deleteBtn.translatesAutoresizingMaskIntoConstraints = false addConstraint(NSLayoutConstraint(item: deleteBtn, attribute: .leading, relatedBy: .equal, toItem: timeLb, attribute: .trailing, multiplier: 1, constant: 10)) addConstraint(NSLayoutConstraint(item: deleteBtn, attribute: .centerY, relatedBy: .equal, toItem: timeLb, attribute: .centerY, multiplier: 1, constant: 0)) } @objc func click(_ btn: UIButton) { switch btn.tag { case 0: // more OperateMenuView.show(self.moreBtn, isLiked: false, canComment: true) {[weak self] idx in guard let `self` = self else { return } if idx == 0 { self.onClick?(.thumbup) }else { self.commentnputView.show() } } case 1: // delete self.onClick?(.delete) default: break } } } extension MomentBottomCell: ListBindable { func bindViewModel(_ viewModel: Any) { guard let viewModel = viewModel as? FYMomentInfo else { return } self.viewModel = viewModel timeLb.text = viewModel.publicTime timeLb.sizeToFit() deleteBtn.isHidden = false } } extension MomentBottomCell: CommentInputViewDelegate { func onTopChanged(_ top: CGFloat) { if let onRelativeRect = onRelativeRect { commentnputView.scrollForComment(onRelativeRect()) } } func onTextChanged(_ text: String) { print("comment draft: \(text)") self.onClick?(.commentDraft(text)) } func onSend(_ text: String) { if !text.isEmpty { self.onClick?(.comment(text)) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/Cell/MomentCommentCell.swift ================================================ // // MomentCommentCell.swift // JetChat // // Created by Jett on 2020/4/17. // Copyright © 2022 Jett. All rights reserved. // import Foundation protocol MomentCommentDelegate: NSObjectProtocol { func contentDidSelected(_ model: FYCommentInfo, action: CommentContentClickAction) func thumbDidSelected(_ model: FYCommentInfo) /// 只用于commentView获取当前cell的frame func commentRect() -> CGRect } extension MomentCommentDelegate { func commentRect() -> CGRect { return .zero } } class MomentCommentCell: UICollectionViewCell { fileprivate lazy var containerView: UIView = { let v = UIView() let x = MomentHeaderCell.padding v.frame = CGRect(x: x, y: 10, width: bounds.width-x*2, height: 0) v.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V1 } v.layer.cornerRadius = 5 v.layer.masksToBounds = true return v }() fileprivate lazy var thumbView: NineImageView = { let view = CommentThumbView(frame: .zero) view.onClick = {[weak self] idx in if let model = self?.viewModel?.comments[idx] { self?.thumbDidSelected(model) } } return view }() fileprivate lazy var thumbIcon: UIImageView = { let iv = UIImageView(image: R.image.ic_star_selected()) iv.frame = CGRect(x: 10, y: 10, width: 14, height: 12) return iv }() fileprivate lazy var commentIcon: UIImageView = { let iv = UIImageView(image: R.image.ic_comment_selected()) iv.frame = CGRect(x: 10, y: 10, width: 14, height: 12) return iv }() fileprivate lazy var commentView: CommentContentView = { let view = CommentContentView(frame: .zero) view.actionDelegate = self return view }() fileprivate lazy var divisionV: UIView = { let v = UIView() v.frame = CGRect(x: 0, y: 0, width: containerView.bounds.width, height: 0.65) v.theme.backgroundColor = themed { $0.FYColor_BorderColor_V2 } return v }() fileprivate lazy var separatorV: UIView = { let v = UIView(frame: bounds) v.theme.backgroundColor = themed { $0.FYColor_BorderColor_V1 } return v }() weak var actionDelegate: MomentCommentDelegate? override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } func setup() { self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } addSubview(containerView) containerView.addSubview(thumbIcon) containerView.addSubview(thumbView) containerView.addSubview(divisionV) containerView.addSubview(commentIcon) containerView.addSubview(commentView) addSubview(separatorV) // 离屏渲染 + 栅格化 layer.drawsAsynchronously = true layer.shouldRasterize = true layer.rasterizationScale = UIScreen.main.scale } var viewModel: FYMomentInfo? } extension MomentCommentCell: ListBindable { func bindViewModel(_ viewModel: Any) { guard let viewModel = viewModel as? FYMomentInfo else { return } self.viewModel = viewModel containerView.frame.size.height = viewModel.contentHeight let minX = thumbIcon.frame.maxX + thumbIcon.frame.minX thumbView.frame = CGRect(x: minX, y: 5, width: containerView.bounds.width-minX, height: viewModel.thumbsHeight-10) thumbView.isRounds = true thumbView.images = viewModel.comments.map({$0.avatar_url}) divisionV.frame.origin.y = thumbView.frame.maxY+5-divisionV.frame.height divisionV.isHidden = viewModel.comments.count == 0 if viewModel.comments.count == 0 { thumbIcon.frame.size.height = 0 divisionV.frame.origin.y = 0 } commentIcon.frame.origin.y = thumbIcon.frame.minY + divisionV.frame.maxY commentView.frame = CGRect(x: minX, y: divisionV.frame.maxY, width: thumbView.bounds.width, height: viewModel.commentHeight) commentView.comments = viewModel.comments separatorV.frame = CGRect(x: 0, y: bounds.height-1, width: bounds.width, height: 1) } } extension MomentCommentCell: MomentCommentDelegate { func contentDidSelected(_ model: FYCommentInfo, action: CommentContentClickAction) { actionDelegate?.contentDidSelected(model, action: action) } func thumbDidSelected(_ model: FYCommentInfo) { actionDelegate?.thumbDidSelected(model) } func commentRect() -> CGRect { var rect = frame rect.origin.y += commentView.frame.minY return rect } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/Cell/MomentHeaderCell.swift ================================================ // // MomentHeaderCell.swift // JetChat // // Created by Jett on 2022/4/14. // Copyright © 2022 Jett. All rights reserved. // import Kingfisher import YBImageBrowser import UIKit /// 多张图片显示 class MomentHeaderCell: UICollectionViewCell { var onClick: ((Int)->Void)? static let padding: CGFloat = 16 static let contentLeft = padding+10+50 static let contentW = kScreenW-padding-contentLeft fileprivate lazy var avatarImageView: UIImageView = { let iv = UIImageView() iv.frame = CGRect(x: MomentHeaderCell.padding, y: 10, width: 50, height: 50) iv.layer.cornerRadius = 10 iv.layer.masksToBounds = true iv.isUserInteractionEnabled = true let tap = UITapGestureRecognizer(target: self, action: #selector(previewImage(_:))) iv.addGestureRecognizer(tap) return iv }() fileprivate lazy var usernameLb: UILabel = { let lb = UILabel() lb.frame = CGRect(x: avatarImageView.frame.maxX+10, y: avatarImageView.frame.minY+2, width: MomentHeaderCell.contentW, height: 20) lb.theme.textColor = themed { $0.FYColor_Main_TextColor_V2 } lb.font = UIFont.boldSystemFont(ofSize: 17) return lb }() fileprivate lazy var contentLb: FYLabel = { let lb = FYLabel() lb.frame = CGRect(x: usernameLb.frame.minX, y: usernameLb.frame.maxY+5, width: MomentHeaderCell.contentW, height: 0) lb.font = UIFont.systemFont(ofSize: 17) lb.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } lb.numberOfLines = 0 lb.showFavor = {[weak self] in guard let self = self else { return } } return lb }() fileprivate lazy var expendBtn: UIButton = { let btn = UIButton(type: .custom) btn.setTitle("展开".rLocalized(), for: .normal) btn.setTitle("收起".rLocalized(), for: .selected) btn.setTitleColor(.Color_Blue_1890FF, for: .normal) btn.setTitleColor(.Color_Blue_1890FF, for: .selected) btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.addTarget(self, action: #selector(click(_:)), for: .touchUpInside) btn.sizeToFit() btn.isHidden = true return btn }() fileprivate lazy var nineImageView: NineImageView = { let view = NineImageView(frame: .zero) view.frame = contentLb.frame view.frame.size.width -= 50 view.onPreviewImages = { [weak self] images, indexPath, sourceView in self?.browserImages(images, index: indexPath.item, sourceView: sourceView) } return view }() override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } var viewModel: FYMomentInfo? func browserImages(_ images: [String], index: Int, sourceView: UIView? = nil) { guard images.count > 0 else { return } var imageDatas: [YBIBImageData] = [] for imageUrl in images { let data = YBIBImageData() data.imageURL = URL(string: imageUrl) imageDatas.append(data) } let browser = YBImageBrowser() browser.dataSourceArray = imageDatas browser.currentPage = index browser.show() } } extension MomentHeaderCell { func setup() { self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } addSubview(avatarImageView) addSubview(usernameLb) addSubview(contentLb) addSubview(expendBtn) addSubview(nineImageView) setupLabel() } func setupLabel() { contentLb.enabledTypes = [.URL, .phone] contentLb.handleURLTap { (text) in NotificationCenter.default.post(name: NSNotification.Name.list.openURL, object: URL(string: text)) } contentLb.handlePhoneTap { (phone) in if let url = NSURL(string: "tel://\(phone)") as? URL { UIApplication.shared.open(url, options: [:], completionHandler: nil) } } } @objc func previewImage(_ ges: UIGestureRecognizer) { switch ges.view?.tag { case 0: NotificationCenter.default.post(name: NSNotification.Name.list.push, object: viewModel?.userInfo) default: break } } @objc func click(_ btn: UIButton) { onClick?(btn.tag) } } extension MomentHeaderCell: ListBindable { func bindViewModel(_ viewModel: Any) { guard let viewModel = viewModel as? FYMomentInfo else { return } self.viewModel = viewModel if (viewModel.avatar.length > 0) { avatarImageView.setImageWithURL(viewModel.avatar, placeholder: R.image.ic_avatar_placeholder()!) } if (viewModel.userName.length > 0) { usernameLb.text = viewModel.userName } if (viewModel.content.length > 0) { contentLb.text = viewModel.content } contentLb.frame.size.height = viewModel.textHeight if viewModel.isNeedExpend { expendBtn.isSelected = viewModel.isTextExpend contentLb.numberOfLines = viewModel.isTextExpend ? 0 : 3 expendBtn.frame.origin = CGPoint(x: contentLb.frame.minX, y: contentLb.frame.maxY) } expendBtn.isHidden = !viewModel.isNeedExpend // 不能通过if判断切换同一位置的显示视图 if viewModel.images.count > 0 { let maxY = contentLb.frame.maxY + 10 + (!expendBtn.isHidden ? expendBtn.frame.height : 0) nineImageView.images = viewModel.images nineImageView.frame.origin.y = maxY nineImageView.frame.size.height = viewModel.momentPicsHeight(viewModel.images.count) }else { nineImageView.frame.size.height = 0 } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/Cell/MomentHeaderImageCell.swift ================================================ // // MomentHeaderImageCell.swift // JetChat // // Created by Jett on 2022/4/16. // Copyright © 2022 Jett. All rights reserved. // import Kingfisher import YBImageBrowser /// 单张图片显示 class MomentHeaderImageCell: UICollectionViewCell { static let padding: CGFloat = 16 static let contentLeft = padding + 60 static let contentW = kScreenW-padding-contentLeft var onClick: ((Int)->Void)? fileprivate lazy var avatarImageView: UIImageView = { let iv = UIImageView() iv.frame = CGRect(x: MomentHeaderCell.padding, y: 10, width: 50, height: 50) iv.layer.cornerRadius = 10 iv.layer.masksToBounds = true iv.isUserInteractionEnabled = true let tap = UITapGestureRecognizer(target: self, action: #selector(previewImage(_:))) iv.addGestureRecognizer(tap) return iv }() fileprivate lazy var usernameLb: UILabel = { let lb = UILabel() lb.frame = CGRect(x: avatarImageView.frame.maxX+10, y: avatarImageView.frame.minY+2, width: MomentHeaderCell.contentW, height: 20) lb.theme.textColor = themed{ $0.FYColor_Main_TextColor_V2 } lb.font = UIFont.boldSystemFont(ofSize: 17) return lb }() fileprivate lazy var contentLb: FYLabel = { let lb = FYLabel() lb.frame = CGRect(x: usernameLb.frame.minX, y: usernameLb.frame.maxY+5, width: MomentHeaderCell.contentW, height: 0) lb.font = UIFont.systemFont(ofSize: 17) lb.theme.textColor = themed{ $0.FYColor_Main_TextColor_V1 } lb.numberOfLines = 0 lb.showFavor = {[weak self] in guard let self = self else { return } } return lb }() /// 单张图片 fileprivate lazy var singleImageView: UIImageView = { let iv = UIImageView() iv.frame = contentLb.frame iv.clipsToBounds = true iv.autoresizesSubviews = true iv.clearsContextBeforeDrawing = true iv.isUserInteractionEnabled = true let tap = UITapGestureRecognizer(target: self, action: #selector(previewImage(_:))) iv.addGestureRecognizer(tap) iv.tag = 10 return iv }() fileprivate lazy var expendBtn: UIButton = { let btn = UIButton(type: .custom) btn.setTitle("展开", for: .normal) btn.setTitle("收起", for: .selected) btn.setTitleColor(.Color_Blue_1890FF, for: .normal) btn.setTitleColor(.Color_Blue_1890FF, for: .selected) btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.addTarget(self, action: #selector(click(_:)), for: .touchUpInside) btn.sizeToFit() btn.isHidden = true return btn }() override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } var viewModel: FYMomentInfo? } extension MomentHeaderImageCell { func setup() { self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } addSubview(avatarImageView) addSubview(usernameLb) addSubview(contentLb) addSubview(singleImageView) addSubview(expendBtn) setLabel() } func setLabel() { contentLb.enabledTypes = [.URL, .phone] contentLb.handleURLTap { (text) in NotificationCenter.default.post(name: NSNotification.Name.list.openURL, object: URL(string: text)) } contentLb.handlePhoneTap { (phone) in UIApplication.shared.openURL(URL(string: "tel://\(phone)")!) } } @objc func previewImage(_ ges: UIGestureRecognizer) { switch ges.view?.tag { case 0: NotificationCenter.default.post(name: NSNotification.Name.list.push, object: viewModel?.userInfo) case 10: print("预览图片") if let images = self.viewModel?.images { singleBrowserImages(images, index: 0, sourceView: singleImageView) } default: break } } @objc func click(_ btn: UIButton) { onClick?(btn.tag) } func singleBrowserImages(_ images: [String], index: Int, sourceView: UIView?) { guard images.count > 0 else { return } guard let projectiveView = sourceView else { return } var imageDatas: [YBIBImageData] = [] for imageUrl in images { let data = YBIBImageData() data.imageURL = URL(string: imageUrl) data.projectiveView = projectiveView imageDatas.append(data) } let browser = YBImageBrowser() browser.dataSourceArray = imageDatas browser.currentPage = index browser.show() } } extension MomentHeaderImageCell: ListBindable { func bindViewModel(_ viewModel: Any) { guard let viewModel = viewModel as? FYMomentInfo else { return } self.viewModel = viewModel avatarImageView.setImageWithURL(viewModel.avatar, placeholder: R.image.ic_avatar_placeholder()!) usernameLb.text = viewModel.userName contentLb.text = viewModel.content contentLb.frame.size.height = viewModel.textHeight if viewModel.isNeedExpend { expendBtn.isSelected = viewModel.isTextExpend contentLb.numberOfLines = viewModel.isTextExpend ? 0 : 3 expendBtn.frame.origin = CGPoint(x: contentLb.frame.minX, y: contentLb.frame.maxY) } expendBtn.isHidden = !viewModel.isNeedExpend if viewModel.images.count > 0 { let maxY = contentLb.frame.maxY + 10 + (!expendBtn.isHidden ? expendBtn.frame.height : 0) singleImageView.setImageWithURL(viewModel.images[0], placeholder:R.image.ic_placeholder()!) singleImageView.frame.origin.y = maxY singleImageView.frame.size.height = viewModel.momentPicsHeight(viewModel.images.count) singleImageView.frame.size.width = singleImageView.frame.size.height }else { singleImageView.frame.size.height = 0 } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/Cell/MomentLocationCell.swift ================================================ // // MomentLocationCell.swift // JetChat // // Created by Jett on 2020/4/15. // Copyright © 2022 Jett. All rights reserved. // import Foundation class MomentLocationCell: UICollectionViewCell { var viewModel: FYMomentInfo? fileprivate lazy var locationBtn: UIButton = { let btn = UIButton(type: .custom) btn.frame.origin = CGPoint(x: MomentHeaderCell.contentLeft, y: 0) btn.theme.titleColor(from: themed { $0.FYColor_Main_TextColor_V4 }, for: .normal) btn.titleLabel?.font = UIFont.systemFont(ofSize: 13) btn.sizeToFit() btn.addTarget(self, action: #selector(click(_:)), for: .touchUpInside) return btn }() override init(frame: CGRect) { super.init(frame: frame) makeUI() } required init?(coder: NSCoder) { super.init(coder: coder) makeUI() } func makeUI() { self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } addSubview(locationBtn) } @objc func click(_ btn: UIButton) { NotificationCenter.default.post(name: NSNotification.Name.list.location, object: viewModel) } } extension MomentLocationCell: ListBindable { func bindViewModel(_ viewModel: Any) { guard let viewModel = viewModel as? FYMomentInfo else { return } self.viewModel = viewModel locationBtn.setTitle(viewModel.location, for: .normal) locationBtn.sizeToFit() } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/Cell/MomentTopCell.swift ================================================ // // MomentTopCell.swift // JetChat // // Created by Jett on 2022/4/16. // Copyright © 2022 Jett. All rights reserved. // import Foundation private let topOffset: CGFloat = 60 private let bottomIndent: CGFloat = 40 private let avatorW: CGFloat = 80 private let space: CGFloat = 20 /// 顶部视图 class MomentTopCell: UICollectionViewCell { fileprivate lazy var bgImageView: UIImageView = { let iv = UIImageView() iv.frame = CGRect(x: 0, y: 0, width: kScreenW, height: bounds.size.height - bottomIndent) iv.contentMode = .scaleAspectFill return iv }() fileprivate lazy var avatarImageView: UIImageView = { let iv = UIImageView() iv.frame = CGRect(x: kScreenW - avatorW - 16, y: bgImageView.frame.maxY, width: avatorW, height: avatorW) iv.layer.cornerRadius = 10 iv.layer.masksToBounds = true return iv }() fileprivate lazy var userNameLabel: UILabel = { let lb = UILabel() lb.frame = CGRect(x: 0, y: avatarImageView.frame.minY + space, width: avatarImageView.frame.minX - space, height: 30) lb.textAlignment = .right lb.theme.textColor = themed { $0.FYColor_Placeholder_Color_V3 } lb.font = .PingFangSemibold(18) return lb }() override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } func setup() { theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V5 } addSubview(bgImageView) addSubview(avatarImageView) addSubview(userNameLabel) bgImageView.frame.origin.y -= topOffset avatarImageView.frame.origin.y -= topOffset userNameLabel.frame.origin.y -= topOffset bgImageView.frame.size.height += topOffset } } extension MomentTopCell: ListBindable { func bindViewModel(_ viewModel: Any) { guard let viewModel = viewModel as? FYMomentInfo, let info = viewModel.userInfo else { return } bgImageView.setImageWithURL(info.background_url, placeholder: R.image.ic_placeholder()!) avatarImageView.setImageWithURL(info.avatar_url , placeholder: R.image.ic_avatar_placeholder()!) userNameLabel.text = info.user_name } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/FYMomentNavBar.swift ================================================ // // FYMomentNavBar.swift // FY-JetChat // // Created by Jett on 2022/4/28. // Copyright © 2022 Jett. All rights reserved. // import UIKit class FYMomentNavBar: UIView { var onClick: ((Int)->Void)? lazy var navBarView: UIView = { let v = UIView(frame: bounds) v.backgroundColor = UIColor(red: 239, green: 239, blue: 239, alpha: 1.0) v.alpha = 0 return v }() lazy var titleLabel: UILabel = { let label = UILabel() label.text = "朋友圈".rLocalized() label.alpha = 0 label.font = UIFont.boldSystemFont(ofSize: 17) label.textAlignment = .center return label }() lazy var backBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "nav_back_white"), for: .normal) btn.addTarget(self, action: #selector(clickAction(_:)), for: .touchUpInside) btn.tag = 100; return btn }() lazy var camareBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "nav_camera_white"), for: .normal) btn.addTarget(self, action: #selector(clickAction(_:)), for: .touchUpInside) btn.tag = 200 return btn }() var isScrollUp: Bool = false { didSet { let backImage = isScrollUp ? "nav_back_black" : "nav_back_white" let cameraImage = isScrollUp ? "nav_camera_black" : "nav_camera_white" backBtn.setImage(UIImage(named: backImage), for: .normal) camareBtn.setImage(UIImage(named: cameraImage), for: .normal) } } override init(frame: CGRect) { super.init(frame: frame) makeUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } fileprivate extension FYMomentNavBar { func makeUI() { addSubview(navBarView) addSubview(titleLabel) addSubview(backBtn) addSubview(camareBtn) navBarView.snp.makeConstraints { make in make.edges.equalTo(self) } titleLabel.snp.makeConstraints { make in make.top.equalTo(self).offset(44) make.centerX.equalTo(self); } backBtn.snp.makeConstraints { make in make.left.equalTo(self).offset(20) make.top.equalTo(self).offset(44) make.width.height.equalTo(30) } camareBtn.snp.makeConstraints { make in make.right.equalTo(self).offset(-20) make.top.equalTo(self.backBtn) make.width.height.equalTo(30) } } @objc func clickAction(_ btn: UIButton) { onClick?(btn.tag) } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/MomentLabel/FYLabel.swift ================================================ // // FYLabel.swift // // Created by Jett on 2022/04/28. // Copyright 2022 Jett. All rights reserved. // import UIKit typealias ElementTuple = (range: NSRange, element: FYElements, type: FYLabelType) open class FYLabel: UILabel { override public init(frame: CGRect) { super.init(frame: frame) setupLabel() } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupLabel() } open override func awakeFromNib() { super.awakeFromNib() updateTextStorage() } // MARK: 公用 属性 open var enabledTypes: [FYLabelType] = [.URL, .phone, .hashtag, .mention] open var hashtagColor : UIColor = .blue{ didSet {updateTextStorage(updateString: false)} } open var hashtagSelectColor : UIColor = UIColor.blue.withAlphaComponent(0.5) { didSet {updateTextStorage(updateString: false)} } open var mentionColor : UIColor = .blue{ didSet {updateTextStorage(updateString: false)} } open var mentionSelectColor : UIColor = UIColor.blue.withAlphaComponent(0.5){ didSet {updateTextStorage(updateString: false)} } open var URLColor : UIColor = .blue{ didSet {updateTextStorage(updateString: false)} } open var URLSelectColor : UIColor = UIColor.blue.withAlphaComponent(0.5){ didSet {updateTextStorage(updateString: false)} } open var customColor : [FYLabelType : UIColor] = [:] { didSet {updateTextStorage(updateString: false)} } open var customSelectColor : [FYLabelType : UIColor] = [:] { didSet {updateTextStorage(updateString: false)} } // MARK: 重写 override open var text: String? { didSet {updateTextStorage()} } override open var attributedText: NSAttributedString?{ didSet {updateTextStorage()} } override open var font: UIFont! { didSet {updateTextStorage(updateString: false)} } override open var textColor: UIColor! { didSet {updateTextStorage(updateString: false)} } override open var textAlignment: NSTextAlignment { didSet {updateTextStorage(updateString: false)} } open override var numberOfLines: Int { didSet { textContainer.maximumNumberOfLines = numberOfLines } } /// 行间距 open var lineSpacing : CGFloat = 0 { didSet {updateTextStorage(updateString: false)} } /// 段间距 open var paragraphSpacing : CGFloat = 0 { didSet {updateTextStorage(updateString: false)} } // MARK: 私有属性 /* NSTextStorage保存并管理UITextView要展示的文字内容,该类是NSMutableAttributedString的子类,由于可以灵活地往文字添加或修改属性,所以非常适用于保存并修改文字属性。 NSLayoutManager用于管理NSTextStorage其中的文字内容的排版布局。 NSTextContainer则定义了一个矩形区域用于存放已经进行了排版并设置好属性的文字。 以上三者是相互包含相互作用的层次关系。 NSTextStorage -> NSLayoutManager -> NSTextContainer */ fileprivate var textStorage : NSTextStorage = NSTextStorage() fileprivate var layoutManager : NSLayoutManager = NSLayoutManager() fileprivate var textContainer : NSTextContainer = NSTextContainer() fileprivate lazy var elementDict = [FYLabelType: [ElementTuple]]() fileprivate var selectedElementTuple : ElementTuple? public typealias LabelCallBack = (String) -> () fileprivate var hashNormalHandler: LabelCallBack? fileprivate var hashtagTapHandler: LabelCallBack? fileprivate var mentionTapHandler: LabelCallBack? fileprivate var urlTapHandler: LabelCallBack? fileprivate var phoneTapHandler: LabelCallBack? fileprivate var customHandler: [FYLabelType : LabelCallBack] = [:] // MARK: 公用 方法 /// 文本点击事件 (响应除`enabledTypes`属性设置外的点击事件 open func handleNormalTap(_ handler: @escaping LabelCallBack) { hashNormalHandler = handler } /// 标签点击事件 open func handleHashtagTap(_ handler: @escaping LabelCallBack) { hashtagTapHandler = handler } /// 提醒点击事件 open func handleMentionTap(_ handler: @escaping LabelCallBack) { mentionTapHandler = handler } /// URL 点击事件 open func handleURLTap(_ handler: @escaping LabelCallBack) { urlTapHandler = handler } /// URL 点击事件 open func handlePhoneTap(_ handler: @escaping LabelCallBack) { phoneTapHandler = handler } /// 自定义 点击事件 open func handleCustomTap(_ type: FYLabelType, handler: @escaping LabelCallBack) { customHandler[type] = handler } fileprivate var drawBeginY : CGFloat = 0 override open func drawText(in rect: CGRect) { let range = NSRange(location: 0, length: textStorage.length) textContainer.size = rect.size let newRect = layoutManager.usedRect(for: textContainer) drawBeginY = (rect.size.height - newRect.size.height) / 2 let newOrigin = CGPoint(x: rect.origin.x, y: drawBeginY) layoutManager.drawGlyphs(forGlyphRange: range, at: newOrigin) } override open func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else {return} onTouch(touch) } override open func touchesMoved(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else {return} onTouch(touch) } override open func touchesEnded(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else {return} onTouch(touch) } open override var canBecomeFirstResponder: Bool { return true } open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { return performAction(action, sender: sender) } /// 是否允许长按复制 var enableLongPress: Bool? { didSet { isUserInteractionEnabled = enableLongPress ?? false if oldValue != nil && enableLongPress != true { removeGestureRecognizer(longPress) } else if oldValue == nil && enableLongPress == true { addGestureRecognizer(longPress) } } } fileprivate lazy var longPress: UILongPressGestureRecognizer = { return UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:))) }() } // MARK: - init fileprivate extension FYLabel { func setupLabel() { numberOfLines = 1 textStorage.addLayoutManager(layoutManager) layoutManager.addTextContainer(textContainer) textContainer.maximumNumberOfLines = numberOfLines textContainer.lineFragmentPadding = 0 textContainer.lineBreakMode = .byWordWrapping isUserInteractionEnabled = true enableLongPress = true } func updateTextStorage(updateString: Bool = true) { guard let attributedText = attributedText else {return} let mutAttrString = addOrdinarilyAttributes(attributedText) if updateString { clearElementTupleDict() getElementTupleDict(mutAttrString) } addPatternAttributes(mutAttrString) textStorage.setAttributedString(mutAttrString) setNeedsDisplay() } func clearElementTupleDict() { for (type, _) in elementDict { elementDict[type]?.removeAll() } } /// 核心方法,配置elementDict func getElementTupleDict(_ mutAttrString : NSMutableAttributedString) { let textString = mutAttrString.string let range = NSRange(location: 0, length: textString.count) for type in enabledTypes { elementDict[type] = creatElementTupleArr(type: type, from: textString, range: range) } } // 给所有字符串添加文字属性 func addOrdinarilyAttributes(_ attrString : NSAttributedString) -> NSMutableAttributedString { let mutAttrString = NSMutableAttributedString(attributedString: attrString) if mutAttrString.string.count == 0 { return mutAttrString } var range = NSRange(location: 0, length: 0) //给指定索引的字符返回属性 var attributes = mutAttrString.attributes(at: 0, effectiveRange: &range) attributes[NSAttributedString.Key.font] = font attributes[NSAttributedString.Key.foregroundColor] = textColor let paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() paragraphStyle.lineBreakMode = NSLineBreakMode.byWordWrapping paragraphStyle.alignment = textAlignment paragraphStyle.lineSpacing = lineSpacing paragraphStyle.paragraphSpacing = paragraphSpacing attributes[NSAttributedString.Key.paragraphStyle] = paragraphStyle mutAttrString.setAttributes(attributes, range: range) return mutAttrString } /// 给目标字符串添加文字属性 func addPatternAttributes(_ mutAttrString : NSMutableAttributedString) { if mutAttrString.string.count == 0 { return } var range = NSRange(location: 0, length: 0) //给指定索引的字符返回属性 var attributes = mutAttrString.attributes(at: 0, effectiveRange: &range) for (type, elements) in elementDict { switch type { case .hashtag: attributes[.foregroundColor] = hashtagColor case .mention: attributes[.foregroundColor] = mentionColor case .URL : attributes[.foregroundColor] = URLColor case .phone : attributes[.foregroundColor] = URLColor case .custom : attributes[.foregroundColor] = customColor[type] ?? textColor } for element in elements { if mutAttrString.string.count < element.range.location + element.range.length { return } mutAttrString.setAttributes(attributes, range: element.range) } } } func creatElementTupleArr(type: FYLabelType, from textString: String, range: NSRange) -> [ElementTuple] { var elementTupleArr = [ElementTuple]() let nsstring = textString as NSString let matches = FYLabelRegex.getMatches(type: type, from: textString, range: range) for match in matches { let range = NSRange(location: match.range.location + type.startIndex, length: match.range.length + type.tenderLength - type.startIndex) let word = nsstring.substring(with: range) guard word.count > 0 else { continue } elementTupleArr.append((range, FYElements.creat(with: type, text: word), type)) } return elementTupleArr } } // MARK: - touch fileprivate extension FYLabel { func updateWhenSelected(_ isSelected :Bool){ guard let elementTuple = selectedElementTuple else {return} //给指定索引的字符返回属性 var attributes = textStorage.attributes(at: 0, effectiveRange: nil) if isSelected { switch elementTuple.type { case .hashtag: attributes[NSAttributedString.Key.foregroundColor] = hashtagSelectColor case .mention: attributes[NSAttributedString.Key.foregroundColor] = mentionSelectColor case .URL : attributes[NSAttributedString.Key.foregroundColor] = URLSelectColor case .phone : attributes[NSAttributedString.Key.foregroundColor] = URLSelectColor case .custom : var color = self.customSelectColor[elementTuple.type] ?? self.customColor[elementTuple.type] color = color ?? textColor attributes[NSAttributedString.Key.foregroundColor] = color } } else{ switch elementTuple.type { case .hashtag: attributes[NSAttributedString.Key.foregroundColor] = hashtagColor case .mention: attributes[NSAttributedString.Key.foregroundColor] = mentionColor case .URL : attributes[NSAttributedString.Key.foregroundColor] = URLColor case .phone : attributes[NSAttributedString.Key.foregroundColor] = URLColor case .custom : attributes[NSAttributedString.Key.foregroundColor] = customColor[elementTuple.type] ?? textColor } } textStorage.addAttributes(attributes, range: elementTuple.range) setNeedsDisplay() } func getSelectElementTuple(_ index: Int) -> ElementTuple? { for elementTuples in elementDict.map({ $0.1 }) { for elementTuple in elementTuples { guard index >= elementTuple.range.location else { continue } guard index < elementTuple.range.location + elementTuple.range.length else { continue } return elementTuple } } return nil } func onTouch(_ touch : UITouch) { var location = touch.location(in: self) location.y -= drawBeginY let textRect = layoutManager.boundingRect(forGlyphRange: NSRange(location: 0, length: textStorage.length), in: textContainer) guard textRect.contains(location) else {return} let index = layoutManager.glyphIndex(for: location, in: textContainer) let elementTuple = getSelectElementTuple(index) switch touch.phase { case .began,.moved: if elementTuple?.range.location != selectedElementTuple?.range.location { updateWhenSelected(false) selectedElementTuple = elementTuple updateWhenSelected(true) } case .ended: guard let elementTuple = elementTuple else { if let text = text { hashNormalHandler?(text) } return } updateWhenSelected(false) switch elementTuple.element { case .hashtag(let hashtag) : didTapHashtag(hashtag) case .mention(let mention) : didTapMention(mention) case .URL(let URL) : didTapURL(URL) case .phone(let phone) : didTapPhone(phone) case .custom(let custom) : didTapCustom(elementTuple.type, custom: custom) } default: break } } /// 点击的是标签 func didTapHashtag(_ hashtagString : String) -> Void { hashtagTapHandler?(hashtagString) } /// 点击的是提醒 func didTapMention(_ mentionString : String) -> Void { mentionTapHandler?(mentionString) } /// 点击的是URL func didTapURL(_ URLString : String) -> Void { urlTapHandler?(URLString) } /// 点击的是phone func didTapPhone(_ phoneString : String) -> Void { phoneTapHandler?(phoneString) } /// 点击的是自定义 func didTapCustom(_ type : FYLabelType,custom : String) -> Void { guard let customHandler = customHandler[type] else {return} customHandler(custom) } } private var showFavorKey: Void? public extension FYLabel { var showFavor: (()->Void)? { set { objc_setAssociatedObject(self, &showFavorKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } get { return objc_getAssociatedObject(self, &showFavorKey) as? (()->Void)? ?? nil } } } // MARK: 手势 fileprivate extension FYLabel { @objc func longPressAction(_ sender: UIGestureRecognizer) { guard sender.state == .began else { return } becomeFirstResponder() let menuController = UIMenuController.shared let copyItem = UIMenuItem(title: "复制", action: #selector(copyText)) var items = [copyItem] if showFavor != nil { items.append(UIMenuItem(title: "收藏", action: #selector(favor))) } menuController.menuItems = items // 设置菜单控制器点击区域为当前控件 bounds menuController.setTargetRect(bounds, in: self) menuController.setMenuVisible(true, animated: true) } @objc func copyText() { UIPasteboard.general.string = self.text } @objc func favor() { showFavor?() } @objc func performAction(_ action: Selector, sender: Any?) -> Bool { switch action { case #selector(copyText), #selector(favor): return true default: return false } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/MomentLabel/FYLabelType.swift ================================================ // // FYLabelType.swift // // Created by Jett on 2022/04/28. // Copyright 2022 Jett. All rights reserved. // import UIKit /* 枚举关联值 */ public enum FYElements{ case hashtag(String) case mention(String) case URL(String) case phone(String) case custom(String) static func creat(with type : FYLabelType, text : String) -> FYElements { switch type { case .hashtag :return hashtag(text) case .mention :return mention(text) case .URL :return URL(text) case .phone :return phone(text) case .custom :return custom(text) } } } public struct FYLabelRegex { static let hashtagPattern = "#.*?#" static let mentionPattern = "@[\\p{L}0-9_]*" static let URLPattern = "[a-zA-Z]*://[a-zA-Z0-9/\\.]*" static let phonePattern = "400[0-9]{7}|1[34578]\\d{9}$|0[0-9]{2,3}-[0-9]{8}" static func getMatches(type: FYLabelType, from textString: String, range: NSRange) -> [NSTextCheckingResult]{ let pattern = type.pattern let regex = try! NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) let matches = regex.matches(in: textString, options: [], range: range) return matches } } public enum FYLabelType { /// #话题# case hashtag /// @用户名 case mention /// URL case URL /// 400正则)|(800正则)|(手机号)|(座机号)| case phone case custom(pattern: String, start: Int, tender: Int) var pattern : String { switch self { case .hashtag : return FYLabelRegex.hashtagPattern case .mention : return FYLabelRegex.mentionPattern case .URL : return FYLabelRegex.URLPattern case .phone : return FYLabelRegex.phonePattern case .custom(let pattern, _, _) : return pattern } } /// 长度减少 var tenderLength: Int { switch self { case .hashtag : return -2 case .mention : return -1 case .URL, .phone : return 0 case .custom(_, _, let tender) : return tender } } /// 相对起始位置 var startIndex: Int { switch self { case .URL, .phone: return 0 case .mention, .hashtag: return 1 case .custom(_, let start, _): return start } } } extension FYLabelType : Hashable, Equatable { public var hashValue : Int { switch self { case .hashtag : return -3 case .mention : return -2 case .URL : return -1 case .phone : return -4 case .custom(let pattern, _, _) : return pattern.hashValue } } public func hash(into hasher: inout Hasher) { switch self { case .hashtag : hasher.combine(-3) case .mention : hasher.combine(-2) case .URL : hasher.combine(-1) case .phone : hasher.combine(-4) case .custom(let pattern, _, _) : hasher.combine(pattern) } } public static func == (lhs: FYLabelType, rhs: FYLabelType) -> Bool { switch (lhs, rhs) { case (.mention, .mention): return true case (.hashtag, .hashtag): return true case (.URL, .URL): return true case (.phone, .phone): return true case (.custom(let pattern1, _, _), .custom(let pattern2, _, _)): return pattern1 == pattern2 default: return false } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/NineImageView.swift ================================================ // // NineImageView.swift // JetChat // // Created by Jett on 2022/4/14. // Copyright © 2022 Jett. All rights reserved. // import Kingfisher import YBImageBrowser import UIKit class NineImageView: UICollectionView { var images = [String]() { didSet { reloadData() } } var isRounds = false init(frame: CGRect) { let layout = UICollectionViewFlowLayout() layout.minimumInteritemSpacing = 5 layout.minimumLineSpacing = 5 super.init(frame: frame, collectionViewLayout: layout) delegate = self dataSource = self backgroundColor = .clear if #available(iOS 11.0, *) { contentInsetAdjustmentBehavior = .never } } required init?(coder: NSCoder) { fatalError() } @objc var onPreviewImages: (([String], IndexPath, UIView)->Void)? } extension NineImageView: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return images.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.cell(NineImageViewCell.self, indexPath: indexPath) cell.imageView.setImageWithURL(images[indexPath.item], placeholder: R.image.ic_placeholder()!) if isRounds { cell.imageView.layer.cornerRadius = 5 cell.imageView.layer.masksToBounds = true } return cell } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { // 不能动态设置size return CGSize(width: mImageW, height: mImageW) } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { print("图片预览\(indexPath)") let cell = collectionView.cell(NineImageViewCell.self, indexPath: indexPath) if images.count > 0 { onPreviewImages?(images, indexPath, cell.imageView) } } } class NineImageViewCell: UICollectionViewCell { lazy var imageView: UIImageView = { let iv = UIImageView() iv.contentMode = .scaleAspectFill iv.clipsToBounds = true iv.autoresizesSubviews = true iv.clearsContextBeforeDrawing = true return iv }() override init(frame: CGRect) { super.init(frame: frame) addSubview(imageView) imageView.frame = bounds } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/OperateMenuView.swift ================================================ // // OperateMenuView.swift // JetChat // // Created by Jett on 2020/5/11. // Copyright © 2022 Jett. All rights reserved. // import SnapKit import UIKit /// 赞|评论菜单 class OperateMenuView: UIView { fileprivate lazy var thumbupBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(R.image.ic_star_normal(), for: .normal) btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 3, bottom: 0, right: 0) btn.setTitle("赞".rLocalized(), for: .normal) btn.setTitle("取消".rLocalized(), for: .selected) btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.setTitleColor(.white, for: .normal) btn.addTarget(self, action: #selector(click(_:)), for: .touchUpInside) return btn }() fileprivate lazy var commentBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(R.image.ic_comment_normal(), for: .normal) btn.setTitle("评论".rLocalized(), for: .normal) btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 3, bottom: 0, right: 0) btn.titleLabel?.font = UIFont.systemFont(ofSize: 14) btn.setTitleColor(.white, for: .normal) btn.addTarget(self, action: #selector(click(_:)), for: .touchUpInside) btn.tag = 1 return btn }() fileprivate lazy var contentView: UIView = { let v = UIView() v.frame = CGRect(x: 0, y: 0, width: 160, height: 36) v.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V6 } v.layer.cornerRadius = 5 v.layer.masksToBounds = true return v }() fileprivate lazy var separatorView: UIView = { let v = UIView() v.backgroundColor = .black return v }() static func show(_ relative: UIView, isLiked: Bool, canComment: Bool, completed: ((Int)->Void)?) { let v = OperateMenuView(isLiked, canComment: canComment, completed: completed) UIApplication.shared.keyWindow?.addSubview(v) // 计算相对于屏幕的位置 let frame = relative.convert(relative.bounds, to: UIApplication.shared.keyWindow) v.show(by: frame) } fileprivate var completed: ((Int)->Void)? fileprivate var canComment = true private init(_ isLiked: Bool, canComment: Bool, completed: ((Int)->Void)?) { super.init(frame: UIScreen.main.bounds) self.completed = completed thumbupBtn.isSelected = isLiked self.canComment = canComment setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } } fileprivate extension OperateMenuView { func setup() { addSubview(contentView) isUserInteractionEnabled = true let tap = UITapGestureRecognizer(target: self, action: #selector(hide)) addGestureRecognizer(tap) contentView.addSubview(thumbupBtn) thumbupBtn.snp.makeConstraints { (make) in make.width.equalToSuperview().multipliedBy(0.5) make.height.leading.centerY.equalToSuperview() } if canComment { contentView.addSubview(commentBtn) contentView.addSubview(separatorView) separatorView.snp.makeConstraints { (make) in make.width.equalTo(0.5) make.height.equalToSuperview().inset(5) make.leading.equalTo(thumbupBtn.snp.trailing) make.centerY.equalToSuperview() } commentBtn.snp.makeConstraints { (make) in make.width.equalToSuperview().multipliedBy(0.5) make.height.trailing.centerY.equalToSuperview() } }else { thumbupBtn.snp.remakeConstraints { (make) in make.edges.equalToSuperview() } } } @objc func click(_ btn: UIButton) { hide() btn.isSelected = !btn.isSelected completed?(btn.tag) } func show(by relative: CGRect) { let width: CGFloat = canComment ? 160 : 80 let originY = relative.midY - 36/2 let originX = relative.minX - width - 10 contentView.frame = CGRect(x: relative.minX, y: originY, width: 0, height: 36) UIView.animate(withDuration: 0.25) { self.contentView.frame.origin.y = originY self.contentView.frame.origin.x = originX self.contentView.frame.size.width = width } } @objc func hide() { UIView.animate(withDuration: 0.25, animations: { self.contentView.alpha = 0 }) { (_) in self.alpha = 0 self.removeFromSuperview() } } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/Moments/View/TextView/FYTextView.swift ================================================ // // JXTextView.swift // // Created by Jett on 2020/5/9. // Copyright © 2022 Jett. All rights reserved. // import UIKit public enum TextAction { case change(text: String) case delete case done case keyboard(rect: CGRect, duration: Double) } class FYTextView: UITextView { fileprivate lazy var placeholderLb: UILabel = { let lb = UILabel() lb.text = "请输入内容...." lb.font = self.font lb.textColor = UIColor.lightGray lb.frame.origin = CGPoint(x: 5, y: 8) lb.sizeToFit() return lb }() var placeholderColor: UIColor = UIColor.lightGray { didSet { placeholderLb.textColor = placeholderColor } } var placeholder: String? { didSet { placeholderLb.text = placeholder placeholderLb.sizeToFit() } } var maxCount: Int = 200 var onKeyAction: ((TextAction)->Void)? fileprivate var textObservation: NSKeyValueObservation? /// 自动计算高度 var autoHeight: CGFloat { let size = CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude) let constraint = self.sizeThatFits(size) return constraint.height } /// 是否允许换行 var lineBreak = true override init(frame: CGRect, textContainer: NSTextContainer?) { super.init(frame: frame, textContainer: textContainer) setupUI() } required init?(coder: NSCoder) { super.init(coder: coder) setupUI() } deinit { NotificationCenter.default.removeObserver(self) } override func layoutSubviews() { super.layoutSubviews() if responds(to: #selector(setter: UITextView.textContainerInset)) { placeholderLb.frame.origin.x = textContainerInset.left + 5 placeholderLb.frame.origin.y = textContainerInset.top } } } fileprivate extension FYTextView { func setupUI(){ addSubview(placeholderLb) delegate = self textObservation = observe(\.text, changeHandler: { (tv, change) in self.placeholderLb.isHidden = tv.hasText }) NotificationCenter.default.addObserver(self, selector: #selector(textChanged(n:)), name: UITextView.textDidChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardChanged(n:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) } @objc func keyboardChanged(n: Notification){ guard let rect = (n.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let duration = (n.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue else{ return } onKeyAction?(.keyboard(rect: rect, duration: duration)) } @objc func textChanged(n: Notification){ placeholderLb.isHidden = self.hasText } } extension FYTextView: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { onKeyAction?(.change(text: textView.text)) } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if !lineBreak && text == "\n" { onKeyAction?(.done) return false } if textView.text.count >= maxCount { textView.text = String(textView.text.prefix(maxCount-1)) return false } return true } } ================================================ FILE: JetChat/FY-IMChat/Classes/MainModule/QrScan/ScanQRCodeViewController.swift ================================================ // // ScanQRCodeViewController.swift // JChat // // Created by JIGUANG on 2017/8/16. // Copyright © 2017年 HXHG. All rights reserved. // import UIKit import AVFoundation class ScanQRCodeViewController: UIViewController { final let borderW = CGFloat(240) final let borderY = (UIScreen.main.bounds.size.height - 240) * 0.5; override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "扫一扫".rLocalized() view.backgroundColor = .white self.extendedLayoutIncludesOpaqueBars = true; let authStatus = AVCaptureDevice.authorizationStatus(for: AVMediaType(rawValue: convertFromAVMediaType(AVMediaType.video))) if(authStatus == .restricted || authStatus == .denied){ let alertView = UIAlertView(title: "无法访问相机", message: "请在设备的设置-趣阅中允许访问相机。",delegate: self, cancelButtonTitle: "好的", otherButtonTitles: "去设置") alertView.show() return; } previewLayer.frame = view.frame view.layer.addSublayer(previewLayer) session.startRunning() let borderView = UIImageView(frame: CGRect(x: (view.width - borderW) / 2, y: borderY, width: borderW, height: borderW)) borderView.image = UIImage(named: "icon_qrc_border") view.addSubview(borderView) qrcLine = UIImageView(frame: CGRect(x: (view.width - 190) / 2, y: borderY, width: 190, height: 6)) qrcLine.image = UIImage(named: "icon_qrc_line") view.addSubview(qrcLine) let imageView = UIImageView(frame: view.frame) imageView.image = _getBackgroundImage() view.addSubview(imageView) let tipsLabel = UILabel(frame: CGRect(x: 10, y: borderY + borderW + 17.5, width: view.width - 20, height: 20)) tipsLabel.font = UIFont.systemFont(ofSize: 14) tipsLabel.text = "将取景框对准二维码,即可自动扫描".rLocalized() tipsLabel.textColor = .appThemeHexColor() tipsLabel.textAlignment = .center tipsLabel.numberOfLines = 2 view.addSubview(tipsLabel) NotificationCenter.default.addObserver(self, selector: #selector(startQRCAnimate), name: UIApplication.didBecomeActiveNotification, object: nil) } var qrcLine: UIImageView! var isStopAnimate = false override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) navigationController?.navigationBar.shadowImage = UIImage() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) session.startRunning() isStopAnimate = false isAnimating = false startQRCAnimate() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) navigationController?.navigationBar.setBackgroundImage(nil, for: .default) navigationController?.navigationBar.shadowImage = nil navigationController?.navigationBar.barTintColor = UIColor(netHex: 0x2dd0cf) self.qrcLine.layer.removeAllAnimations() } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() } deinit { NotificationCenter.default.removeObserver(self) } fileprivate lazy var session: AVCaptureSession = { var session = AVCaptureSession() var device = AVCaptureDevice.default(for: AVMediaType(rawValue: convertFromAVMediaType(AVMediaType.video))) var input: AVCaptureDeviceInput? do { input = try AVCaptureDeviceInput(device: device!) } catch { print(error) } if input != nil { session.addInput(input!) } var output = AVCaptureMetadataOutput() session.addOutput(output) output.metadataObjectTypes = [AVMetadataObject.ObjectType.qr] output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) return session }() fileprivate lazy var previewLayer: AVCaptureVideoPreviewLayer = { var previewLayer = AVCaptureVideoPreviewLayer(session: self.session) return previewLayer }() private func _getBackgroundImage() -> UIImage? { UIGraphicsBeginImageContext(view.frame.size) guard let ctx = UIGraphicsGetCurrentContext() else { return nil } ctx.setFillColor(red: 0, green: 0, blue: 0, alpha: 0.5) let screenSize = UIScreen.main.bounds.size var drawRect = CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height) ctx.fill(drawRect) drawRect = CGRect(x: (view.width - borderW) / 2, y: borderY, width: borderW, height: borderW) ctx.clear(drawRect) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } var isAnimating = false @objc func startQRCAnimate() { if isStopAnimate || isAnimating { return } isAnimating = true qrcLine.frame = CGRect(x: (self.view.width - 190) / 2, y: borderY, width: 190, height: 6) UIView.animate(withDuration: 2.5, animations: { self.qrcLine.frame = CGRect(x: (self.view.width - 190) / 2, y: self.borderY + self.borderW - 5, width: 190, height: 6) }) { (finish) in self.isAnimating = false self.qrcLine.frame = CGRect(x: (self.view.width - 190) / 2, y: self.borderY, width: 190, height: 6) if finish { self.startQRCAnimate() } } } } // MARK: - AVCaptureMetadataOutputObjectsDelegate extension ScanQRCodeViewController: AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { for metadataObject in metadataObjects { guard let object = metadataObject as? AVMetadataMachineReadableCodeObject else { return } let barCodeObject = previewLayer.transformedMetadataObject(for: object) let frame = barCodeObject!.bounds let validFrame = CGRect(x: (view.width - borderW) / 2, y: borderY, width: borderW, height: borderW) if frame.origin.x < validFrame.origin.x || frame.origin.x + frame.size.width > validFrame.origin.x + validFrame.size.width { return } if frame.size.width > validFrame.size.width { return } if frame.origin.y < validFrame.origin.y || frame.origin.y + frame.size.height > validFrame.origin.y + validFrame.size.height { return } if frame.size.height > validFrame.size.height { return } if object.type.rawValue == convertFromAVMetadataObjectObjectType(AVMetadataObject.ObjectType.qr) { guard let url = object.stringValue else { return } if let openURL = URL(string: url) { if UIApplication.shared.canOpenURL(openURL) { UIApplication.shared.open(openURL, options: [:], completionHandler: nil) } else { guard let safeURL = URL(string: "https://" + url) else { return } UIApplication.shared.open(safeURL, options: [:], completionHandler: nil) } return } let jsonData: Data = url.data(using: .utf8)! let dict = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) guard let info = dict as? NSDictionary else { return } session.stopRunning() } } } func convertStringToDictionary(text: String) -> [String : AnyObject]? { if let data = text.data(using: .utf8) { do { return try JSONSerialization.jsonObject(with: data, options: [JSONSerialization.ReadingOptions.init(rawValue: 0)]) as? [String:AnyObject] } catch let error as NSError { print(error) } } return nil } } extension ScanQRCodeViewController: UIAlertViewDelegate { func alertView(_ alertView: UIAlertView, clickedButtonAt buttonIndex: Int) { if buttonIndex == 1 { } } } // Helper function inserted by Swift 4.2 migrator. fileprivate func convertFromAVMediaType(_ input: AVMediaType) -> String { return input.rawValue } // Helper function inserted by Swift 4.2 migrator. fileprivate func convertFromAVMetadataObjectObjectType(_ input: AVMetadataObject.ObjectType) -> String { return input.rawValue } ================================================ FILE: JetChat/FY-IMChat/Classes/Resource/Languages/en.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings FY-IMChat 美式英语 Created by iOS.Jet on 2019/3/4. Copyright © 2019 development. All rights reserved. */ NSCameraUsageDescription = "To scan the QR code, do you allow using the camera?"; NSPhotoLibraryUsageDescription = "App requests your permission to access the photos in the gallery."; NSPhotoLibraryAddUsageDescription = "App requests your permission to access the photos in the gallery."; ================================================ FILE: JetChat/FY-IMChat/Classes/Resource/Languages/en.lproj/Localizable.strings ================================================ "温馨提示" = "Tips"; "确定" = "Confirm"; "取消" = "Cancel"; "设置" = "Settings"; "会话" = "Session"; "群组" = "Group"; "好友" = "Friends"; "我" = "Mine"; "加入群" = "Join group"; "删除所有群组" = "Delete all"; "添加好友" = "Add Friends"; "删除所有好友" = "Delete all"; "语言设置" = "Language settings"; "版本" = "Version"; "简体中文" = "Chinese"; "英文" = "English"; "当前网络已断开" = "Network disconnected"; "版本号:" = "Version number:"; "已是最新版本" = "Is the latest version"; "发起单聊" = "Single chat"; "发起群聊" = "Group chat"; "添加朋友" = "Add Friends"; "扫一扫" = "Scan"; "将取景框对准二维码,即可自动扫描" = "Aim the viewfinder at the QR code and it can be scanned automatically"; "确定退出当前群组吗?" = "Are you sure you want to leave the current group?"; "你已退出%@群聊" = "You have quit %@ group chat"; "确定删除全部好友吗?" = "Are you sure to delete all friends?"; "确定删除全部群组吗?" = "Are you sure to delete all groups?"; "删除后,会话记录也将清除" = "After deletion, the session record will also be cleared"; "退出群" = "Exit group"; "已清除全部未读消息数" = "All unread messages cleared"; "全部已读" = "Read all"; "群聊" = "Group chat"; "确定删除该好友吗?" = "Are you sure to delete this friend?"; "保存" = "Save"; "备注名称不超过12个字" = "Remark name no more than 12 words"; "修改备注名称" = "Modify remark name"; "设置备注名" = "Setting remark name"; "正在保存..." = "Saving"; "发送" = "Send"; "消息转发" = "Forward"; "发消息" = "Sending"; "个人信息" = "Personal information"; "用户名:" = "Username:"; "备注名:" = "Remark name:"; "转发" = "Forward"; "复制" = "Copy"; "删除" = "Delete"; "请输入..." = "Please enter..."; "图片" = "Album"; "拍照" = "Camera"; "视频" = "Video"; "位置" = "Location"; "语音" = "Voice"; "钱包" = "Wallet"; "转账" = "Transfer"; "名片" = "Card"; "收藏" = "Collection"; "隐藏" = "Hide"; "昨天" = "Yesterday"; "今天" = "Today"; "星期一" = "Monday"; "星期二" = "Tuesday"; "星期三" = "Wednesday"; "星期四" = "Thursday"; "星期五" = "Friday"; "星期六" = "Saturday"; "星期日" = "Sunday"; "清除图片缓存" = "Clear picture cache"; "清除成功" = "Cleared success"; "朋友圈" = "Wechat Moments"; "评论" = "Comment"; "赞" = "Star"; "主题模式" = "Theme mode"; "白天模式" = "Light"; "黑夜模式" = "Drak"; "跟随系统" = "Follow system"; "选取后,将跟随系统设定模式" = "Follow system setting"; "作者github" = "Author GitHub"; ================================================ FILE: JetChat/FY-IMChat/Classes/Resource/Languages/zh-Hans.lproj/InfoPlist.strings ================================================ /* InfoPlist.strings FY-IMChat 简体中文 Created by iOS.Jet on 2019/3/4. Copyright © 2019 development. All rights reserved. */ NSCameraUsageDescription = "需要扫描二维码或拍照,是否允许打开相机?"; NSPhotoLibraryUsageDescription = "App需要您的同意,才能访问相册保存图片"; NSPhotoLibraryAddUsageDescription = "App需要您的同意,才能访问相册保存图片"; ================================================ FILE: JetChat/FY-IMChat/Classes/Resource/Languages/zh-Hans.lproj/LWLocalizations.strings ================================================ "a_launch_title" = ""; "a_launch_create" = ""; "a_launch_choose" = ""; "a_launch_login" = ""; "a_launch_current" = ""; "b_login_title" = ""; "b_login_phone" = ""; "b_login_password" = ""; "b_login_phone_input" = ""; "b_login_password_input" = ""; "b_login_forget" = ""; "b_login_login_btn" = ""; "c_register_create_title" = ""; "c_register_create_id" = ""; "c_register_create_phone" = ""; "c_register_create_code" = ""; "c_register_create_id_input" = ""; "c_register_create_phone_input" = ""; "c_register_create_code_input" = ""; "c_register_create_agree" = ""; "c_register_create_agreement" = ""; "c_register_create_send" = ""; "c_register_create_next" = ""; "c_register_set_password_title" = ""; "c_register_set_password" = ""; "c_register_set_password_again" = ""; "c_register_set_password_code" = ""; "c_register_set_password_input" = ""; "c_register_set_password_again_input" = ""; "c_register_set_password_code_input" = ""; "c_register_set_password_next" = ""; "c_register_set_password_tip" = ""; "c_register_set_password_tip2" = ""; "c_register_set_payment_title" = ""; "c_register_set_payment_msg" = ""; "c_register_set_payment_skip" = ""; "c_register_set_payment_next" = ""; "c_register_confirm_payment_title" = ""; "c_register_confirm_payment_btn" = ""; "c_register_mnemonic_backup_title" = ""; "c_register_mnemonic_backup_msg" = ""; "c_register_mnemonic_backup_next" = ""; "c_register_mnemonic_confirm_title" = ""; "c_register_mnemonic_confirm_finish" = ""; "c_register_mnemonic_confirm_msg" = ""; "c_register_mnemonic_confirm_tip" = ""; "d_retrieve_title" = ""; "d_retrieve_phone" = ""; "d_retrieve_code" = ""; "d_retrieve_phone_input" = ""; "d_retrieve_code_input" = ""; "d_retrieve_send" = ""; "d_retrieve_next" = ""; "d_retrieve_password" = ""; "d_retrieve_password_again" = ""; "d_retrieve_finish" = ""; "d_retrieve_reset_success_tip" = ""; "e_wallet_title" = ""; "e_wallet_assets" = ""; "e_wallet_earned" = ""; "e_wallet_revenue" = ""; "e_wallet_coin_set_title" = ""; "e_wallet_coin_current" = ""; "e_wallet_coin_more" = ""; "e_wallet_coin_remove" = ""; "f_wallet_detail_available" = ""; "f_wallet_detail_freeze" = ""; "f_wallet_detail_equivalent" = ""; "f_wallet_detail_ad" = ""; "f_wallet_detail_record" = ""; "f_wallet_detail_filter" = ""; "f_wallet_detail_in" = ""; "f_wallet_detail_out" = ""; "f_wallet_detail_balance" = ""; "f_wallet_detail_status" = ""; "f_wallet_detail_quanity" = ""; "f_wallet_detail_status_confirmed" = ""; "f_wallet_detail_status_confirming" = ""; "f_wallet_detail_btn_eeceipt" = ""; "f_wallet_detail_btn_transfer" = ""; "f_wallet_transfer_detail_title" = ""; "f_wallet_transfer_detail_amount" = ""; "f_wallet_transfer_detail_address" = ""; "f_wallet_transfer_detail_id" = ""; "f_wallet_transfer_detail_date" = ""; "f_wallet_transfer_detail_status" = ""; "g_wallet_receipt_title" = ""; "g_wallet_receipt_copy" = ""; "g_wallet_receipt_album" = ""; "h_transfer_title" = ""; "h_transfer_address_placeholder" = ""; "h_transfer_fee" = ""; "h_transfer_balance" = ""; "h_transfer_fee_msg" = ""; "h_transfer_btn" = ""; "h_transfer_pay_title" = ""; "h_transfer_pay_msg" = ""; "h_transfer_pay_btn" = ""; "j_mine_main_usercenter" = ""; "j_mine_main_security" = ""; "j_mine_main_contact" = ""; "j_mine_main_setting" = ""; "j_mine_main_ad" = ""; "j_mine_setting_language" = ""; "j_mine_setting_version" = ""; "j_mine_security_login" = ""; "j_mine_security_pay" = ""; "j_mine_security_login_1_title" = ""; "j_mine_security_login_1_phone" = ""; "j_mine_security_login_1_code" = ""; "j_mine_security_login_1_phone_input" = ""; "j_mine_security_login_1_code_input" = ""; "j_mine_security_login_1_send" = ""; "j_mine_security_login_1_next" = ""; "j_mine_security_login_2_title" = ""; "j_mine_security_login_2_password" = ""; "j_mine_security_login_2_password_again" = ""; "j_mine_security_login_2_finish" = ""; "j_mine_security_login_2_success_tip" = ""; "j_mine_security_pay_1_title" = ""; "j_mine_security_pay_1_msg" = ""; "j_mine_security_pay_1_next" = ""; "j_mine_security_pay_2_title" = ""; "j_mine_security_pay_2_finish" = ""; "j_mine_user_idname" = ""; "j_mine_user_phone" = ""; "j_mine_user_mnemonic" = ""; ================================================ FILE: JetChat/FY-IMChat/Classes/Resource/Languages/zh-Hans.lproj/Localizable.strings ================================================ ================================================ FILE: JetChat/FY-IMChat/Classes/Resource/LaunchScreen.storyboard ================================================ ================================================ FILE: JetChat/FY-IMChat/Classes/Resource/R.generated.swift ================================================ // // This is a generated file, do not edit! // Generated by R.swift, see https://github.com/mac-cain13/R.swift // import Foundation import Rswift import UIKit /// This `R` struct is generated and contains references to static resources. struct R: Rswift.Validatable { fileprivate static let applicationLocale = hostingBundle.preferredLocalizations.first.flatMap { Locale(identifier: $0) } ?? Locale.current fileprivate static let hostingBundle = Bundle(for: R.Class.self) /// Find first language and bundle for which the table exists fileprivate static func localeBundle(tableName: String, preferredLanguages: [String]) -> (Foundation.Locale, Foundation.Bundle)? { // Filter preferredLanguages to localizations, use first locale var languages = preferredLanguages .map { Locale(identifier: $0) } .prefix(1) .flatMap { locale -> [String] in if hostingBundle.localizations.contains(locale.identifier) { if let language = locale.languageCode, hostingBundle.localizations.contains(language) { return [locale.identifier, language] } else { return [locale.identifier] } } else if let language = locale.languageCode, hostingBundle.localizations.contains(language) { return [language] } else { return [] } } // If there's no languages, use development language as backstop if languages.isEmpty { if let developmentLocalization = hostingBundle.developmentLocalization { languages = [developmentLocalization] } } else { // Insert Base as second item (between locale identifier and languageCode) languages.insert("Base", at: 1) // Add development language as backstop if let developmentLocalization = hostingBundle.developmentLocalization { languages.append(developmentLocalization) } } // Find first language for which table exists // Note: key might not exist in chosen language (in that case, key will be shown) for language in languages { if let lproj = hostingBundle.url(forResource: language, withExtension: "lproj"), let lbundle = Bundle(url: lproj) { let strings = lbundle.url(forResource: tableName, withExtension: "strings") let stringsdict = lbundle.url(forResource: tableName, withExtension: "stringsdict") if strings != nil || stringsdict != nil { return (Locale(identifier: language), lbundle) } } } // If table is available in main bundle, don't look for localized resources let strings = hostingBundle.url(forResource: tableName, withExtension: "strings", subdirectory: nil, localization: nil) let stringsdict = hostingBundle.url(forResource: tableName, withExtension: "stringsdict", subdirectory: nil, localization: nil) if strings != nil || stringsdict != nil { return (applicationLocale, hostingBundle) } // If table is not found for requested languages, key will be shown return nil } /// Load string from Info.plist file fileprivate static func infoPlistString(path: [String], key: String) -> String? { var dict = hostingBundle.infoDictionary for step in path { guard let obj = dict?[step] as? [String: Any] else { return nil } dict = obj } return dict?[key] as? String } static func validate() throws { try intern.validate() } #if os(iOS) || os(tvOS) /// This `R.storyboard` struct is generated, and contains static references to 1 storyboards. struct storyboard { /// Storyboard `LaunchScreen`. static let launchScreen = _R.storyboard.launchScreen() #if os(iOS) || os(tvOS) /// `UIStoryboard(name: "LaunchScreen", bundle: ...)` static func launchScreen(_: Void = ()) -> UIKit.UIStoryboard { return UIKit.UIStoryboard(resource: R.storyboard.launchScreen) } #endif fileprivate init() {} } #endif /// This `R.entitlements` struct is generated, and contains static references to 1 properties. struct entitlements { struct comAppleSecurityApplicationGroups { static let groupComJetchat2022JetChatWidget = infoPlistString(path: ["com.apple.security.application-groups"], key: "group.com.jetchat.2022.JetChatWidget") ?? "group.com.jetchat.2022.JetChatWidget" fileprivate init() {} } fileprivate init() {} } /// This `R.file` struct is generated, and contains static references to 49 files. struct file { /// Resource file `Emoticons.bundle`. static let emoticonsBundle = Rswift.FileResource(bundle: R.hostingBundle, name: "Emoticons", pathExtension: "bundle") /// Resource file `Expression.bundle`. static let expressionBundle = Rswift.FileResource(bundle: R.hostingBundle, name: "Expression", pathExtension: "bundle") /// Resource file `Expression.plist`. static let expressionPlist = Rswift.FileResource(bundle: R.hostingBundle, name: "Expression", pathExtension: "plist") /// Resource file `HUDAssets.bundle`. static let hudAssetsBundle = Rswift.FileResource(bundle: R.hostingBundle, name: "HUDAssets", pathExtension: "bundle") /// Resource file `ToolViewEmotion@2x.png`. static let toolViewEmotion2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewEmotion@2x", pathExtension: "png") /// Resource file `ToolViewEmotion@3x.png`. static let toolViewEmotion3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewEmotion@3x", pathExtension: "png") /// Resource file `ToolViewEmotionHL@2x.png`. static let toolViewEmotionHL2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewEmotionHL@2x", pathExtension: "png") /// Resource file `ToolViewEmotionHL@3x.png`. static let toolViewEmotionHL3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewEmotionHL@3x", pathExtension: "png") /// Resource file `ToolViewInputVoice@2x.png`. static let toolViewInputVoice2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewInputVoice@2x", pathExtension: "png") /// Resource file `ToolViewInputVoice@3x.png`. static let toolViewInputVoice3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewInputVoice@3x", pathExtension: "png") /// Resource file `ToolViewInputVoiceHL@2x.png`. static let toolViewInputVoiceHL2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewInputVoiceHL@2x", pathExtension: "png") /// Resource file `ToolViewInputVoiceHL@3x.png`. static let toolViewInputVoiceHL3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewInputVoiceHL@3x", pathExtension: "png") /// Resource file `ToolViewKeyboard@2x.png`. static let toolViewKeyboard2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewKeyboard@2x", pathExtension: "png") /// Resource file `ToolViewKeyboard@3x.png`. static let toolViewKeyboard3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewKeyboard@3x", pathExtension: "png") /// Resource file `ToolViewKeyboardHL@2x.png`. static let toolViewKeyboardHL2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewKeyboardHL@2x", pathExtension: "png") /// Resource file `ToolViewKeyboardHL@3x.png`. static let toolViewKeyboardHL3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ToolViewKeyboardHL@3x", pathExtension: "png") /// Resource file `TypeSelectorBtnHL_Black@2x.png`. static let typeSelectorBtnHL_Black2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "TypeSelectorBtnHL_Black@2x", pathExtension: "png") /// Resource file `TypeSelectorBtnHL_Black@3x.png`. static let typeSelectorBtnHL_Black3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "TypeSelectorBtnHL_Black@3x", pathExtension: "png") /// Resource file `TypeSelectorBtn_Black@2x.png`. static let typeSelectorBtn_Black2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "TypeSelectorBtn_Black@2x", pathExtension: "png") /// Resource file `TypeSelectorBtn_Black@3x.png`. static let typeSelectorBtn_Black3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "TypeSelectorBtn_Black@3x", pathExtension: "png") /// Resource file `ic_emotion_delete@2x.png`. static let ic_emotion_delete2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_emotion_delete@2x", pathExtension: "png") /// Resource file `ic_emotion_delete@3x.png`. static let ic_emotion_delete3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_emotion_delete@3x", pathExtension: "png") /// Resource file `ic_more_album@2x.png`. static let ic_more_album2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_album@2x", pathExtension: "png") /// Resource file `ic_more_album@3x.png`. static let ic_more_album3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_album@3x", pathExtension: "png") /// Resource file `ic_more_camera@2x.png`. static let ic_more_camera2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_camera@2x", pathExtension: "png") /// Resource file `ic_more_camera@3x.png`. static let ic_more_camera3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_camera@3x", pathExtension: "png") /// Resource file `ic_more_favorite@2x.png`. static let ic_more_favorite2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_favorite@2x", pathExtension: "png") /// Resource file `ic_more_favorite@3x.png`. static let ic_more_favorite3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_favorite@3x", pathExtension: "png") /// Resource file `ic_more_friendcard@2x.png`. static let ic_more_friendcard2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_friendcard@2x", pathExtension: "png") /// Resource file `ic_more_friendcard@3x.png`. static let ic_more_friendcard3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_friendcard@3x", pathExtension: "png") /// Resource file `ic_more_location@2x.png`. static let ic_more_location2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_location@2x", pathExtension: "png") /// Resource file `ic_more_location@3x.png`. static let ic_more_location3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_location@3x", pathExtension: "png") /// Resource file `ic_more_pay@2x.png`. static let ic_more_pay2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_pay@2x", pathExtension: "png") /// Resource file `ic_more_pay@3x.png`. static let ic_more_pay3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_pay@3x", pathExtension: "png") /// Resource file `ic_more_sight@2x.png`. static let ic_more_sight2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_sight@2x", pathExtension: "png") /// Resource file `ic_more_sight@3x.png`. static let ic_more_sight3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_sight@3x", pathExtension: "png") /// Resource file `ic_more_video@2x.png`. static let ic_more_video2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_video@2x", pathExtension: "png") /// Resource file `ic_more_video@3x.png`. static let ic_more_video3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_video@3x", pathExtension: "png") /// Resource file `ic_more_voice@2x.png`. static let ic_more_voice2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_voice@2x", pathExtension: "png") /// Resource file `ic_more_voice@3x.png`. static let ic_more_voice3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_voice@3x", pathExtension: "png") /// Resource file `ic_more_wallet@2x.png`. static let ic_more_wallet2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_wallet@2x", pathExtension: "png") /// Resource file `ic_more_wallet@3x.png`. static let ic_more_wallet3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "ic_more_wallet@3x", pathExtension: "png") /// Resource file `icon_emoji_expression@2x.png`. static let icon_emoji_expression2xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "icon_emoji_expression@2x", pathExtension: "png") /// Resource file `icon_emoji_expression@3x.png`. static let icon_emoji_expression3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "icon_emoji_expression@3x", pathExtension: "png") /// Resource file `icon_qrc_border@3x.png`. static let icon_qrc_border3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "icon_qrc_border@3x", pathExtension: "png") /// Resource file `icon_qrc_line@3x.png`. static let icon_qrc_line3xPng = Rswift.FileResource(bundle: R.hostingBundle, name: "icon_qrc_line@3x", pathExtension: "png") /// Resource file `localVideo0.mp4`. static let localVideo0Mp4 = Rswift.FileResource(bundle: R.hostingBundle, name: "localVideo0", pathExtension: "mp4") /// Resource file `moments1.json`. static let moments1Json = Rswift.FileResource(bundle: R.hostingBundle, name: "moments1", pathExtension: "json") /// Resource file `moments2.json`. static let moments2Json = Rswift.FileResource(bundle: R.hostingBundle, name: "moments2", pathExtension: "json") /// `bundle.url(forResource: "Emoticons", withExtension: "bundle")` static func emoticonsBundle(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.emoticonsBundle return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "Expression", withExtension: "bundle")` static func expressionBundle(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.expressionBundle return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "Expression", withExtension: "plist")` static func expressionPlist(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.expressionPlist return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "HUDAssets", withExtension: "bundle")` static func hudAssetsBundle(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.hudAssetsBundle return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewEmotion@2x", withExtension: "png")` static func toolViewEmotion2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewEmotion2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewEmotion@3x", withExtension: "png")` static func toolViewEmotion3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewEmotion3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewEmotionHL@2x", withExtension: "png")` static func toolViewEmotionHL2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewEmotionHL2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewEmotionHL@3x", withExtension: "png")` static func toolViewEmotionHL3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewEmotionHL3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewInputVoice@2x", withExtension: "png")` static func toolViewInputVoice2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewInputVoice2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewInputVoice@3x", withExtension: "png")` static func toolViewInputVoice3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewInputVoice3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewInputVoiceHL@2x", withExtension: "png")` static func toolViewInputVoiceHL2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewInputVoiceHL2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewInputVoiceHL@3x", withExtension: "png")` static func toolViewInputVoiceHL3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewInputVoiceHL3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewKeyboard@2x", withExtension: "png")` static func toolViewKeyboard2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewKeyboard2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewKeyboard@3x", withExtension: "png")` static func toolViewKeyboard3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewKeyboard3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewKeyboardHL@2x", withExtension: "png")` static func toolViewKeyboardHL2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewKeyboardHL2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ToolViewKeyboardHL@3x", withExtension: "png")` static func toolViewKeyboardHL3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.toolViewKeyboardHL3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "TypeSelectorBtnHL_Black@2x", withExtension: "png")` static func typeSelectorBtnHL_Black2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.typeSelectorBtnHL_Black2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "TypeSelectorBtnHL_Black@3x", withExtension: "png")` static func typeSelectorBtnHL_Black3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.typeSelectorBtnHL_Black3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "TypeSelectorBtn_Black@2x", withExtension: "png")` static func typeSelectorBtn_Black2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.typeSelectorBtn_Black2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "TypeSelectorBtn_Black@3x", withExtension: "png")` static func typeSelectorBtn_Black3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.typeSelectorBtn_Black3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_emotion_delete@2x", withExtension: "png")` static func ic_emotion_delete2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_emotion_delete2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_emotion_delete@3x", withExtension: "png")` static func ic_emotion_delete3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_emotion_delete3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_album@2x", withExtension: "png")` static func ic_more_album2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_album2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_album@3x", withExtension: "png")` static func ic_more_album3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_album3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_camera@2x", withExtension: "png")` static func ic_more_camera2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_camera2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_camera@3x", withExtension: "png")` static func ic_more_camera3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_camera3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_favorite@2x", withExtension: "png")` static func ic_more_favorite2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_favorite2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_favorite@3x", withExtension: "png")` static func ic_more_favorite3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_favorite3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_friendcard@2x", withExtension: "png")` static func ic_more_friendcard2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_friendcard2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_friendcard@3x", withExtension: "png")` static func ic_more_friendcard3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_friendcard3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_location@2x", withExtension: "png")` static func ic_more_location2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_location2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_location@3x", withExtension: "png")` static func ic_more_location3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_location3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_pay@2x", withExtension: "png")` static func ic_more_pay2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_pay2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_pay@3x", withExtension: "png")` static func ic_more_pay3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_pay3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_sight@2x", withExtension: "png")` static func ic_more_sight2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_sight2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_sight@3x", withExtension: "png")` static func ic_more_sight3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_sight3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_video@2x", withExtension: "png")` static func ic_more_video2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_video2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_video@3x", withExtension: "png")` static func ic_more_video3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_video3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_voice@2x", withExtension: "png")` static func ic_more_voice2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_voice2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_voice@3x", withExtension: "png")` static func ic_more_voice3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_voice3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_wallet@2x", withExtension: "png")` static func ic_more_wallet2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_wallet2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "ic_more_wallet@3x", withExtension: "png")` static func ic_more_wallet3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.ic_more_wallet3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "icon_emoji_expression@2x", withExtension: "png")` static func icon_emoji_expression2xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.icon_emoji_expression2xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "icon_emoji_expression@3x", withExtension: "png")` static func icon_emoji_expression3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.icon_emoji_expression3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "icon_qrc_border@3x", withExtension: "png")` static func icon_qrc_border3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.icon_qrc_border3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "icon_qrc_line@3x", withExtension: "png")` static func icon_qrc_line3xPng(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.icon_qrc_line3xPng return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "localVideo0", withExtension: "mp4")` static func localVideo0Mp4(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.localVideo0Mp4 return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "moments1", withExtension: "json")` static func moments1Json(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.moments1Json return fileResource.bundle.url(forResource: fileResource) } /// `bundle.url(forResource: "moments2", withExtension: "json")` static func moments2Json(_: Void = ()) -> Foundation.URL? { let fileResource = R.file.moments2Json return fileResource.bundle.url(forResource: fileResource) } fileprivate init() {} } /// This `R.image` struct is generated, and contains static references to 69 images. struct image { /// Image `LaunchImage`. static let launchImage = Rswift.ImageResource(bundle: R.hostingBundle, name: "LaunchImage") /// Image `MessageVideoDownload`. static let messageVideoDownload = Rswift.ImageResource(bundle: R.hostingBundle, name: "MessageVideoDownload") /// Image `MessageVideoPlay`. static let messageVideoPlay = Rswift.ImageResource(bundle: R.hostingBundle, name: "MessageVideoPlay") /// Image `ReceiverImageNodeBorder`. static let receiverImageNodeBorder = Rswift.ImageResource(bundle: R.hostingBundle, name: "ReceiverImageNodeBorder") /// Image `ToolViewEmotionHL`. static let toolViewEmotionHL = Rswift.ImageResource(bundle: R.hostingBundle, name: "ToolViewEmotionHL") /// Image `ToolViewEmotion`. static let toolViewEmotion = Rswift.ImageResource(bundle: R.hostingBundle, name: "ToolViewEmotion") /// Image `ToolViewInputVoiceHL`. static let toolViewInputVoiceHL = Rswift.ImageResource(bundle: R.hostingBundle, name: "ToolViewInputVoiceHL") /// Image `ToolViewInputVoice`. static let toolViewInputVoice = Rswift.ImageResource(bundle: R.hostingBundle, name: "ToolViewInputVoice") /// Image `ToolViewKeyboardHL`. static let toolViewKeyboardHL = Rswift.ImageResource(bundle: R.hostingBundle, name: "ToolViewKeyboardHL") /// Image `ToolViewKeyboard`. static let toolViewKeyboard = Rswift.ImageResource(bundle: R.hostingBundle, name: "ToolViewKeyboard") /// Image `TypeSelectorBtnHL_Black`. static let typeSelectorBtnHL_Black = Rswift.ImageResource(bundle: R.hostingBundle, name: "TypeSelectorBtnHL_Black") /// Image `TypeSelectorBtn_Black`. static let typeSelectorBtn_Black = Rswift.ImageResource(bundle: R.hostingBundle, name: "TypeSelectorBtn_Black") /// Image `ic_album_reflash`. static let ic_album_reflash = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_album_reflash") /// Image `ic_avatar_placeholder`. static let ic_avatar_placeholder = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_avatar_placeholder") /// Image `ic_comment_normal`. static let ic_comment_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_comment_normal") /// Image `ic_comment_selected`. static let ic_comment_selected = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_comment_selected") /// Image `ic_emotion_delete`. static let ic_emotion_delete = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_emotion_delete") /// Image `ic_group_placeholder`. static let ic_group_placeholder = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_group_placeholder") /// Image `ic_list_selection`. static let ic_list_selection = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_list_selection") /// Image `ic_more_album`. static let ic_more_album = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_more_album") /// Image `ic_more_camera`. static let ic_more_camera = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_more_camera") /// Image `ic_more_favorite`. static let ic_more_favorite = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_more_favorite") /// Image `ic_more_friendcard`. static let ic_more_friendcard = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_more_friendcard") /// Image `ic_more_location`. static let ic_more_location = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_more_location") /// Image `ic_more_pay`. static let ic_more_pay = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_more_pay") /// Image `ic_more_sight`. static let ic_more_sight = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_more_sight") /// Image `ic_more_video`. static let ic_more_video = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_more_video") /// Image `ic_more_voice`. static let ic_more_voice = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_more_voice") /// Image `ic_more_wallet`. static let ic_more_wallet = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_more_wallet") /// Image `ic_msg_forward_n`. static let ic_msg_forward_n = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_msg_forward_n") /// Image `ic_msg_forward_s`. static let ic_msg_forward_s = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_msg_forward_s") /// Image `ic_placeholder`. static let ic_placeholder = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_placeholder") /// Image `ic_star_normal`. static let ic_star_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_star_normal") /// Image `ic_star_selected`. static let ic_star_selected = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_star_selected") /// Image `ic_tabbar01_normal`. static let ic_tabbar01_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_tabbar01_normal") /// Image `ic_tabbar01_selected`. static let ic_tabbar01_selected = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_tabbar01_selected") /// Image `ic_tabbar02_normal`. static let ic_tabbar02_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_tabbar02_normal") /// Image `ic_tabbar02_selected`. static let ic_tabbar02_selected = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_tabbar02_selected") /// Image `ic_tabbar03_normal`. static let ic_tabbar03_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_tabbar03_normal") /// Image `ic_tabbar03_selected`. static let ic_tabbar03_selected = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_tabbar03_selected") /// Image `ic_tabbar04_normal`. static let ic_tabbar04_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_tabbar04_normal") /// Image `ic_tabbar04_selected`. static let ic_tabbar04_selected = Rswift.ImageResource(bundle: R.hostingBundle, name: "ic_tabbar04_selected") /// Image `icon_arrow_right`. static let icon_arrow_right = Rswift.ImageResource(bundle: R.hostingBundle, name: "icon_arrow_right") /// Image `icon_emoji_expression`. static let icon_emoji_expression = Rswift.ImageResource(bundle: R.hostingBundle, name: "icon_emoji_expression") /// Image `icon_more_add`. static let icon_more_add = Rswift.ImageResource(bundle: R.hostingBundle, name: "icon_more_add") /// Image `icon_qrc_border`. static let icon_qrc_border = Rswift.ImageResource(bundle: R.hostingBundle, name: "icon_qrc_border") /// Image `icon_qrc_line`. static let icon_qrc_line = Rswift.ImageResource(bundle: R.hostingBundle, name: "icon_qrc_line") /// Image `message_receiver_background_highlight`. static let message_receiver_background_highlight = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_receiver_background_highlight") /// Image `message_receiver_background_normal`. static let message_receiver_background_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_receiver_background_normal") /// Image `message_receiver_background_reversed`. static let message_receiver_background_reversed = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_receiver_background_reversed") /// Image `message_sender_background_highlight`. static let message_sender_background_highlight = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_sender_background_highlight") /// Image `message_sender_background_normal`. static let message_sender_background_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_sender_background_normal") /// Image `message_sender_background_reversed`. static let message_sender_background_reversed = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_sender_background_reversed") /// Image `message_voice_receiver_normal`. static let message_voice_receiver_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_voice_receiver_normal") /// Image `message_voice_receiver_playing_1`. static let message_voice_receiver_playing_1 = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_voice_receiver_playing_1") /// Image `message_voice_receiver_playing_2`. static let message_voice_receiver_playing_2 = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_voice_receiver_playing_2") /// Image `message_voice_receiver_playing_3`. static let message_voice_receiver_playing_3 = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_voice_receiver_playing_3") /// Image `message_voice_sender_normal`. static let message_voice_sender_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_voice_sender_normal") /// Image `message_voice_sender_playing_1`. static let message_voice_sender_playing_1 = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_voice_sender_playing_1") /// Image `message_voice_sender_playing_2`. static let message_voice_sender_playing_2 = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_voice_sender_playing_2") /// Image `message_voice_sender_playing_3`. static let message_voice_sender_playing_3 = Rswift.ImageResource(bundle: R.hostingBundle, name: "message_voice_sender_playing_3") /// Image `nav_back_black`. static let nav_back_black = Rswift.ImageResource(bundle: R.hostingBundle, name: "nav_back_black") /// Image `nav_back_white`. static let nav_back_white = Rswift.ImageResource(bundle: R.hostingBundle, name: "nav_back_white") /// Image `nav_camera_black`. static let nav_camera_black = Rswift.ImageResource(bundle: R.hostingBundle, name: "nav_camera_black") /// Image `nav_camera_white`. static let nav_camera_white = Rswift.ImageResource(bundle: R.hostingBundle, name: "nav_camera_white") /// Image `play_btn_normal`. static let play_btn_normal = Rswift.ImageResource(bundle: R.hostingBundle, name: "play_btn_normal") /// Image `play_btn_pressed`. static let play_btn_pressed = Rswift.ImageResource(bundle: R.hostingBundle, name: "play_btn_pressed") /// Image `player_back_button`. static let player_back_button = Rswift.ImageResource(bundle: R.hostingBundle, name: "player_back_button") /// Image `player_suspend_button`. static let player_suspend_button = Rswift.ImageResource(bundle: R.hostingBundle, name: "player_suspend_button") #if os(iOS) || os(tvOS) /// `UIImage(named: "LaunchImage", bundle: ..., traitCollection: ...)` static func launchImage(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.launchImage, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "MessageVideoDownload", bundle: ..., traitCollection: ...)` static func messageVideoDownload(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.messageVideoDownload, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "MessageVideoPlay", bundle: ..., traitCollection: ...)` static func messageVideoPlay(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.messageVideoPlay, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ReceiverImageNodeBorder", bundle: ..., traitCollection: ...)` static func receiverImageNodeBorder(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.receiverImageNodeBorder, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ToolViewEmotion", bundle: ..., traitCollection: ...)` static func toolViewEmotion(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.toolViewEmotion, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ToolViewEmotionHL", bundle: ..., traitCollection: ...)` static func toolViewEmotionHL(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.toolViewEmotionHL, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ToolViewInputVoice", bundle: ..., traitCollection: ...)` static func toolViewInputVoice(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.toolViewInputVoice, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ToolViewInputVoiceHL", bundle: ..., traitCollection: ...)` static func toolViewInputVoiceHL(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.toolViewInputVoiceHL, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ToolViewKeyboard", bundle: ..., traitCollection: ...)` static func toolViewKeyboard(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.toolViewKeyboard, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ToolViewKeyboardHL", bundle: ..., traitCollection: ...)` static func toolViewKeyboardHL(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.toolViewKeyboardHL, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "TypeSelectorBtnHL_Black", bundle: ..., traitCollection: ...)` static func typeSelectorBtnHL_Black(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.typeSelectorBtnHL_Black, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "TypeSelectorBtn_Black", bundle: ..., traitCollection: ...)` static func typeSelectorBtn_Black(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.typeSelectorBtn_Black, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_album_reflash", bundle: ..., traitCollection: ...)` static func ic_album_reflash(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_album_reflash, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_avatar_placeholder", bundle: ..., traitCollection: ...)` static func ic_avatar_placeholder(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_avatar_placeholder, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_comment_normal", bundle: ..., traitCollection: ...)` static func ic_comment_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_comment_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_comment_selected", bundle: ..., traitCollection: ...)` static func ic_comment_selected(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_comment_selected, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_emotion_delete", bundle: ..., traitCollection: ...)` static func ic_emotion_delete(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_emotion_delete, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_group_placeholder", bundle: ..., traitCollection: ...)` static func ic_group_placeholder(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_group_placeholder, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_list_selection", bundle: ..., traitCollection: ...)` static func ic_list_selection(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_list_selection, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_more_album", bundle: ..., traitCollection: ...)` static func ic_more_album(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_more_album, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_more_camera", bundle: ..., traitCollection: ...)` static func ic_more_camera(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_more_camera, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_more_favorite", bundle: ..., traitCollection: ...)` static func ic_more_favorite(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_more_favorite, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_more_friendcard", bundle: ..., traitCollection: ...)` static func ic_more_friendcard(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_more_friendcard, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_more_location", bundle: ..., traitCollection: ...)` static func ic_more_location(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_more_location, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_more_pay", bundle: ..., traitCollection: ...)` static func ic_more_pay(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_more_pay, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_more_sight", bundle: ..., traitCollection: ...)` static func ic_more_sight(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_more_sight, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_more_video", bundle: ..., traitCollection: ...)` static func ic_more_video(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_more_video, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_more_voice", bundle: ..., traitCollection: ...)` static func ic_more_voice(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_more_voice, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_more_wallet", bundle: ..., traitCollection: ...)` static func ic_more_wallet(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_more_wallet, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_msg_forward_n", bundle: ..., traitCollection: ...)` static func ic_msg_forward_n(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_msg_forward_n, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_msg_forward_s", bundle: ..., traitCollection: ...)` static func ic_msg_forward_s(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_msg_forward_s, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_placeholder", bundle: ..., traitCollection: ...)` static func ic_placeholder(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_placeholder, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_star_normal", bundle: ..., traitCollection: ...)` static func ic_star_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_star_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_star_selected", bundle: ..., traitCollection: ...)` static func ic_star_selected(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_star_selected, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_tabbar01_normal", bundle: ..., traitCollection: ...)` static func ic_tabbar01_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_tabbar01_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_tabbar01_selected", bundle: ..., traitCollection: ...)` static func ic_tabbar01_selected(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_tabbar01_selected, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_tabbar02_normal", bundle: ..., traitCollection: ...)` static func ic_tabbar02_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_tabbar02_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_tabbar02_selected", bundle: ..., traitCollection: ...)` static func ic_tabbar02_selected(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_tabbar02_selected, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_tabbar03_normal", bundle: ..., traitCollection: ...)` static func ic_tabbar03_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_tabbar03_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_tabbar03_selected", bundle: ..., traitCollection: ...)` static func ic_tabbar03_selected(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_tabbar03_selected, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_tabbar04_normal", bundle: ..., traitCollection: ...)` static func ic_tabbar04_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_tabbar04_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "ic_tabbar04_selected", bundle: ..., traitCollection: ...)` static func ic_tabbar04_selected(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.ic_tabbar04_selected, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "icon_arrow_right", bundle: ..., traitCollection: ...)` static func icon_arrow_right(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.icon_arrow_right, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "icon_emoji_expression", bundle: ..., traitCollection: ...)` static func icon_emoji_expression(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.icon_emoji_expression, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "icon_more_add", bundle: ..., traitCollection: ...)` static func icon_more_add(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.icon_more_add, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "icon_qrc_border", bundle: ..., traitCollection: ...)` static func icon_qrc_border(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.icon_qrc_border, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "icon_qrc_line", bundle: ..., traitCollection: ...)` static func icon_qrc_line(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.icon_qrc_line, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_receiver_background_highlight", bundle: ..., traitCollection: ...)` static func message_receiver_background_highlight(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_receiver_background_highlight, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_receiver_background_normal", bundle: ..., traitCollection: ...)` static func message_receiver_background_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_receiver_background_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_receiver_background_reversed", bundle: ..., traitCollection: ...)` static func message_receiver_background_reversed(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_receiver_background_reversed, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_sender_background_highlight", bundle: ..., traitCollection: ...)` static func message_sender_background_highlight(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_sender_background_highlight, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_sender_background_normal", bundle: ..., traitCollection: ...)` static func message_sender_background_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_sender_background_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_sender_background_reversed", bundle: ..., traitCollection: ...)` static func message_sender_background_reversed(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_sender_background_reversed, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_voice_receiver_normal", bundle: ..., traitCollection: ...)` static func message_voice_receiver_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_voice_receiver_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_voice_receiver_playing_1", bundle: ..., traitCollection: ...)` static func message_voice_receiver_playing_1(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_voice_receiver_playing_1, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_voice_receiver_playing_2", bundle: ..., traitCollection: ...)` static func message_voice_receiver_playing_2(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_voice_receiver_playing_2, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_voice_receiver_playing_3", bundle: ..., traitCollection: ...)` static func message_voice_receiver_playing_3(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_voice_receiver_playing_3, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_voice_sender_normal", bundle: ..., traitCollection: ...)` static func message_voice_sender_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_voice_sender_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_voice_sender_playing_1", bundle: ..., traitCollection: ...)` static func message_voice_sender_playing_1(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_voice_sender_playing_1, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_voice_sender_playing_2", bundle: ..., traitCollection: ...)` static func message_voice_sender_playing_2(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_voice_sender_playing_2, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "message_voice_sender_playing_3", bundle: ..., traitCollection: ...)` static func message_voice_sender_playing_3(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.message_voice_sender_playing_3, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "nav_back_black", bundle: ..., traitCollection: ...)` static func nav_back_black(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.nav_back_black, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "nav_back_white", bundle: ..., traitCollection: ...)` static func nav_back_white(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.nav_back_white, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "nav_camera_black", bundle: ..., traitCollection: ...)` static func nav_camera_black(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.nav_camera_black, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "nav_camera_white", bundle: ..., traitCollection: ...)` static func nav_camera_white(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.nav_camera_white, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "play_btn_normal", bundle: ..., traitCollection: ...)` static func play_btn_normal(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.play_btn_normal, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "play_btn_pressed", bundle: ..., traitCollection: ...)` static func play_btn_pressed(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.play_btn_pressed, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "player_back_button", bundle: ..., traitCollection: ...)` static func player_back_button(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.player_back_button, compatibleWith: traitCollection) } #endif #if os(iOS) || os(tvOS) /// `UIImage(named: "player_suspend_button", bundle: ..., traitCollection: ...)` static func player_suspend_button(compatibleWith traitCollection: UIKit.UITraitCollection? = nil) -> UIKit.UIImage? { return UIKit.UIImage(resource: R.image.player_suspend_button, compatibleWith: traitCollection) } #endif fileprivate init() {} } /// This `R.string` struct is generated, and contains static references to 2 localization tables. struct string { /// This `R.string.infoPlist` struct is generated, and contains static references to 3 localization keys. struct infoPlist { /// en translation: App requests your permission to access the photos in the gallery. /// /// Locales: en, zh-Hans static let nsPhotoLibraryAddUsageDescription = Rswift.StringResource(key: "NSPhotoLibraryAddUsageDescription", tableName: "InfoPlist", bundle: R.hostingBundle, locales: ["en", "zh-Hans"], comment: nil) /// en translation: App requests your permission to access the photos in the gallery. /// /// Locales: en, zh-Hans static let nsPhotoLibraryUsageDescription = Rswift.StringResource(key: "NSPhotoLibraryUsageDescription", tableName: "InfoPlist", bundle: R.hostingBundle, locales: ["en", "zh-Hans"], comment: nil) /// en translation: To scan the QR code, do you allow using the camera? /// /// Locales: en, zh-Hans static let nsCameraUsageDescription = Rswift.StringResource(key: "NSCameraUsageDescription", tableName: "InfoPlist", bundle: R.hostingBundle, locales: ["en", "zh-Hans"], comment: nil) /// en translation: App requests your permission to access the photos in the gallery. /// /// Locales: en, zh-Hans static func nsPhotoLibraryAddUsageDescription(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("NSPhotoLibraryAddUsageDescription", tableName: "InfoPlist", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "InfoPlist", preferredLanguages: preferredLanguages) else { return "NSPhotoLibraryAddUsageDescription" } return NSLocalizedString("NSPhotoLibraryAddUsageDescription", tableName: "InfoPlist", bundle: bundle, comment: "") } /// en translation: App requests your permission to access the photos in the gallery. /// /// Locales: en, zh-Hans static func nsPhotoLibraryUsageDescription(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("NSPhotoLibraryUsageDescription", tableName: "InfoPlist", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "InfoPlist", preferredLanguages: preferredLanguages) else { return "NSPhotoLibraryUsageDescription" } return NSLocalizedString("NSPhotoLibraryUsageDescription", tableName: "InfoPlist", bundle: bundle, comment: "") } /// en translation: To scan the QR code, do you allow using the camera? /// /// Locales: en, zh-Hans static func nsCameraUsageDescription(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("NSCameraUsageDescription", tableName: "InfoPlist", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "InfoPlist", preferredLanguages: preferredLanguages) else { return "NSCameraUsageDescription" } return NSLocalizedString("NSCameraUsageDescription", tableName: "InfoPlist", bundle: bundle, comment: "") } fileprivate init() {} } /// This `R.string.localizable` struct is generated, and contains static references to 79 localization keys. struct localizable { /// en translation: Add Friends /// /// Locales: en static let 添加好友 = Rswift.StringResource(key: "添加好友", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Add Friends /// /// Locales: en static let 添加朋友 = Rswift.StringResource(key: "添加朋友", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: After deletion, the session record will also be cleared /// /// Locales: en static let 删除后会话记录也将清除 = Rswift.StringResource(key: "删除后,会话记录也将清除", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Aim the viewfinder at the QR code and it can be scanned automatically /// /// Locales: en static let 将取景框对准二维码即可自动扫描 = Rswift.StringResource(key: "将取景框对准二维码,即可自动扫描", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Album /// /// Locales: en static let 图片 = Rswift.StringResource(key: "图片", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: All unread messages cleared /// /// Locales: en static let 已清除全部未读消息数 = Rswift.StringResource(key: "已清除全部未读消息数", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Are you sure to delete all friends? /// /// Locales: en static let 确定删除全部好友吗 = Rswift.StringResource(key: "确定删除全部好友吗?", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Are you sure to delete all groups? /// /// Locales: en static let 确定删除全部群组吗 = Rswift.StringResource(key: "确定删除全部群组吗?", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Are you sure to delete this friend? /// /// Locales: en static let 确定删除该好友吗 = Rswift.StringResource(key: "确定删除该好友吗?", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Are you sure you want to leave the current group? /// /// Locales: en static let 确定退出当前群组吗 = Rswift.StringResource(key: "确定退出当前群组吗?", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Author GitHub /// /// Locales: en static let 作者github = Rswift.StringResource(key: "作者github", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Camera /// /// Locales: en static let 拍照 = Rswift.StringResource(key: "拍照", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Cancel /// /// Locales: en static let 取消 = Rswift.StringResource(key: "取消", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Card /// /// Locales: en static let 名片 = Rswift.StringResource(key: "名片", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Chinese /// /// Locales: en static let 简体中文 = Rswift.StringResource(key: "简体中文", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Clear picture cache /// /// Locales: en static let 清除图片缓存 = Rswift.StringResource(key: "清除图片缓存", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Cleared success /// /// Locales: en static let 清除成功 = Rswift.StringResource(key: "清除成功", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Collection /// /// Locales: en static let 收藏 = Rswift.StringResource(key: "收藏", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Comment /// /// Locales: en static let 评论 = Rswift.StringResource(key: "评论", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Confirm /// /// Locales: en static let 确定 = Rswift.StringResource(key: "确定", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Copy /// /// Locales: en static let 复制 = Rswift.StringResource(key: "复制", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Delete /// /// Locales: en static let 删除 = Rswift.StringResource(key: "删除", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Delete all /// /// Locales: en static let 删除所有好友 = Rswift.StringResource(key: "删除所有好友", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Delete all /// /// Locales: en static let 删除所有群组 = Rswift.StringResource(key: "删除所有群组", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Drak /// /// Locales: en static let 黑夜模式 = Rswift.StringResource(key: "黑夜模式", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: English /// /// Locales: en static let 英文 = Rswift.StringResource(key: "英文", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Exit group /// /// Locales: en static let 退出群 = Rswift.StringResource(key: "退出群", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Follow system /// /// Locales: en static let 跟随系统 = Rswift.StringResource(key: "跟随系统", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Follow system setting /// /// Locales: en static let 选取后将跟随系统设定模式 = Rswift.StringResource(key: "选取后,将跟随系统设定模式", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Forward /// /// Locales: en static let 消息转发 = Rswift.StringResource(key: "消息转发", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Forward /// /// Locales: en static let 转发 = Rswift.StringResource(key: "转发", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Friday /// /// Locales: en static let 星期五 = Rswift.StringResource(key: "星期五", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Friends /// /// Locales: en static let 好友 = Rswift.StringResource(key: "好友", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Group /// /// Locales: en static let 群组 = Rswift.StringResource(key: "群组", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Group chat /// /// Locales: en static let 发起群聊 = Rswift.StringResource(key: "发起群聊", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Group chat /// /// Locales: en static let 群聊 = Rswift.StringResource(key: "群聊", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Hide /// /// Locales: en static let 隐藏 = Rswift.StringResource(key: "隐藏", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Is the latest version /// /// Locales: en static let 已是最新版本 = Rswift.StringResource(key: "已是最新版本", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Join group /// /// Locales: en static let 加入群 = Rswift.StringResource(key: "加入群", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Language settings /// /// Locales: en static let 语言设置 = Rswift.StringResource(key: "语言设置", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Light /// /// Locales: en static let 白天模式 = Rswift.StringResource(key: "白天模式", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Location /// /// Locales: en static let 位置 = Rswift.StringResource(key: "位置", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Mine /// /// Locales: en static let 我 = Rswift.StringResource(key: "我", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Modify remark name /// /// Locales: en static let 修改备注名称 = Rswift.StringResource(key: "修改备注名称", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Monday /// /// Locales: en static let 星期一 = Rswift.StringResource(key: "星期一", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Network disconnected /// /// Locales: en static let 当前网络已断开 = Rswift.StringResource(key: "当前网络已断开", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Personal information /// /// Locales: en static let 个人信息 = Rswift.StringResource(key: "个人信息", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Please enter... /// /// Locales: en static let 请输入 = Rswift.StringResource(key: "请输入...", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Read all /// /// Locales: en static let 全部已读 = Rswift.StringResource(key: "全部已读", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Remark name no more than 12 words /// /// Locales: en static let 备注名称不超过12个字 = Rswift.StringResource(key: "备注名称不超过12个字", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Remark name: /// /// Locales: en static let 备注名 = Rswift.StringResource(key: "备注名:", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Saturday /// /// Locales: en static let 星期六 = Rswift.StringResource(key: "星期六", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Save /// /// Locales: en static let 保存 = Rswift.StringResource(key: "保存", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Saving /// /// Locales: en static let 正在保存 = Rswift.StringResource(key: "正在保存...", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Scan /// /// Locales: en static let 扫一扫 = Rswift.StringResource(key: "扫一扫", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Send /// /// Locales: en static let 发送 = Rswift.StringResource(key: "发送", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Sending /// /// Locales: en static let 发消息 = Rswift.StringResource(key: "发消息", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Session /// /// Locales: en static let 会话 = Rswift.StringResource(key: "会话", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Setting remark name /// /// Locales: en static let 设置备注名 = Rswift.StringResource(key: "设置备注名", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Settings /// /// Locales: en static let 设置 = Rswift.StringResource(key: "设置", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Single chat /// /// Locales: en static let 发起单聊 = Rswift.StringResource(key: "发起单聊", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Star /// /// Locales: en static let 赞 = Rswift.StringResource(key: "赞", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Sunday /// /// Locales: en static let 星期日 = Rswift.StringResource(key: "星期日", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Theme mode /// /// Locales: en static let 主题模式 = Rswift.StringResource(key: "主题模式", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Thursday /// /// Locales: en static let 星期四 = Rswift.StringResource(key: "星期四", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Tips /// /// Locales: en static let 温馨提示 = Rswift.StringResource(key: "温馨提示", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Today /// /// Locales: en static let 今天 = Rswift.StringResource(key: "今天", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Transfer /// /// Locales: en static let 转账 = Rswift.StringResource(key: "转账", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Tuesday /// /// Locales: en static let 星期二 = Rswift.StringResource(key: "星期二", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Username: /// /// Locales: en static let 用户名 = Rswift.StringResource(key: "用户名:", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Version /// /// Locales: en static let 版本 = Rswift.StringResource(key: "版本", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Version number: /// /// Locales: en static let 版本号 = Rswift.StringResource(key: "版本号:", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Video /// /// Locales: en static let 视频 = Rswift.StringResource(key: "视频", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Voice /// /// Locales: en static let 语音 = Rswift.StringResource(key: "语音", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Wallet /// /// Locales: en static let 钱包 = Rswift.StringResource(key: "钱包", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Wechat Moments /// /// Locales: en static let 朋友圈 = Rswift.StringResource(key: "朋友圈", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Wednesday /// /// Locales: en static let 星期三 = Rswift.StringResource(key: "星期三", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Yesterday /// /// Locales: en static let 昨天 = Rswift.StringResource(key: "昨天", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: You have quit %@ group chat /// /// Locales: en static let 你已退出群聊 = Rswift.StringResource(key: "你已退出%@群聊", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en"], comment: nil) /// en translation: Add Friends /// /// Locales: en static func 添加好友(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("添加好友", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "添加好友" } return NSLocalizedString("添加好友", bundle: bundle, comment: "") } /// en translation: Add Friends /// /// Locales: en static func 添加朋友(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("添加朋友", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "添加朋友" } return NSLocalizedString("添加朋友", bundle: bundle, comment: "") } /// en translation: After deletion, the session record will also be cleared /// /// Locales: en static func 删除后会话记录也将清除(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("删除后,会话记录也将清除", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "删除后,会话记录也将清除" } return NSLocalizedString("删除后,会话记录也将清除", bundle: bundle, comment: "") } /// en translation: Aim the viewfinder at the QR code and it can be scanned automatically /// /// Locales: en static func 将取景框对准二维码即可自动扫描(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("将取景框对准二维码,即可自动扫描", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "将取景框对准二维码,即可自动扫描" } return NSLocalizedString("将取景框对准二维码,即可自动扫描", bundle: bundle, comment: "") } /// en translation: Album /// /// Locales: en static func 图片(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("图片", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "图片" } return NSLocalizedString("图片", bundle: bundle, comment: "") } /// en translation: All unread messages cleared /// /// Locales: en static func 已清除全部未读消息数(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("已清除全部未读消息数", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "已清除全部未读消息数" } return NSLocalizedString("已清除全部未读消息数", bundle: bundle, comment: "") } /// en translation: Are you sure to delete all friends? /// /// Locales: en static func 确定删除全部好友吗(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("确定删除全部好友吗?", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "确定删除全部好友吗?" } return NSLocalizedString("确定删除全部好友吗?", bundle: bundle, comment: "") } /// en translation: Are you sure to delete all groups? /// /// Locales: en static func 确定删除全部群组吗(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("确定删除全部群组吗?", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "确定删除全部群组吗?" } return NSLocalizedString("确定删除全部群组吗?", bundle: bundle, comment: "") } /// en translation: Are you sure to delete this friend? /// /// Locales: en static func 确定删除该好友吗(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("确定删除该好友吗?", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "确定删除该好友吗?" } return NSLocalizedString("确定删除该好友吗?", bundle: bundle, comment: "") } /// en translation: Are you sure you want to leave the current group? /// /// Locales: en static func 确定退出当前群组吗(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("确定退出当前群组吗?", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "确定退出当前群组吗?" } return NSLocalizedString("确定退出当前群组吗?", bundle: bundle, comment: "") } /// en translation: Author GitHub /// /// Locales: en static func 作者github(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("作者github", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "作者github" } return NSLocalizedString("作者github", bundle: bundle, comment: "") } /// en translation: Camera /// /// Locales: en static func 拍照(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("拍照", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "拍照" } return NSLocalizedString("拍照", bundle: bundle, comment: "") } /// en translation: Cancel /// /// Locales: en static func 取消(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("取消", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "取消" } return NSLocalizedString("取消", bundle: bundle, comment: "") } /// en translation: Card /// /// Locales: en static func 名片(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("名片", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "名片" } return NSLocalizedString("名片", bundle: bundle, comment: "") } /// en translation: Chinese /// /// Locales: en static func 简体中文(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("简体中文", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "简体中文" } return NSLocalizedString("简体中文", bundle: bundle, comment: "") } /// en translation: Clear picture cache /// /// Locales: en static func 清除图片缓存(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("清除图片缓存", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "清除图片缓存" } return NSLocalizedString("清除图片缓存", bundle: bundle, comment: "") } /// en translation: Cleared success /// /// Locales: en static func 清除成功(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("清除成功", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "清除成功" } return NSLocalizedString("清除成功", bundle: bundle, comment: "") } /// en translation: Collection /// /// Locales: en static func 收藏(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("收藏", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "收藏" } return NSLocalizedString("收藏", bundle: bundle, comment: "") } /// en translation: Comment /// /// Locales: en static func 评论(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("评论", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "评论" } return NSLocalizedString("评论", bundle: bundle, comment: "") } /// en translation: Confirm /// /// Locales: en static func 确定(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("确定", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "确定" } return NSLocalizedString("确定", bundle: bundle, comment: "") } /// en translation: Copy /// /// Locales: en static func 复制(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("复制", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "复制" } return NSLocalizedString("复制", bundle: bundle, comment: "") } /// en translation: Delete /// /// Locales: en static func 删除(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("删除", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "删除" } return NSLocalizedString("删除", bundle: bundle, comment: "") } /// en translation: Delete all /// /// Locales: en static func 删除所有好友(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("删除所有好友", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "删除所有好友" } return NSLocalizedString("删除所有好友", bundle: bundle, comment: "") } /// en translation: Delete all /// /// Locales: en static func 删除所有群组(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("删除所有群组", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "删除所有群组" } return NSLocalizedString("删除所有群组", bundle: bundle, comment: "") } /// en translation: Drak /// /// Locales: en static func 黑夜模式(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("黑夜模式", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "黑夜模式" } return NSLocalizedString("黑夜模式", bundle: bundle, comment: "") } /// en translation: English /// /// Locales: en static func 英文(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("英文", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "英文" } return NSLocalizedString("英文", bundle: bundle, comment: "") } /// en translation: Exit group /// /// Locales: en static func 退出群(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("退出群", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "退出群" } return NSLocalizedString("退出群", bundle: bundle, comment: "") } /// en translation: Follow system /// /// Locales: en static func 跟随系统(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("跟随系统", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "跟随系统" } return NSLocalizedString("跟随系统", bundle: bundle, comment: "") } /// en translation: Follow system setting /// /// Locales: en static func 选取后将跟随系统设定模式(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("选取后,将跟随系统设定模式", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "选取后,将跟随系统设定模式" } return NSLocalizedString("选取后,将跟随系统设定模式", bundle: bundle, comment: "") } /// en translation: Forward /// /// Locales: en static func 消息转发(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("消息转发", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "消息转发" } return NSLocalizedString("消息转发", bundle: bundle, comment: "") } /// en translation: Forward /// /// Locales: en static func 转发(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("转发", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "转发" } return NSLocalizedString("转发", bundle: bundle, comment: "") } /// en translation: Friday /// /// Locales: en static func 星期五(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("星期五", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "星期五" } return NSLocalizedString("星期五", bundle: bundle, comment: "") } /// en translation: Friends /// /// Locales: en static func 好友(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("好友", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "好友" } return NSLocalizedString("好友", bundle: bundle, comment: "") } /// en translation: Group /// /// Locales: en static func 群组(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("群组", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "群组" } return NSLocalizedString("群组", bundle: bundle, comment: "") } /// en translation: Group chat /// /// Locales: en static func 发起群聊(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("发起群聊", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "发起群聊" } return NSLocalizedString("发起群聊", bundle: bundle, comment: "") } /// en translation: Group chat /// /// Locales: en static func 群聊(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("群聊", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "群聊" } return NSLocalizedString("群聊", bundle: bundle, comment: "") } /// en translation: Hide /// /// Locales: en static func 隐藏(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("隐藏", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "隐藏" } return NSLocalizedString("隐藏", bundle: bundle, comment: "") } /// en translation: Is the latest version /// /// Locales: en static func 已是最新版本(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("已是最新版本", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "已是最新版本" } return NSLocalizedString("已是最新版本", bundle: bundle, comment: "") } /// en translation: Join group /// /// Locales: en static func 加入群(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("加入群", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "加入群" } return NSLocalizedString("加入群", bundle: bundle, comment: "") } /// en translation: Language settings /// /// Locales: en static func 语言设置(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("语言设置", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "语言设置" } return NSLocalizedString("语言设置", bundle: bundle, comment: "") } /// en translation: Light /// /// Locales: en static func 白天模式(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("白天模式", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "白天模式" } return NSLocalizedString("白天模式", bundle: bundle, comment: "") } /// en translation: Location /// /// Locales: en static func 位置(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("位置", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "位置" } return NSLocalizedString("位置", bundle: bundle, comment: "") } /// en translation: Mine /// /// Locales: en static func 我(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("我", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "我" } return NSLocalizedString("我", bundle: bundle, comment: "") } /// en translation: Modify remark name /// /// Locales: en static func 修改备注名称(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("修改备注名称", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "修改备注名称" } return NSLocalizedString("修改备注名称", bundle: bundle, comment: "") } /// en translation: Monday /// /// Locales: en static func 星期一(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("星期一", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "星期一" } return NSLocalizedString("星期一", bundle: bundle, comment: "") } /// en translation: Network disconnected /// /// Locales: en static func 当前网络已断开(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("当前网络已断开", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "当前网络已断开" } return NSLocalizedString("当前网络已断开", bundle: bundle, comment: "") } /// en translation: Personal information /// /// Locales: en static func 个人信息(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("个人信息", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "个人信息" } return NSLocalizedString("个人信息", bundle: bundle, comment: "") } /// en translation: Please enter... /// /// Locales: en static func 请输入(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("请输入...", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "请输入..." } return NSLocalizedString("请输入...", bundle: bundle, comment: "") } /// en translation: Read all /// /// Locales: en static func 全部已读(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("全部已读", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "全部已读" } return NSLocalizedString("全部已读", bundle: bundle, comment: "") } /// en translation: Remark name no more than 12 words /// /// Locales: en static func 备注名称不超过12个字(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("备注名称不超过12个字", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "备注名称不超过12个字" } return NSLocalizedString("备注名称不超过12个字", bundle: bundle, comment: "") } /// en translation: Remark name: /// /// Locales: en static func 备注名(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("备注名:", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "备注名:" } return NSLocalizedString("备注名:", bundle: bundle, comment: "") } /// en translation: Saturday /// /// Locales: en static func 星期六(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("星期六", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "星期六" } return NSLocalizedString("星期六", bundle: bundle, comment: "") } /// en translation: Save /// /// Locales: en static func 保存(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("保存", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "保存" } return NSLocalizedString("保存", bundle: bundle, comment: "") } /// en translation: Saving /// /// Locales: en static func 正在保存(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("正在保存...", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "正在保存..." } return NSLocalizedString("正在保存...", bundle: bundle, comment: "") } /// en translation: Scan /// /// Locales: en static func 扫一扫(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("扫一扫", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "扫一扫" } return NSLocalizedString("扫一扫", bundle: bundle, comment: "") } /// en translation: Send /// /// Locales: en static func 发送(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("发送", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "发送" } return NSLocalizedString("发送", bundle: bundle, comment: "") } /// en translation: Sending /// /// Locales: en static func 发消息(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("发消息", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "发消息" } return NSLocalizedString("发消息", bundle: bundle, comment: "") } /// en translation: Session /// /// Locales: en static func 会话(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("会话", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "会话" } return NSLocalizedString("会话", bundle: bundle, comment: "") } /// en translation: Setting remark name /// /// Locales: en static func 设置备注名(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("设置备注名", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "设置备注名" } return NSLocalizedString("设置备注名", bundle: bundle, comment: "") } /// en translation: Settings /// /// Locales: en static func 设置(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("设置", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "设置" } return NSLocalizedString("设置", bundle: bundle, comment: "") } /// en translation: Single chat /// /// Locales: en static func 发起单聊(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("发起单聊", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "发起单聊" } return NSLocalizedString("发起单聊", bundle: bundle, comment: "") } /// en translation: Star /// /// Locales: en static func 赞(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("赞", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "赞" } return NSLocalizedString("赞", bundle: bundle, comment: "") } /// en translation: Sunday /// /// Locales: en static func 星期日(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("星期日", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "星期日" } return NSLocalizedString("星期日", bundle: bundle, comment: "") } /// en translation: Theme mode /// /// Locales: en static func 主题模式(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("主题模式", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "主题模式" } return NSLocalizedString("主题模式", bundle: bundle, comment: "") } /// en translation: Thursday /// /// Locales: en static func 星期四(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("星期四", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "星期四" } return NSLocalizedString("星期四", bundle: bundle, comment: "") } /// en translation: Tips /// /// Locales: en static func 温馨提示(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("温馨提示", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "温馨提示" } return NSLocalizedString("温馨提示", bundle: bundle, comment: "") } /// en translation: Today /// /// Locales: en static func 今天(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("今天", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "今天" } return NSLocalizedString("今天", bundle: bundle, comment: "") } /// en translation: Transfer /// /// Locales: en static func 转账(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("转账", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "转账" } return NSLocalizedString("转账", bundle: bundle, comment: "") } /// en translation: Tuesday /// /// Locales: en static func 星期二(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("星期二", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "星期二" } return NSLocalizedString("星期二", bundle: bundle, comment: "") } /// en translation: Username: /// /// Locales: en static func 用户名(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("用户名:", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "用户名:" } return NSLocalizedString("用户名:", bundle: bundle, comment: "") } /// en translation: Version /// /// Locales: en static func 版本(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("版本", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "版本" } return NSLocalizedString("版本", bundle: bundle, comment: "") } /// en translation: Version number: /// /// Locales: en static func 版本号(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("版本号:", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "版本号:" } return NSLocalizedString("版本号:", bundle: bundle, comment: "") } /// en translation: Video /// /// Locales: en static func 视频(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("视频", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "视频" } return NSLocalizedString("视频", bundle: bundle, comment: "") } /// en translation: Voice /// /// Locales: en static func 语音(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("语音", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "语音" } return NSLocalizedString("语音", bundle: bundle, comment: "") } /// en translation: Wallet /// /// Locales: en static func 钱包(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("钱包", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "钱包" } return NSLocalizedString("钱包", bundle: bundle, comment: "") } /// en translation: Wechat Moments /// /// Locales: en static func 朋友圈(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("朋友圈", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "朋友圈" } return NSLocalizedString("朋友圈", bundle: bundle, comment: "") } /// en translation: Wednesday /// /// Locales: en static func 星期三(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("星期三", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "星期三" } return NSLocalizedString("星期三", bundle: bundle, comment: "") } /// en translation: Yesterday /// /// Locales: en static func 昨天(preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { return NSLocalizedString("昨天", bundle: hostingBundle, comment: "") } guard let (_, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "昨天" } return NSLocalizedString("昨天", bundle: bundle, comment: "") } /// en translation: You have quit %@ group chat /// /// Locales: en static func 你已退出群聊(_ value1: String, preferredLanguages: [String]? = nil) -> String { guard let preferredLanguages = preferredLanguages else { let format = NSLocalizedString("你已退出%@群聊", bundle: hostingBundle, comment: "") return String(format: format, locale: applicationLocale, value1) } guard let (locale, bundle) = localeBundle(tableName: "Localizable", preferredLanguages: preferredLanguages) else { return "你已退出%@群聊" } let format = NSLocalizedString("你已退出%@群聊", bundle: bundle, comment: "") return String(format: format, locale: locale, value1) } fileprivate init() {} } fileprivate init() {} } fileprivate struct intern: Rswift.Validatable { fileprivate static func validate() throws { try _R.validate() } fileprivate init() {} } fileprivate class Class {} fileprivate init() {} } struct _R: Rswift.Validatable { static func validate() throws { #if os(iOS) || os(tvOS) try storyboard.validate() #endif } #if os(iOS) || os(tvOS) struct storyboard: Rswift.Validatable { static func validate() throws { #if os(iOS) || os(tvOS) try launchScreen.validate() #endif } #if os(iOS) || os(tvOS) struct launchScreen: Rswift.StoryboardResourceWithInitialControllerType, Rswift.Validatable { typealias InitialController = UIKit.UIViewController let bundle = R.hostingBundle let name = "LaunchScreen" static func validate() throws { if #available(iOS 11.0, tvOS 11.0, *) { } } fileprivate init() {} } #endif fileprivate init() {} } #endif fileprivate init() {} } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/BottomPopupController/BottomPopupDismissAnimator.swift ================================================ // // DraggableDismissAnimator.swift // PresentationController // // Created by Emre on 11.09.2018. // Copyright © 2018 Emre. All rights reserved. // import UIKit final class BottomPopupDismissAnimator: NSObject, UIViewControllerAnimatedTransitioning { private unowned var attributesOwner: BottomPresentableViewController init(attributesOwner: BottomPresentableViewController) { self.attributesOwner = attributesOwner } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return attributesOwner.popupDismissDuration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let fromVC = transitionContext.viewController(forKey: .from)! let dismissFrame = CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.size.height), size: fromVC.view.frame.size) UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { fromVC.view.frame = dismissFrame }) { (_) in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/BottomPopupController/BottomPopupDismissInteractionController.swift ================================================ // // DraggableDismissInteractionController.swift // PresentationController // // Created by Emre on 11.09.2018. // Copyright © 2018 Emre. All rights reserved. // import UIKit protocol BottomPopupDismissInteractionControllerDelegate: class { func dismissInteractionPercentChanged(from oldValue: CGFloat, to newValue: CGFloat) } final class BottomPopupDismissInteractionController: UIPercentDrivenInteractiveTransition { private let kMinPercentOfVisiblePartToCompleteAnimation = CGFloat(0.5) private let kSwipeDownThreshold = CGFloat(1000) private weak var presentedViewController: BottomPresentableViewController? private weak var transitioningDelegate: BottomPopupTransitionHandler? private unowned var attributesDelegate: BottomPopupAttributesDelegate weak var delegate: BottomPopupDismissInteractionControllerDelegate? private var currentPercent: CGFloat = 0 { didSet { delegate?.dismissInteractionPercentChanged(from: oldValue, to: currentPercent) } } init(presentedViewController: BottomPresentableViewController?, attributesDelegate: BottomPopupAttributesDelegate) { self.presentedViewController = presentedViewController self.transitioningDelegate = presentedViewController?.transitioningDelegate as? BottomPopupTransitionHandler self.attributesDelegate = attributesDelegate super.init() preparePanGesture(in: presentedViewController?.view) } private func finishAnimation(withVelocity velocity: CGPoint) { if currentPercent > kMinPercentOfVisiblePartToCompleteAnimation || velocity.y > kSwipeDownThreshold { finish() } else { cancel() } } private func preparePanGesture(in view: UIView?) { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) presentedViewController?.view?.addGestureRecognizer(panGesture) } @objc private func handlePanGesture(_ pan: UIPanGestureRecognizer) { guard attributesDelegate.popupShouldBeganDismiss else { return } let translationY = pan.translation(in: presentedViewController?.view).y currentPercent = min(max(translationY/(presentedViewController?.view.frame.size.height ?? 0), 0), 1) switch pan.state { case .began: transitioningDelegate?.isInteractiveDismissStarted = true presentedViewController?.dismiss(animated: true, completion: nil) case .changed: update(currentPercent) default: let velocity = pan.velocity(in: presentedViewController?.view) transitioningDelegate?.isInteractiveDismissStarted = false finishAnimation(withVelocity: velocity) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/BottomPopupController/BottomPopupNavigationController.swift ================================================ // // BottomPopupNavigationController.swift // BottomPopup // // Created by Emre on 11.10.2018. // Copyright © 2018 Emre. All rights reserved. // import UIKit @objcMembers class BottomPopupNavigationController: UINavigationController, BottomPopupAttributesDelegate { private var transitionHandler: BottomPopupTransitionHandler? open weak var popupDelegate: BottomPopupDelegate? // MARK: Initializations override public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) initialize() } public override init(rootViewController: UIViewController) { super.init(rootViewController: rootViewController) initialize() } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) initialize() } open override func viewDidLoad() { super.viewDidLoad() transitionHandler?.notifyViewLoaded(withPopupDelegate: popupDelegate) popupDelegate?.bottomPopupViewLoaded() self.view.accessibilityIdentifier = popupViewAccessibilityIdentifier } override open func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) curveTopCorners() popupDelegate?.bottomPopupWillAppear() } open override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) popupDelegate?.bottomPopupDidAppear() } open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) popupDelegate?.bottomPopupWillDismiss() } open override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) popupDelegate?.bottomPopupDidDismiss() } //MARK: Private Methods private func initialize() { transitionHandler = BottomPopupTransitionHandler(popupViewController: self) transitioningDelegate = transitionHandler modalPresentationStyle = .custom } private func curveTopCorners() { let path = UIBezierPath(roundedRect: self.view.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: popupTopCornerRadius, height: 0)) let maskLayer = CAShapeLayer() maskLayer.frame = self.view.bounds maskLayer.path = path.cgPath self.view.layer.mask = maskLayer } //MARK: BottomPopupAttributesDelegate Variables open var popupHeight: CGFloat { return BottomPopupConstants.kDefaultHeight } open var popupTopCornerRadius: CGFloat { return BottomPopupConstants.kDefaultTopCornerRadius } open var popupPresentDuration: Double { return BottomPopupConstants.kDefaultPresentDuration } open var popupDismissDuration: Double { return BottomPopupConstants.kDefaultDismissDuration } open var popupShouldDismissInteractivelty: Bool { return BottomPopupConstants.dismissInteractively } open var popupDimmingViewAlpha: CGFloat { return BottomPopupConstants.kDimmingViewDefaultAlphaValue } open var popupShouldBeganDismiss: Bool { return BottomPopupConstants.shouldBeganDismiss } open var popupViewAccessibilityIdentifier: String { return BottomPopupConstants.defaultPopupViewAccessibilityIdentifier } } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/BottomPopupController/BottomPopupPresentAnimator.swift ================================================ // // DraggablePresentAnimator.swift // PresentationController // // Created by Emre on 11.09.2018. // Copyright © 2018 Emre. All rights reserved. // import UIKit final class BottomPopupPresentAnimator: NSObject, UIViewControllerAnimatedTransitioning { private unowned var attributesOwner: BottomPresentableViewController init(attributesOwner: BottomPresentableViewController) { self.attributesOwner = attributesOwner } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return attributesOwner.popupPresentDuration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let toVC = transitionContext.viewController(forKey: .to)! transitionContext.containerView.addSubview(toVC.view) let presentFrame = transitionContext.finalFrame(for: toVC) let initialFrame = CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.size.height), size: presentFrame.size) toVC.view.frame = initialFrame UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { toVC.view.frame = presentFrame }) { (_) in transitionContext.completeTransition(true) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/BottomPopupController/BottomPopupPresentationController.swift ================================================ // // BottomPopupPresentationController.swift // PresentationController // // Created by Emre on 11.09.2018. // Copyright © 2018 Emre. All rights reserved. // import UIKit @objcMembers final class BottomPopupPresentationController: UIPresentationController { private var dimmingView: UIView! private unowned var attributesDelegate: BottomPopupAttributesDelegate override var frameOfPresentedViewInContainerView: CGRect { get { return CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.size.height - attributesDelegate.popupHeight), size: CGSize(width: presentedViewController.view.frame.size.width, height: attributesDelegate.popupHeight)) } } private func changeDimmingViewAlphaAlongWithAnimation(to alpha: CGFloat) { guard let coordinator = presentedViewController.transitionCoordinator else { dimmingView.backgroundColor = UIColor.black.withAlphaComponent(alpha) return } coordinator.animate(alongsideTransition: { _ in self.dimmingView.backgroundColor = UIColor.black.withAlphaComponent(alpha) }) } init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, attributesDelegate: BottomPopupAttributesDelegate) { self.attributesDelegate = attributesDelegate super.init(presentedViewController: presentedViewController, presenting: presentingViewController) setupDimmingView() } override func containerViewWillLayoutSubviews() { presentedView?.frame = frameOfPresentedViewInContainerView } override func presentationTransitionWillBegin() { containerView?.insertSubview(dimmingView, at: 0) changeDimmingViewAlphaAlongWithAnimation(to: attributesDelegate.popupDimmingViewAlpha) } override func dismissalTransitionWillBegin() { changeDimmingViewAlphaAlongWithAnimation(to: 0) } @objc private func handleTap(_ tap: UITapGestureRecognizer) { guard attributesDelegate.popupShouldBeganDismiss else { return } presentedViewController.dismiss(animated: true, completion: nil) } @objc private func handleSwipe(_ swipe: UISwipeGestureRecognizer) { guard attributesDelegate.popupShouldBeganDismiss else { return } presentedViewController.dismiss(animated: true, completion: nil) } } private extension BottomPopupPresentationController { func setupDimmingView() { dimmingView = UIView() dimmingView.frame = CGRect(origin: .zero, size: UIScreen.main.bounds.size) dimmingView.backgroundColor = UIColor.black.withAlphaComponent(0) let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) swipeGesture.direction = [.down, .up] dimmingView.isUserInteractionEnabled = true [tapGesture, swipeGesture].forEach { dimmingView.addGestureRecognizer($0) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/BottomPopupController/BottomPopupTransitionHandler.swift ================================================ // // DraggableTransitioningDelegate.swift // PresentationController // // Created by Emre on 11.09.2018. // Copyright © 2018 Emre. All rights reserved. // import UIKit final class BottomPopupTransitionHandler: NSObject, UIViewControllerTransitioningDelegate { private let presentAnimator: BottomPopupPresentAnimator private let dismissAnimator: BottomPopupDismissAnimator private var interactionController: BottomPopupDismissInteractionController? private unowned var popupViewController: BottomPresentableViewController fileprivate weak var popupDelegate: BottomPopupDelegate? var isInteractiveDismissStarted = false init(popupViewController: BottomPresentableViewController) { self.popupViewController = popupViewController presentAnimator = BottomPopupPresentAnimator(attributesOwner: popupViewController) dismissAnimator = BottomPopupDismissAnimator(attributesOwner: popupViewController) } //MARK: Public func notifyViewLoaded(withPopupDelegate delegate: BottomPopupDelegate?) { self.popupDelegate = delegate if popupViewController.popupShouldDismissInteractivelty { interactionController = BottomPopupDismissInteractionController(presentedViewController: popupViewController, attributesDelegate: popupViewController) interactionController?.delegate = self } } //MARK: Specific animators func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return BottomPopupPresentationController(presentedViewController: presented, presenting: presenting, attributesDelegate: popupViewController) } func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return presentAnimator } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return dismissAnimator } func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return isInteractiveDismissStarted ? interactionController : nil } } extension BottomPopupTransitionHandler: BottomPopupDismissInteractionControllerDelegate { func dismissInteractionPercentChanged(from oldValue: CGFloat, to newValue: CGFloat) { popupDelegate?.bottomPopupDismissInteractionPercentChanged(from: oldValue, to: newValue) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/BottomPopupController/BottomPopupUtils.swift ================================================ // // BottomPopupUtils.swift // BottomPopup // // Created by Emre on 11.10.2018. // Copyright © 2018 Emre. All rights reserved. // import UIKit typealias BottomPresentableViewController = BottomPopupAttributesDelegate & UIViewController public protocol BottomPopupDelegate: AnyObject { func bottomPopupViewLoaded() func bottomPopupWillAppear() func bottomPopupDidAppear() func bottomPopupWillDismiss() func bottomPopupDidDismiss() func bottomPopupDismissInteractionPercentChanged(from oldValue: CGFloat, to newValue: CGFloat) } public extension BottomPopupDelegate { func bottomPopupViewLoaded() { } func bottomPopupWillAppear() { } func bottomPopupDidAppear() { } func bottomPopupWillDismiss() { } func bottomPopupDidDismiss() { } func bottomPopupDismissInteractionPercentChanged(from oldValue: CGFloat, to newValue: CGFloat) { } } public protocol BottomPopupAttributesDelegate: AnyObject { var popupHeight: CGFloat { get } var popupTopCornerRadius: CGFloat { get } var popupPresentDuration: Double { get } var popupDismissDuration: Double { get } var popupShouldDismissInteractivelty: Bool { get } var popupDimmingViewAlpha: CGFloat { get } var popupShouldBeganDismiss: Bool { get } var popupViewAccessibilityIdentifier: String { get } } public struct BottomPopupConstants { static let kDefaultHeight: CGFloat = 377.0 static let kDefaultTopCornerRadius: CGFloat = 10.0 static let kDefaultPresentDuration = 0.5 static let kDefaultDismissDuration = 0.5 static let dismissInteractively = true static let shouldBeganDismiss = true static let kDimmingViewDefaultAlphaValue: CGFloat = 0.5 static let defaultPopupViewAccessibilityIdentifier: String = "bottomPopupView" } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/BottomPopupController/BottomPopupViewController.swift ================================================ // // BottomPopupViewController.swift // Trendyol // // Created by Emre on 11.09.2018. // Copyright © 2018 Trendyol.com. All rights reserved. // import UIKit @objcMembers open class BottomPopupViewController: UIViewController, BottomPopupAttributesDelegate { private var transitionHandler: BottomPopupTransitionHandler? open weak var popupDelegate: BottomPopupDelegate? // MARK: Initializations override public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) initialize() } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) initialize() } open override func viewDidLoad() { super.viewDidLoad() transitionHandler?.notifyViewLoaded(withPopupDelegate: popupDelegate) popupDelegate?.bottomPopupViewLoaded() self.view.accessibilityIdentifier = popupViewAccessibilityIdentifier } open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) curveTopCorners() popupDelegate?.bottomPopupWillAppear() } open override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) popupDelegate?.bottomPopupDidAppear() } open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) popupDelegate?.bottomPopupWillDismiss() } open override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) popupDelegate?.bottomPopupDidDismiss() } //MARK: Private Methods private func initialize() { transitionHandler = BottomPopupTransitionHandler(popupViewController: self) transitioningDelegate = transitionHandler modalPresentationStyle = .custom } private func curveTopCorners() { let path = UIBezierPath(roundedRect: self.view.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: popupTopCornerRadius, height: 0)) let maskLayer = CAShapeLayer() maskLayer.frame = self.view.bounds maskLayer.path = path.cgPath self.view.layer.mask = maskLayer } //MARK: BottomPopupAttributesDelegate Variables open var popupHeight: CGFloat { return BottomPopupConstants.kDefaultHeight } open var popupTopCornerRadius: CGFloat { return BottomPopupConstants.kDefaultTopCornerRadius } open var popupPresentDuration: Double { return BottomPopupConstants.kDefaultPresentDuration } open var popupDismissDuration: Double { return BottomPopupConstants.kDefaultDismissDuration } open var popupShouldDismissInteractivelty: Bool { return BottomPopupConstants.dismissInteractively } open var popupDimmingViewAlpha: CGFloat { return BottomPopupConstants.kDimmingViewDefaultAlphaValue } open var popupShouldBeganDismiss: Bool { return BottomPopupConstants.shouldBeganDismiss } open var popupViewAccessibilityIdentifier: String { return BottomPopupConstants.defaultPopupViewAccessibilityIdentifier } } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/BottomPopupController/CSBottomPopupNavigationController.swift ================================================ // // CSBottomPopupNavigationController.swift // ConsensusStorage // // Created by Apple on 2021/10/25. // Copyright © 2021 Apple. All rights reserved. // import UIKit @objcMembers class CSBottomPopupNavigationController: BottomPopupNavigationController { @objc var showHeight: CGFloat = 500 @objc var topCornerRadius: CGFloat = 10 @objc var presentDuration: Double = 0.25 @objc var dismissDuration: Double = 0.25 @objc var shouldDismissInteractivelty: Bool = true override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } // Bottom popup attribute variables // You can override the desired variable to change appearance override var popupHeight: CGFloat { return showHeight } override var popupTopCornerRadius: CGFloat { return topCornerRadius } override var popupPresentDuration: Double { return presentDuration } override var popupDismissDuration: Double { return dismissDuration } override var popupShouldDismissInteractivelty: Bool { return true } override var popupDimmingViewAlpha: CGFloat { return BottomPopupConstants.kDimmingViewDefaultAlphaValue } } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/NavigationHandy/NSObject+BinAdd.h ================================================ // // NSObject+BinAdd.h // CommonElement // // Created by 熊彬 on 16/6/3. // Copyright © 2016年 熊彬. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface NSObject (BinAdd) #pragma mark - About Class ///============================================================================= /// @name About Class ///============================================================================= //类名 - (NSString *)className; + (NSString *)className; //父类名称 - (NSString *)superClassName; + (NSString *)superClassName; //实例属性字典 -(NSDictionary *)propertyDictionary; //属性名称列表 - (NSArray*)propertyKeys; + (NSArray *)propertyKeys; //属性详细信息列表 - (NSArray *)propertiesInfo; + (NSArray *)propertiesInfo; //格式化后的属性列表 + (NSArray *)propertiesWithCodeFormat; //方法列表 -(NSArray*)methodList; +(NSArray*)methodList; -(NSArray*)methodListInfo; //创建并返回一个指向所有已注册类的指针列表 + (NSArray *)registedClassList; //实例变量 + (NSArray *)instanceVariable; //协议列表 -(NSDictionary *)protocolList; + (NSDictionary *)protocolList; - (BOOL)hasPropertyForKey:(NSString*)key; - (BOOL)hasIvarForKey:(NSString*)key; #pragma mark - Swap method (Swizzling) ///============================================================================= /// @name Swap method (Swizzling) ///============================================================================= /** * 交换两个实例方法的实现. * @param originalSel Selector 1. * @param newSel Selector 2. * @return YES if swizzling succeed; otherwise, NO. */ + (BOOL)swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel; /** * 交换类方法的实现 * @param originalSel Selector 1. * @param newSel Selector 2. * @return YES if swizzling succeed; otherwise, NO. */ + (BOOL)swizzleClassMethod:(SEL)originalSel with:(SEL)newSel; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/NavigationHandy/NSObject+BinAdd.m ================================================ // // NSObject+BinAdd.m // CommonElement // // Created by 熊彬 on 16/6/3. // Copyright © 2016年 熊彬. All rights reserved. // #import "NSObject+BinAdd.h" #import #import @implementation NSObject (BinAdd) - (NSString *)className { return NSStringFromClass([self class]); } - (NSString *)superClassName { return NSStringFromClass([self superclass]); } + (NSString *)className { return NSStringFromClass([self class]); } + (NSString *)superClassName { return NSStringFromClass([self superclass]); } -(NSDictionary *)propertyDictionary { //创建可变字典 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; unsigned int outCount; objc_property_t *props = class_copyPropertyList([self class], &outCount); for(int i=0;i 0) { NSString *attributeStr = [NSString stringWithFormat:@"(%@)",[attribute componentsJoinedByString:@", "]]; [formatString appendString:attributeStr]; } //type NSString *type = [item objectForKey:@"type"]; if (type) { [formatString appendString:@" "]; [formatString appendString:type]; } //name NSString *name = [item objectForKey:@"name"]; if (name) { [formatString appendString:@" "]; [formatString appendString:name]; [formatString appendString:@";"]; } formatString; }); [array addObject:format]; } return array; } -(NSArray*)methodList{ u_int count; NSMutableArray *methodList = [NSMutableArray array]; Method *methods= class_copyMethodList([self class], &count); for (int i = 0; i < count ; i++) { SEL name = method_getName(methods[i]); NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding]; [methodList addObject:strName]; } free(methods); return methodList; } -(NSArray*)methodListInfo{ u_int count; NSMutableArray *methodList = [NSMutableArray array]; Method *methods= class_copyMethodList([self class], &count); for (int i = 0; i < count ; i++) { NSMutableDictionary *info = [NSMutableDictionary dictionary]; Method method = methods[i]; // IMP imp = method_getImplementation(method); SEL name = method_getName(method); // 返回方法的参数的个数 int argumentsCount = method_getNumberOfArguments(method); //获取描述方法参数和返回值类型的字符串 const char *encoding =method_getTypeEncoding(method); //取方法的返回值类型的字符串 const char *returnType =method_copyReturnType(method); NSMutableArray *arguments = [NSMutableArray array]; for (int index=0; index | The property defines a custom getter selector name. The name follows the G (for example, GcustomGetter,). S | The property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,). D | The property is dynamic (@dynamic). W | The property is a weak reference (__weak). P | The property is eligible for garbage collection. t | Specifies the type using old-style encoding. */ //R if ([attributeDictionary objectForKey:@"R"]) { [attributeArray addObject:@"readonly"]; } //C if ([attributeDictionary objectForKey:@"C"]) { [attributeArray addObject:@"copy"]; } //& if ([attributeDictionary objectForKey:@"&"]) { [attributeArray addObject:@"strong"]; } //N if ([attributeDictionary objectForKey:@"N"]) { [attributeArray addObject:@"nonatomic"]; } else { [attributeArray addObject:@"atomic"]; } //G if ([attributeDictionary objectForKey:@"G"]) { [attributeArray addObject:[NSString stringWithFormat:@"getter=%@", [attributeDictionary objectForKey:@"G"]]]; } //S if ([attributeDictionary objectForKey:@"S"]) { [attributeArray addObject:[NSString stringWithFormat:@"setter=%@", [attributeDictionary objectForKey:@"G"]]]; } //D if ([attributeDictionary objectForKey:@"D"]) { [result setObject:[NSNumber numberWithBool:YES] forKey:@"isDynamic"]; } else { [result setObject:[NSNumber numberWithBool:NO] forKey:@"isDynamic"]; } //W if ([attributeDictionary objectForKey:@"W"]) { [attributeArray addObject:@"weak"]; } //P if ([attributeDictionary objectForKey:@"P"]) { //TODO:P | The property is eligible for garbage collection. } //T if ([attributeDictionary objectForKey:@"T"]) { /* https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html c A char i An int s A short l A long l is treated as a 32-bit quantity on 64-bit programs. q A long long C An unsigned char I An unsigned int S An unsigned short L An unsigned long Q An unsigned long long f A float d A double B A C++ bool or a C99 _Bool v A void * A character string (char *) @ An object (whether statically typed or typed id) # A class object (Class) : A method selector (SEL) [array type] An array {name=type...} A structure (name=type...) A union bnum A bit field of num bits ^type A pointer to type ? An unknown type (among other things, this code is used for function pointers) */ NSDictionary *typeDic = @{@"c":@"char", @"i":@"int", @"s":@"short", @"l":@"long", @"q":@"long long", @"C":@"unsigned char", @"I":@"unsigned int", @"S":@"unsigned short", @"L":@"unsigned long", @"Q":@"unsigned long long", @"f":@"float", @"d":@"double", @"B":@"BOOL", @"v":@"void", @"*":@"char *", @"@":@"id", @"#":@"Class", @":":@"SEL", }; //TODO:An array NSString *key = [attributeDictionary objectForKey:@"T"]; id type_str = [typeDic objectForKey:key]; if (type_str == nil) { if ([[key substringToIndex:1] isEqualToString:@"@"] && [key rangeOfString:@"?"].location == NSNotFound) { type_str = [[key substringWithRange:NSMakeRange(2, key.length - 3)] stringByAppendingString:@"*"]; } else if ([[key substringToIndex:1] isEqualToString:@"^"]) { id str = [typeDic objectForKey:[key substringFromIndex:1]]; if (str) { type_str = [NSString stringWithFormat:@"%@ *",str]; } } else { type_str = @"unknow"; } } [result setObject:type_str forKey:@"type"]; } [result setObject:attributeArray forKey:@"attribute"]; return result; } + (NSString *)decodeType:(const char *)cString { if (!strcmp(cString, @encode(char))) return @"char"; if (!strcmp(cString, @encode(int))) return @"int"; if (!strcmp(cString, @encode(short))) return @"short"; if (!strcmp(cString, @encode(long))) return @"long"; if (!strcmp(cString, @encode(long long))) return @"long long"; if (!strcmp(cString, @encode(unsigned char))) return @"unsigned char"; if (!strcmp(cString, @encode(unsigned int))) return @"unsigned int"; if (!strcmp(cString, @encode(unsigned short))) return @"unsigned short"; if (!strcmp(cString, @encode(unsigned long))) return @"unsigned long"; if (!strcmp(cString, @encode(unsigned long long))) return @"unsigned long long"; if (!strcmp(cString, @encode(float))) return @"float"; if (!strcmp(cString, @encode(double))) return @"double"; if (!strcmp(cString, @encode(bool))) return @"bool"; if (!strcmp(cString, @encode(_Bool))) return @"_Bool"; if (!strcmp(cString, @encode(void))) return @"void"; if (!strcmp(cString, @encode(char *))) return @"char *"; if (!strcmp(cString, @encode(id))) return @"id"; if (!strcmp(cString, @encode(Class))) return @"class"; if (!strcmp(cString, @encode(SEL))) return @"SEL"; if (!strcmp(cString, @encode(BOOL))) return @"BOOL"; // NSDictionary *typeDic = @{@"c":@"char", // @"i":@"int", // @"s":@"short", // @"l":@"long", // @"q":@"long long", // @"C":@"unsigned char", // @"I":@"unsigned int", // @"S":@"unsigned short", // @"L":@"unsigned long", // @"Q":@"unsigned long long", // @"f":@"float", // @"d":@"double", // @"B":@"BOOL", // @"v":@"void", // @"*":@"char *", // @"@":@"id", // @"#":@"Class", // @":":@"SEL", // }; //@TODO: do handle bitmasks NSString *result = [NSString stringWithCString:cString encoding:NSUTF8StringEncoding]; // if ([typeDic objectForKey:result]) { // return [typeDic objectForKey:result]; // } if ([[result substringToIndex:1] isEqualToString:@"@"] && [result rangeOfString:@"?"].location == NSNotFound) { result = [[result substringWithRange:NSMakeRange(2, result.length - 3)] stringByAppendingString:@"*"]; } else { if ([[result substringToIndex:1] isEqualToString:@"^"]) { result = [NSString stringWithFormat:@"%@ *", [NSString decodeType:[[result substringFromIndex:1] cStringUsingEncoding:NSUTF8StringEncoding]]]; } } return result; } + (BOOL)swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel { Method originalMethod = class_getInstanceMethod(self, originalSel); Method newMethod = class_getInstanceMethod(self, newSel); if (!originalMethod || !newMethod) return NO; class_addMethod(self, originalSel, class_getMethodImplementation(self, originalSel), method_getTypeEncoding(originalMethod)); class_addMethod(self, newSel, class_getMethodImplementation(self, newSel), method_getTypeEncoding(newMethod)); method_exchangeImplementations(class_getInstanceMethod(self, originalSel), class_getInstanceMethod(self, newSel)); return YES; } + (BOOL)swizzleClassMethod:(SEL)originalSel with:(SEL)newSel { Class class = object_getClass(self); Method originalMethod = class_getInstanceMethod(class, originalSel); Method newMethod = class_getInstanceMethod(class, newSel); if (!originalMethod || !newMethod) return NO; method_exchangeImplementations(originalMethod, newMethod); return YES; } @end ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/NavigationHandy/UINavigationController+Extensions.h ================================================ // // UINavigationController+Extensions.h // IDCMWallet // // Created by IDCM on 2018/5/21. // Copyright © 2018年 IDCM. All rights reserved. // #import @interface UINavigationController (Extensions) /** 返回对应控制器 根据类名 @param vcClassName 根据类名返回vc */ - (UIViewController *)getViewControllerByname:(NSString *)vcClassName; /** 返回对应控制器 根据index @param index 根据index返回vc */ - (UIViewController *)getViewControllerByIndex:(NSInteger )index; /** 返回对应的层 从前往后 @param index 从前往后第几个 如果超过childs长度则返回root */ - (void)popBackViewControllerToIndex:(NSInteger)index; /** 返回对应的层 从后往前 @param index 从后往前第几个 如果超过childs长度则返回root */ - (void)popBackViewControllerFromIndex:(NSInteger)index; /** 返回对应的层 根据类名 @param vcClassName 根据类名返回到对应页面 */ - (void)popBackViewController:(NSString *)vcClassName; - (void)removeControllerWithName:(NSString *)name; - (void)removeControllerFromIndex:(NSInteger)index; - (void)removeControllerToIndex:(NSInteger)index; /** 判断是否包含类 @param vcClassName 类名 */ - (BOOL)containViewController:(NSString *)vcClassName; /** 替换当前栈堆 控制器位置 @param index 替换的位置 index @param replaceVC 替换的控制器 */ -(void)repleaseControllerAtIndex:(NSInteger )index withController:(UIViewController *)replaceVC; @end // UIViewController+BackButtonHandler.h @protocol BackButtonHandlerProtocol @optional // 重写下面的方法以拦截导航栏返回按钮点击事件,返回 YES 则 pop,NO 则不 pop -(BOOL)navigationShouldPopOnBackButton; @end @interface UIViewController (BackButtonHandler) @end ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/NavigationHandy/UINavigationController+Extensions.m ================================================ // // UINavigationController+Extensions.m // IDCMWallet // // Created by IDCM on 2018/5/21. // Copyright © 2018年 IDCM. All rights reserved. // #import "UINavigationController+Extensions.h" #import "NSObject+BinAdd.h" @implementation UINavigationController (Extensions) - (void)popBackViewController:(NSString *)vcClassName{ UIViewController *vc; for (UIViewController *childVC in self.childViewControllers) { if ([childVC.className isEqualToString:vcClassName]) { vc = childVC; break; } //swift class name 会带上项目名称 NSArray *arr = [childVC.className componentsSeparatedByString:@"."]; if (arr.lastObject != nil && [arr.lastObject isEqualToString:vcClassName]) { vc = childVC; break; } } if (vc) { [self popToViewController:vc animated:true]; } } - (void)popBackViewControllerToIndex:(NSInteger)index{ if (index > self.childViewControllers.count) { return; } UIViewController *vc = [self.childViewControllers objectAtIndex:index]; [self popToViewController:vc animated:true]; } - (void)popBackViewControllerFromIndex:(NSInteger)index{ if (index > self.childViewControllers.count) { return; } UIViewController *vc = [self.childViewControllers objectAtIndex:self.childViewControllers.count - index - 1]; [self popToViewController:vc animated:true]; } - (UIViewController *)getViewControllerByname:(NSString *)vcClassName{ UIViewController *vc; for (UIViewController *childVC in self.childViewControllers) { NSLog(@"childVC.className-------------------->%@",childVC.className); if ([childVC.className containsString:vcClassName]) { vc = childVC; break; } } if (vc) { return vc; } return nil; } - (UIViewController *)getViewControllerByIndex:(NSInteger)index{ if (index > self.childViewControllers.count) { return nil; } UIViewController *vc = [self.childViewControllers objectAtIndex:index]; return vc; } - (void)removeControllerWithName:(NSString *)name { UIViewController *controller = [self getViewControllerByname:name]; if (controller) { NSMutableArray *arr = self.viewControllers.mutableCopy; [arr removeObject:controller]; [self setViewControllers:arr.copy]; } } - (void)removeControllerFromIndex:(NSInteger)index { if (index >= 0 && index < self.viewControllers.count) { NSMutableArray *arr = self.viewControllers.mutableCopy; [arr removeObjectAtIndex:index]; [self setViewControllers:arr.copy]; } } - (void)removeControllerToIndex:(NSInteger)index { if (index >= 0 && index < self.viewControllers.count) { NSMutableArray *arr = self.viewControllers.mutableCopy; [arr removeObjectAtIndex:(arr.count - 1) - index]; [self setViewControllers:arr.copy]; } } -(void)repleaseControllerAtIndex:(NSInteger)index withController:(UIViewController *)replaceVC{ if (index >= 0 && index < self.viewControllers.count) { replaceVC.hidesBottomBarWhenPushed = index!= 0?YES:NO; NSMutableArray *arr = self.viewControllers.mutableCopy; [arr replaceObjectAtIndex:index withObject:replaceVC]; [self setViewControllers:arr.copy]; } } - (BOOL)containViewController:(NSString *)vcClassName{ UIViewController *vc; for (UIViewController *childVC in self.childViewControllers) { if ([childVC.className isEqualToString:vcClassName]) { vc = childVC; break; } } if (vc) { return YES; } return NO; } @end @implementation UIViewController (BackButtonHandler) @end @implementation UINavigationController (ShouldPopOnBackButton) - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item { if([self.viewControllers count] < [navigationBar.items count]) { return YES; } BOOL shouldPop = YES; UIViewController* vc = [self topViewController]; if([vc respondsToSelector:@selector(navigationShouldPopOnBackButton)]) { shouldPop = [vc navigationShouldPopOnBackButton]; } if(shouldPop) { dispatch_async(dispatch_get_main_queue(), ^{ [self popViewControllerAnimated:YES]; }); } else { // 取消 pop 后,复原返回按钮的状态 for(UIView *subview in [navigationBar subviews]) { if(0. < subview.alpha && subview.alpha < 1.) { [UIView animateWithDuration:.25 animations:^{ subview.alpha = 1.; }]; } } } return NO; } @end ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/RxActivityIndicator/ActivityIndicator.swift ================================================ // // ActivityIndicator.swift // RxExample // // Created by Krunoslav Zaher on 10/18/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if !RX_NO_MODULE import RxSwift import RxCocoa #endif private struct ActivityToken: ObservableConvertibleType, Disposable { private let _source: Observable private let _dispose: Cancelable init(source: Observable, disposeAction: @escaping () -> Void) { _source = source _dispose = Disposables.create(with: disposeAction) } func dispose() { _dispose.dispose() } func asObservable() -> Observable { return _source } } /** Enables monitoring of sequence computation. If there is at least one sequence computation in progress, `true` will be sent. When all activities complete `false` will be sent. */ public class ActivityIndicator: SharedSequenceConvertibleType { public typealias Element = Bool public typealias SharingStrategy = DriverSharingStrategy private let _lock = NSRecursiveLock() private let _relay = BehaviorRelay(value: 0) private let _loading: SharedSequence public init() { _loading = _relay.asDriver() .map { $0 > 0 } .distinctUntilChanged() } fileprivate func trackActivityOfObservable(_ source: Source) -> Observable { return Observable.using({ () -> ActivityToken in self.increment() return ActivityToken(source: source.asObservable(), disposeAction: self.decrement) }, observableFactory: { value in return value.asObservable() }) } private func increment() { _lock.lock() _relay.accept(_relay.value + 1) _lock.unlock() } private func decrement() { _lock.lock() _relay.accept(_relay.value - 1) _lock.unlock() } public func asSharedSequence() -> SharedSequence { return _loading } } extension ObservableConvertibleType { public func trackActivity(_ activityIndicator: ActivityIndicator) -> Observable { return activityIndicator.trackActivityOfObservable(self) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Thirdparty/RxErrorTracker/ErrorTracker.swift ================================================ // // Created by sergdort on 03/02/2017. // Copyright (c) 2017 sergdort. All rights reserved. // import Foundation import RxSwift import RxCocoa final class ErrorTracker: SharedSequenceConvertibleType { typealias SharingStrategy = DriverSharingStrategy private let _subject = PublishSubject() func trackError(from source: O) -> Observable { return source.asObservable().do(onError: onError) } func asSharedSequence() -> SharedSequence { return _subject.asObservable().asDriverOnErrorJustComplete() } func asObservable() -> Observable { return _subject.asObservable() } private func onError(_ error: Error) { _subject.onNext(error) } deinit { _subject.onCompleted() } } extension ObservableConvertibleType { func trackError(_ errorTracker: ErrorTracker) -> Observable { return errorTracker.trackError(from: self) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/DataBase/WCDBManager/DBQuery/FYDBQueryHelper.swift ================================================ // // FYDBQueryHelper.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/27. // Copyright © 2019 Jett. All rights reserved. // import UIKit import WCDBSwift class FYDBQueryHelper: NSObject { /// 操作单利 static let shared = FYDBQueryHelper.init() override init() { super.init() } // MARK: - 消息缓存处理 /// 添加当前消息 /// - Parameter objects: 消息实体 func insertFromMessage(_ object: FYMessageItem) { WCDataBaseManager.shared.insertToDb(objects: [object], intoTable:kTABLE.message) } /// 查询对应的消息列表 /// - Parameter chatId: 会话id func qureyFromMessagesWithChatId(_ chatId: Int) -> [FYMessageItem] { var sesstions: [FYMessageItem] = [] let query = FYMessageItem.Properties.chatId == chatId if let dbData = WCDataBaseManager.shared.qureyObjectsFromDb(fromTable: kTABLE.message, cls: FYMessageItem.self, where: query) { sesstions.append(contentsOf: dbData) } return sesstions } /// 删除单个消息 /// - Parameter messageId: 消息id func deleteFromMessageId(_ messageId: Int) { let query = FYMessageItem.Properties.messageId == messageId WCDataBaseManager.shared.deleteFromDb(fromTable: kTABLE.message, where: query) } /// 更新某个聊天体 /// - Parameters: /// - message: 消息 /// - messageId: 消息id func updateChatWithMsgId(_ message: FYMessageItem, messageId: Int) { let query = FYMessageItem.Properties.messageId == messageId WCDataBaseManager.shared.updateToDb(table: kTABLE.chat, on: FYMessageChatModel.Properties.all, with: message, where: query) } /// 更新单个消息 /// - Parameter messageId: 消息id func updateMessageWithMsgId(message: FYMessageItem, messageId: Int) { let query = FYMessageItem.Properties.messageId == messageId WCDataBaseManager.shared.updateToDb(table: kTABLE.message, on: FYMessageItem.Properties.all, with: message, where: query) } /// 查询单个消息 /// - Parameter messageId: 消息id func queryMessageWithMsgId(_ messageId: Int) -> FYMessageItem? { let query = FYMessageItem.Properties.messageId == messageId return WCDataBaseManager.shared.qureyObjectFromDb(fromTable: kTABLE.message, cls: FYMessageItem.self, where: query) } /// 根据聊天类型删除消息列表 /// - Parameter chatType: (聊天类型:1:单聊;2:群聊) func deleteFromMessagesWithType(_ chatType: Int) { let query = FYMessageItem.Properties.chatType == chatType WCDataBaseManager.shared.deleteFromDb(fromTable: kTABLE.message, where: query) } /// 删除对应的消息列表 /// - Parameter chatId: 会话id func deleteFromMesssageWithId(_ chatId: Int) { let query = FYMessageItem.Properties.chatId == chatId WCDataBaseManager.shared.deleteFromDb(fromTable: kTABLE.message, where: query) } /// 查询当前的消息会话列表 func qureyFromLastSesstions() -> [FYMessageItem] { let chats = FYDBQueryHelper.shared.queryFromAllChats() var sesstions: [FYMessageItem] = [] for chat in chats { if let chatId = chat.uid { let messages = FYDBQueryHelper.shared.qureyFromMessagesWithChatId(chatId) if messages.count > 0 { if let model = messages.last { model.name = chat.name model.nickName = chat.nickName //备注名称 model.avatar = chat.avatar model.chatType = chat.chatType model.unReadCount = chat.unReadCount sesstions.append(model) //获取最后一条消息 } } } } // 排序处理 return sesstions.sorted { return $0.date?.stringToTimeStamp().doubleValue ?? 0 >= $1.date?.stringToTimeStamp().doubleValue ?? 0 } } /// 获取当前的未读消息总数 func queryFromSesstionsUnReadCount() -> Int { var badge: Int = 0 let chats = FYDBQueryHelper.shared.queryFromAllChats() for chat in chats { if let unReadCount = chat.unReadCount { badge += unReadCount } } return badge } // MARK: - 用户或者群列表缓存处理 /// 添加聊天用户或者群 /// - Parameter chat: 用户或者群 func insertFromChat(_ chat: FYMessageChatModel) { WCDataBaseManager.shared.insertToDb(objects: [chat], intoTable:kTABLE.chat) } /// 更新好友群信息 /// - Parameters: /// - chat: 好友或者群 /// - uid: 好友或群id func updateFromChatModel(_ chat: FYMessageChatModel, uid: Int) { let query = FYMessageChatModel.Properties.uid == uid WCDataBaseManager.shared.updateToDb(table: kTABLE.chat, on: FYMessageChatModel.Properties.all, with: chat, where: query) } /// 删除所有单聊用户或者群聊 /// - Parameter chatType: (聊天类型:1:单聊;2:群聊) func deleteFromChatsWithType(_ chatType: Int) { let query = FYMessageChatModel.Properties.chatType == chatType WCDataBaseManager.shared.deleteFromDb(fromTable: kTABLE.chat, where: query) } /// 删除单个好友或者群 /// - Parameter uid: 好友或群id func deleteFromChatWithId(_ uid: Int) { let query = FYMessageChatModel.Properties.uid == uid WCDataBaseManager.shared.deleteFromDb(fromTable: kTABLE.chat, where: query) } /// 查询当前好友或者群列表 /// - Parameter chatType: 聊天类型 func qureyFromChatsWithType(_ chatType: Int) -> [FYMessageChatModel] { var sesstions: [FYMessageChatModel] = [] let query = FYMessageChatModel.Properties.chatType == chatType if let dbData = WCDataBaseManager.shared.qureyObjectsFromDb(fromTable: kTABLE.chat, cls: FYMessageChatModel.self, where: query) { sesstions.append(contentsOf: dbData) } return sesstions } /// 查询当前所有好友或者群列表 func queryFromAllChats() -> [FYMessageChatModel] { var sesstions: [FYMessageChatModel] = [] let query = FYMessageChatModel.Properties.all.count if let dbData = WCDataBaseManager.shared.qureyObjectsFromDb(fromTable: kTABLE.chat, cls: FYMessageChatModel.self, where: query) { sesstions.append(contentsOf: dbData) } return sesstions } /// 根据uid查询当前好友所有聊天记录 func queryChatFromAllChats(_ uid: Int) -> [FYMessageChatModel] { var sesstions: [FYMessageChatModel] = [] let query = FYMessageChatModel.Properties.uid == uid if let dbData = WCDataBaseManager.shared.qureyObjectsFromDb(fromTable: kTABLE.chat, cls: FYMessageChatModel.self, where: query) { sesstions.append(contentsOf: dbData) } return sesstions } /// 查询单个好友或者群 /// - Parameter uid: 好友或群ID func qureyFromChatId(_ uid: Int) -> FYMessageChatModel { var chat = FYMessageChatModel() let query = FYMessageChatModel.Properties.uid == uid if let dbData = WCDataBaseManager.shared.qureyObjectsFromDb(fromTable: kTABLE.chat, cls: FYMessageChatModel.self, where: query) { if dbData.count > 0 { chat = dbData.map{ $0 }.first! } } return chat } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/DataBase/WCDBManager/DBQuery/FYMessageUserModel.swift ================================================ // // FYMessageUserModel.swift // FY-JetChat // // Created by fangyuan on 2019/12/14. // Copyright © 2019 Jett. All rights reserved. // import UIKit import WCDBSwift class FYMessageUserModel: FYMessageBaseModel, TableCodable { /// 主键自增id var identifier: Int? = nil /// 用户id var uid: Int? = nil /// 用户名称 var name: String? = nil /// 用户头像 var avatar: String? = nil /// 备注名(昵称) var nickName: String? = nil enum CodingKeys: String, CodingTableKey { typealias Root = FYMessageUserModel static let objectRelationalMapping = TableBinding(CodingKeys.self) case identifier = "id" case uid case name case avatar case nickName //Column constraints for primary key, unique, not null, default value and so on. It is optional. static var columnConstraintBindings: [CodingKeys: ColumnConstraintBinding]? { return [ .identifier: ColumnConstraintBinding(isPrimary: true, isAutoIncrement: true) ] } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/DataBase/WCDataBaseManager.swift ================================================ // // WCDataBaseManager.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/6. // Copyright © 2019 Jett. All rights reserved. // import UIKit import Foundation import WCDBSwift fileprivate struct WCDataBasePath { let dbPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last! + "/WCDB/FYIM.db" } class WCDataBaseManager: NSObject { static let shared = WCDataBaseManager() let dataBasePath = URL(fileURLWithPath: WCDataBasePath().dbPath) var dataBase: Database? private override init() { super.init() dataBase = createDb() } ///创建库 private func createDb() -> Database { debugPrint("数据库路径==\(dataBasePath.absoluteString)") return Database(withFileURL: dataBasePath) } ///创建表 func createTable(table: String, of ttype:T.Type) -> Void { do { try dataBase?.create(table: table, of:ttype) } catch let error { debugPrint("create table error \(error.localizedDescription)") } } ///插入 func insertToDb(objects: [T], intoTable table: String) -> Void { do { try dataBase?.insert(objects: objects, intoTable: table) } catch let error { debugPrint(" insert obj error \(error.localizedDescription)") } } ///修改 func updateToDb(table: String, on propertys:[PropertyConvertible],with object:T,where condition: Condition? = nil) -> Void { do { try dataBase?.update(table: table, on: propertys, with: object, where: condition) } catch let error { debugPrint(" update obj error \(error.localizedDescription)") } } ///删除 func deleteFromDb(fromTable: String, where condition: Condition? = nil) -> Void { do { try dataBase?.delete(fromTable: fromTable, where:condition) } catch let error { debugPrint("delete error \(error.localizedDescription)") } } ///查询多个 func qureyObjectsFromDb(fromTable: String, cls cName: T.Type, where condition: Condition? = nil, orderBy orderList:[OrderBy]? = nil) -> [T]? { do { let allObjects: [T] = try (dataBase?.getObjects(fromTable: fromTable, where:condition, orderBy:orderList))! debugPrint("\(allObjects)"); return allObjects } catch let error { debugPrint("no data find \(error.localizedDescription)") } return nil } ///查询单个 func qureyObjectFromDb(fromTable: String, cls cName: T.Type, where condition: Condition? = nil, orderBy orderList:[OrderBy]? = nil) -> T? { do { let object: T = try (dataBase?.getObject(fromTable: fromTable, where:condition, orderBy:orderList))! debugPrint("\(object)"); return object } catch let error { debugPrint("no data find \(error.localizedDescription)") } return nil } ///删除数据表 func dropTable(table: String) -> Void { do { try dataBase?.drop(table: table) } catch let error { debugPrint("drop table error \(error)") } } /// 删除所有与该数据库相关的文件 func removeDbFile() -> Void { do { try dataBase?.close(onClosed: { try dataBase?.removeFiles() }) } catch let error { debugPrint("not close db \(error)") } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/DataBase/WCDataBaseTable.swift ================================================ // // WCDataBaseTable.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/6. // Copyright © 2019 Jett. All rights reserved. // 本地缓存数据库表名称 import Foundation typealias kTABLE = WCDataBaseTable /// 数据库 - 表名称 enum WCDataBaseTable: String { case chatTable = "chatTable" case messageTable = "messageTable" case sessionTable = "sessionTable" } extension WCDataBaseTable { static var chat : String { get { return self.chatTable.rawValue } } static var message : String { get { return self.messageTable.rawValue } } static var session : String { get { return self.sessionTable.rawValue } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/FPSLabel/FPSLabel.swift ================================================ // // FPSLabel.swift // FPSDemo // // Created by just so so on 2019/10/7. // Copyright © 2019 bruce yao. All rights reserved. // import UIKit class FPSLabel: UILabel { //CADisplayLink fileprivate var link: CADisplayLink? fileprivate var count: UInt = 0 fileprivate var lastTime: TimeInterval = 0 override init(frame: CGRect) { super.init(frame: frame) ///样式 layer.cornerRadius = 5 layer.masksToBounds = true backgroundColor = UIColor.init(white: 0, alpha: 0.7) font = UIFont.init(name: "Menlo", size: 14) self.textAlignment = .center ///防止循环引用 link = CADisplayLink.init(target: WeakProxy.init(self), selector: #selector(tick(_:))) ///main runloop 添加到 link?.add(to: RunLoop.main, forMode: .common) } deinit { link?.invalidate() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } ///滴答滴答 @objc fileprivate func tick(_ link: CADisplayLink) { if lastTime == 0 { lastTime = link.timestamp return } count = count + 1 let delta = link.timestamp - lastTime if delta < 1 { return } lastTime = link.timestamp; let fps = Double(count) / delta count = 0 let progress = fps / 60.0 let color = UIColor.init(hue: CGFloat(0.27 * (progress - 0.2)), saturation: 1, brightness: 0.9, alpha: 1) self.textColor = color self.text = String.init(format: "%.0lf FPS", round(fps)) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/FPSLabel/WeakProxy.h ================================================ // // WeakProxy.h // FPSDemo // // Created by just so so on 2019/10/7. // Copyright © 2019 bruce yao. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface WeakProxy : NSProxy + (instancetype)proxyWith:(id)target; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/FPSLabel/WeakProxy.m ================================================ // // WeakProxy.m // FPSDemo // // Created by just so so on 2019/10/7. // Copyright © 2019 bruce yao. All rights reserved. //链接:https://blog.csdn.net/weixin_38735568/article/details/101772108 #import "WeakProxy.h" @interface WeakProxy() @property (nonatomic, weak) id target; @end @implementation WeakProxy ///类方法 初始化 + (instancetype)proxyWith:(id)target { WeakProxy *proxy = [WeakProxy alloc]; proxy.target = target; return proxy; } - (id)forwardingTargetForSelector:(SEL)aSelector { return self.target; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [NSObject instanceMethodSignatureForSelector:@selector(init)]; } - (void)forwardInvocation:(NSInvocation *)invocation { void *null = NULL; [invocation getReturnValue:&null]; } @end ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/FileSizeManager/FYFileSizeManager.swift ================================================ // // FYFileSizeManager.swift // FY-JetChat // // Created by Jett on 2022/4/28. // Copyright © 2022 Jett. All rights reserved. // import UIKit import Kingfisher import YBImageBrowser import SDWebImage class FYFileSizeManager: NSObject { static let manager = FYFileSizeManager() /// 显示缓存大小 public func cacheSize() -> String { guard let cachePath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first else { return "0M" } return String(format: "%.2fM", folderSize(filePath: cachePath)) } /// 计算单个文件的大小 public func fileSize(filePath: String) -> UInt64 { let manager = FileManager.default if manager.fileExists(atPath: filePath) { do { let attr = try manager.attributesOfItem(atPath: filePath) let size = attr[FileAttributeKey.size] as! UInt64 return size } catch { print("error :\(error)") return 0 } } return 0 } /// 遍历文件夹,返回多少M public func folderSize(filePath: String) -> CGFloat { let folderPath = filePath as NSString let manager = FileManager.default if manager.fileExists(atPath: filePath) { let childFilesEnumerator = manager.enumerator(atPath: filePath) var fileName = "" var folderSize: UInt64 = 0 while childFilesEnumerator?.nextObject() != nil { guard let nextObject = childFilesEnumerator?.nextObject() as? String else { return 0 } fileName = nextObject let fileAbsolutePath = folderPath.strings(byAppendingPaths: [fileName]) folderSize += fileSize(filePath: fileAbsolutePath[0]) } return CGFloat(folderSize) / (1024.0 * 1024.0) } return 0 } /// 清除缓存 func clearCache() { let cachPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] as NSString let files = FileManager.default.subpaths(atPath: cachPath as String) for p in files! { let path = cachPath.appendingPathComponent(p) if FileManager.default.fileExists(atPath: path) { do { try FileManager.default.removeItem(atPath: path) } catch { print("error:\(error)") } } } } /// 删除沙盒里的文件 public func deleteFile(filePath: String) { let manager = FileManager.default let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] as NSString let uniquePath = path.appendingPathComponent(filePath) if manager.fileExists(atPath: uniquePath) { do { try FileManager.default.removeItem(atPath: uniquePath) } catch { print("error:\(error)") } } } /// 获取图片缓存 public func imageCacheSize( _ callback: @escaping (String) -> ()) { let cache = ImageCache.default cache.diskStorage.config.sizeLimit = UInt(200 * 1024 * 1024) cache.diskStorage.config.expiration = .days(15) cache.calculateDiskStorageSize { result in switch result { case .success(let size): var dataSize : String{ guard size >= 1024 else { return "\(size)bytes" } guard size >= 1048576 else { return "\(size / 1024)K" } guard size >= 1073741824 else { return "\(size / 1048576)M" } return "\(size / 1073741824)G" } callback(dataSize) case .failure(let error): print("统计图片缓存失败", error) callback("0M") } } } /// 清除图片缓存 public func clearImageCaches(completion handler: (()->())?) { clearCache() SDImageCachesManager.shared.clear(with: .all) { handler?() } let cache = KingfisherManager.shared.cache cache.clearMemoryCache()//清理网络缓存 cache.cleanExpiredDiskCache()//清理过期的,或者超过硬盘限制大小的 cache.clearDiskCache { handler?() } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/FileSizeManager/FYUserDefaultManager.swift ================================================ // // FYUserDefaultManager.swift // FY-IMChat // // Created by Jett on 2022/5/7. // Copyright © 2022 Jett. All rights reserved. // import UIKit import Foundation class FYUserDefaultManager: NSObject { /// 存储当前值 /// - Parameters: /// - object: 需要存储的数据 /// - key: 存储数据对应的key class func saveObject(_ object: Any, key: String) { UserDefaults.standard.setValue(object, forKey: key) UserDefaults.standard.synchronize() } /// 获取已存储的值 /// - Parameter key: 已存储数据对应的key class func readObjectForKey(_ key: String) -> Any? { return UserDefaults.standard.value(forKey: key) } // MARK: - Widget class func saveWidgetObject(_ object: Any, widgetKey: String = "widgetKey", suiteName: String = "group.com.jetchat.2022.JetChatWidget") { let userDefaults = UserDefaults(suiteName: suiteName) userDefaults?.set(object, forKey: widgetKey) userDefaults?.synchronize() } class func readWidgetObject(_ widgetKey: String = "widgetKey", suiteName: String = "group.com.jetchat.2022.JetChatWidget") -> Any? { let userDefaults = UserDefaults(suiteName: suiteName) if let object = userDefaults?.value(forKey: widgetKey) as? Any { return object } return nil } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/Helpers/CountDownHandy.swift ================================================ // // LWCommonClock.swift // FY-JetChat // // Created by iOS.Jet on 2019/3/6. // Copyright © 2019 Jett. All rights reserved. // import UIKit @available(iOS 10.0, *) class CountDownHandy: NSObject { /// 默认的短信验证码发送倒计时(60s) /// /// - Parameters: /// - button: 按钮 class func beginClockSMS(_ button: UIButton) { CountDownHandy().startClockWithButton(button, time: 120) } /// 多个按钮控制短信验证码发送倒计时(60s) /// /// - Parameters: /// - button1: 按钮1 /// - button2: 按钮2 class func setGreyClockSMS(_ button1: UIButton, button2: UIButton) { CountDownHandy().startGrayWithButton(button1, button2: button2, time: 60) } /// 短信验证码发送倒计时 /// /// - Parameters: /// - button: 按钮 /// - time: 总倒计时长 class func beginClockTime(_ button: UIButton, time: Int) { guard time > 0 else { return } CountDownHandy().startClockWithButton(button, time: time) } /// 开始倒计时 func startClockWithButton(_ button: UIButton, time: Int) { let oldDate = Date() var newTime = time let codeTimer = DispatchSource.makeTimerSource(flags: .init(rawValue: 0), queue: DispatchQueue.global()) codeTimer.schedule(deadline: .now(), repeating: .milliseconds(1000)) //此处方法与Swift 3.0 不同 codeTimer.setEventHandler { // 停止计时 if newTime <= 0 { codeTimer.cancel() DispatchQueue.main.async { button.isEnabled = true //button.setTitle(Lca.c_register_create_send.rLocalized(), for: .normal) } return } else{ let newDate = Date() let timeInterva = newDate.timeIntervalSince(oldDate) var calTime = time - Int(timeInterva) if calTime <= 0{ calTime = 0 } DispatchQueue.main.async { button.setTitle("\(calTime)s", for: .normal) button.isEnabled = false } if calTime <= 1{ newTime = 1 } newTime = newTime - 1 } } /// 开启定时器 codeTimer.activate() } /// 开始置灰色倒计时 func startGrayWithButton(_ button1: UIButton, button2: UIButton, time: Int) { var newTime = time let codeTimer = DispatchSource.makeTimerSource(flags: .init(rawValue: 0), queue: DispatchQueue.global()) codeTimer.schedule(deadline: .now(), repeating: .milliseconds(1000)) //此处方法与Swift 3.0 不同 codeTimer.setEventHandler { newTime = newTime - 1 DispatchQueue.main.async { button1.isEnabled = false button2.isEnabled = false } // 停止计时 if newTime < 0 { codeTimer.cancel() DispatchQueue.main.async { button1.isEnabled = true button2.isEnabled = true //button1.setTitle(Lca.c_register_create_send.rLocalized(), for: .normal) } return } DispatchQueue.main.async { button1.setTitle("\(newTime)s", for: .normal) } } /// 开启定时器 codeTimer.activate() } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/Helpers/LanguageManager.swift ================================================ // // LWLanguageManager.swift // FY-JetChat // // Created by iOS.Jet on 2019/3/4. // Copyright © 2019 Jett. All rights reserved. // import UIKit import Foundation import Localize_Swift import Rswift public enum kLanguageType: String { case kEnglish = "en" case kChinese = "zh-Hans" static let allLanguages = [kEnglish, kChinese] static let alllocalizedStr = allLanguages.map { (type) -> String in return type.rawValue } func serverLanguage() -> String { switch self { case .kEnglish: return "en" default: return "cn" } } func getLanguageRaw() -> String { switch self { case .kEnglish: return "English" default: return "简体中文" } } } class LanguageManager: NSObject { /// 单利 static let manager = LanguageManager() /// 当前已选语言 var selectedLanguage = kLanguageType(rawValue: "zh-Hans") /// 当前所有语言 var currentLanguages: [String] { return ["简体中文".rLocalized(), "英文".rLocalized()] } override init() {} /// 初始化设置 func initConfig() { let languageCode = Localize.defaultLanguage() let language = UserDefaults.standard.string(forKey: kAppLanguageUserDefaultsKey) ?? languageCode self.selectedLanguage = kLanguageType.init(rawValue: language) Localize.setCurrentLanguage(language) } /// 切换语言 /// /// - Parameter type: 语言类型 func setCurrentLanguage(_ languageType: kLanguageType) { if languageType == self.selectedLanguage { return } UserDefaults.standard.set(languageType.rawValue, forKey: kAppLanguageUserDefaultsKey) UserDefaults.standard.synchronize() LanguageManager.manager.selectedLanguage = languageType Localize.setCurrentLanguage(languageType.rawValue) // rest restRootController() } // 切换根控制器 private func restRootController() { let tabBar = FYBaseTabBarController() UIApplication.shared.keyWindow?.rootViewController = tabBar UIApplication.shared.keyWindow?.makeKeyAndVisible() // FPS AppDelegate.app.setupFPSStatus() } } // MARK: - R.string.localizable extension String { /// r优化的国际化语言 /// /// - Returns: 对应国际化语言 func rLocalized() -> String { return self.localized() } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/Cell/ChatAppleEmojiCell.swift ================================================ // // ChatAppleEmojiCell.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/18. // Copyright © 2019 Jett. All rights reserved. // import UIKit class ChatAppleEmojiCell: UICollectionViewCell { // MARK: - lazy var var model: ChatEmoticon? { didSet { guard let emoticon = model else { return } if emoticon.isDelete { emojiBtn.setTitle(nil, for: .normal) emojiBtn.setImage(UIImage(named: "ic_emotion_delete"), for: .normal) emojiBtn.setImage(UIImage(named: "ic_emotion_delete"), for: .highlighted) }else if emoticon.isSpace { emojiBtn.setImage(nil, for: .normal) emojiBtn.setTitle(nil, for: .normal) }else if emoticon.emojiCode?.length ?? 0 > 0 { emojiBtn.setTitle(emoticon.emojiCode, for: .normal) emojiBtn.setImage(nil, for: .normal) }else { guard let imgPath = emoticon.imgPath else { return } emojiBtn.setTitle(nil, for: .normal) emojiBtn.setImage(UIImage(contentsOfFile: imgPath), for: .normal) } } } lazy var emojiBtn: UIButton = { let button = UIButton(type: .custom) button.backgroundColor = .clear button.titleLabel?.font = UIFont.systemFont(ofSize: 16) button.isUserInteractionEnabled = false return button }() // MARK: - life cycle override init(frame: CGRect) { super.init(frame: frame) self.addSubview(emojiBtn) emojiBtn.snp.makeConstraints { (make) in make.centerX.centerY.equalToSuperview() make.width.height.equalTo(36) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/Cell/ChatKeyboardFlowLayout.swift ================================================ // // ChatMoreViewFlowLayout.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/16. // Copyright © 2019 Jett. All rights reserved. // import UIKit class ChatKeyboardFlowLayout: UICollectionViewFlowLayout { // 保存所有item fileprivate var attributesArr: [UICollectionViewLayoutAttributes] = [] fileprivate var col: Int = 0 fileprivate var row: Int = 0 init(column: Int, row: Int) { super.init() self.col = column self.row = row } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - 重新布局 override func prepare() { super.prepare() let itemWH: CGFloat = kScreenW / CGFloat(col) // 设置itemSize itemSize = CGSize(width: itemWH, height: itemWH) minimumLineSpacing = 0 minimumInteritemSpacing = 0 scrollDirection = .horizontal // 设置collectionView属性 collectionView?.isPagingEnabled = true collectionView?.showsHorizontalScrollIndicator = false collectionView?.showsVerticalScrollIndicator = true let insertMargin = (collectionView!.bounds.height - CGFloat(row) * itemWH) * 0.5 collectionView?.contentInset = UIEdgeInsets(top: insertMargin, left: 0, bottom: insertMargin, right: 0) var page = 0 let itemsCount = collectionView?.numberOfItems(inSection: 0) ?? 0 for itemIndex in 0.. [UICollectionViewLayoutAttributes]? { var rectAttributes: [UICollectionViewLayoutAttributes] = [] _ = attributesArr.map({ if rect.contains($0.frame) { rectAttributes.append($0) } }) return rectAttributes } override var collectionViewContentSize: CGSize { let size: CGSize = super.collectionViewContentSize let collectionViewWidth: CGFloat = self.collectionView!.frame.size.width let nbOfScreen: Int = Int(ceil(size.width / collectionViewWidth)) let newSize: CGSize = CGSize(width: collectionViewWidth * CGFloat(nbOfScreen), height: size.height) return newSize } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/Cell/ChatMoreMenuCell.swift ================================================ // // FYChatMoreMenuCell.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/16. // Copyright © 2019 Jett. All rights reserved. // import UIKit class ChatMoreMenuCell: UICollectionViewCell { // MARK: - lazy var var model: ChatMoreMnueConfig? { didSet { guard model != nil else { return } self.imageView.image = UIImage(named: model!.image!) self.titleLabel.text = model?.title ?? "" } } private lazy var titleLabel: UILabel = { let label = UILabel() label.textAlignment = .center label.theme.textColor = themed{ $0.FYColor_Main_TextColor_V1 } label.font = UIFont.systemFont(ofSize: 14) return label }() private lazy var imageView: UIImageView = { let imageView = UIImageView() return imageView }() // MARK: - life cycle override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(imageView) contentView.addSubview(titleLabel) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() imageView.snp.makeConstraints { (make) in make.centerX.equalToSuperview() make.centerY.equalToSuperview().offset(-10) make.width.height.equalTo(36) } titleLabel.snp.makeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(imageView.snp.bottom) make.height.equalTo(21) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/ChatEmojiListView.swift ================================================ // // ChatEmojiListView.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/14. // Copyright © 2019 Jett. All rights reserved. // import UIKit /// 行数 fileprivate let kRowNumber = 3 /// 列数 fileprivate let kColumnNumber = 8 fileprivate let kEmotionCellNumberOfOnePage = kRowNumber * kColumnNumber protocol ChatEmojiListViewDelegate { /// 获取的表情 func emojiView(_ emojiView: ChatEmojiListView, DidFinish emotion: ChatEmoticon) /// 发送内容 func emojiView(_ emojiView: ChatEmojiListView, DidFinish isSend: Bool) /// 删除上一步内容 func emojiView(_ emojiView: ChatEmojiListView, DidDelete backward: Bool) } extension ChatEmojiListViewDelegate { func emojiView(_ emojiView: ChatEmojiListView, DidFinish emoji: String) {} func emojiView(_ emojiView: ChatEmojiListView, DidFinish isSend: Bool) {} func emojiView(_ emojiView: ChatEmojiListView, DidDelete backward: Bool) {} } class ChatEmojiListView: UIView { private var selectedIndex: Int = 0 private let kBottomMargin: CGFloat = 8 private let kBottomHeight: CGFloat = 44 // MARK: - lazy var var selectedType: Int = 0 /// 设置代理 var delegate: ChatEmojiListViewDelegate? /// 隐藏分页指示器 var hidePageController: Bool = false { didSet { self.sendButton.alpha = self.hidePageController ? 0.0 : 1.0 self.pageControl.alpha = self.hidePageController ? 0.0 : 1.0 self.bottomView.alpha = self.hidePageController ? 0.0 : 1.0 UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseOut, animations: { self.sendButton.isHidden = self.hidePageController self.pageControl.isHidden = self.hidePageController self.bottomView.isHidden = self.hidePageController }, completion: nil) scrollToLeft() } } var pageIndicatorTintColor: UIColor? { didSet { guard pageIndicatorTintColor != nil else { return } pageControl.pageIndicatorTintColor = pageIndicatorTintColor } } var currentPageIndicatorTintColor: UIColor? { didSet { guard currentPageIndicatorTintColor != nil else { return } pageControl.currentPageIndicatorTintColor = currentPageIndicatorTintColor } } lazy var emojiButtons: [UIButton] = { let buttons = [self.appleEmojiBtn, self.weChatEmojiBtn] return buttons }() lazy var dataSource: [ChatEmoticon] = { return ChatEmotionHelper.getAppleAllEmotions() }() lazy var collectionView: UICollectionView = { let layout = ChatKeyboardFlowLayout(column: kColumnNumber, row: kRowNumber) // collectionView let collection = UICollectionView(frame: self.bounds, collectionViewLayout: layout) collection.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V14 } collection.register(cellWithClass: ChatAppleEmojiCell.self) collection.showsHorizontalScrollIndicator = true collection.showsVerticalScrollIndicator = true collection.dataSource = self collection.delegate = self collection.isPagingEnabled = true return collection }() lazy var pageControl: UIPageControl = { let pager = UIPageControl() pager.backgroundColor = .clear pager.theme.pageIndicatorTintColor = themed { $0.FYColor_BorderColor_V1 } pager.theme.currentPageIndicatorTintColor = themed { $0.FYColor_Main_TextColor_V3 } pager.currentPage = 0 pager.numberOfPages = self.dataSource.count / kEmotionCellNumberOfOnePage + (self.dataSource.count % kEmotionCellNumberOfOnePage == 0 ? 0 : 1) pager.isHidden = true return pager }() lazy var sendButton: UIButton = { let button = UIButton(type: .custom) button.setTitle("发送".rLocalized(), for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 15) button.theme.titleColor(from: themed{ $0.FYColor_Main_TextColor_V12 }, for: .normal) button.setTitleColor(.lightGray, for: .disabled) button.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V13 } button.isEnabled = false button.addTarget(self, action: #selector(sendContent), for: .touchUpInside) return button }() lazy var bottomView: UIView = { let toolBar = UIView() toolBar.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V14 } toolBar.isUserInteractionEnabled = true return toolBar }() lazy var appleEmojiBtn: UIButton = { let button = UIButton(type: .custom) button.setTitle("😊", for: .normal) button.addTarget(self, action: #selector(emojiAction), for: .touchUpInside) button.backgroundColor = .clear button.tag = 1000 return button }() lazy var weChatEmojiBtn: UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "icon_emoji_expression"), for: .normal) button.addTarget(self, action: #selector(emojiAction), for: .touchUpInside) button.backgroundColor = .clear button.tag = 1001 return button }() lazy var emojiSelectView: UIView = { let v = UIView() v.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V15 } return v }() // MARK: - life cycle override init(frame: CGRect) { super.init(frame: frame) makeUI() reloadData() registerNotification() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) makeUI() reloadData() registerNotification() } func makeUI() { theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V14 } bottomView.addSubview(emojiSelectView) bottomView.addSubview(appleEmojiBtn) bottomView.addSubview(weChatEmojiBtn) bottomView.addSubview(sendButton) addSubview(bottomView) bringSubviewToFront(bottomView) addSubview(pageControl) addSubview(collectionView) bottomView.snp.makeConstraints { (make) in make.left.right.equalToSuperview() make.bottom.equalToSuperview().offset(-kSafeAreaBottom) make.height.equalTo(kBottomHeight) } emojiSelectView.snp.makeConstraints { (make) in make.left.equalToSuperview() make.height.equalToSuperview() make.width.equalTo(70) } appleEmojiBtn.snp.makeConstraints { (make) in make.left.equalToSuperview() make.height.equalToSuperview() make.width.equalTo(emojiSelectView) } weChatEmojiBtn.snp.makeConstraints { (make) in make.left.equalTo(appleEmojiBtn.snp.right) make.height.equalToSuperview() make.width.equalTo(emojiSelectView) } sendButton.snp.makeConstraints { (make) in make.right.equalToSuperview() make.height.equalToSuperview() make.width.equalTo(70) } pageControl.snp.makeConstraints { make in make.bottom.equalTo(bottomView.snp.top) make.centerX.equalToSuperview() make.height.equalTo(20) } collectionView.snp.makeConstraints { make in make.top.left.right.equalToSuperview() make.bottom.equalTo(pageControl.snp.top) } } func registerNotification() { // 实时监听输入值的改变 NotificationCenter.default.addObserver(self, selector: #selector(contentDidChanged(_:)), name: .kChatTextKeyboardChanged, object: nil) } open func reloadData() { self.needsUpdateConstraints() self.layoutIfNeeded() self.sendButton.alpha = 1.0 self.pageControl.alpha = 1.0 self.bottomView.alpha = 1.0 UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseOut, animations: { self.sendButton.isHidden = false self.pageControl.isHidden = false self.bottomView.isHidden = false }, completion: nil) DispatchQueue.main.async { self.collectionView.reloadData() self.scrollToLeft() } } /// 发送 @objc func sendContent() { delegate?.emojiView(self, DidFinish: true) } @objc func emojiAction(_ button: UIButton) { if self.selectedIndex == button.tag - 1000 { return; } switch button.tag { case 1000: dataSource = ChatEmotionHelper.getAppleAllEmotions() break default: dataSource = ChatEmotionHelper.getWeChatAllEmotions() break } selectedIndex = button.tag - 1000 emojiSelection(index: selectedIndex) reloadData() } // MARK: - Action @objc func emojiSelection(index: Int) { // 选择emoji类型 UIView.animate(withDuration: 0.25) { self.emojiSelectView.snp.updateConstraints { make in make.left.equalToSuperview().offset(70 * index) } } emojiSelectView.superview?.layoutIfNeeded() } @objc func scrollToLeft(_ animated: Bool = false) { collectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .centeredHorizontally, animated: animated) } } // MARK: - Notification extension ChatEmojiListView { @objc func contentDidChanged(_ noti: Notification) { if (noti.object == nil) { return } if let insertText = noti.object as? String { printLog("String -- \(insertText)") sendButton.isEnabled = insertText.length > 0 } if let insertAttrs = noti.object as? NSAttributedString { printLog("NSAttributedString -- \(insertAttrs)") sendButton.isEnabled = insertAttrs.length > 0 } } } // MARK: - UICollectionViewDataSource extension ChatEmojiListView: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return dataSource.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withClass: ChatAppleEmojiCell.self, for: indexPath) if let model = dataSource[safe: indexPath.row] { cell.model = model } return cell } } // MARK: - UICollectionViewDelegate extension ChatEmojiListView: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if dataSource.count > indexPath.row { if let emojiModel = dataSource[safe: indexPath.row] { if emojiModel.isDelete { delegate?.emojiView(self, DidDelete: true) }else { if (emojiModel.isSpace) { return } delegate?.emojiView(self, DidFinish: emojiModel) } } } } } extension ChatEmojiListView { func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView == collectionView { let offsetX = scrollView.contentOffset.x let index = offsetX / kScreenW pageControl.currentPage = Int(index) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/ChatGrowingTextView.swift ================================================ // // ChatGrowingTextView.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/14. // Copyright © 2019 Jett. All rights reserved. // import UIKit class ChatGrowingTextView: UITextView { /// 行数部分间距调整 fileprivate let kEdgeInset: CGFloat = 12 /// 内容字体默认大小 fileprivate let kDefultSize: CGFloat = 15.0 // MARK: - var lazy /// 默认3行的高度 fileprivate var maxTextViewHeight: CGFloat = 80 var placeholder: String? = "" { didSet { self.placeholderLabel.text = placeholder } } var placeholderColor: UIColor? = .black { didSet { self.placeholderLabel.textColor = placeholderColor } } var maxNumberOfLines: Int = 3 { didSet { let numberOfLines = CGFloat(maxNumberOfLines) maxTextViewHeight = CGFloat(ceilf(Float(self.font!.lineHeight * numberOfLines + self.textContainerInset.top + self.textContainerInset.bottom))) - kEdgeInset } } /// 输入框高度监听回调 var didTextChangedHeightClosure : ((CGFloat)->Void)? /// 占位标签 fileprivate lazy var placeholderLabel: UILabel = { let frame = CGRect(x: 5, y: 7, width: kScreenW - 10, height: 21) let label = UILabel(frame: frame) label.numberOfLines = 1 label.text = "请输入你要发送的消息" label.theme.textColor = themed{ $0.FYColor_Placeholder_Color_V3 } label.font = UIFont.systemFont(ofSize: self.kDefultSize) return label }() // MARK: - life cycle override init(frame: CGRect, textContainer: NSTextContainer?) { super.init(frame: frame, textContainer: textContainer) setup() } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } fileprivate func setup() { theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V12 } self.isScrollEnabled = false self.scrollsToTop = false //self.contentInset = UIEdgeInsets(top: 1, left: 0, bottom: 1, right: 0) self.showsHorizontalScrollIndicator = false self.enablesReturnKeyAutomatically = true self.font = UIFont.systemFont(ofSize: kDefultSize) self.returnKeyType = .send self.layer.cornerRadius = 4 self.layer.borderWidth = 1 self.layer.theme.borderColor = themed { $0.FYColor_BorderColor_V9.cgColor } self.layer.masksToBounds = true // 添加占位控件 addSubview(self.placeholderLabel) // register registerChangeNotification() } fileprivate func registerChangeNotification() { // 实时监听输入值的改变 NotificationCenter.default.addObserver(self, selector: #selector(textDidChanged), name: UITextView.textDidChangeNotification, object: self) self.addObserver(self, forKeyPath: "attributedText", options: .new, context: nil) } // MARK: - KVO监听 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "attributedText" { textDidChanged() }else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) } } deinit { NotificationCenter.default.removeObserver(self) self.removeObserver(self, forKeyPath: "attributedText") } } // MARK: - Action extension ChatGrowingTextView { @objc func textDidChanged() { placeholderLabel.isHidden = self.hasText // 计算高度 let constrainSize = CGSize(width: self.frame.size.width, height: CGFloat(MAXFLOAT)) var size = self.sizeThatFits(constrainSize) if size.height >= maxTextViewHeight { self.isScrollEnabled = true size.height = maxTextViewHeight }else { self.isScrollEnabled = false if (didTextChangedHeightClosure != nil && !self.isScrollEnabled) { didTextChangedHeightClosure?(size.height) } } let nowFrame = self.frame frame.size.height = size.height self.frame = nowFrame self.layoutIfNeeded() sendChangedNoti() } func sendChangedNoti() { NotificationCenter.default.post(name: .kChatTextKeyboardChanged, object: self.text, userInfo: nil) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/ChatKeyboard+Extensions.swift ================================================ // // ChatKeyboardView+Exted.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/14. // Copyright © 2019 Jett. All rights reserved. // import Foundation import UIKit let kChatScreenW: CGFloat = UIScreen.main.bounds.size.width let kChatScreenH: CGFloat = UIScreen.main.bounds.size.height // MARK: - NSNotificationName public extension NSNotification.Name { /// 获取点击空白处回收键盘的处理通知 static let kChatTextKeyboardNeedHide = Notification.Name("kChatTextKeyboardNeedHide") /// 获取文本输入框值变化 static let kChatTextKeyboardChanged = Notification.Name("kChatTextKeyboardChanged") } // MARK: - Emoji URL public extension URL { static let kAppleEmojiURL = URL(fileURLWithPath: Bundle.main.path(forResource: "Emoticons.bundle/com.apple.emoji/info", ofType:"plist")!) static let kWeChatEmojiURL = URL(fileURLWithPath: Bundle.main.path(forResource: "Expression.bundle/Expression", ofType:"plist")!) } // MARK: - Emoji Scanner public extension String { static func scannerEmoji(_ code: String = "") -> String { guard code.length > 0 else { return "🙂" } //创建扫描器 let scanner = Scanner(string: code) var result: UInt32 = 0 //利用扫描器扫出结果 scanner.scanHexInt32(&result) //将结果转换成字符 let c = Character(UnicodeScalar(result)!) //将字符转换成字符串 return String(c) } } // MARK: - 获取textView属性字符串,换成对应的表情字符串 extension UITextView { func getEmotionString() -> String { let attrMStr = NSMutableAttributedString(attributedString: attributedText) let range = NSRange(location: 0, length: attrMStr.length) attrMStr.enumerateAttributes(in: range, options: []) { (dict, range, _) in if let attachment = dict[.attachment] as? ChatEmotionAttachment { attrMStr.replaceCharacters(in: range, with: attachment.text!) } } return attrMStr.string } /// 添加表情图片 func insertEmotion(emotion: ChatEmoticon) { // 空白 if emotion.isSpace { return } // 删除 if emotion.isDelete { deleteBackward() return } // 表情 let attachment = ChatEmotionAttachment() attachment.text = emotion.text attachment.image = UIImage(contentsOfFile: emotion.imgPath!) let font = self.font! attachment.bounds = CGRect(x: 0, y: -4, width: font.lineHeight, height: font.lineHeight) let attrImageStr = NSAttributedString(attachment: attachment) let attrMStr = NSMutableAttributedString(attributedString: attributedText) let range = selectedRange attrMStr.replaceCharacters(in: range, with: attrImageStr) attributedText = attrMStr self.font = font selectedRange = NSRange(location: range.location + 1, length: 0) } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/ChatKeyboardView.swift ================================================ // // ChatKeyboardView.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/14. // Copyright © 2019 Jett. All rights reserved. // import UIKit import SwiftyJSON protocol ChatKeyboardViewDelegate: AnyObject { /// 输入完消息 func keyboard(_ keyboard: ChatKeyboardView, DidFinish content: String) /// 键盘收起/弹出 func keyboard(_ keyboard: ChatKeyboardView, DidBecome isBecome: Bool) /// 键盘的y值 func keyboard(_ keyboard: ChatKeyboardView, DidObserver offsetY: CGFloat) /// 菜单栏点击 func keyboard(_ keyboard: ChatKeyboardView, DidMoreMenu type: ChatMoreMenuType) } extension ChatKeyboardViewDelegate { func keyboard(_ keyboard: ChatKeyboardView, DidFinish content: String) {} func keyboard(_ keyboard: ChatKeyboardView, DidBecome isBecome: Bool) {} func keyboard(_ keyboard: ChatKeyboardView, DidObserver offsetY: CGFloat) {} func keyboard(_ keyboard: ChatKeyboardView, DidMoreMenu type: ChatMoreMenuType) {} } class ChatKeyboardView: UIView { private let kSpace: CGFloat = 8.0 private let kViewWH: CGFloat = 36.0 private let kLineHeight: CGFloat = 0.75 // MARK: - var lazy weak var delegate: ChatKeyboardViewDelegate? fileprivate var toolBarHeight: CGFloat = 52.0 fileprivate var lastTextHeight: CGFloat = 34.0 fileprivate var keyboardHeight: CGFloat = 0.0 /// 底部菜单容器高度 fileprivate var contentHeight: CGFloat = 0.0 fileprivate var isShowEmoji = false fileprivate var isShowMore = false /// 是否弹出了系统键盘 fileprivate var isShowKeyboard = false /// 表情&键盘按钮 fileprivate lazy var emojiButton : UIButton = { let button = UIButton(type: .custom) let x: CGFloat = kScreenW - self.kViewWH * 2 - self.kSpace * 2 button.frame = CGRect(x: x, y: self.kSpace, width: self.kViewWH, height: self.kViewWH) button.setImage(UIImage(named: "ToolViewEmotion"), for: .normal) button.setImage(UIImage(named: "ToolViewEmotionHL"), for: .highlighted) button.setImage(UIImage(named: "ToolViewKeyboard"), for: .selected) button.isSelected = false button.addTarget(self, action: #selector(emojiDidAction(_:)), for: .touchUpInside) return button }() /// 更多按钮 fileprivate lazy var moreButton : UIButton = { let button = UIButton(type: .custom) let x: CGFloat = kScreenW - self.kViewWH - self.kSpace button.frame = CGRect(x: x, y: self.kSpace, width: self.kViewWH, height: self.kViewWH) button.setImage(UIImage(named: "TypeSelectorBtn_Black"), for: .normal) button.setImage(UIImage(named: "TypeSelectorBtnHL_Black"), for: .highlighted) button.addTarget(self, action: #selector(moreDidAction(_:)), for: .touchUpInside) return button }() /// 文本输入框 fileprivate lazy var chatTextView: ChatGrowingTextView = { let w: CGFloat = kScreenW - self.kViewWH * 2 - self.kSpace * 3 - self.kSpace let textView = ChatGrowingTextView(frame: CGRect(x: self.kSpace, y: self.kSpace, width: w, height: self.kViewWH)) textView.placeholder = "请输入...".rLocalized() textView.theme.textColor = themed { $0.FYColor_Main_TextColor_V1 } textView.maxNumberOfLines = 5 textView.delegate = self textView.didTextChangedHeightClosure = { [weak self] height in self?.changeKeyboardHeight(height: height) } return textView }() fileprivate lazy var topLineView: UIView = { let lineView1 = UIView(frame: CGRect(x: 0, y: 0, width: kScreenW, height: kLineHeight)) lineView1.theme.backgroundColor = themed { $0.FYColor_BorderColor_V9 } return lineView1 }() fileprivate lazy var bottomLineView: UIView = { let lineView2 = UIView(frame: CGRect(x: 0, y: self.toolBarHeight - kLineHeight, width: kScreenW, height: kLineHeight)) lineView2.theme.backgroundColor = themed { $0.FYColor_BorderColor_V9 } return lineView2 }() fileprivate lazy var toolBarView: UIView = { let view = UIView(frame: CGRect(x: 0, y: 0, width: kScreenW, height: self.toolBarHeight)) view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V13 } return view }() /// 底部背景容器 fileprivate lazy var contentView: UIView = { let y = self.toolBarView.maxY let view = UIView(frame: CGRect(x: 0, y: y, width: kScreenW, height: self.contentHeight)) view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V14 } return view }() /// 表情列表 fileprivate lazy var emojiListView: ChatEmojiListView = { let view = ChatEmojiListView(frame: self.contentView.bounds) view.delegate = self view.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V14 } view.isHidden = true return view }() /// 更多菜单 fileprivate lazy var moreMenuView: ChatMoreMenuView = { let view = ChatMoreMenuView(frame: self.contentView.bounds) view.isHidden = true view.delegate = self return view }() // MARK: - life cycle override init(frame: CGRect) { super.init(frame: frame) setupKeyboardView() registerNotification() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupKeyboardView() registerNotification() } func setupKeyboardView() { self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V13 } self.isUserInteractionEnabled = true addSubview(toolBarView) toolBarView.addSubview(moreButton) toolBarView.addSubview(emojiButton) toolBarView.addSubview(chatTextView) toolBarView.addSubview(topLineView) toolBarView.addSubview(bottomLineView) addSubview(contentView) contentView.addSubview(moreMenuView) contentView.addSubview(emojiListView) } // MARK: - 监听键盘通知 private func registerNotification() { // 监听键盘弹出通知 NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name:UIResponder.keyboardWillShowNotification,object: nil) // 监听键盘隐藏通知 NotificationCenter.default.addObserver(self,selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) // 主要是为了获取点击空白处回收键盘的处理 NotificationCenter.default.addObserver(self,selector: #selector(keyboardNeedHide), name: .kChatTextKeyboardNeedHide, object: nil) // 添加KVO监听输入键盘y值 addObserver(self, forKeyPath: "frame", options: [.new, .old], context: nil) } // MARK: - KVO监听 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "frame" && ((object as? UIView) != nil) { if let changeObject = change.value { if let newFrame = changeObject[.newKey] as? CGRect { delegate?.keyboard(self, DidObserver: newFrame.origin.y) printLog("y值发生改变\(newFrame.origin.y)") } } }else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) } } deinit { self.removeObserver(self, forKeyPath: "frame") } } // MARK: - ChatEmojiListViewDelegate extension ChatKeyboardView: ChatEmojiListViewDelegate { func emojiView(_ emojiView: ChatEmojiListView, DidFinish emotion: ChatEmoticon) { if (emotion.emojiCode?.length ?? 0 > 0) { chatTextView.insertText(emotion.emojiCode!) }else if (emotion.imgPath?.length ?? 0 > 0) { chatTextView.insertEmotion(emotion: emotion) } } func emojiView(_ emojiView: ChatEmojiListView, DidDelete backward: Bool) { chatTextView.deleteBackward() } func emojiView(_ emojiView: ChatEmojiListView, DidFinish isSend: Bool) { if (isSend && chatTextView.text.length > 0) { sendChatMessage() } } } // MARK: - ChatMoreMenuViewDelegate extension ChatKeyboardView: ChatMoreMenuViewDelegate { func menu(_ view: ChatMoreMenuView, DidSelected type: ChatMoreMenuType) { delegate?.keyboard(self, DidMoreMenu: type) } } // MARK: - UITextViewDelegate extension ChatKeyboardView: UITextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { // 发送键&回车键处理 if (text == "\n") { if (isShowKeyboard) { isShowKeyboard = true } sendChatMessage() return false } return true } /// 发送消息内容 private func sendChatMessage() { delegate?.keyboard(self, DidFinish: self.chatTextView.text ?? "") changeKeyboardHeight(height: lastTextHeight) chatTextView.clear() } } // MARK: - KeyBoard Manager extension ChatKeyboardView { /// 键盘将要显示 @objc func keyboardWillShow(_ noti: NSNotification) { guard let userInfo = noti.userInfo else { return } contentHeight = 0 delegate?.keyboard(self, DidBecome: true) let duration = userInfo["UIKeyboardAnimationDurationUserInfoKey"] as! Double let endFrame = (userInfo["UIKeyboardFrameEndUserInfoKey"] as! NSValue).cgRectValue let y = endFrame.origin.y // 获取键盘的高度 keyboardHeight = endFrame.height // 键盘弹出状态 isShowKeyboard = true let option = userInfo["UIKeyboardAnimationCurveUserInfoKey"] as! Int var changedY = y - self.toolBarHeight - kNavigaH - contentHeight if (isShowEmoji || isShowMore) { //显示系统键盘 isShowMore = false isShowEmoji = false self.emojiListView.isHidden = true self.moreMenuView.isHidden = true self.moreMenuView.hidePageController = true self.emojiListView.hidePageController = true changedY = y - self.toolBarHeight - kNavigaH } UIView.animate(withDuration: duration, delay: 0, options: UIView.AnimationOptions(rawValue: UIView.AnimationOptions.RawValue(option)), animations: { self.frame = CGRect(x: 0, y: changedY, width: kScreenW, height: self.toolBarHeight + self.contentHeight) }, completion: nil) } /// 键盘将要消失 @objc func keyboardWillHide(_ noti: NSNotification) { guard let userInfo = noti.userInfo else { return } delegate?.keyboard(self, DidBecome: false) let duration = userInfo["UIKeyboardAnimationDurationUserInfoKey"] as! Double let endFrame = (userInfo["UIKeyboardFrameEndUserInfoKey"] as! NSValue).cgRectValue //let y = endFrame.origin.y // 获取键盘的高度 keyboardHeight = endFrame.height // 键盘弹出状态 isShowKeyboard = false let option = userInfo["UIKeyboardAnimationCurveUserInfoKey"] as! Int let changedY = kScreenH - kNavigaH - self.toolBarHeight - kSafeAreaBottom - self.contentHeight UIView.animate(withDuration: duration, delay: 0, options: UIView.AnimationOptions(rawValue: UIView.AnimationOptions.RawValue(option)), animations: { self.frame = CGRect(x: 0, y: changedY, width: kScreenW, height: self.toolBarHeight + self.contentHeight) }, completion: nil) } @objc func keyboardNeedHide(_ noti: NSNotification) { chatTextView.resignFirstResponder() moreMenuView.hidePageController = true emojiListView.hidePageController = true contentHeight = 0.0 restToolbarContentHeight(true) delegate?.keyboard(self, DidBecome: false) } } // MARK: - Action extension ChatKeyboardView { /// 表情按钮点击处理 @objc func emojiDidAction(_ button: UIButton) { button.isSelected = !button.isSelected if button.isSelected { if !isShowEmoji { isShowEmoji = true if isShowMore { isShowMore = false moreMenuView.isHidden = true moreMenuView.hidePageController = true } } showEmojiView() }else { if (isShowKeyboard) { button.isSelected = true showEmojiView() return } isShowEmoji = false contentHeight = 0.0 contentView.isHidden = true moreMenuView.hidePageController = true emojiListView.isHidden = true chatTextView.becomeFirstResponder() } } func showEmojiView() { contentHeight = 250 contentView.isHidden = false emojiListView.isHidden = false chatTextView.resignFirstResponder() restToolbarContentHeight() emojiListView.reloadData() delegate?.keyboard(self, DidBecome: true) } /// 更多按钮点击处理 @objc func moreDidAction(_ button: UIButton) { // 如有弹出菜单 if isShowMore { return } isShowMore = true contentHeight = 250 contentView.isHidden = false moreMenuView.isHidden = false emojiListView.isHidden = true chatTextView.resignFirstResponder() restToolbarContentHeight() moreMenuView.reloadData() delegate?.keyboard(self, DidBecome: true) } /// 更改容器高度 func restToolbarContentHeight(_ isRest: Bool = false) { var changedY = kScreenH - self.toolBarHeight - kNavigaH - contentHeight if (isRest) { if isShowEmoji { isShowEmoji = false } if (isShowMore) { isShowMore = false } changedY = kScreenH - self.toolBarHeight - kNavigaH - contentHeight - kSafeAreaBottom } UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: { self.contentView.frame = CGRect(x: 0, y: self.toolBarView.maxY, width: kScreenW, height: self.contentHeight) self.emojiListView.frame = self.contentView.bounds self.moreMenuView.frame = self.contentView.bounds self.frame = CGRect(x: 0, y: changedY, width: kScreenW, height: self.toolBarHeight + self.contentHeight) }, completion: nil) self.layoutIfNeeded() } } // MARK: - 改变输入框高度位置 extension ChatKeyboardView { func changeKeyboardHeight(_ isClear: Bool = false, height: CGFloat) { let textHeight = height toolBarHeight = textHeight + kSpace * 2 toolBarView.frame = CGRect(x: toolBarView.x, y: 0, width: toolBarView.width, height: toolBarHeight) let spaceY = toolBarView.height - kSpace - kViewWH chatTextView.frame = CGRect(x: chatTextView.x, y: chatTextView.x, width: chatTextView.width, height: textHeight) moreButton.frame = CGRect(x: moreButton.x, y: spaceY, width: moreButton.width, height: moreButton.height) emojiButton.frame = CGRect(x: emojiButton.x, y: spaceY, width: emojiButton.width, height: emojiButton.height) contentView.frame = CGRect(x: contentView.x, y: toolBarView.maxY, width: contentView.width, height: contentHeight) topLineView.frame = CGRect(x: 0, y: 0, width: kScreenW, height: kLineHeight) bottomLineView.frame = CGRect(x: 0, y: toolBarView.height - kLineHeight, width: kScreenW, height: kLineHeight) if (isShowKeyboard) { if isShowEmoji { isShowEmoji = false } if (isShowMore) { isShowMore = false } let changedY = kScreenH - keyboardHeight - toolBarHeight - kNavigaH self.frame = CGRect(x: 0, y: changedY, width: kScreenW, height: toolBarView.height + contentView.height) }else { let changedY = kScreenH - kNavigaH - (toolBarView.height + contentView.height) self.frame = CGRect(x: 0, y: changedY, width: kScreenW, height: toolBarView.height + contentView.height) } self.setNeedsLayout() //printLog("ToolBarHeight === \(toolBarView.height)") printLog("self y === \(self.frame.origin.y)") } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/ChatMoreMenuView.swift ================================================ // // ChatMoreMenuView.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/14. // Copyright © 2019 Jett. All rights reserved. // import UIKit /// 行数 fileprivate let kRowNumber = 2 /// 列数 fileprivate let kColumnNumber = 4 fileprivate let kMoreMenuCellNumberOfOnePage = kRowNumber * kColumnNumber protocol ChatMoreMenuViewDelegate { /// 获取选择的菜单 func menu(_ view: ChatMoreMenuView, DidSelected type: ChatMoreMenuType) } extension ChatMoreMenuViewDelegate { func menu(_ view: ChatMoreMenuView, DidSelected type: ChatMoreMenuType) {} } class ChatMoreMenuView: UIView { // MARK: - lazy var var delegate: ChatMoreMenuViewDelegate? /// 隐藏分页指示器 var hidePageController: Bool = false { didSet { self.pageControl.alpha = self.hidePageController ? 0.0 : 1.0 UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseOut, animations: { self.pageControl.isHidden = self.hidePageController }, completion: nil) self.collectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .centeredHorizontally, animated: false) } } var pageIndicatorTintColor: UIColor? { didSet { guard pageIndicatorTintColor != nil else { return } pageControl.pageIndicatorTintColor = pageIndicatorTintColor } } var currentPageIndicatorTintColor: UIColor? { didSet { guard currentPageIndicatorTintColor != nil else { return } pageControl.currentPageIndicatorTintColor = currentPageIndicatorTintColor } } lazy var dataSource: [ChatMoreMnueConfig] = { let configs = [ ChatMoreMnueConfig(title: "图片".rLocalized(), image: "ic_more_album", type: .album), ChatMoreMnueConfig(title: "拍照".rLocalized(), image: "ic_more_camera", type: .camera), ChatMoreMnueConfig(title: "视频".rLocalized(), image: "ic_more_video", type: .video), ChatMoreMnueConfig(title: "位置".rLocalized(), image: "ic_more_location", type: .location), ChatMoreMnueConfig(title: "语音".rLocalized(), image: "ic_more_voice", type: .voice), ChatMoreMnueConfig(title: "钱包".rLocalized(), image: "ic_more_wallet", type: .wallet), ChatMoreMnueConfig(title: "转账".rLocalized(), image: "ic_more_pay", type: .pay), ChatMoreMnueConfig(title: "名片".rLocalized(), image: "ic_more_friendcard", type: .friendcard), ChatMoreMnueConfig(title: "收藏".rLocalized(), image: "ic_more_favorite", type: .favorite), ChatMoreMnueConfig(title: "隐藏".rLocalized(), image: "ic_more_sight", type: .sight)] return configs }() lazy var collectionView: UICollectionView = { let layout = ChatKeyboardFlowLayout(column: kColumnNumber, row: kRowNumber) // collectionView let collection = UICollectionView(frame: self.bounds, collectionViewLayout: layout) collection.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V14 } collection.register(cellWithClass: ChatMoreMenuCell.self) collection.showsHorizontalScrollIndicator = true collection.showsVerticalScrollIndicator = true collection.dataSource = self collection.delegate = self collection.isPagingEnabled = true return collection }() lazy var pageControl: UIPageControl = { let pager = UIPageControl() pager.backgroundColor = .clear pager.theme.pageIndicatorTintColor = themed { $0.FYColor_BorderColor_V1 } pager.theme.currentPageIndicatorTintColor = themed { $0.FYColor_Main_TextColor_V3 } pager.currentPage = 0 pager.isHidden = true pager.numberOfPages = self.dataSource.count / kMoreMenuCellNumberOfOnePage + (self.dataSource.count % kMoreMenuCellNumberOfOnePage == 0 ? 0 : 1) return pager }() // MARK: - life cycle override init(frame: CGRect) { super.init(frame: frame) makeUI() reloadData() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) makeUI() reloadData() } func makeUI() { self.theme.backgroundColor = themed { $0.FYColor_BackgroundColor_V14 } addSubview(pageControl) addSubview(collectionView) pageControl.snp.makeConstraints { make in make.bottom.equalToSuperview().offset(-kSafeAreaBottom) make.centerX.equalToSuperview() make.height.equalTo(30) } collectionView.snp.makeConstraints { make in make.top.left.right.equalToSuperview() make.bottom.equalTo(pageControl.snp.top) } } open func reloadData() { self.needsUpdateConstraints() self.layoutIfNeeded() UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseOut, animations: { self.pageControl.alpha = 1.0 self.pageControl.isHidden = false }, completion: nil) DispatchQueue.main.async { self.collectionView.reloadData() } } } // MARK: - UICollectionViewDataSource extension ChatMoreMenuView: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return dataSource.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withClass: ChatMoreMenuCell.self, for: indexPath) if let model = dataSource[safe: indexPath.row] { cell.model = model } return cell } } // MARK: - UICollectionViewDelegate extension ChatMoreMenuView: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if let model = dataSource[safe: indexPath.row] { delegate?.menu(self, DidSelected: model.type!) } } } extension ChatMoreMenuView { func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView == collectionView { let offsetX = scrollView.contentOffset.x let index = offsetX / kScreenW pageControl.currentPage = Int(index) } } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/Helper/ChatEmotionAttachment.swift ================================================ // // ChatEmotionAttachment.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/22. // Copyright © 2019 Jett. All rights reserved. // import UIKit class ChatEmotionAttachment: NSTextAttachment { var text: String? } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/Helper/ChatEmotionHelper.swift ================================================ // // ChatEmotionHelper.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/21. // Copyright © 2019 Jett. All rights reserved. // import UIKit class ChatEmotionHelper: NSObject { // MARK: - 获取Apple表情模型数组 class func getAppleAllEmotions() -> [ChatEmoticon] { var emotions: [ChatEmoticon] = [ChatEmoticon]() let root = NSDictionary(contentsOf: .kAppleEmojiURL) let array = root?["emoticons"] as! [[String : String]] var index = 0 for dict in array { emotions.append(ChatEmoticon(dict: dict)) index += 1 if index == 23 { // 添加删除表情 emotions.append(ChatEmoticon(isDelete: true)) index = 0 } } // 添加空白表情 emotions = self.addEmptyEmotion(emotiions: emotions) return emotions } // MARK: - 获取WeChat表情模型数组 class func getWeChatAllEmotions() -> [ChatEmoticon] { var emotions: [ChatEmoticon] = [ChatEmoticon]() let plistPath = Bundle.main.path(forResource: "Expression", ofType: "plist") let array = NSArray(contentsOfFile: plistPath!) as! [[String : String]] var index = 0 for dict in array { emotions.append(ChatEmoticon(dict: dict)) index += 1 if index == 23 { // 添加删除表情 emotions.append(ChatEmoticon(isDelete: true)) index = 0 } } // 添加空白表情 emotions = self.addEmptyEmotion(emotiions: emotions) return emotions } // 添加空白表情 fileprivate class func addEmptyEmotion(emotiions: [ChatEmoticon]) -> [ChatEmoticon] { var emos = emotiions let count = emos.count % 24 if count == 0 { return emos } for _ in count..<23 { emos.append(ChatEmoticon(isSpace: true)) } emos.append(ChatEmoticon(isDelete: true)) return emos } class func getImagePath(emotionName: String?) -> String? { if emotionName == nil { return nil } return Bundle.main.bundlePath + "/Expression.bundle/" + emotionName! + ".png" } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/Helper/ChatFindEmotion.swift ================================================ // // ChatFindEmotion.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/22. // Copyright © 2019 Jett. All rights reserved. // import UIKit class ChatFindEmotion: NSObject { // MARK: - 单例 static let shared: ChatFindEmotion = ChatFindEmotion() // MARK: - 查找属性字符串的方法 func findAttrStr(text: String?, font: UIFont) -> NSMutableAttributedString? { guard let text = text else { return nil } let pattern = "\\[.*?\\]" // 匹配表情 guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil } let resutlts = regex.matches(in: text, options: [], range: NSMakeRange(0, text.count)) let attrMStr = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.font : font]) for (_, result) in resutlts.enumerated().reversed() { let emojiCode = (text as NSString).substring(with: result.range) guard let imgPath = findImagePath(emojiCode: emojiCode) else { return nil } let attachment = NSTextAttachment() attachment.image = UIImage(contentsOfFile: imgPath) attachment.bounds = CGRect(x: 0, y: -4, width: font.lineHeight, height: font.lineHeight) let attrImageStr = NSAttributedString(attachment: attachment) attrMStr.replaceCharacters(in: result.range, with: attrImageStr) } return attrMStr } func findImagePath(emojiCode: String) -> String? { for emotion in ChatEmotionHelper.getWeChatAllEmotions() { if emotion.text! == emojiCode { return emotion.imgPath } } return nil } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/Model/ChatEmoticon.swift ================================================ // // ChatEmoticon.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/18. // Copyright © 2019 Jett. All rights reserved. // import UIKit @objcMembers class ChatEmoticon: NSObject { // MARK: - 定义属性 var type: String? var chs: String? var text: String? var image: String? { // 表情对应的图片名称 didSet { imgPath = Bundle.main.bundlePath + "/Expression.bundle/" + image! + ".png" } } var code: String? { didSet { if let code = code { //创建扫描器 let scanner = Scanner(string: code) var result: UInt32 = 0 //利用扫描器扫出结果 scanner.scanHexInt32(&result) //将结果转换成字符 let c = Character(UnicodeScalar(result)!) //将字符转换成字符串 emojiCode = String(c) } } } /// emoji表情解析后的code码 var emojiCode: String? /// 图片的绝对路径 var imgPath: String? /// 是否是移除键 var isDelete: Bool = false /// 是否是空格 var isSpace: Bool = false init(dict: [String: String]) { super.init() setValuesForKeys(dict) } init(isDelete: Bool) { super.init() self.isDelete = true } init(isSpace: Bool) { super.init() self.isSpace = true } override func setValue(_ value: Any?, forUndefinedKey key: String) { } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/Model/ChatMoreMnueConfig.swift ================================================ // // ChatMoreMnueConfig.swift // FY-JetChat // // Created by iOS.Jet on 2019/11/18. // Copyright © 2019 Jett. All rights reserved. // import UIKit enum ChatMoreMenuType: Int { case album = 1001 case camera = 1002 case video = 1003 case location = 1004 case voice = 1005 case wallet = 1006 case pay = 1007 case friendcard = 1008 case favorite = 1009 case sight = 1010 } class ChatMoreMnueConfig: NSObject { var title: String? var image: String? var type: ChatMoreMenuType? init(title: String, image: String, type: ChatMoreMenuType) { super.init() self.title = title self.image = image self.type = type } required override init() { } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/Resource/Emoji/Emoticons.bundle/com.apple.emoji/info.plist ================================================ id com.apple.emoji version 2 group_name_cn Emoji group_name_en Emoji group_name_tw Emoji display_only 0 group_type 0 emoticons code 0x1f603 type 1 code 0x1f60d type 1 code 0x1f612 type 1 code 0x1f633 type 1 code 0x1f601 type 1 code 0x1f618 type 1 code 0x1f609 type 1 code 0x1f620 type 1 code 0x1f61e type 1 code 0x1f625 type 1 code 0x1f62d type 1 code 0x1f61d type 1 code 0x1f621 type 1 code 0x1f623 type 1 code 0x1f614 type 1 code 0x1f604 type 1 code 0x1f637 type 1 code 0x1f61a type 1 code 0x1f613 type 1 code 0x1f602 type 1 code 0x1f60a type 1 code 0x1f622 type 1 code 0x1f61c type 1 code 0x1f628 type 1 code 0x1f630 type 1 code 0x1f632 type 1 code 0x1f60f type 1 code 0x1f631 type 1 code 0x1f62a type 1 code 0x1f616 type 1 code 0x1f60c type 1 code 0x1f47f type 1 code 0x1f47b type 1 code 0x1f385 type 1 code 0x1f467 type 1 code 0x1f466 type 1 code 0x1f469 type 1 code 0x1f468 type 1 code 0x1f436 type 1 code 0x1f431 type 1 code 0x1f44d type 1 code 0x1f44e type 1 code 0x1f44a type 1 code 0x270a type 1 code 0x270c type 1 code 0x1f4aa type 1 code 0x1f44f type 1 code 0x1f448 type 1 code 0x1f446 type 1 code 0x1f449 type 1 code 0x1f447 type 1 code 0x1f44c type 1 code 0x2764 type 1 code 0x1f494 type 1 code 0x1f64f type 1 code 0x2600 type 1 code 0x1f319 type 1 code 0x1f31f type 1 code 0x26a1 type 1 code 0x2601 type 1 code 0x2614 type 1 code 0x1f341 type 1 code 0x1f33b type 1 code 0x1f343 type 1 code 0x1f457 type 1 code 0x1f380 type 1 code 0x1f444 type 1 code 0x1f339 type 1 code 0x2615 type 1 code 0x1f382 type 1 code 0x1f559 type 1 code 0x1f37a type 1 code 0x1f50d type 1 code 0x1f4f1 type 1 code 0x1f3e0 type 1 code 0x1f697 type 1 code 0x1f381 type 1 code 0x26bd type 1 code 0x1f4a3 type 1 code 0x1f48e type 1 ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/KeyboardCore/Resource/Emotion/Expression.plist ================================================ text [微笑] image Expression_1 text [撇嘴] image Expression_2 text [色] image Expression_3 text [发呆] image Expression_4 text [得意] image Expression_5 text [流泪] image Expression_6 text [害羞] image Expression_7 text [闭嘴] image Expression_8 text [睡] image Expression_9 text [大哭] image Expression_10 text [尴尬] image Expression_11 text [发怒] image Expression_12 text [调皮] image Expression_13 text [呲牙] image Expression_14 text [惊讶] image Expression_15 text [难过] image Expression_16 text [酷] image Expression_17 text [冷汗] image Expression_18 text [抓狂] image Expression_19 text [吐] image Expression_20 text [偷笑] image Expression_21 text [愉快] image Expression_22 text [白眼] image Expression_23 text [傲慢] image Expression_24 text [饥饿] image Expression_25 text [困] image Expression_26 text [惊恐] image Expression_27 text [流汗] image Expression_28 text [憨笑] image Expression_29 text [悠闲] image Expression_30 text [奋斗] image Expression_31 text [咒骂] image Expression_32 text [疑问] image Expression_33 text [嘘] image Expression_34 text [晕] image Expression_35 text [疯了] image Expression_36 text [衰] image Expression_37 text [骷髅] image Expression_38 text [敲打] image Expression_39 text [再见] image Expression_40 text [擦汗] image Expression_41 text [抠鼻] image Expression_42 text [鼓掌] image Expression_43 text [糗大了] image Expression_44 text [坏笑] image Expression_45 text [左哼哼] image Expression_46 text [右哼哼] image Expression_47 text [哈欠] image Expression_48 text [鄙视] image Expression_49 text [委屈] image Expression_50 text [快哭了] image Expression_51 text [阴险] image Expression_52 text [亲亲] image Expression_53 text [吓] image Expression_54 text [可怜] image Expression_55 text [菜刀] image Expression_56 text [西瓜] image Expression_57 text [啤酒] image Expression_58 text [篮球] image Expression_59 text [乒乓] image Expression_60 text [咖啡] image Expression_61 text [饭] image Expression_62 text [猪头] image Expression_63 text [玫瑰] image Expression_64 text [凋谢] image Expression_65 text [嘴唇] image Expression_66 text [爱心] image Expression_67 text [心碎] image Expression_68 text [蛋糕] image Expression_69 text [闪电] image Expression_70 text [炸弹] image Expression_71 text [刀] image Expression_72 text [足球] image Expression_73 text [瓢虫] image Expression_74 text [便便] image Expression_75 text [月亮] image Expression_76 text [太阳] image Expression_77 text [礼物] image Expression_78 text [拥抱] image Expression_79 text [强] image Expression_80 text [弱] image Expression_81 text [握手] image Expression_82 text [胜利] image Expression_83 text [抱拳] image Expression_84 text [勾引] image Expression_85 text [拳头] image Expression_86 text [差劲] image Expression_87 text [爱你] image Expression_88 text [NO] image Expression_89 text [OK] image Expression_90 text [爱情] image Expression_91 text [飞吻] image Expression_92 text [跳跳] image Expression_93 text [发抖] image Expression_94 text [怄火] image Expression_95 text [转圈] image Expression_96 text [磕头] image Expression_97 text [回头] image Expression_98 text [跳绳] image Expression_99 text [投降] image Expression_100 ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/Theme/FYDarkTheme.swift ================================================ // // FYDarkTheme.swift // FY-JetChat // // Created by Jett on 2022/4/30. // Copyright © 2022 Jett. All rights reserved. // import UIKit import RxSwift import RxTheme struct FYDarkTheme: Theme { // MARK: - **************************** Dark Color **************************** // MARK: - 导航栏 /// 导航栏背景色 黑-> 10171B var FYColor_Nav_BackgroundColor = UIColor.Color_Black_10171B // MARK: - TabBar /// TabBar背景色 黑-> 181D21 var FYColor_Tab_BackgroundColor = UIColor.Color_Black_181D21 // MARK: - 背景色 /// 一级模块背景色 黑 -> 181D21 白 -> FFFFFF var FYColor_BackgroundColor_V1 = UIColor.Color_Black_181D21 /// 二级模块背景色 黑 -> 252D33 白 -> F6F6F6 var FYColor_BackgroundColor_V2 = UIColor.Color_Black_252D33 /// 三级模块背景色 黑 -> FFFFFF 白 -> 000000 var FYColor_BackgroundColor_V3 = UIColor.Color_White_FFFFFF /// 黑 -> FFFFFF 白 -> F6F6F6 var FYColor_BackgroundColor_V4 = UIColor.Color_White_FFFFFF /// 黑 -> 252D33 白 -> FFFFFF var FYColor_BackgroundColor_V5 = UIColor.Color_Black_252D33 /// 黑 -> 2B343B 白 -> 384955 var FYColor_BackgroundColor_V6 = UIColor.Color_Black_2B343B /// 黑 -> 10171B 白 -> C0C0C0 var FYColor_BackgroundColor_V7 = UIColor.Color_Black_10171B /// 黑 -> 000000 白 -> F6F6F6 var FYColor_BackgroundColor_V8 = UIColor.Color_Black_000000 /// 黑 -> 10171B 白 -> 2C363E var FYColor_BackgroundColor_V9 = UIColor.Color_Black_10171B /// 黑 -> 2C363E 白 -> F6F6F6 var FYColor_BackgroundColor_V10 = UIColor.Color_Black_2C363E /// 黑 -> 0F1317 白 -> F6F6F6 var FYColor_BackgroundColor_V11 = UIColor.Color_Black_0F1317 /// 黑 -> 2C363E 白 -> FFFFFF var FYColor_BackgroundColor_V12 = UIColor.Color_Black_2C363E /// 黑 -> 272D34 白 -> F7F7F7 var FYColor_BackgroundColor_V13 = UIColor.Color_Black_272D34 /// 黑 -> 272D34 白 -> F8F8F8 var FYColor_BackgroundColor_V14 = UIColor.Color_Black_272D34 /// 黑 -> 10171B 白 -> CCCCCC var FYColor_BackgroundColor_V15 = UIColor.Color_Black_10171B //MARK: - 边框颜色 /// 黑 -> 1E2328 白 -> E5E5E5 var FYColor_BorderColor_V1 = UIColor.Color_Gray_1E2328 /// 黑 -> 5A636D 白 -> E5E5E5 var FYColor_BorderColor_V2 = UIColor.Color_Gray_5A636D /// 黑 -> 12171B 白 -> F6F6F6 var FYColor_BorderColor_V3 = UIColor.Color_Black_12171B /// 黑 -> 181D21 白 -> E5E5E5 var FYColor_BorderColor_V4 = UIColor.Color_Black_181D21 /// 黑 -> FFFFFF 白 -> E5E5E5 var FYColor_BorderColor_V5 = UIColor.Color_White_FFFFFF /// 黑 -> 1E2328 白 -> F6F6F6 var FYColor_BorderColor_V6 = UIColor.Color_Gray_1E2328 /// 黑 -> 272E37 白 -> E5E5E5 var FYColor_BorderColor_V7 = UIColor.Color_Gray_272E37 /// 黑 -> 12171B 白 -> E5E5E5 var FYColor_BorderColor_V8 = UIColor.Color_Black_12171B /// 黑 -> 2C363E 白 -> E5E5E5 var FYColor_BorderColor_V9 = UIColor.Color_Black_2C363E // MARK: - 文本颜色 (Placeholder) /// 黑 -> 919191 白 -> B4B4B4 var FYColor_Placeholder_Color_V1 = UIColor.Color_Gray_919191 /// 黑 -> 6D777C 白 -> 999999 var FYColor_Placeholder_Color_V2 = UIColor.Color_Gray_6D777C /// 黑 -> FFFFFF 白 -> 999999 var FYColor_Placeholder_Color_V3 = UIColor.Color_Gray_999999 // MARK: - 文本颜色 (TextColor) /// 黑 -> FFFFFF 白 -> 000000 var FYColor_Main_TextColor_V1 = UIColor.Color_White_FFFFFF /// 黑 -> 5A636D 白 -> 77808A var FYColor_Main_TextColor_V2 = UIColor.Color_Gray_5A636D /// 黑 -> FFFFFF 白 -> 77808A var FYColor_Main_TextColor_V3 = UIColor.Color_White_FFFFFF /// 黑 -> 919191 白 -> 1D1E34 var FYColor_Main_TextColor_V4 = UIColor.Color_Gray_919191 /// 黑 -> 000000 白 -> FFFFFF var FYColor_Main_TextColor_V5 = UIColor.Color_Gray_919191 /// 黑 -> 000000 白 -> 000000 var FYColor_Main_TextColor_V6 = UIColor.Color_Black_000000 /// 黑 -> 5A636D 白 -> B4B4B4 var FYColor_Main_TextColor_V7 = UIColor.Color_Gray_5A636D /// 黑 -> FFBF27 白 -> 000000 var FYColor_Main_TextColor_V8 = UIColor.Color_Yellow_FFBF27 /// 黑 -> FFFFFF 白 -> 666666 var FYColor_Main_TextColor_V9 = UIColor.Color_White_FFFFFF /// 黑 -> 9BA1A4 白 -> 666666 var FYColor_Main_TextColor_V10 = UIColor.Color_Gray_9BA1A4 /// 黑 -> FFFFFF 白 -> 1A1F24 var FYColor_Main_TextColor_V11 = UIColor.Color_White_FFFFFF /// 黑 -> FFFFFF 白 -> 1890FF var FYColor_Main_TextColor_V12 = UIColor.Color_White_FFFFFF // MARK: - UIImage Configuration var nav_back_image = R.image.nav_back_white()! } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/Theme/FYLightTheme.swift ================================================ // // FYLightTheme.swift // FY-JetChat // // Created by Jett on 2022/4/30. // Copyright © 2022 Jett. All rights reserved. // import UIKit import RxSwift import RxTheme struct FYLightTheme: Theme { // MARK: - **************************** Light Color **************************** // MARK: - 导航栏 /// 导航栏背景色 白-> 696969 var FYColor_Nav_BackgroundColor = UIColor.Color_Gray_696969 // MARK: - TabBar /// TabBar背景色 白-> FFFFFF var FYColor_Tab_BackgroundColor = UIColor.Color_White_FFFFFF // MARK: - 背景色 /// 一级模块背景色 黑 -> 181D21 白 -> FFFFFF var FYColor_BackgroundColor_V1 = UIColor.Color_White_FFFFFF /// 二级模块背景色 黑 -> 252D33 白 -> F6F6F6 var FYColor_BackgroundColor_V2 = UIColor.Color_Gray_F6F6F6 /// 三级模块背景色 黑 -> FFFFFF 白 -> 000000 var FYColor_BackgroundColor_V3 = UIColor.Color_Black_000000 /// 黑 -> FFFFFF 白 -> F6F6F6 var FYColor_BackgroundColor_V4 = UIColor.Color_Gray_F6F6F6 /// 黑 -> 252D33 白 -> FFFFFF var FYColor_BackgroundColor_V5 = UIColor.Color_White_FFFFFF /// 黑 -> 2B343B 白 -> 384955 var FYColor_BackgroundColor_V6 = UIColor.Color_Gray_384955 /// 黑 -> 10171B 白 -> C0C0C0 var FYColor_BackgroundColor_V7 = UIColor.Color_Gray_C0C0C0 /// 黑 -> 000000 白 -> F6F6F6 var FYColor_BackgroundColor_V8 = UIColor.Color_Gray_F6F6F6 /// 黑 -> 10171B 白 -> 2C363E var FYColor_BackgroundColor_V9 = UIColor.Color_Black_2C363E /// 黑 -> 2C363E 白 -> F6F6F6 var FYColor_BackgroundColor_V10 = UIColor.Color_Gray_F6F6F6 /// 黑 -> 0F1317 白 -> F6F6F6 var FYColor_BackgroundColor_V11 = UIColor.Color_Gray_F6F6F6 /// 黑 -> 2C363E 白 -> FFFFFF var FYColor_BackgroundColor_V12 = UIColor.Color_White_FFFFFF /// 黑 -> 272D34 白 -> F7F7F7 var FYColor_BackgroundColor_V13 = UIColor.Color_Gray_F7F7F7 /// 黑 -> 272D34 白 -> F8F8F8 var FYColor_BackgroundColor_V14 = UIColor.Color_Gray_F8F8F8 /// 黑 -> 10171B 白 -> CCCCCC var FYColor_BackgroundColor_V15 = UIColor.Color_Gray_CCCCCC //MARK: - 边框颜色 /// 黑 -> 1E2328 白 -> E5E5E5 var FYColor_BorderColor_V1 = UIColor.Color_Gray_E5E5E5 /// 黑 -> 5A636D 白 -> E5E5E5 var FYColor_BorderColor_V2 = UIColor.Color_Gray_E5E5E5 /// 黑 -> 12171B 白 -> F6F6F6 var FYColor_BorderColor_V3 = UIColor.Color_Gray_F6F6F6 /// 黑 -> 181D21 白 -> E5E5E5 var FYColor_BorderColor_V4 = UIColor.Color_Gray_E5E5E5 /// 黑 -> FFFFFF 白 -> E5E5E5 var FYColor_BorderColor_V5 = UIColor.Color_Gray_E5E5E5 /// 黑 -> 1E2328 白 -> F6F6F6 var FYColor_BorderColor_V6 = UIColor.Color_Gray_F6F6F6 /// 黑 -> 272E37 白 -> E5E5E5 var FYColor_BorderColor_V7 = UIColor.Color_Gray_E5E5E5 /// 黑 -> 12171B 白 -> E5E5E5 var FYColor_BorderColor_V8 = UIColor.Color_Gray_E5E5E5 /// 黑 -> 2C363E 白 -> E5E5E5 var FYColor_BorderColor_V9 = UIColor.Color_Gray_E5E5E5 // MARK: - 文本颜色 (Placeholder) /// 黑 -> 919191 白 -> B4B4B4 var FYColor_Placeholder_Color_V1 = UIColor.Color_Gray_B4B4B4 /// 黑 -> 6D777C 白 -> 999999 var FYColor_Placeholder_Color_V2 = UIColor.Color_Gray_999999 /// 黑 -> FFFFFF 白 -> 999999 var FYColor_Placeholder_Color_V3 = UIColor.Color_White_FFFFFF // MARK: - 文本颜色 (TextColor) /// 黑 -> FFFFFF 白 -> 000000 var FYColor_Main_TextColor_V1 = UIColor.Color_Black_000000 /// 黑 -> 5A636D 白 -> 77808A var FYColor_Main_TextColor_V2 = UIColor.Color_Gray_77808A /// 黑 -> FFFFFF 白 -> 77808A var FYColor_Main_TextColor_V3 = UIColor.Color_Gray_77808A /// 黑 -> 919191 白 -> 1D1E34 var FYColor_Main_TextColor_V4 = UIColor.Color_Black_1D1E34 /// 黑 -> 000000 白 -> FFFFFF var FYColor_Main_TextColor_V5 = UIColor.Color_White_FFFFFF /// 黑 -> 000000 白 -> 000000 var FYColor_Main_TextColor_V6 = UIColor.Color_Black_000000 /// 黑 -> 5A636D 白 -> B4B4B4 var FYColor_Main_TextColor_V7 = UIColor.Color_Gray_B4B4B4 /// 黑 -> FFBF27 白 -> 000000 var FYColor_Main_TextColor_V8 = UIColor.Color_Black_000000 /// 黑 -> FFFFFF 白 -> 666666 var FYColor_Main_TextColor_V9 = UIColor.Color_Gray_666666 /// 黑 -> 9BA1A4 白 -> 666666 var FYColor_Main_TextColor_V10 = UIColor.Color_Gray_666666 /// 黑 -> FFFFFF 白 -> 1A1F24 var FYColor_Main_TextColor_V11 = UIColor.Color_Gray_1A1F24 /// 黑 -> FFFFFF 白 -> 1890FF var FYColor_Main_TextColor_V12 = UIColor.Color_Blue_1890FF // MARK: - UIImage Configuration var nav_back_image = R.image.nav_back_black()! } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/Theme/FYThemeCenter.swift ================================================ // // FYThemeCenter.swift // FY-JetChat // // Created by Jett on 2022/4/30. // Copyright © 2022 Jett. All rights reserved. // import UIKit import RxSwift import RxTheme let themeService = ThemeType.service(initial: .dark) public enum FYThemeMode: Int { /// 跟随系统 case system = 0 /// 白天模式 case light = 1 /// 黑夜模式 case dark = 2 } protocol Theme { // MARK: - 导航栏 /// 导航栏背景色 黑-> 10171B 白-> 696969 var FYColor_Nav_BackgroundColor: UIColor { get } // MARK: - TabBar /// TabBar背景色 黑-> 181D21 白-> FFFFFF var FYColor_Tab_BackgroundColor: UIColor { get } // MARK: - 背景色 /// 一级模块背景色 黑 -> 181D21 白 -> FFFFFF var FYColor_BackgroundColor_V1: UIColor { get } /// 二级模块背景色 黑 -> 252D33 白 -> F6F6F6 var FYColor_BackgroundColor_V2: UIColor { get } /// 三级级模块背景色 黑 -> FFFFFF 白 -> 000000 var FYColor_BackgroundColor_V3: UIColor { get } /// 黑 -> FFFFFF 白 -> F6F6F6 var FYColor_BackgroundColor_V4: UIColor { get } /// 黑 -> 252D33 白 -> FFFFFF var FYColor_BackgroundColor_V5: UIColor { get } /// 黑 -> 2B343B 白 -> 384955 var FYColor_BackgroundColor_V6: UIColor { get } /// 黑 -> 10171B 白 -> C0C0C0 var FYColor_BackgroundColor_V7: UIColor { get } /// 黑 -> 000000 白 -> F6F6F6 var FYColor_BackgroundColor_V8: UIColor { get } /// 黑 -> 10171B 白 -> 2C363E var FYColor_BackgroundColor_V9: UIColor { get } /// 黑 -> 2C363E 白 -> F6F6F6 var FYColor_BackgroundColor_V10: UIColor { get } /// 黑 -> 0F1317 白 -> F6F6F6 var FYColor_BackgroundColor_V11: UIColor { get } /// 黑 -> 2C363E 白 -> FFFFFF var FYColor_BackgroundColor_V12: UIColor { get } /// 黑 -> 272D34 白 -> F7F7F7 var FYColor_BackgroundColor_V13: UIColor { get } /// 黑 -> 272D34 白 -> F8F8F8 var FYColor_BackgroundColor_V14: UIColor { get } /// 黑 -> 10171B 白 -> CCCCCC var FYColor_BackgroundColor_V15: UIColor { get } //MARK: - 边框颜色 /// 黑 -> 1E2328 白 -> E5E5E5 var FYColor_BorderColor_V1: UIColor { get } /// 黑 -> 5A636D 白 -> E5E5E5 var FYColor_BorderColor_V2: UIColor { get } /// 黑 -> 12171B 白 -> F6F6F6 var FYColor_BorderColor_V3: UIColor { get } /// 黑 -> 181D21 白 -> E5E5E5 var FYColor_BorderColor_V4: UIColor { get } /// 黑 -> FFFFFF 白 -> E5E5E5 var FYColor_BorderColor_V5: UIColor { get } /// 黑 -> 1E2328 白 -> F6F6F6 var FYColor_BorderColor_V6: UIColor { get } /// 黑 -> 272E37 白 -> E5E5E5 var FYColor_BorderColor_V7: UIColor { get } /// 黑 -> 12171B 白 -> E5E5E5 var FYColor_BorderColor_V8: UIColor { get } /// 黑 -> 2C363E 白 -> E5E5E5 var FYColor_BorderColor_V9: UIColor { get } // MARK: - 文本颜色 (Placeholder) /// 黑 -> 919191 白 -> B4B4B4 var FYColor_Placeholder_Color_V1: UIColor { get } /// 黑 -> 6D777C 白 -> 999999 var FYColor_Placeholder_Color_V2: UIColor { get } /// 黑 -> FFFFFF 白 -> 999999 var FYColor_Placeholder_Color_V3: UIColor { get } // MARK: - 文本颜色 (TextColor) /// 黑 -> FFFFFF 白 -> 000000 var FYColor_Main_TextColor_V1: UIColor { get } /// 黑 -> 5A636D 白 -> 77808A var FYColor_Main_TextColor_V2: UIColor { get } /// 黑 -> FFFFFF 白 -> 77808A var FYColor_Main_TextColor_V3: UIColor { get } /// 黑 -> 919191 白 -> 1D1E34 var FYColor_Main_TextColor_V4: UIColor { get } /// 黑 -> 000000 白 -> FFFFFF var FYColor_Main_TextColor_V5: UIColor { get } /// 黑 -> 000000 白 -> 000000 var FYColor_Main_TextColor_V6: UIColor { get } /// 黑 -> 5A636D 白 -> B4B4B4 var FYColor_Main_TextColor_V7: UIColor { get } /// 黑 -> FFBF27 白 -> 000000 var FYColor_Main_TextColor_V8: UIColor { get } /// 黑 -> FFFFFF 白 -> 666666 var FYColor_Main_TextColor_V9: UIColor { get } /// 黑 -> 9BA1A4 白 -> 666666 var FYColor_Main_TextColor_V10: UIColor { get } /// 黑 -> FFFFFF 白 -> 1A1F24 var FYColor_Main_TextColor_V11: UIColor { get } /// 黑 -> FFFFFF 白 -> 1890FF var FYColor_Main_TextColor_V12: UIColor { get } // MARK: - UIImage Configuration var nav_back_image: UIImage { get } } enum ThemeType: ThemeProvider { case light case dark var associatedObject: Theme { switch self { case .light: return FYLightTheme() case .dark: return FYDarkTheme() } } } func themed(_ mapper: @escaping ((Theme) -> T)) -> ThemeAttribute { return themeService.attribute(mapper) } // MARK: - Center Manager class FYThemeCenter: NSObject { /// 单利 static let shared = FYThemeCenter() override init() { } /// 保存当前所选主题模式 /// - Parameters: /// - themeMode: 主题模式 /// - isRestWindow: 是否重新载入窗口 func saveSelectionTheme(mode: FYThemeMode, isRestWindow: Bool = false) { UserDefaults.standard.set(mode.rawValue, forKey: kThemeSettingUserDefaultKey) UserDefaults.standard.synchronize() if (isRestWindow) { self.resetAppWindow() } } /// 当前已选主题模式 /// - Returns: 已选主题模式 var currentTheme: FYThemeMode { if let lastTheme = UserDefaults.standard.value(forKey: kThemeSettingUserDefaultKey) as? Int { return FYThemeMode(rawValue: lastTheme) ?? .light }else { return .light } } /// 切换根控制器 private func resetAppWindow() { let tabBar = FYBaseTabBarController() UIApplication.shared.keyWindow?.rootViewController = tabBar UIApplication.shared.keyWindow?.makeKeyAndVisible() // FPS AppDelegate.app.setupFPSStatus() } } ================================================ FILE: JetChat/FY-IMChat/Classes/Utilites/Theme/FYThemeColors.swift ================================================ // // FYThemeColor.swift // FY-JetChat // // Created by Jett on 2022/4/30. // Copyright © 2022 Jett. All rights reserved. // import UIKit extension UIColor { convenience init(hex: Int, alpha: CGFloat = 1.0) { let red = CGFloat((hex & 0xFF0000) >> 16) / 255 let green = CGFloat((hex & 0xFF00) >> 8) / 255 let blue = CGFloat(hex & 0xFF) / 255 self.init(red: red, green: green, blue: blue, alpha: alpha) } // 白 static var Color_White_FFFFFF: UIColor { UIColor(hex: 0xFFFFFF) } static var Color_White_F7F7F7: UIColor { UIColor(hex: 0xF7F7F7) } static var Color_White_F9F9F9: UIColor { UIColor(hex: 0xF9F9F9) } static var Color_White_F7F7FA: UIColor { UIColor(hex: 0xF7F7FA) } static var Color_White_F3F3F3: UIColor { UIColor(hex: 0xF3F3F3) } static var Color_White_F5F5F5: UIColor { UIColor(hex: 0xF5F5F5) } // 黑 static var Color_Black_000000: UIColor { UIColor(hex: 0x000000) } static var Color_Black_202C33: UIColor { UIColor(hex: 0x202C33) } static var Color_Black_2F404A: UIColor { UIColor(hex: 0x2F404A) } static var Color_Black_223037: UIColor { UIColor(hex: 0x223037) } static var Color_Black_252D33: UIColor { UIColor(hex: 0x252D33) } static var Color_Black_25333A: UIColor { UIColor(hex: 0x25333A) } static var Color_Black_344A56: UIColor { UIColor(hex: 0x344A56) } static var Color_Black_272D34: UIColor { UIColor(hex: 0x272D34) } static var Color_Black_030303: UIColor { UIColor(hex: 0x030303) } static var Color_Black_10171B: UIColor { UIColor(hex: 0x10171B) } static var Color_Black_181D21: UIColor { UIColor(hex: 0x181D21) } static var Color_Black_1B2025: UIColor { UIColor(hex: 0x1B2025) } static var Color_Black_12171B: UIColor { UIColor(hex: 0x12171B) } static var Color_Black_565F68: UIColor { UIColor(hex: 0x565F68) } static var Color_Black_2C363E: UIColor { UIColor(hex: 0x2C363E) } static var Color_Black_161B1E: UIColor { UIColor(hex: 0x161B1E) } static var Color_Black_2B343B: UIColor { UIColor(hex: 0x2B343B) } static var Color_Black_181818: UIColor { UIColor(hex: 0x181818) } static var Color_Black_433C37: UIColor { UIColor(hex: 0x433C37) } static var Color_Black_0E1418: UIColor { UIColor(hex: 0x0E1418) } static var Color_Black_1D1E34: UIColor { UIColor(hex: 0x0E1418) } static var Color_Black_384955: UIColor { UIColor(hex: 0x384955) } static var Color_Black_0F1317: UIColor { UIColor(hex: 0x0F1317) } static var Color_Black_21272F: UIColor { UIColor(hex: 0x21272F) } static var Color_Black_28323B: UIColor { UIColor(hex: 0x28323B) } static var Color_Black_15191E: UIColor { UIColor(hex: 0x15191E) } static var Color_Black_313944: UIColor { UIColor(hex: 0x313944) } static var Color_Black_1D232A: UIColor { UIColor(hex: 0x1D232A) } static var Color_Black_0E2B2D: UIColor { UIColor(hex: 0x0E2B2D) } static var Color_Black_193434: UIColor { UIColor(hex: 0x193434) } static var Color_Black_29313A: UIColor { UIColor(hex: 0x29313A) } static var Color_Black_1A2029: UIColor { UIColor(hex: 0x1A2029) } static var Color_Black_1E262C: UIColor { UIColor(hex: 0x1E262C) } static var Color_Black_2A2F33: UIColor { UIColor(hex: 0x2A2F33) } static var Color_Black_171D28: UIColor { UIColor(hex: 0x171D28) } static var Color_Black_252C33: UIColor { UIColor(hex: 0x252C33) } static var Color_Black_333333: UIColor { UIColor(hex: 0x333333) } // 绿 static var Color_Green_02EAD0: UIColor { UIColor(hex: 0x02EAD0) } static var Color_Green_00D4BC: UIColor { UIColor(hex: 0x00D4BC) } static var Color_Green_27B87D: UIColor { UIColor(hex: 0x27B87D) } static var Color_Green_01E8CE: UIColor { UIColor(hex: 0x01E8CE) } static var Color_Green_00C1AB: UIColor { UIColor(hex: 0x00C1AB) } static var Color_Green_008878: UIColor { UIColor(hex: 0x008878) } static var Color_Green_136440: UIColor { UIColor(hex: 0x136440) } static var Color_Green_16BD74: UIColor { UIColor(hex: 0x16BD74) } static var Color_Green_1ED760: UIColor { UIColor(hex: 0x1ED760) } static var Color_Green_09BB07: UIColor { UIColor(hex: 0x09BB07) } static var Color_Green_124F4C: UIColor { UIColor(hex: 0x124F4C) } static var Color_Green_0A978A: UIColor { UIColor(hex: 0x0A978A) } static var Color_Green_E5FAF8: UIColor { UIColor(hex: 0xE5FAF8) } static var Color_Green_E8F8F6: UIColor { UIColor(hex: 0xE8F8F6) } static var Color_Green_0E413F: UIColor { UIColor(hex: 0x0E413F) } static var Color_Green_CCF2EE: UIColor { UIColor(hex: 0xCCF2EE) } static var Color_Green_102B2D: UIColor { UIColor(hex: 0x102B2D) } static var Color_Green_00C2AD: UIColor { UIColor(hex: 0x00C2AD) } static var Color_Green_163E3E: UIColor { UIColor(hex: 0x163E3E) } static var Color_Green_DBF7F4: UIColor { UIColor(hex: 0xDBF7F4) } static var Color_Green_144848: UIColor { UIColor(hex: 0x144848) } static var Color_Green_00C7B1: UIColor { UIColor(hex: 0x00C7B1) } static var Color_Green_00C0AB: UIColor { UIColor(hex: 0x00C0AB, alpha: 0.2) } // 红 static var Color_Red_FF5055: UIColor { UIColor(hex: 0xFF5055) } static var Color_Red_FF4646: UIColor { UIColor(hex: 0xFF4646) } static var Color_Red_FF2B00: UIColor { UIColor(hex: 0xFF2B00) } static var Color_Red_EB6164: UIColor { UIColor(hex: 0xEB6164) } static var Color_Red_BB5254: UIColor { UIColor(hex: 0xBB5254) } static var Color_Red_9A4245: UIColor { UIColor(hex: 0x9A4245) } static var Color_Red_FEF6F5: UIColor { UIColor(hex: 0xFEF6F5) } static var Color_Red_FFE8E5: UIColor { UIColor(hex: 0xFFE8E5) } static var Color_Red_F25534: UIColor { UIColor(hex: 0xF25534) } static var Color_Red_8C3624: UIColor { UIColor(hex: 0x8C3624) } static var Color_Red_ED2931: UIColor { UIColor(hex: 0xED2931) } static var Color_Red_F95A3E: UIColor { UIColor(hex: 0xF95A3E) } static var Color_Red_51282D: UIColor { UIColor(hex: 0x51282D) } static var Color_Red_A22E3B: UIColor { UIColor(hex: 0xA22E3B) } static var Color_Red_FDEFEF: UIColor { UIColor(hex: 0xFDEFEF) } static var Color_Red_392024: UIColor { UIColor(hex: 0x392024) } static var Color_Red_FFDCDD: UIColor { UIColor(hex: 0xFFDCDD) } static var Color_Red_351F23: UIColor { UIColor(hex: 0x351F23) } static var Color_Red_FE3A1C: UIColor { UIColor(hex: 0xFE3A1C) } static var Color_Red_FF0000: UIColor { UIColor(hex: 0xFF0000) } static var Color_Red_3C272C: UIColor { UIColor(hex: 0x3C272C) } static var Color_Red_E5474C: UIColor { UIColor(hex: 0xE5474C) } static var Color_Red_F75B48: UIColor { UIColor(hex: 0xF75B48) } // 灰 static var Color_Gray_919191: UIColor { UIColor(hex: 0x919191) } static var Color_Gray_323A42: UIColor { UIColor(hex: 0x323A42) } static var Color_Gray_3E4951: UIColor { UIColor(hex: 0x3E4951) } static var Color_Gray_384955: UIColor { UIColor(hex: 0x384955) } static var Color_Gray_272D34: UIColor { UIColor(hex: 0x272D34) } static var Color_Gray_5A636D: UIColor { UIColor(hex: 0x5A636D) } static var Color_Gray_1E2328: UIColor { UIColor(hex: 0x1E2328) } static var Color_Gray_7E8B99: UIColor { UIColor(hex: 0x7E8B99) } static var Color_Gray_7B7C7D: UIColor { UIColor(hex: 0x7B7C7D) } static var Color_Gray_E5E5E5: UIColor { UIColor(hex: 0xE5E5E5) } static var Color_Gray_C0C0C0: UIColor { UIColor(hex: 0xC0C0C0) } static var Color_Gray_CCCCCC: UIColor { UIColor(hex: 0xCCCCCC) } static var Color_Gray_B4B4B4: UIColor { UIColor(hex: 0xB4B4B4) } static var Color_Gray_B2B2B2: UIColor { UIColor(hex: 0xB2B2B2) } static var Color_Gray_BEBEBE: UIColor { UIColor(hex: 0xBEBEBE) } static var Color_Gray_1A1F24: UIColor { UIColor(hex: 0x1A1F24) } static var Color_Gray_F5F5F5: UIColor { UIColor(hex: 0xF5F5F5) } static var Color_Gray_F7F7F7: UIColor { UIColor(hex: 0xF7F7F7) } static var Color_Gray_F8F8F8: UIColor { UIColor(hex: 0xF8F8F8) } static var Color_Gray_DBDBDB: UIColor { UIColor(hex: 0xDBDBDB) } static var Color_Gray_EAEAEA: UIColor { UIColor(hex: 0xEAEAEA) } static var Color_Gray_F0BD5C: UIColor { UIColor(hex: 0xF0BD5C) } static var Color_Gray_F0F0F0: UIColor { UIColor(hex: 0xF0F0F0) } static var Color_Gray_DEDEDE: UIColor { UIColor(hex: 0xDEDEDE) } static var Color_Gray_F6F7FA: UIColor { UIColor(hex: 0xF6F7FA) } static var Color_Gray_EEEEEE: UIColor { UIColor(hex: 0xEEEEEE) } static var Color_Gray_EDEDED: UIColor { UIColor(hex: 0xEDEDED) } static var Color_Gray_77808A: UIColor { UIColor(hex: 0x77808A) } static var Color_Gray_F6F6F6: UIColor { UIColor(hex: 0xF6F6F6) } static var Color_Gray_272E37: UIColor { UIColor(hex: 0x272E37) } static var Color_Gray_F4F4F4: UIColor { UIColor(hex: 0xF4F4F4) } static var Color_Gray_E9E9E9: UIColor { UIColor(hex: 0xE9E9E9) } static var Color_Gray_364450: UIColor { UIColor(hex: 0x364450) } static var Color_Gray_E2E2E2: UIColor { UIColor(hex: 0xE2E2E2) } static var Color_Gray_F5FCFC: UIColor { UIColor(hex: 0xF5FCFC) } static var Color_Gray_EBF5F5: UIColor { UIColor(hex: 0xEBF5F5) } static var Color_Gray_9BA1A4: UIColor { UIColor(hex: 0x9BA1A4) } static var Color_Gray_666666: UIColor { UIColor(hex: 0x666666) } static var Color_Gray_6D777C: UIColor { UIColor(hex: 0x6D777C) } static var Color_Gray_999999: UIColor { UIColor(hex: 0x999999) } static var Color_Gray_959B9E: UIColor { UIColor(hex: 0x959B9E) } static var Color_Gray_C6C6C6: UIColor { UIColor(hex: 0xC6C6C6) } static var Color_Gray_484D50: UIColor { UIColor(hex: 0x484D50) } static var Color_Gray_97A2B0: UIColor { UIColor(hex: 0x97A2B0) } static var Color_Gray_3D4950: UIColor { UIColor(hex: 0x3D4950) } static var Color_Gray_CAD1D8: UIColor { UIColor(hex: 0xCAD1D8) } static var Color_Gray_9AA5B5: UIColor { UIColor(hex: 0x9AA5B5) } static var Color_Gray_D2D2D2: UIColor { UIColor(hex: 0xD2D2D2) } static var Color_Gray_D8D8D8: UIColor { UIColor(hex: 0xD8D8D8) } static var Color_Gray_696969: UIColor { UIColor(hex: 0x696969) } // 橙 static var Color_Orange_FF793A: UIColor { UIColor(hex: 0xFF793A) } static var Color_Orange_FFA946: UIColor { UIColor(hex: 0xFFA946) } static var Color_Orange_FD5900: UIColor { UIColor(hex: 0xFD5900) } static var Color_Orange_CFA972: UIColor { UIColor(hex: 0xCFA972) } static var Color_Orange_F59B23: UIColor { UIColor(hex: 0xF59B23) } static var Color_Orange_FF8F61: UIColor { UIColor(hex: 0xFF8F61) } static var Color_Orange_FE8E1C: UIColor { UIColor(hex: 0xFE8E1C) } // 金 static var Color_Gold_CFA972: UIColor { UIColor(hex: 0xCFA972) } static var Color_Gold_F7F0E7: UIColor { UIColor(hex: 0xF7F0E7) } static var Color_Gold_7D5923: UIColor { UIColor(hex: 0x7D5923) } static var Color_Gold_DBBA86: UIColor { UIColor(hex: 0xDBBA86) } static var Color_Gold_D4AB91: UIColor { UIColor(hex: 0xD4AB91) } static var Color_Gold_ECD5C4: UIColor { UIColor(hex: 0xECD5C4) } static var Color_Gold_CA9F84: UIColor { UIColor(hex: 0xCA9F84) } // 黄 static var Color_Yellow_FFCC00: UIColor { UIColor(hex: 0xFFCC00) } static var Color_Yellow_FFE5A7: UIColor { UIColor(hex: 0xFFE5A7) } static var Color_Yellow_FFBF27: UIColor { UIColor(hex: 0xFFBF27) } // 蓝 static var Color_Blue_6CD0D8: UIColor { UIColor(hex: 0x6CD0D8) } static var Color_Blue_186DD5: UIColor { UIColor(hex: 0x186DD5) } static var Color_Blue_1890FF: UIColor { UIColor(hex: 0x1890FF) } static var Color_Blue_0000FF: UIColor { UIColor(hex: 0x0000FF) } static var Color_Blue_375793: UIColor { UIColor(hex: 375793) } // 粉 static var Color_Pink_F86882: UIColor { UIColor(hex: 0xF86882) } } ================================================ FILE: JetChat/FY-IMChat/FY-IMChat.entitlements ================================================ com.apple.security.application-groups group.com.jetchat.2022.JetChatWidget ================================================ FILE: JetChat/FY-IMChat/FYObjcBridge.h ================================================ // // FYObjcBridge.h // FY-JetChat // // Created by iOS.Jet on 2019/3/1. // Copyright © 2019 Jett. All rights reserved. // #ifndef FYObjcBridge_h #define FYObjcBridge_h // MARK: - Objc #import ///<图片选择 ///<占位视图 #import #import "UINavigationController+Extensions.h" //<导航栏返回处理 #import /// ///< cell高度动态计算 #import /// 朋友圈布局 #import #import "WeakProxy.h" #endif /* FYObjcBridge_h */ ================================================ FILE: JetChat/FY-IMChat/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName JetChat CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS NSAppTransportSecurity NSAllowsArbitraryLoads NSCameraUsageDescription 需要扫描二维码或拍照,是否允许打开相机? NSFaceIDUsageDescription App需要您的同意,才能使用Face ID NSMicrophoneUsageDescription App需要您的同意,才能访问麦克风 NSPhotoLibraryAddUsageDescription App需要您的同意,才能访问相册保存图片 NSPhotoLibraryUsageDescription App需要您的同意,才能访问相册保存图片 UIRequiredDeviceCapabilities armv7 UIStatusBarStyle UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: JetChat/FY-IMChat.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 271C02BE23929B3A0041617F /* ChatMoreMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C026123929B3A0041617F /* ChatMoreMenuView.swift */; }; 271C02BF23929B3A0041617F /* ChatEmojiListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C026223929B3A0041617F /* ChatEmojiListView.swift */; }; 271C02C023929B3A0041617F /* ChatKeyboard+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C026323929B3A0041617F /* ChatKeyboard+Extensions.swift */; }; 271C02C123929B3A0041617F /* ChatMoreMenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C026523929B3A0041617F /* ChatMoreMenuCell.swift */; }; 271C02C223929B3A0041617F /* ChatKeyboardFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C026623929B3A0041617F /* ChatKeyboardFlowLayout.swift */; }; 271C02C323929B3A0041617F /* ChatAppleEmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C026723929B3A0041617F /* ChatAppleEmojiCell.swift */; }; 271C02C423929B3A0041617F /* ChatKeyboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C026823929B3A0041617F /* ChatKeyboardView.swift */; }; 271C02C523929B3A0041617F /* ChatEmoticon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C026A23929B3A0041617F /* ChatEmoticon.swift */; }; 271C02C623929B3A0041617F /* ChatMoreMnueConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C026B23929B3A0041617F /* ChatMoreMnueConfig.swift */; }; 271C02C723929B3A0041617F /* ChatGrowingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C026C23929B3A0041617F /* ChatGrowingTextView.swift */; }; 271C02C823929B3A0041617F /* ic_emotion_delete@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C026F23929B3A0041617F /* ic_emotion_delete@3x.png */; }; 271C02C923929B3A0041617F /* ic_emotion_delete@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027023929B3A0041617F /* ic_emotion_delete@2x.png */; }; 271C02CA23929B3A0041617F /* Emoticons.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 271C027123929B3A0041617F /* Emoticons.bundle */; }; 271C02CB23929B3A0041617F /* ic_more_favorite@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027323929B3A0041617F /* ic_more_favorite@2x.png */; }; 271C02CC23929B3A0041617F /* ic_more_location@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027423929B3A0041617F /* ic_more_location@3x.png */; }; 271C02CD23929B3A0041617F /* ic_more_location@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027523929B3A0041617F /* ic_more_location@2x.png */; }; 271C02CE23929B3A0041617F /* ic_more_favorite@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027623929B3A0041617F /* ic_more_favorite@3x.png */; }; 271C02CF23929B3A0041617F /* ic_more_wallet@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027723929B3A0041617F /* ic_more_wallet@2x.png */; }; 271C02D023929B3A0041617F /* ic_more_video@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027823929B3A0041617F /* ic_more_video@2x.png */; }; 271C02D123929B3A0041617F /* ic_more_video@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027923929B3A0041617F /* ic_more_video@3x.png */; }; 271C02D223929B3A0041617F /* ic_more_wallet@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027A23929B3A0041617F /* ic_more_wallet@3x.png */; }; 271C02D323929B3A0041617F /* ic_more_camera@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027B23929B3A0041617F /* ic_more_camera@2x.png */; }; 271C02D423929B3A0041617F /* ic_more_album@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027C23929B3A0041617F /* ic_more_album@2x.png */; }; 271C02D523929B3A0041617F /* ic_more_sight@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027D23929B3A0041617F /* ic_more_sight@3x.png */; }; 271C02D623929B3A0041617F /* ic_more_album@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027E23929B3A0041617F /* ic_more_album@3x.png */; }; 271C02D723929B3A0041617F /* ic_more_sight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C027F23929B3A0041617F /* ic_more_sight@2x.png */; }; 271C02D823929B3A0041617F /* ic_more_camera@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028023929B3A0041617F /* ic_more_camera@3x.png */; }; 271C02D923929B3A0041617F /* ic_more_friendcard@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028123929B3A0041617F /* ic_more_friendcard@2x.png */; }; 271C02DA23929B3A0041617F /* ic_more_pay@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028223929B3A0041617F /* ic_more_pay@3x.png */; }; 271C02DB23929B3A0041617F /* ic_more_voice@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028323929B3A0041617F /* ic_more_voice@3x.png */; }; 271C02DC23929B3A0041617F /* ic_more_voice@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028423929B3A0041617F /* ic_more_voice@2x.png */; }; 271C02DD23929B3A0041617F /* ic_more_friendcard@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028523929B3A0041617F /* ic_more_friendcard@3x.png */; }; 271C02DE23929B3A0041617F /* ic_more_pay@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028623929B3A0041617F /* ic_more_pay@2x.png */; }; 271C02DF23929B3A0041617F /* ToolViewKeyboardHL@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028823929B3A0041617F /* ToolViewKeyboardHL@3x.png */; }; 271C02E023929B3A0041617F /* ToolViewEmotion@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028923929B3A0041617F /* ToolViewEmotion@3x.png */; }; 271C02E123929B3A0041617F /* ToolViewEmotion@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028A23929B3A0041617F /* ToolViewEmotion@2x.png */; }; 271C02E223929B3A0041617F /* ToolViewKeyboardHL@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028B23929B3A0041617F /* ToolViewKeyboardHL@2x.png */; }; 271C02E323929B3A0041617F /* ToolViewInputVoice@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028C23929B3A0041617F /* ToolViewInputVoice@3x.png */; }; 271C02E423929B3A0041617F /* ToolViewKeyboard@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028D23929B3A0041617F /* ToolViewKeyboard@3x.png */; }; 271C02E523929B3A0041617F /* ToolViewKeyboard@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028E23929B3A0041617F /* ToolViewKeyboard@2x.png */; }; 271C02E623929B3A0041617F /* ToolViewInputVoice@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C028F23929B3A0041617F /* ToolViewInputVoice@2x.png */; }; 271C02E723929B3A0041617F /* ToolViewEmotionHL@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C029023929B3A0041617F /* ToolViewEmotionHL@3x.png */; }; 271C02E823929B3A0041617F /* ToolViewInputVoiceHL@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C029123929B3A0041617F /* ToolViewInputVoiceHL@2x.png */; }; 271C02E923929B3A0041617F /* ToolViewInputVoiceHL@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C029223929B3A0041617F /* ToolViewInputVoiceHL@3x.png */; }; 271C02EA23929B3A0041617F /* ToolViewEmotionHL@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C029323929B3A0041617F /* ToolViewEmotionHL@2x.png */; }; 271C02EB23929B3A0041617F /* TypeSelectorBtnHL_Black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C029423929B3A0041617F /* TypeSelectorBtnHL_Black@2x.png */; }; 271C02EC23929B3A0041617F /* TypeSelectorBtn_Black@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C029523929B3A0041617F /* TypeSelectorBtn_Black@3x.png */; }; 271C02ED23929B3A0041617F /* TypeSelectorBtn_Black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C029623929B3A0041617F /* TypeSelectorBtn_Black@2x.png */; }; 271C02EE23929B3A0041617F /* TypeSelectorBtnHL_Black@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C029723929B3A0041617F /* TypeSelectorBtnHL_Black@3x.png */; }; 271C02EF23929B3A0041617F /* icon_emoji_expression@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C029923929B3A0041617F /* icon_emoji_expression@2x.png */; }; 271C02F023929B3A0041617F /* Expression.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 271C029A23929B3A0041617F /* Expression.bundle */; }; 271C02F123929B3A0041617F /* icon_emoji_expression@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 271C029B23929B3A0041617F /* icon_emoji_expression@3x.png */; }; 271C02F223929B3A0041617F /* Expression.plist in Resources */ = {isa = PBXBuildFile; fileRef = 271C029C23929B3A0041617F /* Expression.plist */; }; 271C02F323929B3A0041617F /* ChatFindEmotion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C029E23929B3A0041617F /* ChatFindEmotion.swift */; }; 271C02F423929B3A0041617F /* ChatEmotionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C029F23929B3A0041617F /* ChatEmotionHelper.swift */; }; 271C02F523929B3A0041617F /* ChatEmotionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C02A023929B3A0041617F /* ChatEmotionAttachment.swift */; }; 271C02F623929B3A0041617F /* WeakProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 271C02A223929B3A0041617F /* WeakProxy.m */; }; 271C02F723929B3A0041617F /* FPSLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C02A323929B3A0041617F /* FPSLabel.swift */; }; 271C02FB23929B3A0041617F /* LanguageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C02AB23929B3A0041617F /* LanguageManager.swift */; }; 271C02FD23929B3A0041617F /* CountDownHandy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C02AD23929B3A0041617F /* CountDownHandy.swift */; }; 27433F9B238D70D4004D5E3D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EA7238D70D3004D5E3D /* AppDelegate.swift */; }; 27433F9C238D70D4004D5E3D /* AppDelegate+Wcdb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EA8238D70D3004D5E3D /* AppDelegate+Wcdb.swift */; }; 27433F9D238D70D4004D5E3D /* AppDelegate+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EA9238D70D3004D5E3D /* AppDelegate+Utils.swift */; }; 27433FA0238D70D4004D5E3D /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EAF238D70D3004D5E3D /* String+Extension.swift */; }; 27433FA1238D70D4004D5E3D /* String+Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EB0238D70D3004D5E3D /* String+Date.swift */; }; 27433FA2238D70D4004D5E3D /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EB2238D70D3004D5E3D /* UIColor+Extension.swift */; }; 27433FA3238D70D4004D5E3D /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EB4238D70D3004D5E3D /* UIView+Extensions.swift */; }; 27433FA4238D70D4004D5E3D /* UIViewController+Extend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EB5238D70D3004D5E3D /* UIViewController+Extend.swift */; }; 27433FA6238D70D4004D5E3D /* JFButton+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EB8238D70D3004D5E3D /* JFButton+Rx.swift */; }; 27433FA7238D70D4004D5E3D /* Observable+Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EB9238D70D3004D5E3D /* Observable+Operators.swift */; }; 27433FA8238D70D4004D5E3D /* MJRefresh+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EBA238D70D3004D5E3D /* MJRefresh+Rx.swift */; }; 27433FA9238D70D4004D5E3D /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EBC238D70D3004D5E3D /* Array+Extension.swift */; }; 27433FAA238D70D4004D5E3D /* UIAlert+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EBE238D70D3004D5E3D /* UIAlert+Extension.swift */; }; 27433FAB238D70D4004D5E3D /* UILabel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EBF238D70D3004D5E3D /* UILabel+Extension.swift */; }; 27433FAC238D70D4004D5E3D /* UIButton+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EC0238D70D3004D5E3D /* UIButton+Extension.swift */; }; 27433FAD238D70D4004D5E3D /* UIImageView+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EC1238D70D3004D5E3D /* UIImageView+Kingfisher.swift */; }; 27433FAE238D70D4004D5E3D /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EC2238D70D3004D5E3D /* UIImage+Extension.swift */; }; 27433FAF238D70D4004D5E3D /* UINavBarItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EC3238D70D3004D5E3D /* UINavBarItem+Extension.swift */; }; 27433FB0238D70D4004D5E3D /* UIFont+PingFang.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EC4238D70D3004D5E3D /* UIFont+PingFang.swift */; }; 27433FB1238D70D4004D5E3D /* UITableView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EC5238D70D3004D5E3D /* UITableView+Extension.swift */; }; 27433FB2238D70D4004D5E3D /* ConstraintArray+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EC7238D70D3004D5E3D /* ConstraintArray+Extensions.swift */; }; 27433FB3238D70D4004D5E3D /* ConstraintArrayDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433EC8238D70D3004D5E3D /* ConstraintArrayDSL.swift */; }; 27433FB5238D70D4004D5E3D /* Dictionary+Exted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433ECC238D70D3004D5E3D /* Dictionary+Exted.swift */; }; 27434018238D70D5004D5E3D /* ErrorTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433F63238D70D4004D5E3D /* ErrorTracker.swift */; }; 2743401A238D70D5004D5E3D /* UINavigationController+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 27433F68238D70D4004D5E3D /* UINavigationController+Extensions.m */; }; 2743401B238D70D5004D5E3D /* NSObject+BinAdd.m in Sources */ = {isa = PBXBuildFile; fileRef = 27433F69238D70D4004D5E3D /* NSObject+BinAdd.m */; }; 2743401D238D70D5004D5E3D /* BaseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433F6F238D70D4004D5E3D /* BaseViewModel.swift */; }; 2743401E238D70D5004D5E3D /* FYBaseTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433F70238D70D4004D5E3D /* FYBaseTabBarController.swift */; }; 27434022238D70D5004D5E3D /* FYBaseNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433F75238D70D4004D5E3D /* FYBaseNavigationController.swift */; }; 27434024238D70D5004D5E3D /* FYBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433F78238D70D4004D5E3D /* FYBaseViewController.swift */; }; 27434029238D70D5004D5E3D /* AppCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433F7F238D70D4004D5E3D /* AppCommon.swift */; }; 27434035238D70D5004D5E3D /* MBConfiguredHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27433F98238D70D4004D5E3D /* MBConfiguredHUD.swift */; }; 27434036238D70D5004D5E3D /* HUDAssets.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 27433F99238D70D4004D5E3D /* HUDAssets.bundle */; }; 27B954E623AFAA5600B94667 /* localVideo0.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 27B954E523AFAA5600B94667 /* localVideo0.mp4 */; }; 2B166CEE22534DFB0018AF62 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2B166CED22534DFB0018AF62 /* LaunchScreen.storyboard */; }; 2B9E10F6222F752700638202 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2B9E10F8222F752700638202 /* Localizable.strings */; }; 2BC1B0D9229134E600A5CB54 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2BC1B0D8229134E600A5CB54 /* Assets.xcassets */; }; 2E59E750222CFFCA005BC4B7 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2E59E752222CFFCA005BC4B7 /* InfoPlist.strings */; }; 2ECBFDC822269BF400871913 /* FY_IMChatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ECBFDC722269BF400871913 /* FY_IMChatTests.swift */; }; 2ECBFDD322269BF400871913 /* FY_IMChatUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ECBFDD222269BF400871913 /* FY_IMChatUITests.swift */; }; 2EF014772230FC6D00681D4F /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EF014762230FC6D00681D4F /* WebKit.framework */; }; 4C15484D28362BDA00AD7400 /* WCDataBaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C15484B28362BD900AD7400 /* WCDataBaseManager.swift */; }; 4C15484E28362BDA00AD7400 /* WCDataBaseTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C15484C28362BDA00AD7400 /* WCDataBaseTable.swift */; }; 4C4A1A272818DABA00CDD3B1 /* BottomPopupUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A1E2818DABA00CDD3B1 /* BottomPopupUtils.swift */; }; 4C4A1A282818DABA00CDD3B1 /* BottomPopupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A1F2818DABA00CDD3B1 /* BottomPopupViewController.swift */; }; 4C4A1A292818DABA00CDD3B1 /* BottomPopupDismissInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A202818DABA00CDD3B1 /* BottomPopupDismissInteractionController.swift */; }; 4C4A1A2A2818DABA00CDD3B1 /* BottomPopupNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A212818DABA00CDD3B1 /* BottomPopupNavigationController.swift */; }; 4C4A1A2B2818DABA00CDD3B1 /* BottomPopupDismissAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A222818DABA00CDD3B1 /* BottomPopupDismissAnimator.swift */; }; 4C4A1A2C2818DABA00CDD3B1 /* BottomPopupPresentAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A232818DABA00CDD3B1 /* BottomPopupPresentAnimator.swift */; }; 4C4A1A2D2818DABA00CDD3B1 /* CSBottomPopupNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A242818DABA00CDD3B1 /* CSBottomPopupNavigationController.swift */; }; 4C4A1A2E2818DABA00CDD3B1 /* BottomPopupTransitionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A252818DABA00CDD3B1 /* BottomPopupTransitionHandler.swift */; }; 4C4A1A2F2818DABA00CDD3B1 /* BottomPopupPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A262818DABA00CDD3B1 /* BottomPopupPresentationController.swift */; }; 4C4A1A332818DB7200CDD3B1 /* FYActionSheetCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A312818DB7200CDD3B1 /* FYActionSheetCell.swift */; }; 4C4A1A342818DB7200CDD3B1 /* FYActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4A1A322818DB7200CDD3B1 /* FYActionSheet.swift */; }; 4C509CB528267AE700CE66DF /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C509CB428267AE700CE66DF /* WidgetKit.framework */; }; 4C509CB728267AE700CE66DF /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C509CB628267AE700CE66DF /* SwiftUI.framework */; }; 4C509CBA28267AE700CE66DF /* JetChatWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C509CB928267AE700CE66DF /* JetChatWidget.swift */; }; 4C509CBD28267AEA00CE66DF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4C509CBC28267AEA00CE66DF /* Assets.xcassets */; }; 4C509CBF28267AEA00CE66DF /* JetChatWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 4C509CBB28267AE700CE66DF /* JetChatWidget.intentdefinition */; }; 4C509CC028267AEA00CE66DF /* JetChatWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 4C509CBB28267AE700CE66DF /* JetChatWidget.intentdefinition */; }; 4C509CC328267AEA00CE66DF /* JetChatWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 4C509CB328267AE700CE66DF /* JetChatWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4C509CCA2826817A00CE66DF /* FYUserDefaultManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C509CC92826817A00CE66DF /* FYUserDefaultManager.swift */; }; 4C5579622817C3C3006D8A09 /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5579612817C3C3006D8A09 /* ActivityIndicator.swift */; }; 4C5E7B192824FAB000C968D3 /* FYMomentsHeaderRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5E7B182824FAAF00C968D3 /* FYMomentsHeaderRefresh.swift */; }; 4C5FB429281A974C001F70E3 /* FYLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5FB427281A974C001F70E3 /* FYLabel.swift */; }; 4C5FB42A281A974C001F70E3 /* FYLabelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5FB428281A974C001F70E3 /* FYLabelType.swift */; }; 4C5FB42D281AA7DD001F70E3 /* FYFileSizeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5FB42C281AA7DD001F70E3 /* FYFileSizeManager.swift */; }; 4C75A40A2817C8FF00E9EF54 /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4092817C8FF00E9EF54 /* R.generated.swift */; }; 4C75A40D2817CA3300E9EF54 /* FYCellDataConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A40C2817CA3300E9EF54 /* FYCellDataConfig.swift */; }; 4C75A4372817D0F400E9EF54 /* FYEditChatInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4112817D0F300E9EF54 /* FYEditChatInfoViewController.swift */; }; 4C75A4382817D0F400E9EF54 /* FYContactsInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4122817D0F300E9EF54 /* FYContactsInfoViewController.swift */; }; 4C75A4392817D0F400E9EF54 /* FYContactsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4132817D0F300E9EF54 /* FYContactsListViewController.swift */; }; 4C75A43A2817D0F400E9EF54 /* FYContactsInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4152817D0F300E9EF54 /* FYContactsInfoView.swift */; }; 4C75A43B2817D0F400E9EF54 /* FYContactsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4162817D0F300E9EF54 /* FYContactsTableViewCell.swift */; }; 4C75A43C2817D0F400E9EF54 /* ScanQRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4182817D0F300E9EF54 /* ScanQRCodeViewController.swift */; }; 4C75A43D2817D0F400E9EF54 /* icon_qrc_border@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4C75A41A2817D0F300E9EF54 /* icon_qrc_border@3x.png */; }; 4C75A43E2817D0F400E9EF54 /* icon_qrc_line@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4C75A41B2817D0F300E9EF54 /* icon_qrc_line@3x.png */; }; 4C75A43F2817D0F400E9EF54 /* FYMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A41E2817D0F300E9EF54 /* FYMessageViewModel.swift */; }; 4C75A4402817D0F400E9EF54 /* FYChatBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4202817D0F300E9EF54 /* FYChatBaseViewController.swift */; }; 4C75A4412817D0F400E9EF54 /* FYMessageForwardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4212817D0F300E9EF54 /* FYMessageForwardViewController.swift */; }; 4C75A4422817D0F400E9EF54 /* FYChatRoomListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4222817D0F300E9EF54 /* FYChatRoomListViewController.swift */; }; 4C75A4432817D0F400E9EF54 /* FYMessageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4242817D0F300E9EF54 /* FYMessageItem.swift */; }; 4C75A4442817D0F400E9EF54 /* FYMessageChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4252817D0F300E9EF54 /* FYMessageChatModel.swift */; }; 4C75A4452817D0F400E9EF54 /* FYMessageBaseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4262817D0F300E9EF54 /* FYMessageBaseModel.swift */; }; 4C75A4462817D0F400E9EF54 /* FYVideoMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4282817D0F300E9EF54 /* FYVideoMessageCell.swift */; }; 4C75A4472817D0F400E9EF54 /* FYImageMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4292817D0F300E9EF54 /* FYImageMessageCell.swift */; }; 4C75A4482817D0F400E9EF54 /* FYTextMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A42A2817D0F300E9EF54 /* FYTextMessageCell.swift */; }; 4C75A4492817D0F400E9EF54 /* FYMessageBaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A42B2817D0F300E9EF54 /* FYMessageBaseCell.swift */; }; 4C75A44A2817D0F400E9EF54 /* FYMineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A42E2817D0F300E9EF54 /* FYMineViewController.swift */; }; 4C75A44B2817D0F400E9EF54 /* FYSettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A42F2817D0F300E9EF54 /* FYSettingViewController.swift */; }; 4C75A44C2817D0F400E9EF54 /* FYFastGridListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4312817D0F300E9EF54 /* FYFastGridListView.swift */; }; 4C75A44D2817D0F400E9EF54 /* FYSesstionListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4342817D0F400E9EF54 /* FYSesstionListViewController.swift */; }; 4C75A44E2817D0F400E9EF54 /* FYConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75A4362817D0F400E9EF54 /* FYConversationCell.swift */; }; 4CBE2B28281B937900FDB081 /* FYNavPopuListMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBE2B25281B937900FDB081 /* FYNavPopuListMenu.swift */; }; 4CBE2B29281B937900FDB081 /* FYNavDropMenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBE2B27281B937900FDB081 /* FYNavDropMenuCell.swift */; }; 4CBE2B2C281BCDE500FDB081 /* Notification+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBE2B2B281BCDE500FDB081 /* Notification+Name.swift */; }; 4CBE2B35281BCE9500FDB081 /* FYDBQueryHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBE2B30281BCE9500FDB081 /* FYDBQueryHelper.swift */; }; 4CBE2B36281BCE9500FDB081 /* FYMessageUserModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBE2B31281BCE9500FDB081 /* FYMessageUserModel.swift */; }; 4CF27C18281CEFDC00DBD7EC /* FYThemeCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF27C17281CEFDC00DBD7EC /* FYThemeCenter.swift */; }; 4CF27C1A281CF04B00DBD7EC /* FYDarkTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF27C19281CF04B00DBD7EC /* FYDarkTheme.swift */; }; 4CF27C1C281CF05D00DBD7EC /* FYLightTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF27C1B281CF05D00DBD7EC /* FYLightTheme.swift */; }; 4CF27C1E281CF82600DBD7EC /* FYThemeColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF27C1D281CF82600DBD7EC /* FYThemeColors.swift */; }; 4CF27C20281D4EE600DBD7EC /* FYThemeSelectionListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF27C1F281D4EE600DBD7EC /* FYThemeSelectionListVC.swift */; }; 4CF85630281A2FFA00F48D4D /* FYMomentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8562F281A2FFA00F48D4D /* FYMomentsViewController.swift */; }; 4CF8568C281A34C200F48D4D /* FYMomentNavBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8568B281A34C200F48D4D /* FYMomentNavBar.swift */; }; 4CF8568E281A3B8400F48D4D /* FYBaseIGListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8568D281A3B8400F48D4D /* FYBaseIGListViewController.swift */; }; 4CF85690281A3E8100F48D4D /* FYMomentInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8568F281A3E8100F48D4D /* FYMomentInfo.swift */; }; 4CF85692281A3F0F00F48D4D /* FYMoUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF85691281A3F0F00F48D4D /* FYMoUserInfo.swift */; }; 4CF85694281A3F5300F48D4D /* FYCommentInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF85693281A3F5300F48D4D /* FYCommentInfo.swift */; }; 4CF856A0281A40D400F48D4D /* MomentBottomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF85696281A40D400F48D4D /* MomentBottomCell.swift */; }; 4CF856A1281A40D400F48D4D /* MomentHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF85697281A40D400F48D4D /* MomentHeaderCell.swift */; }; 4CF856A2281A40D400F48D4D /* CommentInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF85698281A40D400F48D4D /* CommentInputView.swift */; }; 4CF856A3281A40D400F48D4D /* MomentLocationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF85699281A40D400F48D4D /* MomentLocationCell.swift */; }; 4CF856A4281A40D400F48D4D /* CommentContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8569A281A40D400F48D4D /* CommentContentCell.swift */; }; 4CF856A5281A40D400F48D4D /* MomentCommentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8569B281A40D400F48D4D /* MomentCommentCell.swift */; }; 4CF856A6281A40D400F48D4D /* CommentThumbView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8569C281A40D400F48D4D /* CommentThumbView.swift */; }; 4CF856A7281A40D400F48D4D /* CommentContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8569D281A40D400F48D4D /* CommentContentView.swift */; }; 4CF856A8281A40D400F48D4D /* MomentTopCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8569E281A40D400F48D4D /* MomentTopCell.swift */; }; 4CF856A9281A40D400F48D4D /* MomentHeaderImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8569F281A40D400F48D4D /* MomentHeaderImageCell.swift */; }; 4CF856AC281A40F300F48D4D /* NineImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF856AA281A40F300F48D4D /* NineImageView.swift */; }; 4CF856AD281A40F300F48D4D /* OperateMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF856AB281A40F300F48D4D /* OperateMenuView.swift */; }; 4CF856B5281A433700F48D4D /* FYTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF856B4281A433700F48D4D /* FYTextView.swift */; }; 4CF856B8281A456700F48D4D /* FYMomentBindingSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF856B7281A456700F48D4D /* FYMomentBindingSection.swift */; }; 4CF856BF281A4D2800F48D4D /* moments1.json in Resources */ = {isa = PBXBuildFile; fileRef = 4CF856BD281A4D2800F48D4D /* moments1.json */; }; 4CF856C0281A4D2800F48D4D /* moments2.json in Resources */ = {isa = PBXBuildFile; fileRef = 4CF856BE281A4D2800F48D4D /* moments2.json */; }; B8766C51931F201D9A764FC9 /* Pods_FY_IMChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E374D4E527160347F32F8B70 /* Pods_FY_IMChat.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 2ECBFDC422269BF400871913 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 2ECBFDA722269BF300871913 /* Project object */; proxyType = 1; remoteGlobalIDString = 2ECBFDAE22269BF300871913; remoteInfo = "FY-IMChat"; }; 2ECBFDCF22269BF400871913 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 2ECBFDA722269BF300871913 /* Project object */; proxyType = 1; remoteGlobalIDString = 2ECBFDAE22269BF300871913; remoteInfo = "FY-IMChat"; }; 4C509CC128267AEA00CE66DF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 2ECBFDA722269BF300871913 /* Project object */; proxyType = 1; remoteGlobalIDString = 4C509CB228267AE700CE66DF; remoteInfo = JetChatWidgetExtension; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 4C509CC428267AEA00CE66DF /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( 4C509CC328267AEA00CE66DF /* JetChatWidgetExtension.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 271C026123929B3A0041617F /* ChatMoreMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMoreMenuView.swift; sourceTree = ""; }; 271C026223929B3A0041617F /* ChatEmojiListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatEmojiListView.swift; sourceTree = ""; }; 271C026323929B3A0041617F /* ChatKeyboard+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ChatKeyboard+Extensions.swift"; sourceTree = ""; }; 271C026523929B3A0041617F /* ChatMoreMenuCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMoreMenuCell.swift; sourceTree = ""; }; 271C026623929B3A0041617F /* ChatKeyboardFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatKeyboardFlowLayout.swift; sourceTree = ""; }; 271C026723929B3A0041617F /* ChatAppleEmojiCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatAppleEmojiCell.swift; sourceTree = ""; }; 271C026823929B3A0041617F /* ChatKeyboardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatKeyboardView.swift; sourceTree = ""; }; 271C026A23929B3A0041617F /* ChatEmoticon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatEmoticon.swift; sourceTree = ""; }; 271C026B23929B3A0041617F /* ChatMoreMnueConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMoreMnueConfig.swift; sourceTree = ""; }; 271C026C23929B3A0041617F /* ChatGrowingTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatGrowingTextView.swift; sourceTree = ""; }; 271C026F23929B3A0041617F /* ic_emotion_delete@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_emotion_delete@3x.png"; sourceTree = ""; }; 271C027023929B3A0041617F /* ic_emotion_delete@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_emotion_delete@2x.png"; sourceTree = ""; }; 271C027123929B3A0041617F /* Emoticons.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Emoticons.bundle; sourceTree = ""; }; 271C027323929B3A0041617F /* ic_more_favorite@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_favorite@2x.png"; sourceTree = ""; }; 271C027423929B3A0041617F /* ic_more_location@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_location@3x.png"; sourceTree = ""; }; 271C027523929B3A0041617F /* ic_more_location@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_location@2x.png"; sourceTree = ""; }; 271C027623929B3A0041617F /* ic_more_favorite@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_favorite@3x.png"; sourceTree = ""; }; 271C027723929B3A0041617F /* ic_more_wallet@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_wallet@2x.png"; sourceTree = ""; }; 271C027823929B3A0041617F /* ic_more_video@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_video@2x.png"; sourceTree = ""; }; 271C027923929B3A0041617F /* ic_more_video@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_video@3x.png"; sourceTree = ""; }; 271C027A23929B3A0041617F /* ic_more_wallet@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_wallet@3x.png"; sourceTree = ""; }; 271C027B23929B3A0041617F /* ic_more_camera@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_camera@2x.png"; sourceTree = ""; }; 271C027C23929B3A0041617F /* ic_more_album@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_album@2x.png"; sourceTree = ""; }; 271C027D23929B3A0041617F /* ic_more_sight@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_sight@3x.png"; sourceTree = ""; }; 271C027E23929B3A0041617F /* ic_more_album@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_album@3x.png"; sourceTree = ""; }; 271C027F23929B3A0041617F /* ic_more_sight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_sight@2x.png"; sourceTree = ""; }; 271C028023929B3A0041617F /* ic_more_camera@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_camera@3x.png"; sourceTree = ""; }; 271C028123929B3A0041617F /* ic_more_friendcard@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_friendcard@2x.png"; sourceTree = ""; }; 271C028223929B3A0041617F /* ic_more_pay@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_pay@3x.png"; sourceTree = ""; }; 271C028323929B3A0041617F /* ic_more_voice@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_voice@3x.png"; sourceTree = ""; }; 271C028423929B3A0041617F /* ic_more_voice@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_voice@2x.png"; sourceTree = ""; }; 271C028523929B3A0041617F /* ic_more_friendcard@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_friendcard@3x.png"; sourceTree = ""; }; 271C028623929B3A0041617F /* ic_more_pay@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_more_pay@2x.png"; sourceTree = ""; }; 271C028823929B3A0041617F /* ToolViewKeyboardHL@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewKeyboardHL@3x.png"; sourceTree = ""; }; 271C028923929B3A0041617F /* ToolViewEmotion@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewEmotion@3x.png"; sourceTree = ""; }; 271C028A23929B3A0041617F /* ToolViewEmotion@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewEmotion@2x.png"; sourceTree = ""; }; 271C028B23929B3A0041617F /* ToolViewKeyboardHL@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewKeyboardHL@2x.png"; sourceTree = ""; }; 271C028C23929B3A0041617F /* ToolViewInputVoice@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewInputVoice@3x.png"; sourceTree = ""; }; 271C028D23929B3A0041617F /* ToolViewKeyboard@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewKeyboard@3x.png"; sourceTree = ""; }; 271C028E23929B3A0041617F /* ToolViewKeyboard@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewKeyboard@2x.png"; sourceTree = ""; }; 271C028F23929B3A0041617F /* ToolViewInputVoice@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewInputVoice@2x.png"; sourceTree = ""; }; 271C029023929B3A0041617F /* ToolViewEmotionHL@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewEmotionHL@3x.png"; sourceTree = ""; }; 271C029123929B3A0041617F /* ToolViewInputVoiceHL@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewInputVoiceHL@2x.png"; sourceTree = ""; }; 271C029223929B3A0041617F /* ToolViewInputVoiceHL@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewInputVoiceHL@3x.png"; sourceTree = ""; }; 271C029323929B3A0041617F /* ToolViewEmotionHL@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ToolViewEmotionHL@2x.png"; sourceTree = ""; }; 271C029423929B3A0041617F /* TypeSelectorBtnHL_Black@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "TypeSelectorBtnHL_Black@2x.png"; sourceTree = ""; }; 271C029523929B3A0041617F /* TypeSelectorBtn_Black@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "TypeSelectorBtn_Black@3x.png"; sourceTree = ""; }; 271C029623929B3A0041617F /* TypeSelectorBtn_Black@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "TypeSelectorBtn_Black@2x.png"; sourceTree = ""; }; 271C029723929B3A0041617F /* TypeSelectorBtnHL_Black@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "TypeSelectorBtnHL_Black@3x.png"; sourceTree = ""; }; 271C029923929B3A0041617F /* icon_emoji_expression@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_emoji_expression@2x.png"; sourceTree = ""; }; 271C029A23929B3A0041617F /* Expression.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Expression.bundle; sourceTree = ""; }; 271C029B23929B3A0041617F /* icon_emoji_expression@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_emoji_expression@3x.png"; sourceTree = ""; }; 271C029C23929B3A0041617F /* Expression.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expression.plist; sourceTree = ""; }; 271C029E23929B3A0041617F /* ChatFindEmotion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatFindEmotion.swift; sourceTree = ""; }; 271C029F23929B3A0041617F /* ChatEmotionHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatEmotionHelper.swift; sourceTree = ""; }; 271C02A023929B3A0041617F /* ChatEmotionAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatEmotionAttachment.swift; sourceTree = ""; }; 271C02A223929B3A0041617F /* WeakProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WeakProxy.m; sourceTree = ""; }; 271C02A323929B3A0041617F /* FPSLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FPSLabel.swift; sourceTree = ""; }; 271C02A423929B3A0041617F /* WeakProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WeakProxy.h; sourceTree = ""; }; 271C02AB23929B3A0041617F /* LanguageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageManager.swift; sourceTree = ""; }; 271C02AD23929B3A0041617F /* CountDownHandy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountDownHandy.swift; sourceTree = ""; }; 27433EA7238D70D3004D5E3D /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 27433EA8238D70D3004D5E3D /* AppDelegate+Wcdb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Wcdb.swift"; sourceTree = ""; }; 27433EA9238D70D3004D5E3D /* AppDelegate+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Utils.swift"; sourceTree = ""; }; 27433EAF238D70D3004D5E3D /* String+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 27433EB0238D70D3004D5E3D /* String+Date.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Date.swift"; sourceTree = ""; }; 27433EB2238D70D3004D5E3D /* UIColor+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; 27433EB4238D70D3004D5E3D /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; 27433EB5238D70D3004D5E3D /* UIViewController+Extend.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extend.swift"; sourceTree = ""; }; 27433EB8238D70D3004D5E3D /* JFButton+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JFButton+Rx.swift"; sourceTree = ""; }; 27433EB9238D70D3004D5E3D /* Observable+Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Operators.swift"; sourceTree = ""; }; 27433EBA238D70D3004D5E3D /* MJRefresh+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MJRefresh+Rx.swift"; sourceTree = ""; }; 27433EBC238D70D3004D5E3D /* Array+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = ""; }; 27433EBE238D70D3004D5E3D /* UIAlert+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlert+Extension.swift"; sourceTree = ""; }; 27433EBF238D70D3004D5E3D /* UILabel+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+Extension.swift"; sourceTree = ""; }; 27433EC0238D70D3004D5E3D /* UIButton+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.swift"; sourceTree = ""; }; 27433EC1238D70D3004D5E3D /* UIImageView+Kingfisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+Kingfisher.swift"; sourceTree = ""; }; 27433EC2238D70D3004D5E3D /* UIImage+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; 27433EC3238D70D3004D5E3D /* UINavBarItem+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UINavBarItem+Extension.swift"; sourceTree = ""; }; 27433EC4238D70D3004D5E3D /* UIFont+PingFang.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIFont+PingFang.swift"; sourceTree = ""; }; 27433EC5238D70D3004D5E3D /* UITableView+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Extension.swift"; sourceTree = ""; }; 27433EC7238D70D3004D5E3D /* ConstraintArray+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConstraintArray+Extensions.swift"; sourceTree = ""; }; 27433EC8238D70D3004D5E3D /* ConstraintArrayDSL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstraintArrayDSL.swift; sourceTree = ""; }; 27433ECC238D70D3004D5E3D /* Dictionary+Exted.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Exted.swift"; sourceTree = ""; }; 27433F63238D70D4004D5E3D /* ErrorTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorTracker.swift; sourceTree = ""; }; 27433F67238D70D4004D5E3D /* NSObject+BinAdd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+BinAdd.h"; sourceTree = ""; }; 27433F68238D70D4004D5E3D /* UINavigationController+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UINavigationController+Extensions.m"; sourceTree = ""; }; 27433F69238D70D4004D5E3D /* NSObject+BinAdd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+BinAdd.m"; sourceTree = ""; }; 27433F6A238D70D4004D5E3D /* UINavigationController+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UINavigationController+Extensions.h"; sourceTree = ""; }; 27433F6F238D70D4004D5E3D /* BaseViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseViewModel.swift; sourceTree = ""; }; 27433F70238D70D4004D5E3D /* FYBaseTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYBaseTabBarController.swift; sourceTree = ""; }; 27433F75238D70D4004D5E3D /* FYBaseNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYBaseNavigationController.swift; sourceTree = ""; }; 27433F78238D70D4004D5E3D /* FYBaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYBaseViewController.swift; sourceTree = ""; }; 27433F7F238D70D4004D5E3D /* AppCommon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppCommon.swift; sourceTree = ""; }; 27433F98238D70D4004D5E3D /* MBConfiguredHUD.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MBConfiguredHUD.swift; sourceTree = ""; }; 27433F99238D70D4004D5E3D /* HUDAssets.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = HUDAssets.bundle; sourceTree = ""; }; 27B954E523AFAA5600B94667 /* localVideo0.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = localVideo0.mp4; sourceTree = ""; }; 2B166CED22534DFB0018AF62 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = Classes/Resource/LaunchScreen.storyboard; sourceTree = ""; }; 2B9E10F7222F752700638202 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 2B9E10F9222F752800638202 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 2BC1B0D8229134E600A5CB54 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2E2C3B9E2228C9D300BF1C5D /* FYObjcBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FYObjcBridge.h; sourceTree = ""; }; 2E59E751222CFFCA005BC4B7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 2E59E753222CFFD7005BC4B7 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 2ECBFDAF22269BF300871913 /* JetChat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JetChat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2ECBFDBE22269BF400871913 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2ECBFDC322269BF400871913 /* FY-IMChatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FY-IMChatTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 2ECBFDC722269BF400871913 /* FY_IMChatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FY_IMChatTests.swift; sourceTree = ""; }; 2ECBFDC922269BF400871913 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2ECBFDCE22269BF400871913 /* FY-IMChatUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FY-IMChatUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 2ECBFDD222269BF400871913 /* FY_IMChatUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FY_IMChatUITests.swift; sourceTree = ""; }; 2ECBFDD422269BF400871913 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2EF014762230FC6D00681D4F /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 2EF014782230FC7200681D4F /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 4C15484B28362BD900AD7400 /* WCDataBaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WCDataBaseManager.swift; path = "FY-IMChat/Classes/Utilites/DataBase/WCDataBaseManager.swift"; sourceTree = SOURCE_ROOT; }; 4C15484C28362BDA00AD7400 /* WCDataBaseTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WCDataBaseTable.swift; path = "FY-IMChat/Classes/Utilites/DataBase/WCDataBaseTable.swift"; sourceTree = SOURCE_ROOT; }; 4C4A1A1E2818DABA00CDD3B1 /* BottomPopupUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomPopupUtils.swift; sourceTree = ""; }; 4C4A1A1F2818DABA00CDD3B1 /* BottomPopupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomPopupViewController.swift; sourceTree = ""; }; 4C4A1A202818DABA00CDD3B1 /* BottomPopupDismissInteractionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomPopupDismissInteractionController.swift; sourceTree = ""; }; 4C4A1A212818DABA00CDD3B1 /* BottomPopupNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomPopupNavigationController.swift; sourceTree = ""; }; 4C4A1A222818DABA00CDD3B1 /* BottomPopupDismissAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomPopupDismissAnimator.swift; sourceTree = ""; }; 4C4A1A232818DABA00CDD3B1 /* BottomPopupPresentAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomPopupPresentAnimator.swift; sourceTree = ""; }; 4C4A1A242818DABA00CDD3B1 /* CSBottomPopupNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSBottomPopupNavigationController.swift; sourceTree = ""; }; 4C4A1A252818DABA00CDD3B1 /* BottomPopupTransitionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomPopupTransitionHandler.swift; sourceTree = ""; }; 4C4A1A262818DABA00CDD3B1 /* BottomPopupPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomPopupPresentationController.swift; sourceTree = ""; }; 4C4A1A312818DB7200CDD3B1 /* FYActionSheetCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYActionSheetCell.swift; sourceTree = ""; }; 4C4A1A322818DB7200CDD3B1 /* FYActionSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYActionSheet.swift; sourceTree = ""; }; 4C509CB328267AE700CE66DF /* JetChatWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = JetChatWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 4C509CB428267AE700CE66DF /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 4C509CB628267AE700CE66DF /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 4C509CB928267AE700CE66DF /* JetChatWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetChatWidget.swift; sourceTree = ""; }; 4C509CBB28267AE700CE66DF /* JetChatWidget.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = JetChatWidget.intentdefinition; sourceTree = ""; }; 4C509CBC28267AEA00CE66DF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4C509CBE28267AEA00CE66DF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4C509CC828267B1A00CE66DF /* JetChatWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = JetChatWidgetExtension.entitlements; sourceTree = ""; }; 4C509CC92826817A00CE66DF /* FYUserDefaultManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYUserDefaultManager.swift; sourceTree = ""; }; 4C509CCB28268BC600CE66DF /* FY-IMChat.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "FY-IMChat.entitlements"; sourceTree = ""; }; 4C5579612817C3C3006D8A09 /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = ""; }; 4C5E7B182824FAAF00C968D3 /* FYMomentsHeaderRefresh.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYMomentsHeaderRefresh.swift; sourceTree = ""; }; 4C5FB427281A974C001F70E3 /* FYLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYLabel.swift; sourceTree = ""; }; 4C5FB428281A974C001F70E3 /* FYLabelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYLabelType.swift; sourceTree = ""; }; 4C5FB42C281AA7DD001F70E3 /* FYFileSizeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYFileSizeManager.swift; sourceTree = ""; }; 4C75A4092817C8FF00E9EF54 /* R.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = R.generated.swift; sourceTree = ""; }; 4C75A40C2817CA3300E9EF54 /* FYCellDataConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYCellDataConfig.swift; sourceTree = ""; }; 4C75A4112817D0F300E9EF54 /* FYEditChatInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYEditChatInfoViewController.swift; sourceTree = ""; }; 4C75A4122817D0F300E9EF54 /* FYContactsInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYContactsInfoViewController.swift; sourceTree = ""; }; 4C75A4132817D0F300E9EF54 /* FYContactsListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYContactsListViewController.swift; sourceTree = ""; }; 4C75A4152817D0F300E9EF54 /* FYContactsInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYContactsInfoView.swift; sourceTree = ""; }; 4C75A4162817D0F300E9EF54 /* FYContactsTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYContactsTableViewCell.swift; sourceTree = ""; }; 4C75A4182817D0F300E9EF54 /* ScanQRCodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanQRCodeViewController.swift; sourceTree = ""; }; 4C75A41A2817D0F300E9EF54 /* icon_qrc_border@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_qrc_border@3x.png"; sourceTree = ""; }; 4C75A41B2817D0F300E9EF54 /* icon_qrc_line@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_qrc_line@3x.png"; sourceTree = ""; }; 4C75A41E2817D0F300E9EF54 /* FYMessageViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYMessageViewModel.swift; sourceTree = ""; }; 4C75A4202817D0F300E9EF54 /* FYChatBaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYChatBaseViewController.swift; sourceTree = ""; }; 4C75A4212817D0F300E9EF54 /* FYMessageForwardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYMessageForwardViewController.swift; sourceTree = ""; }; 4C75A4222817D0F300E9EF54 /* FYChatRoomListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYChatRoomListViewController.swift; sourceTree = ""; }; 4C75A4242817D0F300E9EF54 /* FYMessageItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYMessageItem.swift; sourceTree = ""; }; 4C75A4252817D0F300E9EF54 /* FYMessageChatModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYMessageChatModel.swift; sourceTree = ""; }; 4C75A4262817D0F300E9EF54 /* FYMessageBaseModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYMessageBaseModel.swift; sourceTree = ""; }; 4C75A4282817D0F300E9EF54 /* FYVideoMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYVideoMessageCell.swift; sourceTree = ""; }; 4C75A4292817D0F300E9EF54 /* FYImageMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYImageMessageCell.swift; sourceTree = ""; }; 4C75A42A2817D0F300E9EF54 /* FYTextMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYTextMessageCell.swift; sourceTree = ""; }; 4C75A42B2817D0F300E9EF54 /* FYMessageBaseCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYMessageBaseCell.swift; sourceTree = ""; }; 4C75A42E2817D0F300E9EF54 /* FYMineViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYMineViewController.swift; sourceTree = ""; }; 4C75A42F2817D0F300E9EF54 /* FYSettingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYSettingViewController.swift; sourceTree = ""; }; 4C75A4312817D0F300E9EF54 /* FYFastGridListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYFastGridListView.swift; sourceTree = ""; }; 4C75A4342817D0F400E9EF54 /* FYSesstionListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYSesstionListViewController.swift; sourceTree = ""; }; 4C75A4362817D0F400E9EF54 /* FYConversationCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYConversationCell.swift; sourceTree = ""; }; 4CBE2B25281B937900FDB081 /* FYNavPopuListMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYNavPopuListMenu.swift; sourceTree = ""; }; 4CBE2B27281B937900FDB081 /* FYNavDropMenuCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYNavDropMenuCell.swift; sourceTree = ""; }; 4CBE2B2B281BCDE500FDB081 /* Notification+Name.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Notification+Name.swift"; sourceTree = ""; }; 4CBE2B30281BCE9500FDB081 /* FYDBQueryHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYDBQueryHelper.swift; sourceTree = ""; }; 4CBE2B31281BCE9500FDB081 /* FYMessageUserModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYMessageUserModel.swift; sourceTree = ""; }; 4CF27C17281CEFDC00DBD7EC /* FYThemeCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYThemeCenter.swift; sourceTree = ""; }; 4CF27C19281CF04B00DBD7EC /* FYDarkTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYDarkTheme.swift; sourceTree = ""; }; 4CF27C1B281CF05D00DBD7EC /* FYLightTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYLightTheme.swift; sourceTree = ""; }; 4CF27C1D281CF82600DBD7EC /* FYThemeColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYThemeColors.swift; sourceTree = ""; }; 4CF27C1F281D4EE600DBD7EC /* FYThemeSelectionListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYThemeSelectionListVC.swift; sourceTree = ""; }; 4CF8562F281A2FFA00F48D4D /* FYMomentsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYMomentsViewController.swift; sourceTree = ""; }; 4CF8568B281A34C200F48D4D /* FYMomentNavBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYMomentNavBar.swift; sourceTree = ""; }; 4CF8568D281A3B8400F48D4D /* FYBaseIGListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYBaseIGListViewController.swift; sourceTree = ""; }; 4CF8568F281A3E8100F48D4D /* FYMomentInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYMomentInfo.swift; sourceTree = ""; }; 4CF85691281A3F0F00F48D4D /* FYMoUserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYMoUserInfo.swift; sourceTree = ""; }; 4CF85693281A3F5300F48D4D /* FYCommentInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYCommentInfo.swift; sourceTree = ""; }; 4CF85696281A40D400F48D4D /* MomentBottomCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MomentBottomCell.swift; sourceTree = ""; }; 4CF85697281A40D400F48D4D /* MomentHeaderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MomentHeaderCell.swift; sourceTree = ""; }; 4CF85698281A40D400F48D4D /* CommentInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentInputView.swift; sourceTree = ""; }; 4CF85699281A40D400F48D4D /* MomentLocationCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MomentLocationCell.swift; sourceTree = ""; }; 4CF8569A281A40D400F48D4D /* CommentContentCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentContentCell.swift; sourceTree = ""; }; 4CF8569B281A40D400F48D4D /* MomentCommentCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MomentCommentCell.swift; sourceTree = ""; }; 4CF8569C281A40D400F48D4D /* CommentThumbView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentThumbView.swift; sourceTree = ""; }; 4CF8569D281A40D400F48D4D /* CommentContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentContentView.swift; sourceTree = ""; }; 4CF8569E281A40D400F48D4D /* MomentTopCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MomentTopCell.swift; sourceTree = ""; }; 4CF8569F281A40D400F48D4D /* MomentHeaderImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MomentHeaderImageCell.swift; sourceTree = ""; }; 4CF856AA281A40F300F48D4D /* NineImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NineImageView.swift; sourceTree = ""; }; 4CF856AB281A40F300F48D4D /* OperateMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperateMenuView.swift; sourceTree = ""; }; 4CF856B4281A433700F48D4D /* FYTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FYTextView.swift; sourceTree = ""; }; 4CF856B7281A456700F48D4D /* FYMomentBindingSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FYMomentBindingSection.swift; sourceTree = ""; }; 4CF856BD281A4D2800F48D4D /* moments1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = moments1.json; sourceTree = ""; }; 4CF856BE281A4D2800F48D4D /* moments2.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = moments2.json; sourceTree = ""; }; 5858C5E76CAB64DDB07738CD /* Pods-FY-IMChat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FY-IMChat.release.xcconfig"; path = "Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat.release.xcconfig"; sourceTree = ""; }; 98E659EF05275210EC83E03D /* Pods-FY-IMChat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FY-IMChat.debug.xcconfig"; path = "Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat.debug.xcconfig"; sourceTree = ""; }; E374D4E527160347F32F8B70 /* Pods_FY_IMChat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FY_IMChat.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2ECBFDAC22269BF300871913 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2EF014772230FC6D00681D4F /* WebKit.framework in Frameworks */, B8766C51931F201D9A764FC9 /* Pods_FY_IMChat.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 2ECBFDC022269BF400871913 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 2ECBFDCB22269BF400871913 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 4C509CB028267AE700CE66DF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 4C509CB728267AE700CE66DF /* SwiftUI.framework in Frameworks */, 4C509CB528267AE700CE66DF /* WidgetKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 271C025623929B3A0041617F /* Utilites */ = { isa = PBXGroup; children = ( 4CF27C16281CEF3B00DBD7EC /* Theme */, 4CBE2B2D281BCE9500FDB081 /* DataBase */, 4C5FB42B281AA7D0001F70E3 /* FileSizeManager */, 271C026023929B3A0041617F /* KeyboardCore */, 271C02A123929B3A0041617F /* FPSLabel */, 271C02AA23929B3A0041617F /* Helpers */, ); path = Utilites; sourceTree = ""; }; 271C026023929B3A0041617F /* KeyboardCore */ = { isa = PBXGroup; children = ( 271C026123929B3A0041617F /* ChatMoreMenuView.swift */, 271C026223929B3A0041617F /* ChatEmojiListView.swift */, 271C026823929B3A0041617F /* ChatKeyboardView.swift */, 271C026C23929B3A0041617F /* ChatGrowingTextView.swift */, 271C026323929B3A0041617F /* ChatKeyboard+Extensions.swift */, 271C026423929B3A0041617F /* Cell */, 271C026923929B3A0041617F /* Model */, 271C026D23929B3A0041617F /* Resource */, 271C029D23929B3A0041617F /* Helper */, ); path = KeyboardCore; sourceTree = ""; }; 271C026423929B3A0041617F /* Cell */ = { isa = PBXGroup; children = ( 271C026523929B3A0041617F /* ChatMoreMenuCell.swift */, 271C026623929B3A0041617F /* ChatKeyboardFlowLayout.swift */, 271C026723929B3A0041617F /* ChatAppleEmojiCell.swift */, ); path = Cell; sourceTree = ""; }; 271C026923929B3A0041617F /* Model */ = { isa = PBXGroup; children = ( 271C026A23929B3A0041617F /* ChatEmoticon.swift */, 271C026B23929B3A0041617F /* ChatMoreMnueConfig.swift */, ); path = Model; sourceTree = ""; }; 271C026D23929B3A0041617F /* Resource */ = { isa = PBXGroup; children = ( 271C026E23929B3A0041617F /* Emoji */, 271C027223929B3A0041617F /* MoreMnues */, 271C028723929B3A0041617F /* ChatToolbar */, 271C029823929B3A0041617F /* Emotion */, ); path = Resource; sourceTree = ""; }; 271C026E23929B3A0041617F /* Emoji */ = { isa = PBXGroup; children = ( 271C026F23929B3A0041617F /* ic_emotion_delete@3x.png */, 271C027023929B3A0041617F /* ic_emotion_delete@2x.png */, 271C027123929B3A0041617F /* Emoticons.bundle */, ); path = Emoji; sourceTree = ""; }; 271C027223929B3A0041617F /* MoreMnues */ = { isa = PBXGroup; children = ( 271C027323929B3A0041617F /* ic_more_favorite@2x.png */, 271C027423929B3A0041617F /* ic_more_location@3x.png */, 271C027523929B3A0041617F /* ic_more_location@2x.png */, 271C027623929B3A0041617F /* ic_more_favorite@3x.png */, 271C027723929B3A0041617F /* ic_more_wallet@2x.png */, 271C027823929B3A0041617F /* ic_more_video@2x.png */, 271C027923929B3A0041617F /* ic_more_video@3x.png */, 271C027A23929B3A0041617F /* ic_more_wallet@3x.png */, 271C027B23929B3A0041617F /* ic_more_camera@2x.png */, 271C027C23929B3A0041617F /* ic_more_album@2x.png */, 271C027D23929B3A0041617F /* ic_more_sight@3x.png */, 271C027E23929B3A0041617F /* ic_more_album@3x.png */, 271C027F23929B3A0041617F /* ic_more_sight@2x.png */, 271C028023929B3A0041617F /* ic_more_camera@3x.png */, 271C028123929B3A0041617F /* ic_more_friendcard@2x.png */, 271C028223929B3A0041617F /* ic_more_pay@3x.png */, 271C028323929B3A0041617F /* ic_more_voice@3x.png */, 271C028423929B3A0041617F /* ic_more_voice@2x.png */, 271C028523929B3A0041617F /* ic_more_friendcard@3x.png */, 271C028623929B3A0041617F /* ic_more_pay@2x.png */, ); path = MoreMnues; sourceTree = ""; }; 271C028723929B3A0041617F /* ChatToolbar */ = { isa = PBXGroup; children = ( 271C028823929B3A0041617F /* ToolViewKeyboardHL@3x.png */, 271C028923929B3A0041617F /* ToolViewEmotion@3x.png */, 271C028A23929B3A0041617F /* ToolViewEmotion@2x.png */, 271C028B23929B3A0041617F /* ToolViewKeyboardHL@2x.png */, 271C028C23929B3A0041617F /* ToolViewInputVoice@3x.png */, 271C028D23929B3A0041617F /* ToolViewKeyboard@3x.png */, 271C028E23929B3A0041617F /* ToolViewKeyboard@2x.png */, 271C028F23929B3A0041617F /* ToolViewInputVoice@2x.png */, 271C029023929B3A0041617F /* ToolViewEmotionHL@3x.png */, 271C029123929B3A0041617F /* ToolViewInputVoiceHL@2x.png */, 271C029223929B3A0041617F /* ToolViewInputVoiceHL@3x.png */, 271C029323929B3A0041617F /* ToolViewEmotionHL@2x.png */, 271C029423929B3A0041617F /* TypeSelectorBtnHL_Black@2x.png */, 271C029523929B3A0041617F /* TypeSelectorBtn_Black@3x.png */, 271C029623929B3A0041617F /* TypeSelectorBtn_Black@2x.png */, 271C029723929B3A0041617F /* TypeSelectorBtnHL_Black@3x.png */, ); path = ChatToolbar; sourceTree = ""; }; 271C029823929B3A0041617F /* Emotion */ = { isa = PBXGroup; children = ( 271C029923929B3A0041617F /* icon_emoji_expression@2x.png */, 271C029A23929B3A0041617F /* Expression.bundle */, 271C029B23929B3A0041617F /* icon_emoji_expression@3x.png */, 271C029C23929B3A0041617F /* Expression.plist */, ); path = Emotion; sourceTree = ""; }; 271C029D23929B3A0041617F /* Helper */ = { isa = PBXGroup; children = ( 271C029E23929B3A0041617F /* ChatFindEmotion.swift */, 271C029F23929B3A0041617F /* ChatEmotionHelper.swift */, 271C02A023929B3A0041617F /* ChatEmotionAttachment.swift */, ); path = Helper; sourceTree = ""; }; 271C02A123929B3A0041617F /* FPSLabel */ = { isa = PBXGroup; children = ( 271C02A323929B3A0041617F /* FPSLabel.swift */, 271C02A423929B3A0041617F /* WeakProxy.h */, 271C02A223929B3A0041617F /* WeakProxy.m */, ); path = FPSLabel; sourceTree = ""; }; 271C02AA23929B3A0041617F /* Helpers */ = { isa = PBXGroup; children = ( 271C02AB23929B3A0041617F /* LanguageManager.swift */, 271C02AD23929B3A0041617F /* CountDownHandy.swift */, ); path = Helpers; sourceTree = ""; }; 27433EA5238D70D3004D5E3D /* AppDelegate */ = { isa = PBXGroup; children = ( 27433EA7238D70D3004D5E3D /* AppDelegate.swift */, 27433EA8238D70D3004D5E3D /* AppDelegate+Wcdb.swift */, 27433EA9238D70D3004D5E3D /* AppDelegate+Utils.swift */, ); path = AppDelegate; sourceTree = ""; }; 27433EAA238D70D3004D5E3D /* Extensions */ = { isa = PBXGroup; children = ( 27433EAE238D70D3004D5E3D /* Strings */, 27433EB1238D70D3004D5E3D /* UIColor */, 27433EB3238D70D3004D5E3D /* UIView */, 27433EB6238D70D3004D5E3D /* RxSwift */, 27433EBB238D70D3004D5E3D /* Array */, 27433EBD238D70D3004D5E3D /* UIKit */, 4CBE2B2A281BCDE500FDB081 /* Notification */, 27433EC6238D70D3004D5E3D /* SnapKit */, 27433ECB238D70D3004D5E3D /* Dictionary */, ); path = Extensions; sourceTree = ""; }; 27433EAE238D70D3004D5E3D /* Strings */ = { isa = PBXGroup; children = ( 27433EB0238D70D3004D5E3D /* String+Date.swift */, 27433EAF238D70D3004D5E3D /* String+Extension.swift */, ); path = Strings; sourceTree = ""; }; 27433EB1238D70D3004D5E3D /* UIColor */ = { isa = PBXGroup; children = ( 27433EB2238D70D3004D5E3D /* UIColor+Extension.swift */, ); path = UIColor; sourceTree = ""; }; 27433EB3238D70D3004D5E3D /* UIView */ = { isa = PBXGroup; children = ( 27433EB4238D70D3004D5E3D /* UIView+Extensions.swift */, 27433EB5238D70D3004D5E3D /* UIViewController+Extend.swift */, ); path = UIView; sourceTree = ""; }; 27433EB6238D70D3004D5E3D /* RxSwift */ = { isa = PBXGroup; children = ( 27433EB8238D70D3004D5E3D /* JFButton+Rx.swift */, 27433EB9238D70D3004D5E3D /* Observable+Operators.swift */, 27433EBA238D70D3004D5E3D /* MJRefresh+Rx.swift */, ); path = RxSwift; sourceTree = ""; }; 27433EBB238D70D3004D5E3D /* Array */ = { isa = PBXGroup; children = ( 27433EBC238D70D3004D5E3D /* Array+Extension.swift */, ); path = Array; sourceTree = ""; }; 27433EBD238D70D3004D5E3D /* UIKit */ = { isa = PBXGroup; children = ( 27433EBE238D70D3004D5E3D /* UIAlert+Extension.swift */, 27433EBF238D70D3004D5E3D /* UILabel+Extension.swift */, 27433EC0238D70D3004D5E3D /* UIButton+Extension.swift */, 27433EC1238D70D3004D5E3D /* UIImageView+Kingfisher.swift */, 27433EC2238D70D3004D5E3D /* UIImage+Extension.swift */, 27433EC3238D70D3004D5E3D /* UINavBarItem+Extension.swift */, 27433EC4238D70D3004D5E3D /* UIFont+PingFang.swift */, 27433EC5238D70D3004D5E3D /* UITableView+Extension.swift */, ); path = UIKit; sourceTree = ""; }; 27433EC6238D70D3004D5E3D /* SnapKit */ = { isa = PBXGroup; children = ( 27433EC7238D70D3004D5E3D /* ConstraintArray+Extensions.swift */, 27433EC8238D70D3004D5E3D /* ConstraintArrayDSL.swift */, ); path = SnapKit; sourceTree = ""; }; 27433ECB238D70D3004D5E3D /* Dictionary */ = { isa = PBXGroup; children = ( 27433ECC238D70D3004D5E3D /* Dictionary+Exted.swift */, ); path = Dictionary; sourceTree = ""; }; 27433F57238D70D4004D5E3D /* Thirdparty */ = { isa = PBXGroup; children = ( 27433F62238D70D4004D5E3D /* RxErrorTracker */, 4C4A1A1D2818DAB900CDD3B1 /* BottomPopupController */, 4C5579602817C3C3006D8A09 /* RxActivityIndicator */, 27433F66238D70D4004D5E3D /* NavigationHandy */, ); path = Thirdparty; sourceTree = ""; }; 27433F62238D70D4004D5E3D /* RxErrorTracker */ = { isa = PBXGroup; children = ( 27433F63238D70D4004D5E3D /* ErrorTracker.swift */, ); path = RxErrorTracker; sourceTree = ""; }; 27433F66238D70D4004D5E3D /* NavigationHandy */ = { isa = PBXGroup; children = ( 27433F67238D70D4004D5E3D /* NSObject+BinAdd.h */, 27433F69238D70D4004D5E3D /* NSObject+BinAdd.m */, 27433F68238D70D4004D5E3D /* UINavigationController+Extensions.m */, 27433F6A238D70D4004D5E3D /* UINavigationController+Extensions.h */, ); path = NavigationHandy; sourceTree = ""; }; 27433F6D238D70D4004D5E3D /* Base */ = { isa = PBXGroup; children = ( 4C75A40B2817CA3300E9EF54 /* Model */, 27433F6E238D70D4004D5E3D /* ViewModel */, 27433F78238D70D4004D5E3D /* FYBaseViewController.swift */, 4CF8568D281A3B8400F48D4D /* FYBaseIGListViewController.swift */, 27433F70238D70D4004D5E3D /* FYBaseTabBarController.swift */, 27433F75238D70D4004D5E3D /* FYBaseNavigationController.swift */, ); path = Base; sourceTree = ""; }; 27433F6E238D70D4004D5E3D /* ViewModel */ = { isa = PBXGroup; children = ( 27433F6F238D70D4004D5E3D /* BaseViewModel.swift */, ); path = ViewModel; sourceTree = ""; }; 27433F79238D70D4004D5E3D /* Common */ = { isa = PBXGroup; children = ( 27433F7F238D70D4004D5E3D /* AppCommon.swift */, ); path = Common; sourceTree = ""; }; 27433F80238D70D4004D5E3D /* Libraries */ = { isa = PBXGroup; children = ( 4CBE2B24281B937900FDB081 /* NavPopup */, 4C5E7B172824FAAF00C968D3 /* Refreshing */, 4C4A1A302818DB7200CDD3B1 /* ActionSheet */, 27433F97238D70D4004D5E3D /* HUDProgress */, ); path = Libraries; sourceTree = ""; }; 27433F97238D70D4004D5E3D /* HUDProgress */ = { isa = PBXGroup; children = ( 27433F98238D70D4004D5E3D /* MBConfiguredHUD.swift */, 27433F99238D70D4004D5E3D /* HUDAssets.bundle */, ); path = HUDProgress; sourceTree = ""; }; 27B954E423AFAA5600B94667 /* Video */ = { isa = PBXGroup; children = ( 27B954E523AFAA5600B94667 /* localVideo0.mp4 */, ); path = Video; sourceTree = ""; }; 2E59E746222CFEF4005BC4B7 /* Languages */ = { isa = PBXGroup; children = ( 2E59E752222CFFCA005BC4B7 /* InfoPlist.strings */, 2B9E10F8222F752700638202 /* Localizable.strings */, ); path = Languages; sourceTree = ""; }; 2EB8E28E223745C500FD3235 /* Recovered References */ = { isa = PBXGroup; children = ( ); name = "Recovered References"; sourceTree = ""; }; 2ECBFDA622269BF300871913 = { isa = PBXGroup; children = ( 4C509CC828267B1A00CE66DF /* JetChatWidgetExtension.entitlements */, 2ECBFDB122269BF300871913 /* FY-IMChat */, 2ECBFDC622269BF400871913 /* FY-IMChatTests */, 2ECBFDD122269BF400871913 /* FY-IMChatUITests */, 4C509CB828267AE700CE66DF /* JetChatWidget */, 2ECBFDB022269BF300871913 /* Products */, 884BB488DBE648A934939F40 /* Frameworks */, 2EB8E28E223745C500FD3235 /* Recovered References */, 6A4C5C987FCC1B21829C5F08 /* Pods */, ); sourceTree = ""; }; 2ECBFDB022269BF300871913 /* Products */ = { isa = PBXGroup; children = ( 2ECBFDAF22269BF300871913 /* JetChat.app */, 2ECBFDC322269BF400871913 /* FY-IMChatTests.xctest */, 2ECBFDCE22269BF400871913 /* FY-IMChatUITests.xctest */, 4C509CB328267AE700CE66DF /* JetChatWidgetExtension.appex */, ); name = Products; sourceTree = ""; }; 2ECBFDB122269BF300871913 /* FY-IMChat */ = { isa = PBXGroup; children = ( 4C509CCB28268BC600CE66DF /* FY-IMChat.entitlements */, 2BC1B0D8229134E600A5CB54 /* Assets.xcassets */, 2ECBFDE022269DBF00871913 /* Classes */, 2B166CED22534DFB0018AF62 /* LaunchScreen.storyboard */, 2E2C3B9E2228C9D300BF1C5D /* FYObjcBridge.h */, 2ECBFDBE22269BF400871913 /* Info.plist */, ); path = "FY-IMChat"; sourceTree = ""; }; 2ECBFDC622269BF400871913 /* FY-IMChatTests */ = { isa = PBXGroup; children = ( 2ECBFDC722269BF400871913 /* FY_IMChatTests.swift */, 2ECBFDC922269BF400871913 /* Info.plist */, ); path = "FY-IMChatTests"; sourceTree = ""; }; 2ECBFDD122269BF400871913 /* FY-IMChatUITests */ = { isa = PBXGroup; children = ( 2ECBFDD222269BF400871913 /* FY_IMChatUITests.swift */, 2ECBFDD422269BF400871913 /* Info.plist */, ); path = "FY-IMChatUITests"; sourceTree = ""; }; 2ECBFDE022269DBF00871913 /* Classes */ = { isa = PBXGroup; children = ( 27433F6D238D70D4004D5E3D /* Base */, 27433EA5238D70D3004D5E3D /* AppDelegate */, 4C75A40E2817D0F300E9EF54 /* MainModule */, 27433F79238D70D4004D5E3D /* Common */, 27433EAA238D70D3004D5E3D /* Extensions */, 27433F80238D70D4004D5E3D /* Libraries */, 271C025623929B3A0041617F /* Utilites */, 27433F57238D70D4004D5E3D /* Thirdparty */, 2ECBFDEC22269DBF00871913 /* Resource */, ); path = Classes; sourceTree = ""; }; 2ECBFDEC22269DBF00871913 /* Resource */ = { isa = PBXGroup; children = ( 4C75A4092817C8FF00E9EF54 /* R.generated.swift */, 27B954E423AFAA5600B94667 /* Video */, 2E59E746222CFEF4005BC4B7 /* Languages */, ); path = Resource; sourceTree = ""; }; 4C4A1A1D2818DAB900CDD3B1 /* BottomPopupController */ = { isa = PBXGroup; children = ( 4C4A1A1E2818DABA00CDD3B1 /* BottomPopupUtils.swift */, 4C4A1A1F2818DABA00CDD3B1 /* BottomPopupViewController.swift */, 4C4A1A202818DABA00CDD3B1 /* BottomPopupDismissInteractionController.swift */, 4C4A1A212818DABA00CDD3B1 /* BottomPopupNavigationController.swift */, 4C4A1A222818DABA00CDD3B1 /* BottomPopupDismissAnimator.swift */, 4C4A1A232818DABA00CDD3B1 /* BottomPopupPresentAnimator.swift */, 4C4A1A242818DABA00CDD3B1 /* CSBottomPopupNavigationController.swift */, 4C4A1A252818DABA00CDD3B1 /* BottomPopupTransitionHandler.swift */, 4C4A1A262818DABA00CDD3B1 /* BottomPopupPresentationController.swift */, ); path = BottomPopupController; sourceTree = ""; }; 4C4A1A302818DB7200CDD3B1 /* ActionSheet */ = { isa = PBXGroup; children = ( 4C4A1A322818DB7200CDD3B1 /* FYActionSheet.swift */, 4C4A1A312818DB7200CDD3B1 /* FYActionSheetCell.swift */, ); path = ActionSheet; sourceTree = ""; }; 4C509CB828267AE700CE66DF /* JetChatWidget */ = { isa = PBXGroup; children = ( 4C509CB928267AE700CE66DF /* JetChatWidget.swift */, 4C509CBB28267AE700CE66DF /* JetChatWidget.intentdefinition */, 4C509CBC28267AEA00CE66DF /* Assets.xcassets */, 4C509CBE28267AEA00CE66DF /* Info.plist */, ); path = JetChatWidget; sourceTree = ""; }; 4C5579602817C3C3006D8A09 /* RxActivityIndicator */ = { isa = PBXGroup; children = ( 4C5579612817C3C3006D8A09 /* ActivityIndicator.swift */, ); path = RxActivityIndicator; sourceTree = ""; }; 4C5E7B172824FAAF00C968D3 /* Refreshing */ = { isa = PBXGroup; children = ( 4C5E7B182824FAAF00C968D3 /* FYMomentsHeaderRefresh.swift */, ); path = Refreshing; sourceTree = ""; }; 4C5FB426281A974C001F70E3 /* MomentLabel */ = { isa = PBXGroup; children = ( 4C5FB427281A974C001F70E3 /* FYLabel.swift */, 4C5FB428281A974C001F70E3 /* FYLabelType.swift */, ); path = MomentLabel; sourceTree = ""; }; 4C5FB42B281AA7D0001F70E3 /* FileSizeManager */ = { isa = PBXGroup; children = ( 4C5FB42C281AA7DD001F70E3 /* FYFileSizeManager.swift */, 4C509CC92826817A00CE66DF /* FYUserDefaultManager.swift */, ); path = FileSizeManager; sourceTree = ""; }; 4C75A40B2817CA3300E9EF54 /* Model */ = { isa = PBXGroup; children = ( 4C75A40C2817CA3300E9EF54 /* FYCellDataConfig.swift */, ); path = Model; sourceTree = ""; }; 4C75A40E2817D0F300E9EF54 /* MainModule */ = { isa = PBXGroup; children = ( 4C75A40F2817D0F300E9EF54 /* Contacts */, 4C75A4172817D0F300E9EF54 /* QrScan */, 4C75A41C2817D0F300E9EF54 /* ChatRoom */, 4C75A4322817D0F300E9EF54 /* Conversation */, 4CF8562B281A2F8900F48D4D /* Moments */, 4C75A42C2817D0F300E9EF54 /* Mine */, ); path = MainModule; sourceTree = ""; }; 4C75A40F2817D0F300E9EF54 /* Contacts */ = { isa = PBXGroup; children = ( 4C75A4102817D0F300E9EF54 /* Controller */, 4C75A4142817D0F300E9EF54 /* View */, ); path = Contacts; sourceTree = ""; }; 4C75A4102817D0F300E9EF54 /* Controller */ = { isa = PBXGroup; children = ( 4C75A4112817D0F300E9EF54 /* FYEditChatInfoViewController.swift */, 4C75A4122817D0F300E9EF54 /* FYContactsInfoViewController.swift */, 4C75A4132817D0F300E9EF54 /* FYContactsListViewController.swift */, ); path = Controller; sourceTree = ""; }; 4C75A4142817D0F300E9EF54 /* View */ = { isa = PBXGroup; children = ( 4C75A4152817D0F300E9EF54 /* FYContactsInfoView.swift */, 4C75A4162817D0F300E9EF54 /* FYContactsTableViewCell.swift */, ); path = View; sourceTree = ""; }; 4C75A4172817D0F300E9EF54 /* QrScan */ = { isa = PBXGroup; children = ( 4C75A4182817D0F300E9EF54 /* ScanQRCodeViewController.swift */, 4C75A4192817D0F300E9EF54 /* Image */, ); path = QrScan; sourceTree = ""; }; 4C75A4192817D0F300E9EF54 /* Image */ = { isa = PBXGroup; children = ( 4C75A41A2817D0F300E9EF54 /* icon_qrc_border@3x.png */, 4C75A41B2817D0F300E9EF54 /* icon_qrc_line@3x.png */, ); path = Image; sourceTree = ""; }; 4C75A41C2817D0F300E9EF54 /* ChatRoom */ = { isa = PBXGroup; children = ( 4C75A41D2817D0F300E9EF54 /* ViewModel */, 4C75A41F2817D0F300E9EF54 /* Controller */, 4C75A4232817D0F300E9EF54 /* Model */, 4C75A4272817D0F300E9EF54 /* View */, ); path = ChatRoom; sourceTree = ""; }; 4C75A41D2817D0F300E9EF54 /* ViewModel */ = { isa = PBXGroup; children = ( 4C75A41E2817D0F300E9EF54 /* FYMessageViewModel.swift */, ); path = ViewModel; sourceTree = ""; }; 4C75A41F2817D0F300E9EF54 /* Controller */ = { isa = PBXGroup; children = ( 4C75A4202817D0F300E9EF54 /* FYChatBaseViewController.swift */, 4C75A4212817D0F300E9EF54 /* FYMessageForwardViewController.swift */, 4C75A4222817D0F300E9EF54 /* FYChatRoomListViewController.swift */, ); path = Controller; sourceTree = ""; }; 4C75A4232817D0F300E9EF54 /* Model */ = { isa = PBXGroup; children = ( 4C75A4242817D0F300E9EF54 /* FYMessageItem.swift */, 4C75A4252817D0F300E9EF54 /* FYMessageChatModel.swift */, 4C75A4262817D0F300E9EF54 /* FYMessageBaseModel.swift */, ); path = Model; sourceTree = ""; }; 4C75A4272817D0F300E9EF54 /* View */ = { isa = PBXGroup; children = ( 4C75A42B2817D0F300E9EF54 /* FYMessageBaseCell.swift */, 4C75A42A2817D0F300E9EF54 /* FYTextMessageCell.swift */, 4C75A4282817D0F300E9EF54 /* FYVideoMessageCell.swift */, 4C75A4292817D0F300E9EF54 /* FYImageMessageCell.swift */, ); path = View; sourceTree = ""; }; 4C75A42C2817D0F300E9EF54 /* Mine */ = { isa = PBXGroup; children = ( 4C75A42D2817D0F300E9EF54 /* Controller */, 4C75A4302817D0F300E9EF54 /* View */, ); path = Mine; sourceTree = ""; }; 4C75A42D2817D0F300E9EF54 /* Controller */ = { isa = PBXGroup; children = ( 4C75A42E2817D0F300E9EF54 /* FYMineViewController.swift */, 4C75A42F2817D0F300E9EF54 /* FYSettingViewController.swift */, 4CF27C1F281D4EE600DBD7EC /* FYThemeSelectionListVC.swift */, ); path = Controller; sourceTree = ""; }; 4C75A4302817D0F300E9EF54 /* View */ = { isa = PBXGroup; children = ( 4C75A4312817D0F300E9EF54 /* FYFastGridListView.swift */, ); path = View; sourceTree = ""; }; 4C75A4322817D0F300E9EF54 /* Conversation */ = { isa = PBXGroup; children = ( 4C75A4332817D0F400E9EF54 /* Controller */, 4C75A4352817D0F400E9EF54 /* View */, ); path = Conversation; sourceTree = ""; }; 4C75A4332817D0F400E9EF54 /* Controller */ = { isa = PBXGroup; children = ( 4C75A4342817D0F400E9EF54 /* FYSesstionListViewController.swift */, ); path = Controller; sourceTree = ""; }; 4C75A4352817D0F400E9EF54 /* View */ = { isa = PBXGroup; children = ( 4C75A4362817D0F400E9EF54 /* FYConversationCell.swift */, ); path = View; sourceTree = ""; }; 4CBE2B24281B937900FDB081 /* NavPopup */ = { isa = PBXGroup; children = ( 4CBE2B25281B937900FDB081 /* FYNavPopuListMenu.swift */, 4CBE2B26281B937900FDB081 /* Cell */, ); path = NavPopup; sourceTree = ""; }; 4CBE2B26281B937900FDB081 /* Cell */ = { isa = PBXGroup; children = ( 4CBE2B27281B937900FDB081 /* FYNavDropMenuCell.swift */, ); path = Cell; sourceTree = ""; }; 4CBE2B2A281BCDE500FDB081 /* Notification */ = { isa = PBXGroup; children = ( 4CBE2B2B281BCDE500FDB081 /* Notification+Name.swift */, ); path = Notification; sourceTree = ""; }; 4CBE2B2D281BCE9500FDB081 /* DataBase */ = { isa = PBXGroup; children = ( 4CBE2B2E281BCE9500FDB081 /* WCDBManager */, ); path = DataBase; sourceTree = ""; }; 4CBE2B2E281BCE9500FDB081 /* WCDBManager */ = { isa = PBXGroup; children = ( 4CBE2B2F281BCE9500FDB081 /* DBQuery */, 4C15484B28362BD900AD7400 /* WCDataBaseManager.swift */, 4C15484C28362BDA00AD7400 /* WCDataBaseTable.swift */, ); path = WCDBManager; sourceTree = ""; }; 4CBE2B2F281BCE9500FDB081 /* DBQuery */ = { isa = PBXGroup; children = ( 4CBE2B30281BCE9500FDB081 /* FYDBQueryHelper.swift */, 4CBE2B31281BCE9500FDB081 /* FYMessageUserModel.swift */, ); path = DBQuery; sourceTree = ""; }; 4CF27C16281CEF3B00DBD7EC /* Theme */ = { isa = PBXGroup; children = ( 4CF27C17281CEFDC00DBD7EC /* FYThemeCenter.swift */, 4CF27C1D281CF82600DBD7EC /* FYThemeColors.swift */, 4CF27C19281CF04B00DBD7EC /* FYDarkTheme.swift */, 4CF27C1B281CF05D00DBD7EC /* FYLightTheme.swift */, ); path = Theme; sourceTree = ""; }; 4CF8562B281A2F8900F48D4D /* Moments */ = { isa = PBXGroup; children = ( 4CF856BC281A4D2800F48D4D /* JSONData */, 4CF856B6281A454200F48D4D /* Sections */, 4CF8562C281A2F8900F48D4D /* Model */, 4CF8562D281A2F8900F48D4D /* View */, 4CF8562F281A2FFA00F48D4D /* FYMomentsViewController.swift */, ); path = Moments; sourceTree = ""; }; 4CF8562C281A2F8900F48D4D /* Model */ = { isa = PBXGroup; children = ( 4CF8568F281A3E8100F48D4D /* FYMomentInfo.swift */, 4CF85691281A3F0F00F48D4D /* FYMoUserInfo.swift */, 4CF85693281A3F5300F48D4D /* FYCommentInfo.swift */, ); path = Model; sourceTree = ""; }; 4CF8562D281A2F8900F48D4D /* View */ = { isa = PBXGroup; children = ( 4CF856B3281A433700F48D4D /* TextView */, 4C5FB426281A974C001F70E3 /* MomentLabel */, 4CF85695281A40D400F48D4D /* Cell */, 4CF856AA281A40F300F48D4D /* NineImageView.swift */, 4CF856AB281A40F300F48D4D /* OperateMenuView.swift */, 4CF8568B281A34C200F48D4D /* FYMomentNavBar.swift */, ); path = View; sourceTree = ""; }; 4CF85695281A40D400F48D4D /* Cell */ = { isa = PBXGroup; children = ( 4CF8569F281A40D400F48D4D /* MomentHeaderImageCell.swift */, 4CF85696281A40D400F48D4D /* MomentBottomCell.swift */, 4CF85697281A40D400F48D4D /* MomentHeaderCell.swift */, 4CF85698281A40D400F48D4D /* CommentInputView.swift */, 4CF85699281A40D400F48D4D /* MomentLocationCell.swift */, 4CF8569A281A40D400F48D4D /* CommentContentCell.swift */, 4CF8569B281A40D400F48D4D /* MomentCommentCell.swift */, 4CF8569C281A40D400F48D4D /* CommentThumbView.swift */, 4CF8569D281A40D400F48D4D /* CommentContentView.swift */, 4CF8569E281A40D400F48D4D /* MomentTopCell.swift */, ); path = Cell; sourceTree = ""; }; 4CF856B3281A433700F48D4D /* TextView */ = { isa = PBXGroup; children = ( 4CF856B4281A433700F48D4D /* FYTextView.swift */, ); path = TextView; sourceTree = ""; }; 4CF856B6281A454200F48D4D /* Sections */ = { isa = PBXGroup; children = ( 4CF856B7281A456700F48D4D /* FYMomentBindingSection.swift */, ); path = Sections; sourceTree = ""; }; 4CF856BC281A4D2800F48D4D /* JSONData */ = { isa = PBXGroup; children = ( 4CF856BD281A4D2800F48D4D /* moments1.json */, 4CF856BE281A4D2800F48D4D /* moments2.json */, ); path = JSONData; sourceTree = ""; }; 6A4C5C987FCC1B21829C5F08 /* Pods */ = { isa = PBXGroup; children = ( 98E659EF05275210EC83E03D /* Pods-FY-IMChat.debug.xcconfig */, 5858C5E76CAB64DDB07738CD /* Pods-FY-IMChat.release.xcconfig */, ); path = Pods; sourceTree = ""; }; 884BB488DBE648A934939F40 /* Frameworks */ = { isa = PBXGroup; children = ( 2EF014782230FC7200681D4F /* JavaScriptCore.framework */, 2EF014762230FC6D00681D4F /* WebKit.framework */, E374D4E527160347F32F8B70 /* Pods_FY_IMChat.framework */, 4C509CB428267AE700CE66DF /* WidgetKit.framework */, 4C509CB628267AE700CE66DF /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2ECBFDAE22269BF300871913 /* FY-IMChat */ = { isa = PBXNativeTarget; buildConfigurationList = 2ECBFDD722269BF400871913 /* Build configuration list for PBXNativeTarget "FY-IMChat" */; buildPhases = ( 2C91F496D1EC74FC8499020A /* [CP] Check Pods Manifest.lock */, 4C75A4062817C63500E9EF54 /* ShellScript */, 2ECBFDAB22269BF300871913 /* Sources */, 2ECBFDAC22269BF300871913 /* Frameworks */, 2ECBFDAD22269BF300871913 /* Resources */, 2E461CF143CEEA3003A03808 /* [CP] Embed Pods Frameworks */, 4C509CC428267AEA00CE66DF /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( 4C509CC228267AEA00CE66DF /* PBXTargetDependency */, ); name = "FY-IMChat"; productName = "FY-IMChat"; productReference = 2ECBFDAF22269BF300871913 /* JetChat.app */; productType = "com.apple.product-type.application"; }; 2ECBFDC222269BF400871913 /* FY-IMChatTests */ = { isa = PBXNativeTarget; buildConfigurationList = 2ECBFDDA22269BF400871913 /* Build configuration list for PBXNativeTarget "FY-IMChatTests" */; buildPhases = ( 2ECBFDBF22269BF400871913 /* Sources */, 2ECBFDC022269BF400871913 /* Frameworks */, 2ECBFDC122269BF400871913 /* Resources */, ); buildRules = ( ); dependencies = ( 2ECBFDC522269BF400871913 /* PBXTargetDependency */, ); name = "FY-IMChatTests"; productName = "FY-IMChatTests"; productReference = 2ECBFDC322269BF400871913 /* FY-IMChatTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 2ECBFDCD22269BF400871913 /* FY-IMChatUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 2ECBFDDD22269BF400871913 /* Build configuration list for PBXNativeTarget "FY-IMChatUITests" */; buildPhases = ( 2ECBFDCA22269BF400871913 /* Sources */, 2ECBFDCB22269BF400871913 /* Frameworks */, 2ECBFDCC22269BF400871913 /* Resources */, ); buildRules = ( ); dependencies = ( 2ECBFDD022269BF400871913 /* PBXTargetDependency */, ); name = "FY-IMChatUITests"; productName = "FY-IMChatUITests"; productReference = 2ECBFDCE22269BF400871913 /* FY-IMChatUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; 4C509CB228267AE700CE66DF /* JetChatWidgetExtension */ = { isa = PBXNativeTarget; buildConfigurationList = 4C509CC728267AEA00CE66DF /* Build configuration list for PBXNativeTarget "JetChatWidgetExtension" */; buildPhases = ( 4C509CAF28267AE700CE66DF /* Sources */, 4C509CB028267AE700CE66DF /* Frameworks */, 4C509CB128267AE700CE66DF /* Resources */, ); buildRules = ( ); dependencies = ( ); name = JetChatWidgetExtension; productName = JetChatWidgetExtension; productReference = 4C509CB328267AE700CE66DF /* JetChatWidgetExtension.appex */; productType = "com.apple.product-type.app-extension"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2ECBFDA722269BF300871913 /* Project object */ = { isa = PBXProject; attributes = { CLASSPREFIX = FY; KnownAssetTags = ( launchName, ); LastSwiftUpdateCheck = 1330; LastUpgradeCheck = 1020; ORGANIZATIONNAME = Jett; TargetAttributes = { 2ECBFDAE22269BF300871913 = { CreatedOnToolsVersion = 10.1; }; 2ECBFDC222269BF400871913 = { CreatedOnToolsVersion = 10.1; TestTargetID = 2ECBFDAE22269BF300871913; }; 2ECBFDCD22269BF400871913 = { CreatedOnToolsVersion = 10.1; TestTargetID = 2ECBFDAE22269BF300871913; }; 4C509CB228267AE700CE66DF = { CreatedOnToolsVersion = 13.3.1; }; }; }; buildConfigurationList = 2ECBFDAA22269BF300871913 /* Build configuration list for PBXProject "FY-IMChat" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, "zh-Hans", ); mainGroup = 2ECBFDA622269BF300871913; productRefGroup = 2ECBFDB022269BF300871913 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2ECBFDAE22269BF300871913 /* FY-IMChat */, 2ECBFDC222269BF400871913 /* FY-IMChatTests */, 2ECBFDCD22269BF400871913 /* FY-IMChatUITests */, 4C509CB228267AE700CE66DF /* JetChatWidgetExtension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2ECBFDAD22269BF300871913 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 271C02EA23929B3A0041617F /* ToolViewEmotionHL@2x.png in Resources */, 271C02F023929B3A0041617F /* Expression.bundle in Resources */, 271C02E323929B3A0041617F /* ToolViewInputVoice@3x.png in Resources */, 271C02CC23929B3A0041617F /* ic_more_location@3x.png in Resources */, 271C02C923929B3A0041617F /* ic_emotion_delete@2x.png in Resources */, 271C02D023929B3A0041617F /* ic_more_video@2x.png in Resources */, 271C02D623929B3A0041617F /* ic_more_album@3x.png in Resources */, 271C02DF23929B3A0041617F /* ToolViewKeyboardHL@3x.png in Resources */, 271C02C823929B3A0041617F /* ic_emotion_delete@3x.png in Resources */, 271C02DB23929B3A0041617F /* ic_more_voice@3x.png in Resources */, 271C02D423929B3A0041617F /* ic_more_album@2x.png in Resources */, 2E59E750222CFFCA005BC4B7 /* InfoPlist.strings in Resources */, 271C02E623929B3A0041617F /* ToolViewInputVoice@2x.png in Resources */, 271C02D123929B3A0041617F /* ic_more_video@3x.png in Resources */, 271C02E123929B3A0041617F /* ToolViewEmotion@2x.png in Resources */, 271C02F123929B3A0041617F /* icon_emoji_expression@3x.png in Resources */, 271C02CD23929B3A0041617F /* ic_more_location@2x.png in Resources */, 271C02E923929B3A0041617F /* ToolViewInputVoiceHL@3x.png in Resources */, 271C02D523929B3A0041617F /* ic_more_sight@3x.png in Resources */, 271C02E523929B3A0041617F /* ToolViewKeyboard@2x.png in Resources */, 271C02D323929B3A0041617F /* ic_more_camera@2x.png in Resources */, 2B166CEE22534DFB0018AF62 /* LaunchScreen.storyboard in Resources */, 271C02E023929B3A0041617F /* ToolViewEmotion@3x.png in Resources */, 271C02DD23929B3A0041617F /* ic_more_friendcard@3x.png in Resources */, 271C02DA23929B3A0041617F /* ic_more_pay@3x.png in Resources */, 271C02E723929B3A0041617F /* ToolViewEmotionHL@3x.png in Resources */, 271C02EF23929B3A0041617F /* icon_emoji_expression@2x.png in Resources */, 4CF856C0281A4D2800F48D4D /* moments2.json in Resources */, 271C02E223929B3A0041617F /* ToolViewKeyboardHL@2x.png in Resources */, 271C02DE23929B3A0041617F /* ic_more_pay@2x.png in Resources */, 271C02E423929B3A0041617F /* ToolViewKeyboard@3x.png in Resources */, 271C02D223929B3A0041617F /* ic_more_wallet@3x.png in Resources */, 271C02CF23929B3A0041617F /* ic_more_wallet@2x.png in Resources */, 271C02EB23929B3A0041617F /* TypeSelectorBtnHL_Black@2x.png in Resources */, 271C02EC23929B3A0041617F /* TypeSelectorBtn_Black@3x.png in Resources */, 271C02E823929B3A0041617F /* ToolViewInputVoiceHL@2x.png in Resources */, 271C02CE23929B3A0041617F /* ic_more_favorite@3x.png in Resources */, 27434036238D70D5004D5E3D /* HUDAssets.bundle in Resources */, 4CF856BF281A4D2800F48D4D /* moments1.json in Resources */, 271C02D723929B3A0041617F /* ic_more_sight@2x.png in Resources */, 271C02D923929B3A0041617F /* ic_more_friendcard@2x.png in Resources */, 271C02CA23929B3A0041617F /* Emoticons.bundle in Resources */, 27B954E623AFAA5600B94667 /* localVideo0.mp4 in Resources */, 2B9E10F6222F752700638202 /* Localizable.strings in Resources */, 271C02F223929B3A0041617F /* Expression.plist in Resources */, 271C02CB23929B3A0041617F /* ic_more_favorite@2x.png in Resources */, 271C02ED23929B3A0041617F /* TypeSelectorBtn_Black@2x.png in Resources */, 271C02D823929B3A0041617F /* ic_more_camera@3x.png in Resources */, 271C02EE23929B3A0041617F /* TypeSelectorBtnHL_Black@3x.png in Resources */, 4C75A43E2817D0F400E9EF54 /* icon_qrc_line@3x.png in Resources */, 271C02DC23929B3A0041617F /* ic_more_voice@2x.png in Resources */, 2BC1B0D9229134E600A5CB54 /* Assets.xcassets in Resources */, 4C75A43D2817D0F400E9EF54 /* icon_qrc_border@3x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 2ECBFDC122269BF400871913 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 2ECBFDCC22269BF400871913 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 4C509CB128267AE700CE66DF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 4C509CBD28267AEA00CE66DF /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 2C91F496D1EC74FC8499020A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-FY-IMChat-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 2E461CF143CEEA3003A03808 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 4C75A4062817C63500E9EF54 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "$TEMP_DIR/rswift-lastrun", ); outputFileListPaths = ( ); outputPaths = ( "$SRCROOT/FY-IMChat/Classes/Resource/R.generated.swift", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$PODS_ROOT/R.swift/rswift\" generate \"$SRCROOT/FY-IMChat/Classes/Resource/R.generated.swift\"\n\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2ECBFDAB22269BF300871913 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 27433FA0238D70D4004D5E3D /* String+Extension.swift in Sources */, 4CBE2B35281BCE9500FDB081 /* FYDBQueryHelper.swift in Sources */, 4CF85690281A3E8100F48D4D /* FYMomentInfo.swift in Sources */, 27434029238D70D5004D5E3D /* AppCommon.swift in Sources */, 27433FAF238D70D4004D5E3D /* UINavBarItem+Extension.swift in Sources */, 4C75A44C2817D0F400E9EF54 /* FYFastGridListView.swift in Sources */, 27434035238D70D5004D5E3D /* MBConfiguredHUD.swift in Sources */, 4C75A4452817D0F400E9EF54 /* FYMessageBaseModel.swift in Sources */, 4C75A43A2817D0F400E9EF54 /* FYContactsInfoView.swift in Sources */, 4C4A1A292818DABA00CDD3B1 /* BottomPopupDismissInteractionController.swift in Sources */, 271C02C023929B3A0041617F /* ChatKeyboard+Extensions.swift in Sources */, 271C02BF23929B3A0041617F /* ChatEmojiListView.swift in Sources */, 271C02BE23929B3A0041617F /* ChatMoreMenuView.swift in Sources */, 27433FB0238D70D4004D5E3D /* UIFont+PingFang.swift in Sources */, 4CF856A4281A40D400F48D4D /* CommentContentCell.swift in Sources */, 4C75A44D2817D0F400E9EF54 /* FYSesstionListViewController.swift in Sources */, 271C02FB23929B3A0041617F /* LanguageManager.swift in Sources */, 4CF856AD281A40F300F48D4D /* OperateMenuView.swift in Sources */, 27433F9C238D70D4004D5E3D /* AppDelegate+Wcdb.swift in Sources */, 27433F9B238D70D4004D5E3D /* AppDelegate.swift in Sources */, 2743401A238D70D5004D5E3D /* UINavigationController+Extensions.m in Sources */, 4CF856AC281A40F300F48D4D /* NineImageView.swift in Sources */, 27434024238D70D5004D5E3D /* FYBaseViewController.swift in Sources */, 4CF856A7281A40D400F48D4D /* CommentContentView.swift in Sources */, 4C5FB42D281AA7DD001F70E3 /* FYFileSizeManager.swift in Sources */, 4CF856A2281A40D400F48D4D /* CommentInputView.swift in Sources */, 4CF85692281A3F0F00F48D4D /* FYMoUserInfo.swift in Sources */, 27433FA7238D70D4004D5E3D /* Observable+Operators.swift in Sources */, 27433FAD238D70D4004D5E3D /* UIImageView+Kingfisher.swift in Sources */, 27433FAA238D70D4004D5E3D /* UIAlert+Extension.swift in Sources */, 4C5579622817C3C3006D8A09 /* ActivityIndicator.swift in Sources */, 4CF8568E281A3B8400F48D4D /* FYBaseIGListViewController.swift in Sources */, 271C02FD23929B3A0041617F /* CountDownHandy.swift in Sources */, 2743401D238D70D5004D5E3D /* BaseViewModel.swift in Sources */, 4CF27C1E281CF82600DBD7EC /* FYThemeColors.swift in Sources */, 271C02C123929B3A0041617F /* ChatMoreMenuCell.swift in Sources */, 271C02C623929B3A0041617F /* ChatMoreMnueConfig.swift in Sources */, 4CBE2B28281B937900FDB081 /* FYNavPopuListMenu.swift in Sources */, 4C75A4492817D0F400E9EF54 /* FYMessageBaseCell.swift in Sources */, 4CBE2B29281B937900FDB081 /* FYNavDropMenuCell.swift in Sources */, 27433FB5238D70D4004D5E3D /* Dictionary+Exted.swift in Sources */, 4C4A1A342818DB7200CDD3B1 /* FYActionSheet.swift in Sources */, 4C5FB429281A974C001F70E3 /* FYLabel.swift in Sources */, 4C75A44B2817D0F400E9EF54 /* FYSettingViewController.swift in Sources */, 4C75A44A2817D0F400E9EF54 /* FYMineViewController.swift in Sources */, 271C02C523929B3A0041617F /* ChatEmoticon.swift in Sources */, 4C4A1A2C2818DABA00CDD3B1 /* BottomPopupPresentAnimator.swift in Sources */, 27433FAE238D70D4004D5E3D /* UIImage+Extension.swift in Sources */, 27433FA3238D70D4004D5E3D /* UIView+Extensions.swift in Sources */, 4CF856A1281A40D400F48D4D /* MomentHeaderCell.swift in Sources */, 2743401B238D70D5004D5E3D /* NSObject+BinAdd.m in Sources */, 4C5E7B192824FAB000C968D3 /* FYMomentsHeaderRefresh.swift in Sources */, 4C4A1A2A2818DABA00CDD3B1 /* BottomPopupNavigationController.swift in Sources */, 27433FA8238D70D4004D5E3D /* MJRefresh+Rx.swift in Sources */, 4C75A43C2817D0F400E9EF54 /* ScanQRCodeViewController.swift in Sources */, 4C4A1A332818DB7200CDD3B1 /* FYActionSheetCell.swift in Sources */, 4C75A4372817D0F400E9EF54 /* FYEditChatInfoViewController.swift in Sources */, 271C02F523929B3A0041617F /* ChatEmotionAttachment.swift in Sources */, 4C75A43F2817D0F400E9EF54 /* FYMessageViewModel.swift in Sources */, 4C4A1A272818DABA00CDD3B1 /* BottomPopupUtils.swift in Sources */, 2743401E238D70D5004D5E3D /* FYBaseTabBarController.swift in Sources */, 27433FB3238D70D4004D5E3D /* ConstraintArrayDSL.swift in Sources */, 4C15484D28362BDA00AD7400 /* WCDataBaseManager.swift in Sources */, 4CF85694281A3F5300F48D4D /* FYCommentInfo.swift in Sources */, 27433FA9238D70D4004D5E3D /* Array+Extension.swift in Sources */, 4CBE2B2C281BCDE500FDB081 /* Notification+Name.swift in Sources */, 4C75A40A2817C8FF00E9EF54 /* R.generated.swift in Sources */, 27434022238D70D5004D5E3D /* FYBaseNavigationController.swift in Sources */, 271C02F723929B3A0041617F /* FPSLabel.swift in Sources */, 4CF85630281A2FFA00F48D4D /* FYMomentsViewController.swift in Sources */, 4CF856A3281A40D400F48D4D /* MomentLocationCell.swift in Sources */, 4C75A4482817D0F400E9EF54 /* FYTextMessageCell.swift in Sources */, 4CF856B5281A433700F48D4D /* FYTextView.swift in Sources */, 4CF856A5281A40D400F48D4D /* MomentCommentCell.swift in Sources */, 27433F9D238D70D4004D5E3D /* AppDelegate+Utils.swift in Sources */, 4CF8568C281A34C200F48D4D /* FYMomentNavBar.swift in Sources */, 27433FA4238D70D4004D5E3D /* UIViewController+Extend.swift in Sources */, 4CBE2B36281BCE9500FDB081 /* FYMessageUserModel.swift in Sources */, 27433FB1238D70D4004D5E3D /* UITableView+Extension.swift in Sources */, 27433FAC238D70D4004D5E3D /* UIButton+Extension.swift in Sources */, 4CF27C1C281CF05D00DBD7EC /* FYLightTheme.swift in Sources */, 27433FA6238D70D4004D5E3D /* JFButton+Rx.swift in Sources */, 4C75A40D2817CA3300E9EF54 /* FYCellDataConfig.swift in Sources */, 4C75A43B2817D0F400E9EF54 /* FYContactsTableViewCell.swift in Sources */, 4C75A4392817D0F400E9EF54 /* FYContactsListViewController.swift in Sources */, 4CF856B8281A456700F48D4D /* FYMomentBindingSection.swift in Sources */, 271C02F323929B3A0041617F /* ChatFindEmotion.swift in Sources */, 271C02F623929B3A0041617F /* WeakProxy.m in Sources */, 4C4A1A2E2818DABA00CDD3B1 /* BottomPopupTransitionHandler.swift in Sources */, 27433FA1238D70D4004D5E3D /* String+Date.swift in Sources */, 4C5FB42A281A974C001F70E3 /* FYLabelType.swift in Sources */, 4CF27C20281D4EE600DBD7EC /* FYThemeSelectionListVC.swift in Sources */, 4C75A4472817D0F400E9EF54 /* FYImageMessageCell.swift in Sources */, 4C4A1A2D2818DABA00CDD3B1 /* CSBottomPopupNavigationController.swift in Sources */, 271C02F423929B3A0041617F /* ChatEmotionHelper.swift in Sources */, 4CF27C18281CEFDC00DBD7EC /* FYThemeCenter.swift in Sources */, 27433FAB238D70D4004D5E3D /* UILabel+Extension.swift in Sources */, 4C75A4412817D0F400E9EF54 /* FYMessageForwardViewController.swift in Sources */, 4C4A1A2F2818DABA00CDD3B1 /* BottomPopupPresentationController.swift in Sources */, 271C02C723929B3A0041617F /* ChatGrowingTextView.swift in Sources */, 4C75A4462817D0F400E9EF54 /* FYVideoMessageCell.swift in Sources */, 27434018238D70D5004D5E3D /* ErrorTracker.swift in Sources */, 4CF27C1A281CF04B00DBD7EC /* FYDarkTheme.swift in Sources */, 4C509CC028267AEA00CE66DF /* JetChatWidget.intentdefinition in Sources */, 4C4A1A282818DABA00CDD3B1 /* BottomPopupViewController.swift in Sources */, 27433FB2238D70D4004D5E3D /* ConstraintArray+Extensions.swift in Sources */, 4C15484E28362BDA00AD7400 /* WCDataBaseTable.swift in Sources */, 4C75A4402817D0F400E9EF54 /* FYChatBaseViewController.swift in Sources */, 4CF856A9281A40D400F48D4D /* MomentHeaderImageCell.swift in Sources */, 4C75A4432817D0F400E9EF54 /* FYMessageItem.swift in Sources */, 27433FA2238D70D4004D5E3D /* UIColor+Extension.swift in Sources */, 4C75A44E2817D0F400E9EF54 /* FYConversationCell.swift in Sources */, 4C75A4442817D0F400E9EF54 /* FYMessageChatModel.swift in Sources */, 4CF856A6281A40D400F48D4D /* CommentThumbView.swift in Sources */, 271C02C223929B3A0041617F /* ChatKeyboardFlowLayout.swift in Sources */, 4C75A4422817D0F400E9EF54 /* FYChatRoomListViewController.swift in Sources */, 271C02C423929B3A0041617F /* ChatKeyboardView.swift in Sources */, 4C75A4382817D0F400E9EF54 /* FYContactsInfoViewController.swift in Sources */, 4CF856A8281A40D400F48D4D /* MomentTopCell.swift in Sources */, 271C02C323929B3A0041617F /* ChatAppleEmojiCell.swift in Sources */, 4CF856A0281A40D400F48D4D /* MomentBottomCell.swift in Sources */, 4C509CCA2826817A00CE66DF /* FYUserDefaultManager.swift in Sources */, 4C4A1A2B2818DABA00CDD3B1 /* BottomPopupDismissAnimator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 2ECBFDBF22269BF400871913 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2ECBFDC822269BF400871913 /* FY_IMChatTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 2ECBFDCA22269BF400871913 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2ECBFDD322269BF400871913 /* FY_IMChatUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 4C509CAF28267AE700CE66DF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 4C509CBF28267AEA00CE66DF /* JetChatWidget.intentdefinition in Sources */, 4C509CBA28267AE700CE66DF /* JetChatWidget.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 2ECBFDC522269BF400871913 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 2ECBFDAE22269BF300871913 /* FY-IMChat */; targetProxy = 2ECBFDC422269BF400871913 /* PBXContainerItemProxy */; }; 2ECBFDD022269BF400871913 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 2ECBFDAE22269BF300871913 /* FY-IMChat */; targetProxy = 2ECBFDCF22269BF400871913 /* PBXContainerItemProxy */; }; 4C509CC228267AEA00CE66DF /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4C509CB228267AE700CE66DF /* JetChatWidgetExtension */; targetProxy = 4C509CC128267AEA00CE66DF /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 2B9E10F8222F752700638202 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( 2B9E10F7222F752700638202 /* en */, 2B9E10F9222F752800638202 /* zh-Hans */, ); name = Localizable.strings; sourceTree = ""; }; 2E59E752222CFFCA005BC4B7 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 2E59E751222CFFCA005BC4B7 /* en */, 2E59E753222CFFD7005BC4B7 /* zh-Hans */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 2ECBFDD522269BF400871913 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2ECBFDD622269BF400871913 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 2ECBFDD822269BF400871913 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 98E659EF05275210EC83E03D /* Pods-FY-IMChat.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CODE_SIGN_ENTITLEMENTS = "FY-IMChat/FY-IMChat.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = Z3CD857XLC; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "FY-IMChat/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); OTHER_LDFLAGS = ( "-ObjC", "$(inherited)", ); PRODUCT_BUNDLE_IDENTIFIER = com.jetchat.ios; PRODUCT_NAME = JetChat; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "FY-IMChat/FYObjcBridge.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; 2ECBFDD922269BF400871913 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5858C5E76CAB64DDB07738CD /* Pods-FY-IMChat.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CODE_SIGN_ENTITLEMENTS = "FY-IMChat/FY-IMChat.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = Z3CD857XLC; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "FY-IMChat/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); OTHER_LDFLAGS = ( "-ObjC", "$(inherited)", ); PRODUCT_BUNDLE_IDENTIFIER = com.jetchat.ios; PRODUCT_NAME = JetChat; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "FY-IMChat/FYObjcBridge.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; 2ECBFDDB22269BF400871913 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "FY-IMChatTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.miku.LWallet.FY-IMChatTests"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FY-IMChat.app/FY-IMChat"; }; name = Debug; }; 2ECBFDDC22269BF400871913 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "FY-IMChatTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.miku.LWallet.FY-IMChatTests"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FY-IMChat.app/FY-IMChat"; }; name = Release; }; 2ECBFDDE22269BF400871913 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "FY-IMChatUITests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.miku.LWallet.FY-IMChatUITests"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = "FY-IMChat"; }; name = Debug; }; 2ECBFDDF22269BF400871913 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "FY-IMChatUITests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.miku.LWallet.FY-IMChatUITests"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = "FY-IMChat"; }; name = Release; }; 4C509CC528267AEA00CE66DF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_ENTITLEMENTS = JetChatWidgetExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = Z3CD857XLC; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = JetChatWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = JetChatWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Jett. All rights reserved."; IPHONEOS_DEPLOYMENT_TARGET = 15.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.jetchat.ios.JetChatWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 4C509CC628267AEA00CE66DF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_ENTITLEMENTS = JetChatWidgetExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = Z3CD857XLC; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = JetChatWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = JetChatWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Jett. All rights reserved."; IPHONEOS_DEPLOYMENT_TARGET = 15.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.jetchat.ios.JetChatWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2ECBFDAA22269BF300871913 /* Build configuration list for PBXProject "FY-IMChat" */ = { isa = XCConfigurationList; buildConfigurations = ( 2ECBFDD522269BF400871913 /* Debug */, 2ECBFDD622269BF400871913 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2ECBFDD722269BF400871913 /* Build configuration list for PBXNativeTarget "FY-IMChat" */ = { isa = XCConfigurationList; buildConfigurations = ( 2ECBFDD822269BF400871913 /* Debug */, 2ECBFDD922269BF400871913 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2ECBFDDA22269BF400871913 /* Build configuration list for PBXNativeTarget "FY-IMChatTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 2ECBFDDB22269BF400871913 /* Debug */, 2ECBFDDC22269BF400871913 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2ECBFDDD22269BF400871913 /* Build configuration list for PBXNativeTarget "FY-IMChatUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 2ECBFDDE22269BF400871913 /* Debug */, 2ECBFDDF22269BF400871913 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 4C509CC728267AEA00CE66DF /* Build configuration list for PBXNativeTarget "JetChatWidgetExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( 4C509CC528267AEA00CE66DF /* Debug */, 4C509CC628267AEA00CE66DF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 2ECBFDA722269BF300871913 /* Project object */; } ================================================ FILE: JetChat/FY-IMChat.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: JetChat/FY-IMChat.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: JetChat/FY-IMChat.xcodeproj/xcshareddata/xcschemes/FY-IMChat.xcscheme ================================================ ================================================ FILE: JetChat/FY-IMChat.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: JetChat/FY-IMChat.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: JetChat/FY-IMChatTests/FY_IMChatTests.swift ================================================ // // MK_LWalletTests.swift // FY-JetChatTests // // Created by iOS.Jet on 2019/2/27. // Copyright © 2019 Jett.Jet. All rights reserved. // import XCTest @testable import FY_IMChat class FY_IMChatTests: XCTestCase { override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: JetChat/FY-IMChatTests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: JetChat/FY-IMChatUITests/FY_IMChatUITests.swift ================================================ // // MK_LWalletUITests.swift // FY-JetChatUITests // // Created by iOS.Jet on 2019/2/27. // Copyright © 2019 Jett.Jet. All rights reserved. // import XCTest class FY_IMChatUITests: XCTestCase { override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. XCUIApplication().launch() // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } func testExample() { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } } ================================================ FILE: JetChat/FY-IMChatUITests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: JetChat/JetChatWidget/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/JetChatWidget/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/JetChatWidget/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/JetChatWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/JetChatWidget/Assets.xcassets/icon_widget_bg.imageset/Contents.json ================================================ { "images" : [ { "filename" : "icon_widget_bg.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: JetChat/JetChatWidget/Info.plist ================================================ NSExtension NSExtensionPointIdentifier com.apple.widgetkit-extension ================================================ FILE: JetChat/JetChatWidget/JetChatWidget.intentdefinition ================================================ INEnums INIntentDefinitionModelVersion 1.2 INIntentDefinitionNamespace 88xZPY INIntentDefinitionSystemVersion 21E258 INIntentDefinitionToolsBuildVersion 13E500a INIntentDefinitionToolsVersion 13.3.1 INIntents INIntentCategory information INIntentClassPrefix FY INIntentDescriptionID tVvJ9c INIntentEligibleForWidgets INIntentIneligibleForSuggestions INIntentLastParameterTag 1 INIntentName Configuration INIntentResponse INIntentResponseCodes INIntentResponseCodeName success INIntentResponseCodeSuccess INIntentResponseCodeName failure INIntentTitle Configuration INIntentTitleID gpCwrM INIntentType Custom INIntentVerb View INTypes ================================================ FILE: JetChat/JetChatWidget/JetChatWidget.swift ================================================ // // JetChatWidget.swift // JetChatWidget // // Created by Jett on 2022/5/7. // Copyright © 2022 Jett. All rights reserved. // import WidgetKit import SwiftUI import Intents struct Provider: IntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), object: "" , configuration: FYConfigurationIntent()) } func getSnapshot(for configuration: FYConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry(date: Date(), object: "", configuration: configuration) completion(entry) } func getTimeline(for configuration: FYConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) { var entries: [SimpleEntry] = [] // Generate a timeline consisting of five entries an hour apart, starting from the current date. let currentDate = Date() for hourOffset in 0 ..< 5 { let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate, object: "", configuration: configuration) entries.append(entry) } let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) } } struct SimpleEntry: TimelineEntry { let date: Date let object: Any let configuration: FYConfigurationIntent } // 读取最近聊天消息 func readWidgetMessageItem(_ widgetKey: String = "widgetKey", suiteName: String = "group.com.jetchat.2022.JetChatWidget") -> WidgetMsgItem? { let userDefaults = UserDefaults(suiteName: suiteName) if let object = userDefaults?.object(forKey: widgetKey) { // 将objc转成data let data: Data = try! JSONSerialization.data(withJSONObject: object, options: .fragmentsAllowed) // 将data转成相应模型 let decoder = JSONDecoder() do { let msgItem = try decoder.decode(WidgetMsgItem.self, from: data) return msgItem } catch { print("Got error while parsing: \(error)") return nil } } return nil } //从UserDefault中取值 func fileSimpleModel(_ entryDate: Date) -> SimpleEntry { if let object = readWidgetMessageItem() { return SimpleEntry(date: entryDate, object: object, configuration: .init()) } return SimpleEntry(date: entryDate, object: WidgetMsgItem(), configuration: .init()) } struct JetChatWidgetEntryView : View { var entry: Provider.Entry let msgItem = fileSimpleModel(.now).object as! WidgetMsgItem var body: some View { VStack(alignment: .leading, spacing: 0) { Text("最近聊天") .foregroundColor(Color.green) .font(.system(size: 20)) .padding(EdgeInsets(top:10, leading: 15, bottom: 10, trailing: 15)) Text(msgItem.date) .foregroundColor(Color.white) .lineLimit(1) .font(.system(size: 14)) .padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15)) Text("\(msgItem.nickName ?? msgItem.name):\(msgItem.message)") .foregroundColor(Color.white) .lineLimit(4) .font(.system(size: 13)) .padding(EdgeInsets(top:10, leading: 15, bottom: 5, trailing: 15)) }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .leading) .padding(5) //widget背景图片 .background( Image("icon_widget_bg") .scaledToFill() ).widgetURL(URL(string: String(format: "https://www.jetchat.com/chatId=%ld", msgItem.chatId ?? -1))) } } @main struct JetChatWidget: Widget { let kind: String = "JetChatWidget" var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: FYConfigurationIntent.self, provider: Provider()) { entry in JetChatWidgetEntryView(entry: entry) } .configurationDisplayName("JetChat Widget") .description("This is an jetchat widget.") } } struct JetChatWidget_Previews: PreviewProvider { static var previews: some View { JetChatWidgetEntryView(entry: SimpleEntry(date: Date(), object: "", configuration: FYConfigurationIntent())) .previewContext(WidgetPreviewContext(family: .systemSmall)) } } struct WidgetMsgItem: Codable { /// 聊天会话id var chatId: Int? /// 用户名 var name: String = "" /// 最近消息 var message: String = "" /// 消息发送时间 var date: String = "" /// 昵称备注名称 var nickName: String? = "" } ================================================ FILE: JetChat/JetChatWidgetExtension.entitlements ================================================ com.apple.security.application-groups group.com.jetchat.2022.JetChatWidget ================================================ FILE: JetChat/Podfile ================================================ # Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'FY-IMChat' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # *********************************************** Swift *********************************************** pod 'SwiftyJSON' pod 'SnapKit' pod 'RxSwift' pod 'RxCocoa' pod 'RxTheme' pod 'NSObject+Rx' pod 'Moya/RxSwift' pod 'HandyJSON' pod 'ReachabilitySwift' pod 'Localize-Swift' pod 'Kingfisher' pod 'R.swift' pod 'WCDB.swift' #本地数据库缓存 pod 'SwifterSwift' pod 'SwiftDate' # *********************************************** Objective-C *********************************************** pod 'UIView+FDCollapsibleConstraints' pod 'FDFullscreenPopGesture', '1.1' pod 'UITableView+FDTemplateLayoutCell' pod 'TZImagePickerController' pod 'IQKeyboardManagerSwift' pod 'MBProgressHUD' pod 'MJRefresh' pod 'YBImageBrowser' pod 'YBImageBrowser/Video' pod 'LookinServer', :configurations => ['Debug'] #UI层级调试 pod 'IGListKit' #朋友圈列表布局 pod 'YYText' end post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf' end end end install! 'cocoapods', disable_input_output_paths: true, generate_multiple_pod_projects: true ================================================ FILE: JetChat/Pods/Alamofire/LICENSE ================================================ Copyright (c) 2014-2022 Alamofire Software Foundation (http://alamofire.org/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/Alamofire/README.md ================================================ ![Alamofire: Elegant Networking in Swift](https://raw.githubusercontent.com/Alamofire/Alamofire/master/Resources/AlamofireLogo.png) [![Swift](https://img.shields.io/badge/Swift-5.3_5.4_5.5_5.6-orange?style=flat-square)](https://img.shields.io/badge/Swift-5.3_5.4_5.5_5.6-Orange?style=flat-square) [![Platforms](https://img.shields.io/badge/Platforms-macOS_iOS_tvOS_watchOS_Linux_Windows-yellowgreen?style=flat-square)](https://img.shields.io/badge/Platforms-macOS_iOS_tvOS_watchOS_Linux_Windows-Green?style=flat-square) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Alamofire.svg?style=flat-square)](https://img.shields.io/cocoapods/v/Alamofire.svg) [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat-square)](https://github.com/Carthage/Carthage) [![Swift Package Manager](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square)](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square) [![Twitter](https://img.shields.io/badge/twitter-@AlamofireSF-blue.svg?style=flat-square)](https://twitter.com/AlamofireSF) [![Swift Forums](https://img.shields.io/badge/Swift_Forums-Alamofire-orange?style=flat-square)](https://forums.swift.org/c/related-projects/alamofire/37) Alamofire is an HTTP networking library written in Swift. - [Features](#features) - [Component Libraries](#component-libraries) - [Requirements](#requirements) - [Migration Guides](#migration-guides) - [Communication](#communication) - [Installation](#installation) - [Contributing](#contributing) - [Usage](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#using-alamofire) - [**Introduction -**](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#introduction) [Making Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#making-requests), [Response Handling](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-handling), [Response Validation](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-validation), [Response Caching](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-caching) - **HTTP -** [HTTP Methods](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-methods), [Parameters and Parameter Encoder](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md##request-parameters-and-parameter-encoders), [HTTP Headers](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-headers), [Authentication](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#authentication) - **Large Data -** [Downloading Data to a File](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#downloading-data-to-a-file), [Uploading Data to a Server](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#uploading-data-to-a-server) - **Tools -** [Statistical Metrics](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#statistical-metrics), [cURL Command Output](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#curl-command-output) - [Advanced Usage](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md) - **URL Session -** [Session Manager](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#session), [Session Delegate](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#sessiondelegate), [Request](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#request) - **Routing -** [Routing Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#routing-requests), [Adapting and Retrying Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#adapting-and-retrying-requests-with-requestinterceptor) - **Model Objects -** [Custom Response Handlers](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#customizing-response-handlers) - **Advanced Concurrency -** [Swift Concurrency](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#using-alamofire-with-swift-concurrency) and [Combine](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#using-alamofire-with-combine) - **Connection -** [Security](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#security), [Network Reachability](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#network-reachability) - [Open Radars](#open-radars) - [FAQ](#faq) - [Credits](#credits) - [Donations](#donations) - [License](#license) ## Features - [x] Chainable Request / Response Methods - [x] Swift Concurrency Support Back to iOS 13, macOS 10.15, tvOS 13, and watchOS 6. - [x] Combine Support - [x] URL / JSON Parameter Encoding - [x] Upload File / Data / Stream / MultipartFormData - [x] Download File using Request or Resume Data - [x] Authentication with `URLCredential` - [x] HTTP Response Validation - [x] Upload and Download Progress Closures with Progress - [x] cURL Command Output - [x] Dynamically Adapt and Retry Requests - [x] TLS Certificate and Public Key Pinning - [x] Network Reachability - [x] Comprehensive Unit and Integration Test Coverage - [x] [Complete Documentation](https://alamofire.github.io/Alamofire) ## Component Libraries In order to keep Alamofire focused specifically on core networking implementations, additional component libraries have been created by the [Alamofire Software Foundation](https://github.com/Alamofire/Foundation) to bring additional functionality to the Alamofire ecosystem. - [AlamofireImage](https://github.com/Alamofire/AlamofireImage) - An image library including image response serializers, `UIImage` and `UIImageView` extensions, custom image filters, an auto-purging in-memory cache, and a priority-based image downloading system. - [AlamofireNetworkActivityIndicator](https://github.com/Alamofire/AlamofireNetworkActivityIndicator) - Controls the visibility of the network activity indicator on iOS using Alamofire. It contains configurable delay timers to help mitigate flicker and can support `URLSession` instances not managed by Alamofire. ## Requirements | Platform | Minimum Swift Version | Installation | Status | | --- | --- | --- | --- | | iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+ | 5.3 | [CocoaPods](#cocoapods), [Carthage](#carthage), [Swift Package Manager](#swift-package-manager), [Manual](#manually) | Fully Tested | | Linux | Latest Only | [Swift Package Manager](#swift-package-manager) | Building But Unsupported | | Windows | Latest Only | [Swift Package Manager](#swift-package-manager) | Building But Unsupported | #### Known Issues on Linux and Windows Alamofire builds on Linux and Windows but there are missing features and many issues in the underlying `swift-corelibs-foundation` that prevent full functionality and may cause crashes. These include: - `ServerTrustManager` and associated certificate functionality is unavailable, so there is no certificate pinning and no client certificate support. - Various methods of HTTP authentication may crash, including HTTP Basic and HTTP Digest. Crashes may occur if responses contain server challenges. - Cache control through `CachedResponseHandler` and associated APIs is unavailable, as the underlying delegate methods aren't called. - `URLSessionTaskMetrics` are never gathered. Due to these issues, Alamofire is unsupported on Linux and Windows. Please report any crashes to the [Swift bug reporter](https://bugs.swift.org). ## Migration Guides - [Alamofire 5.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%205.0%20Migration%20Guide.md) - [Alamofire 4.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%204.0%20Migration%20Guide.md) - [Alamofire 3.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%203.0%20Migration%20Guide.md) - [Alamofire 2.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%202.0%20Migration%20Guide.md) ## Communication - If you **need help with making network requests** using Alamofire, use [Stack Overflow](https://stackoverflow.com/questions/tagged/alamofire) and tag `alamofire`. - If you need to **find or understand an API**, check [our documentation](http://alamofire.github.io/Alamofire/) or [Apple's documentation for `URLSession`](https://developer.apple.com/documentation/foundation/url_loading_system), on top of which Alamofire is built. - If you need **help with an Alamofire feature**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). - If you'd like to **discuss Alamofire best practices**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). - If you'd like to **discuss a feature request**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). - If you **found a bug**, open an issue here on GitHub and follow the guide. The more detail the better! ## Installation ### CocoaPods [CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate Alamofire into your Xcode project using CocoaPods, specify it in your `Podfile`: ```ruby pod 'Alamofire' ``` ### Carthage [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate Alamofire into your Xcode project using Carthage, specify it in your `Cartfile`: ```ogdl github "Alamofire/Alamofire" ``` ### Swift Package Manager The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. Once you have your Swift package set up, adding Alamofire as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. ```swift dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.6.1")) ] ``` ### Manually If you prefer not to use any of the aforementioned dependency managers, you can integrate Alamofire into your project manually. #### Embedded Framework - Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository: ```bash $ git init ``` - Add Alamofire as a git [submodule](https://git-scm.com/docs/git-submodule) by running the following command: ```bash $ git submodule add https://github.com/Alamofire/Alamofire.git ``` - Open the new `Alamofire` folder, and drag the `Alamofire.xcodeproj` into the Project Navigator of your application's Xcode project. > It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter. - Select the `Alamofire.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target. - Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar. - In the tab bar at the top of that window, open the "General" panel. - Click on the `+` button under the "Embedded Binaries" section. - You will see two different `Alamofire.xcodeproj` folders each with two different versions of the `Alamofire.framework` nested inside a `Products` folder. > It does not matter which `Products` folder you choose from, but it does matter whether you choose the top or bottom `Alamofire.framework`. - Select the top `Alamofire.framework` for iOS and the bottom one for macOS. > You can verify which one you selected by inspecting the build log for your project. The build target for `Alamofire` will be listed as `Alamofire iOS`, `Alamofire macOS`, `Alamofire tvOS`, or `Alamofire watchOS`. - And that's it! > The `Alamofire.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device. ## Contributing Before contributing to Alamofire, please read the instructions detailed in our [contribution guide](https://github.com/Alamofire/Alamofire/blob/master/CONTRIBUTING.md). ## Open Radars The following radars have some effect on the current implementation of Alamofire. - [`rdar://21349340`](http://www.openradar.me/radar?id=5517037090635776) - Compiler throwing warning due to toll-free bridging issue in the test case - `rdar://26870455` - Background URL Session Configurations do not work in the simulator - `rdar://26849668` - Some URLProtocol APIs do not properly handle `URLRequest` ## Resolved Radars The following radars have been resolved over time after being filed against the Alamofire project. - [`rdar://26761490`](http://www.openradar.me/radar?id=5010235949318144) - Swift string interpolation causing memory leak with common usage. - (Resolved): 9/1/17 in Xcode 9 beta 6. - [`rdar://36082113`](http://openradar.appspot.com/radar?id=4942308441063424) - `URLSessionTaskMetrics` failing to link on watchOS 3.0+ - (Resolved): Just add `CFNetwork` to your linked frameworks. - `FB7624529` - `urlSession(_:task:didFinishCollecting:)` never called on watchOS - (Resolved): Metrics now collected on watchOS 7+. ## FAQ ### What's the origin of the name Alamofire? Alamofire is named after the [Alamo Fire flower](https://aggie-horticulture.tamu.edu/wildseed/alamofire.html), a hybrid variant of the Bluebonnet, the official state flower of Texas. ## Credits Alamofire is owned and maintained by the [Alamofire Software Foundation](http://alamofire.org). You can follow them on Twitter at [@AlamofireSF](https://twitter.com/AlamofireSF) for project updates and releases. ### Security Disclosure If you believe you have identified a security vulnerability with Alamofire, you should report it as soon as possible via email to security@alamofire.org. Please do not post it to a public issue tracker. ## Sponsorship The [ASF](https://github.com/Alamofire/Foundation#members) is looking to raise money to officially stay registered as a federal non-profit organization. Registering will allow Foundation members to gain some legal protections and also allow us to put donations to use, tax-free. Sponsoring the ASF will enable us to: - Pay our yearly legal fees to keep the non-profit in good status - Pay for our mail servers to help us stay on top of all questions and security issues - Potentially fund test servers to make it easier for us to test the edge cases - Potentially fund developers to work on one of our projects full-time The community adoption of the ASF libraries has been amazing. We are greatly humbled by your enthusiasm around the projects and want to continue to do everything we can to move the needle forward. With your continued support, the ASF will be able to improve its reach and also provide better legal safety for the core members. If you use any of our libraries for work, see if your employers would be interested in donating. Any amount you can donate, whether once or monthly, to help us reach our goal would be greatly appreciated. [Sponsor Alamofire](https://github.com/sponsors/Alamofire) ## Supporters [MacStadium](https://macstadium.com) provides Alamofire with a free, hosted Mac mini. ![Powered by MacStadium](https://raw.githubusercontent.com/Alamofire/Alamofire/master/Resources/MacStadiumLogo.png) ## License Alamofire is released under the MIT license. [See LICENSE](https://github.com/Alamofire/Alamofire/blob/master/LICENSE) for details. ================================================ FILE: JetChat/Pods/Alamofire/Source/AFError.swift ================================================ // // AFError.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// `AFError` is the error type returned by Alamofire. It encompasses a few different types of errors, each with /// their own associated reasons. public enum AFError: Error { /// The underlying reason the `.multipartEncodingFailed` error occurred. public enum MultipartEncodingFailureReason { /// The `fileURL` provided for reading an encodable body part isn't a file `URL`. case bodyPartURLInvalid(url: URL) /// The filename of the `fileURL` provided has either an empty `lastPathComponent` or `pathExtension. case bodyPartFilenameInvalid(in: URL) /// The file at the `fileURL` provided was not reachable. case bodyPartFileNotReachable(at: URL) /// Attempting to check the reachability of the `fileURL` provided threw an error. case bodyPartFileNotReachableWithError(atURL: URL, error: Error) /// The file at the `fileURL` provided is actually a directory. case bodyPartFileIsDirectory(at: URL) /// The size of the file at the `fileURL` provided was not returned by the system. case bodyPartFileSizeNotAvailable(at: URL) /// The attempt to find the size of the file at the `fileURL` provided threw an error. case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error) /// An `InputStream` could not be created for the provided `fileURL`. case bodyPartInputStreamCreationFailed(for: URL) /// An `OutputStream` could not be created when attempting to write the encoded data to disk. case outputStreamCreationFailed(for: URL) /// The encoded body data could not be written to disk because a file already exists at the provided `fileURL`. case outputStreamFileAlreadyExists(at: URL) /// The `fileURL` provided for writing the encoded body data to disk is not a file `URL`. case outputStreamURLInvalid(url: URL) /// The attempt to write the encoded body data to disk failed with an underlying error. case outputStreamWriteFailed(error: Error) /// The attempt to read an encoded body part `InputStream` failed with underlying system error. case inputStreamReadFailed(error: Error) } /// Represents unexpected input stream length that occur when encoding the `MultipartFormData`. Instances will be /// embedded within an `AFError.multipartEncodingFailed` `.inputStreamReadFailed` case. public struct UnexpectedInputStreamLength: Error { /// The expected byte count to read. public var bytesExpected: UInt64 /// The actual byte count read. public var bytesRead: UInt64 } /// The underlying reason the `.parameterEncodingFailed` error occurred. public enum ParameterEncodingFailureReason { /// The `URLRequest` did not have a `URL` to encode. case missingURL /// JSON serialization failed with an underlying system error during the encoding process. case jsonEncodingFailed(error: Error) /// Custom parameter encoding failed due to the associated `Error`. case customEncodingFailed(error: Error) } /// The underlying reason the `.parameterEncoderFailed` error occurred. public enum ParameterEncoderFailureReason { /// Possible missing components. public enum RequiredComponent { /// The `URL` was missing or unable to be extracted from the passed `URLRequest` or during encoding. case url /// The `HTTPMethod` could not be extracted from the passed `URLRequest`. case httpMethod(rawValue: String) } /// A `RequiredComponent` was missing during encoding. case missingRequiredComponent(RequiredComponent) /// The underlying encoder failed with the associated error. case encoderFailed(error: Error) } /// The underlying reason the `.responseValidationFailed` error occurred. public enum ResponseValidationFailureReason { /// The data file containing the server response did not exist. case dataFileNil /// The data file containing the server response at the associated `URL` could not be read. case dataFileReadFailed(at: URL) /// The response did not contain a `Content-Type` and the `acceptableContentTypes` provided did not contain a /// wildcard type. case missingContentType(acceptableContentTypes: [String]) /// The response `Content-Type` did not match any type in the provided `acceptableContentTypes`. case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String) /// The response status code was not acceptable. case unacceptableStatusCode(code: Int) /// Custom response validation failed due to the associated `Error`. case customValidationFailed(error: Error) } /// The underlying reason the response serialization error occurred. public enum ResponseSerializationFailureReason { /// The server response contained no data or the data was zero length. case inputDataNilOrZeroLength /// The file containing the server response did not exist. case inputFileNil /// The file containing the server response could not be read from the associated `URL`. case inputFileReadFailed(at: URL) /// String serialization failed using the provided `String.Encoding`. case stringSerializationFailed(encoding: String.Encoding) /// JSON serialization failed with an underlying system error. case jsonSerializationFailed(error: Error) /// A `DataDecoder` failed to decode the response due to the associated `Error`. case decodingFailed(error: Error) /// A custom response serializer failed due to the associated `Error`. case customSerializationFailed(error: Error) /// Generic serialization failed for an empty response that wasn't type `Empty` but instead the associated type. case invalidEmptyResponse(type: String) } #if !(os(Linux) || os(Windows)) /// Underlying reason a server trust evaluation error occurred. public enum ServerTrustFailureReason { /// The output of a server trust evaluation. public struct Output { /// The host for which the evaluation was performed. public let host: String /// The `SecTrust` value which was evaluated. public let trust: SecTrust /// The `OSStatus` of evaluation operation. public let status: OSStatus /// The result of the evaluation operation. public let result: SecTrustResultType /// Creates an `Output` value from the provided values. init(_ host: String, _ trust: SecTrust, _ status: OSStatus, _ result: SecTrustResultType) { self.host = host self.trust = trust self.status = status self.result = result } } /// No `ServerTrustEvaluator` was found for the associated host. case noRequiredEvaluator(host: String) /// No certificates were found with which to perform the trust evaluation. case noCertificatesFound /// No public keys were found with which to perform the trust evaluation. case noPublicKeysFound /// During evaluation, application of the associated `SecPolicy` failed. case policyApplicationFailed(trust: SecTrust, policy: SecPolicy, status: OSStatus) /// During evaluation, setting the associated anchor certificates failed. case settingAnchorCertificatesFailed(status: OSStatus, certificates: [SecCertificate]) /// During evaluation, creation of the revocation policy failed. case revocationPolicyCreationFailed /// `SecTrust` evaluation failed with the associated `Error`, if one was produced. case trustEvaluationFailed(error: Error?) /// Default evaluation failed with the associated `Output`. case defaultEvaluationFailed(output: Output) /// Host validation failed with the associated `Output`. case hostValidationFailed(output: Output) /// Revocation check failed with the associated `Output` and options. case revocationCheckFailed(output: Output, options: RevocationTrustEvaluator.Options) /// Certificate pinning failed. case certificatePinningFailed(host: String, trust: SecTrust, pinnedCertificates: [SecCertificate], serverCertificates: [SecCertificate]) /// Public key pinning failed. case publicKeyPinningFailed(host: String, trust: SecTrust, pinnedKeys: [SecKey], serverKeys: [SecKey]) /// Custom server trust evaluation failed due to the associated `Error`. case customEvaluationFailed(error: Error) } #endif /// The underlying reason the `.urlRequestValidationFailed` public enum URLRequestValidationFailureReason { /// URLRequest with GET method had body data. case bodyDataInGETRequest(Data) } /// `UploadableConvertible` threw an error in `createUploadable()`. case createUploadableFailed(error: Error) /// `URLRequestConvertible` threw an error in `asURLRequest()`. case createURLRequestFailed(error: Error) /// `SessionDelegate` threw an error while attempting to move downloaded file to destination URL. case downloadedFileMoveFailed(error: Error, source: URL, destination: URL) /// `Request` was explicitly cancelled. case explicitlyCancelled /// `URLConvertible` type failed to create a valid `URL`. case invalidURL(url: URLConvertible) /// Multipart form encoding failed. case multipartEncodingFailed(reason: MultipartEncodingFailureReason) /// `ParameterEncoding` threw an error during the encoding process. case parameterEncodingFailed(reason: ParameterEncodingFailureReason) /// `ParameterEncoder` threw an error while running the encoder. case parameterEncoderFailed(reason: ParameterEncoderFailureReason) /// `RequestAdapter` threw an error during adaptation. case requestAdaptationFailed(error: Error) /// `RequestRetrier` threw an error during the request retry process. case requestRetryFailed(retryError: Error, originalError: Error) /// Response validation failed. case responseValidationFailed(reason: ResponseValidationFailureReason) /// Response serialization failed. case responseSerializationFailed(reason: ResponseSerializationFailureReason) #if !(os(Linux) || os(Windows)) /// `ServerTrustEvaluating` instance threw an error during trust evaluation. case serverTrustEvaluationFailed(reason: ServerTrustFailureReason) #endif /// `Session` which issued the `Request` was deinitialized, most likely because its reference went out of scope. case sessionDeinitialized /// `Session` was explicitly invalidated, possibly with the `Error` produced by the underlying `URLSession`. case sessionInvalidated(error: Error?) /// `URLSessionTask` completed with error. case sessionTaskFailed(error: Error) /// `URLRequest` failed validation. case urlRequestValidationFailed(reason: URLRequestValidationFailureReason) } extension Error { /// Returns the instance cast as an `AFError`. public var asAFError: AFError? { self as? AFError } /// Returns the instance cast as an `AFError`. If casting fails, a `fatalError` with the specified `message` is thrown. public func asAFError(orFailWith message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) -> AFError { guard let afError = self as? AFError else { fatalError(message(), file: file, line: line) } return afError } /// Casts the instance as `AFError` or returns `defaultAFError` func asAFError(or defaultAFError: @autoclosure () -> AFError) -> AFError { self as? AFError ?? defaultAFError() } } // MARK: - Error Booleans extension AFError { /// Returns whether the instance is `.sessionDeinitialized`. public var isSessionDeinitializedError: Bool { if case .sessionDeinitialized = self { return true } return false } /// Returns whether the instance is `.sessionInvalidated`. public var isSessionInvalidatedError: Bool { if case .sessionInvalidated = self { return true } return false } /// Returns whether the instance is `.explicitlyCancelled`. public var isExplicitlyCancelledError: Bool { if case .explicitlyCancelled = self { return true } return false } /// Returns whether the instance is `.invalidURL`. public var isInvalidURLError: Bool { if case .invalidURL = self { return true } return false } /// Returns whether the instance is `.parameterEncodingFailed`. When `true`, the `underlyingError` property will /// contain the associated value. public var isParameterEncodingError: Bool { if case .parameterEncodingFailed = self { return true } return false } /// Returns whether the instance is `.parameterEncoderFailed`. When `true`, the `underlyingError` property will /// contain the associated value. public var isParameterEncoderError: Bool { if case .parameterEncoderFailed = self { return true } return false } /// Returns whether the instance is `.multipartEncodingFailed`. When `true`, the `url` and `underlyingError` /// properties will contain the associated values. public var isMultipartEncodingError: Bool { if case .multipartEncodingFailed = self { return true } return false } /// Returns whether the instance is `.requestAdaptationFailed`. When `true`, the `underlyingError` property will /// contain the associated value. public var isRequestAdaptationError: Bool { if case .requestAdaptationFailed = self { return true } return false } /// Returns whether the instance is `.responseValidationFailed`. When `true`, the `acceptableContentTypes`, /// `responseContentType`, `responseCode`, and `underlyingError` properties will contain the associated values. public var isResponseValidationError: Bool { if case .responseValidationFailed = self { return true } return false } /// Returns whether the instance is `.responseSerializationFailed`. When `true`, the `failedStringEncoding` and /// `underlyingError` properties will contain the associated values. public var isResponseSerializationError: Bool { if case .responseSerializationFailed = self { return true } return false } #if !(os(Linux) || os(Windows)) /// Returns whether the instance is `.serverTrustEvaluationFailed`. When `true`, the `underlyingError` property will /// contain the associated value. public var isServerTrustEvaluationError: Bool { if case .serverTrustEvaluationFailed = self { return true } return false } #endif /// Returns whether the instance is `requestRetryFailed`. When `true`, the `underlyingError` property will /// contain the associated value. public var isRequestRetryError: Bool { if case .requestRetryFailed = self { return true } return false } /// Returns whether the instance is `createUploadableFailed`. When `true`, the `underlyingError` property will /// contain the associated value. public var isCreateUploadableError: Bool { if case .createUploadableFailed = self { return true } return false } /// Returns whether the instance is `createURLRequestFailed`. When `true`, the `underlyingError` property will /// contain the associated value. public var isCreateURLRequestError: Bool { if case .createURLRequestFailed = self { return true } return false } /// Returns whether the instance is `downloadedFileMoveFailed`. When `true`, the `destination` and `underlyingError` properties will /// contain the associated values. public var isDownloadedFileMoveError: Bool { if case .downloadedFileMoveFailed = self { return true } return false } /// Returns whether the instance is `createURLRequestFailed`. When `true`, the `underlyingError` property will /// contain the associated value. public var isSessionTaskError: Bool { if case .sessionTaskFailed = self { return true } return false } } // MARK: - Convenience Properties extension AFError { /// The `URLConvertible` associated with the error. public var urlConvertible: URLConvertible? { guard case let .invalidURL(url) = self else { return nil } return url } /// The `URL` associated with the error. public var url: URL? { guard case let .multipartEncodingFailed(reason) = self else { return nil } return reason.url } /// The underlying `Error` responsible for generating the failure associated with `.sessionInvalidated`, /// `.parameterEncodingFailed`, `.parameterEncoderFailed`, `.multipartEncodingFailed`, `.requestAdaptationFailed`, /// `.responseSerializationFailed`, `.requestRetryFailed` errors. public var underlyingError: Error? { switch self { case let .multipartEncodingFailed(reason): return reason.underlyingError case let .parameterEncodingFailed(reason): return reason.underlyingError case let .parameterEncoderFailed(reason): return reason.underlyingError case let .requestAdaptationFailed(error): return error case let .requestRetryFailed(retryError, _): return retryError case let .responseValidationFailed(reason): return reason.underlyingError case let .responseSerializationFailed(reason): return reason.underlyingError #if !(os(Linux) || os(Windows)) case let .serverTrustEvaluationFailed(reason): return reason.underlyingError #endif case let .sessionInvalidated(error): return error case let .createUploadableFailed(error): return error case let .createURLRequestFailed(error): return error case let .downloadedFileMoveFailed(error, _, _): return error case let .sessionTaskFailed(error): return error case .explicitlyCancelled, .invalidURL, .sessionDeinitialized, .urlRequestValidationFailed: return nil } } /// The acceptable `Content-Type`s of a `.responseValidationFailed` error. public var acceptableContentTypes: [String]? { guard case let .responseValidationFailed(reason) = self else { return nil } return reason.acceptableContentTypes } /// The response `Content-Type` of a `.responseValidationFailed` error. public var responseContentType: String? { guard case let .responseValidationFailed(reason) = self else { return nil } return reason.responseContentType } /// The response code of a `.responseValidationFailed` error. public var responseCode: Int? { guard case let .responseValidationFailed(reason) = self else { return nil } return reason.responseCode } /// The `String.Encoding` associated with a failed `.stringResponse()` call. public var failedStringEncoding: String.Encoding? { guard case let .responseSerializationFailed(reason) = self else { return nil } return reason.failedStringEncoding } /// The `source` URL of a `.downloadedFileMoveFailed` error. public var sourceURL: URL? { guard case let .downloadedFileMoveFailed(_, source, _) = self else { return nil } return source } /// The `destination` URL of a `.downloadedFileMoveFailed` error. public var destinationURL: URL? { guard case let .downloadedFileMoveFailed(_, _, destination) = self else { return nil } return destination } #if !(os(Linux) || os(Windows)) /// The download resume data of any underlying network error. Only produced by `DownloadRequest`s. public var downloadResumeData: Data? { (underlyingError as? URLError)?.userInfo[NSURLSessionDownloadTaskResumeData] as? Data } #endif } extension AFError.ParameterEncodingFailureReason { var underlyingError: Error? { switch self { case let .jsonEncodingFailed(error), let .customEncodingFailed(error): return error case .missingURL: return nil } } } extension AFError.ParameterEncoderFailureReason { var underlyingError: Error? { switch self { case let .encoderFailed(error): return error case .missingRequiredComponent: return nil } } } extension AFError.MultipartEncodingFailureReason { var url: URL? { switch self { case let .bodyPartURLInvalid(url), let .bodyPartFilenameInvalid(url), let .bodyPartFileNotReachable(url), let .bodyPartFileIsDirectory(url), let .bodyPartFileSizeNotAvailable(url), let .bodyPartInputStreamCreationFailed(url), let .outputStreamCreationFailed(url), let .outputStreamFileAlreadyExists(url), let .outputStreamURLInvalid(url), let .bodyPartFileNotReachableWithError(url, _), let .bodyPartFileSizeQueryFailedWithError(url, _): return url case .outputStreamWriteFailed, .inputStreamReadFailed: return nil } } var underlyingError: Error? { switch self { case let .bodyPartFileNotReachableWithError(_, error), let .bodyPartFileSizeQueryFailedWithError(_, error), let .outputStreamWriteFailed(error), let .inputStreamReadFailed(error): return error case .bodyPartURLInvalid, .bodyPartFilenameInvalid, .bodyPartFileNotReachable, .bodyPartFileIsDirectory, .bodyPartFileSizeNotAvailable, .bodyPartInputStreamCreationFailed, .outputStreamCreationFailed, .outputStreamFileAlreadyExists, .outputStreamURLInvalid: return nil } } } extension AFError.ResponseValidationFailureReason { var acceptableContentTypes: [String]? { switch self { case let .missingContentType(types), let .unacceptableContentType(types, _): return types case .dataFileNil, .dataFileReadFailed, .unacceptableStatusCode, .customValidationFailed: return nil } } var responseContentType: String? { switch self { case let .unacceptableContentType(_, responseType): return responseType case .dataFileNil, .dataFileReadFailed, .missingContentType, .unacceptableStatusCode, .customValidationFailed: return nil } } var responseCode: Int? { switch self { case let .unacceptableStatusCode(code): return code case .dataFileNil, .dataFileReadFailed, .missingContentType, .unacceptableContentType, .customValidationFailed: return nil } } var underlyingError: Error? { switch self { case let .customValidationFailed(error): return error case .dataFileNil, .dataFileReadFailed, .missingContentType, .unacceptableContentType, .unacceptableStatusCode: return nil } } } extension AFError.ResponseSerializationFailureReason { var failedStringEncoding: String.Encoding? { switch self { case let .stringSerializationFailed(encoding): return encoding case .inputDataNilOrZeroLength, .inputFileNil, .inputFileReadFailed(_), .jsonSerializationFailed(_), .decodingFailed(_), .customSerializationFailed(_), .invalidEmptyResponse: return nil } } var underlyingError: Error? { switch self { case let .jsonSerializationFailed(error), let .decodingFailed(error), let .customSerializationFailed(error): return error case .inputDataNilOrZeroLength, .inputFileNil, .inputFileReadFailed, .stringSerializationFailed, .invalidEmptyResponse: return nil } } } #if !(os(Linux) || os(Windows)) extension AFError.ServerTrustFailureReason { var output: AFError.ServerTrustFailureReason.Output? { switch self { case let .defaultEvaluationFailed(output), let .hostValidationFailed(output), let .revocationCheckFailed(output, _): return output case .noRequiredEvaluator, .noCertificatesFound, .noPublicKeysFound, .policyApplicationFailed, .settingAnchorCertificatesFailed, .revocationPolicyCreationFailed, .trustEvaluationFailed, .certificatePinningFailed, .publicKeyPinningFailed, .customEvaluationFailed: return nil } } var underlyingError: Error? { switch self { case let .customEvaluationFailed(error): return error case let .trustEvaluationFailed(error): return error case .noRequiredEvaluator, .noCertificatesFound, .noPublicKeysFound, .policyApplicationFailed, .settingAnchorCertificatesFailed, .revocationPolicyCreationFailed, .defaultEvaluationFailed, .hostValidationFailed, .revocationCheckFailed, .certificatePinningFailed, .publicKeyPinningFailed: return nil } } } #endif // MARK: - Error Descriptions extension AFError: LocalizedError { public var errorDescription: String? { switch self { case .explicitlyCancelled: return "Request explicitly cancelled." case let .invalidURL(url): return "URL is not valid: \(url)" case let .parameterEncodingFailed(reason): return reason.localizedDescription case let .parameterEncoderFailed(reason): return reason.localizedDescription case let .multipartEncodingFailed(reason): return reason.localizedDescription case let .requestAdaptationFailed(error): return "Request adaption failed with error: \(error.localizedDescription)" case let .responseValidationFailed(reason): return reason.localizedDescription case let .responseSerializationFailed(reason): return reason.localizedDescription case let .requestRetryFailed(retryError, originalError): return """ Request retry failed with retry error: \(retryError.localizedDescription), \ original error: \(originalError.localizedDescription) """ case .sessionDeinitialized: return """ Session was invalidated without error, so it was likely deinitialized unexpectedly. \ Be sure to retain a reference to your Session for the duration of your requests. """ case let .sessionInvalidated(error): return "Session was invalidated with error: \(error?.localizedDescription ?? "No description.")" #if !(os(Linux) || os(Windows)) case let .serverTrustEvaluationFailed(reason): return "Server trust evaluation failed due to reason: \(reason.localizedDescription)" #endif case let .urlRequestValidationFailed(reason): return "URLRequest validation failed due to reason: \(reason.localizedDescription)" case let .createUploadableFailed(error): return "Uploadable creation failed with error: \(error.localizedDescription)" case let .createURLRequestFailed(error): return "URLRequest creation failed with error: \(error.localizedDescription)" case let .downloadedFileMoveFailed(error, source, destination): return "Moving downloaded file from: \(source) to: \(destination) failed with error: \(error.localizedDescription)" case let .sessionTaskFailed(error): return "URLSessionTask failed with error: \(error.localizedDescription)" } } } extension AFError.ParameterEncodingFailureReason { var localizedDescription: String { switch self { case .missingURL: return "URL request to encode was missing a URL" case let .jsonEncodingFailed(error): return "JSON could not be encoded because of error:\n\(error.localizedDescription)" case let .customEncodingFailed(error): return "Custom parameter encoder failed with error: \(error.localizedDescription)" } } } extension AFError.ParameterEncoderFailureReason { var localizedDescription: String { switch self { case let .missingRequiredComponent(component): return "Encoding failed due to a missing request component: \(component)" case let .encoderFailed(error): return "The underlying encoder failed with the error: \(error)" } } } extension AFError.MultipartEncodingFailureReason { var localizedDescription: String { switch self { case let .bodyPartURLInvalid(url): return "The URL provided is not a file URL: \(url)" case let .bodyPartFilenameInvalid(url): return "The URL provided does not have a valid filename: \(url)" case let .bodyPartFileNotReachable(url): return "The URL provided is not reachable: \(url)" case let .bodyPartFileNotReachableWithError(url, error): return """ The system returned an error while checking the provided URL for reachability. URL: \(url) Error: \(error) """ case let .bodyPartFileIsDirectory(url): return "The URL provided is a directory: \(url)" case let .bodyPartFileSizeNotAvailable(url): return "Could not fetch the file size from the provided URL: \(url)" case let .bodyPartFileSizeQueryFailedWithError(url, error): return """ The system returned an error while attempting to fetch the file size from the provided URL. URL: \(url) Error: \(error) """ case let .bodyPartInputStreamCreationFailed(url): return "Failed to create an InputStream for the provided URL: \(url)" case let .outputStreamCreationFailed(url): return "Failed to create an OutputStream for URL: \(url)" case let .outputStreamFileAlreadyExists(url): return "A file already exists at the provided URL: \(url)" case let .outputStreamURLInvalid(url): return "The provided OutputStream URL is invalid: \(url)" case let .outputStreamWriteFailed(error): return "OutputStream write failed with error: \(error)" case let .inputStreamReadFailed(error): return "InputStream read failed with error: \(error)" } } } extension AFError.ResponseSerializationFailureReason { var localizedDescription: String { switch self { case .inputDataNilOrZeroLength: return "Response could not be serialized, input data was nil or zero length." case .inputFileNil: return "Response could not be serialized, input file was nil." case let .inputFileReadFailed(url): return "Response could not be serialized, input file could not be read: \(url)." case let .stringSerializationFailed(encoding): return "String could not be serialized with encoding: \(encoding)." case let .jsonSerializationFailed(error): return "JSON could not be serialized because of error:\n\(error.localizedDescription)" case let .invalidEmptyResponse(type): return """ Empty response could not be serialized to type: \(type). \ Use Empty as the expected type for such responses. """ case let .decodingFailed(error): return "Response could not be decoded because of error:\n\(error.localizedDescription)" case let .customSerializationFailed(error): return "Custom response serializer failed with error:\n\(error.localizedDescription)" } } } extension AFError.ResponseValidationFailureReason { var localizedDescription: String { switch self { case .dataFileNil: return "Response could not be validated, data file was nil." case let .dataFileReadFailed(url): return "Response could not be validated, data file could not be read: \(url)." case let .missingContentType(types): return """ Response Content-Type was missing and acceptable content types \ (\(types.joined(separator: ","))) do not match "*/*". """ case let .unacceptableContentType(acceptableTypes, responseType): return """ Response Content-Type "\(responseType)" does not match any acceptable types: \ \(acceptableTypes.joined(separator: ",")). """ case let .unacceptableStatusCode(code): return "Response status code was unacceptable: \(code)." case let .customValidationFailed(error): return "Custom response validation failed with error: \(error.localizedDescription)" } } } #if !(os(Linux) || os(Windows)) extension AFError.ServerTrustFailureReason { var localizedDescription: String { switch self { case let .noRequiredEvaluator(host): return "A ServerTrustEvaluating value is required for host \(host) but none was found." case .noCertificatesFound: return "No certificates were found or provided for evaluation." case .noPublicKeysFound: return "No public keys were found or provided for evaluation." case .policyApplicationFailed: return "Attempting to set a SecPolicy failed." case .settingAnchorCertificatesFailed: return "Attempting to set the provided certificates as anchor certificates failed." case .revocationPolicyCreationFailed: return "Attempting to create a revocation policy failed." case let .trustEvaluationFailed(error): return "SecTrust evaluation failed with error: \(error?.localizedDescription ?? "None")" case let .defaultEvaluationFailed(output): return "Default evaluation failed for host \(output.host)." case let .hostValidationFailed(output): return "Host validation failed for host \(output.host)." case let .revocationCheckFailed(output, _): return "Revocation check failed for host \(output.host)." case let .certificatePinningFailed(host, _, _, _): return "Certificate pinning failed for host \(host)." case let .publicKeyPinningFailed(host, _, _, _): return "Public key pinning failed for host \(host)." case let .customEvaluationFailed(error): return "Custom trust evaluation failed with error: \(error.localizedDescription)" } } } #endif extension AFError.URLRequestValidationFailureReason { var localizedDescription: String { switch self { case let .bodyDataInGETRequest(data): return """ Invalid URLRequest: Requests with GET method cannot have body data: \(String(decoding: data, as: UTF8.self)) """ } } } ================================================ FILE: JetChat/Pods/Alamofire/Source/Alamofire.swift ================================================ // // Alamofire.swift // // Copyright (c) 2014-2021 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Dispatch import Foundation #if canImport(FoundationNetworking) @_exported import FoundationNetworking #endif // Enforce minimum Swift version for all platforms and build systems. #if swift(<5.3) #error("Alamofire doesn't support Swift versions below 5.3.") #endif /// Reference to `Session.default` for quick bootstrapping and examples. public let AF = Session.default /// Current Alamofire version. Necessary since SPM doesn't use dynamic libraries. Plus this will be more accurate. let version = "5.6.1" ================================================ FILE: JetChat/Pods/Alamofire/Source/AlamofireExtended.swift ================================================ // // AlamofireExtended.swift // // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // /// Type that acts as a generic extension point for all `AlamofireExtended` types. public struct AlamofireExtension { /// Stores the type or meta-type of any extended type. public private(set) var type: ExtendedType /// Create an instance from the provided value. /// /// - Parameter type: Instance being extended. public init(_ type: ExtendedType) { self.type = type } } /// Protocol describing the `af` extension points for Alamofire extended types. public protocol AlamofireExtended { /// Type being extended. associatedtype ExtendedType /// Static Alamofire extension point. static var af: AlamofireExtension.Type { get set } /// Instance Alamofire extension point. var af: AlamofireExtension { get set } } extension AlamofireExtended { /// Static Alamofire extension point. public static var af: AlamofireExtension.Type { get { AlamofireExtension.self } set {} } /// Instance Alamofire extension point. public var af: AlamofireExtension { get { AlamofireExtension(self) } set {} } } ================================================ FILE: JetChat/Pods/Alamofire/Source/AuthenticationInterceptor.swift ================================================ // // AuthenticationInterceptor.swift // // Copyright (c) 2020 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Types adopting the `AuthenticationCredential` protocol can be used to authenticate `URLRequest`s. /// /// One common example of an `AuthenticationCredential` is an OAuth2 credential containing an access token used to /// authenticate all requests on behalf of a user. The access token generally has an expiration window of 60 minutes /// which will then require a refresh of the credential using the refresh token to generate a new access token. public protocol AuthenticationCredential { /// Whether the credential requires a refresh. This property should always return `true` when the credential is /// expired. It is also wise to consider returning `true` when the credential will expire in several seconds or /// minutes depending on the expiration window of the credential. /// /// For example, if the credential is valid for 60 minutes, then it would be wise to return `true` when the /// credential is only valid for 5 minutes or less. That ensures the credential will not expire as it is passed /// around backend services. var requiresRefresh: Bool { get } } // MARK: - /// Types adopting the `Authenticator` protocol can be used to authenticate `URLRequest`s with an /// `AuthenticationCredential` as well as refresh the `AuthenticationCredential` when required. public protocol Authenticator: AnyObject { /// The type of credential associated with the `Authenticator` instance. associatedtype Credential: AuthenticationCredential /// Applies the `Credential` to the `URLRequest`. /// /// In the case of OAuth2, the access token of the `Credential` would be added to the `URLRequest` as a Bearer /// token to the `Authorization` header. /// /// - Parameters: /// - credential: The `Credential`. /// - urlRequest: The `URLRequest`. func apply(_ credential: Credential, to urlRequest: inout URLRequest) /// Refreshes the `Credential` and executes the `completion` closure with the `Result` once complete. /// /// Refresh can be called in one of two ways. It can be called before the `Request` is actually executed due to /// a `requiresRefresh` returning `true` during the adapt portion of the `Request` creation process. It can also /// be triggered by a failed `Request` where the authentication server denied access due to an expired or /// invalidated access token. /// /// In the case of OAuth2, this method would use the refresh token of the `Credential` to generate a new /// `Credential` using the authentication service. Once complete, the `completion` closure should be called with /// the new `Credential`, or the error that occurred. /// /// In general, if the refresh call fails with certain status codes from the authentication server (commonly a 401), /// the refresh token in the `Credential` can no longer be used to generate a valid `Credential`. In these cases, /// you will need to reauthenticate the user with their username / password. /// /// Please note, these are just general examples of common use cases. They are not meant to solve your specific /// authentication server challenges. Please work with your authentication server team to ensure your /// `Authenticator` logic matches their expectations. /// /// - Parameters: /// - credential: The `Credential` to refresh. /// - session: The `Session` requiring the refresh. /// - completion: The closure to be executed once the refresh is complete. func refresh(_ credential: Credential, for session: Session, completion: @escaping (Result) -> Void) /// Determines whether the `URLRequest` failed due to an authentication error based on the `HTTPURLResponse`. /// /// If the authentication server **CANNOT** invalidate credentials after they are issued, then simply return `false` /// for this method. If the authentication server **CAN** invalidate credentials due to security breaches, then you /// will need to work with your authentication server team to understand how to identify when this occurs. /// /// In the case of OAuth2, where an authentication server can invalidate credentials, you will need to inspect the /// `HTTPURLResponse` or possibly the `Error` for when this occurs. This is commonly handled by the authentication /// server returning a 401 status code and some additional header to indicate an OAuth2 failure occurred. /// /// It is very important to understand how your authentication server works to be able to implement this correctly. /// For example, if your authentication server returns a 401 when an OAuth2 error occurs, and your downstream /// service also returns a 401 when you are not authorized to perform that operation, how do you know which layer /// of the backend returned you a 401? You do not want to trigger a refresh unless you know your authentication /// server is actually the layer rejecting the request. Again, work with your authentication server team to understand /// how to identify an OAuth2 401 error vs. a downstream 401 error to avoid endless refresh loops. /// /// - Parameters: /// - urlRequest: The `URLRequest`. /// - response: The `HTTPURLResponse`. /// - error: The `Error`. /// /// - Returns: `true` if the `URLRequest` failed due to an authentication error, `false` otherwise. func didRequest(_ urlRequest: URLRequest, with response: HTTPURLResponse, failDueToAuthenticationError error: Error) -> Bool /// Determines whether the `URLRequest` is authenticated with the `Credential`. /// /// If the authentication server **CANNOT** invalidate credentials after they are issued, then simply return `true` /// for this method. If the authentication server **CAN** invalidate credentials due to security breaches, then /// read on. /// /// When an authentication server can invalidate credentials, it means that you may have a non-expired credential /// that appears to be valid, but will be rejected by the authentication server when used. Generally when this /// happens, a number of requests are all sent when the application is foregrounded, and all of them will be /// rejected by the authentication server in the order they are received. The first failed request will trigger a /// refresh internally, which will update the credential, and then retry all the queued requests with the new /// credential. However, it is possible that some of the original requests will not return from the authentication /// server until the refresh has completed. This is where this method comes in. /// /// When the authentication server rejects a credential, we need to check to make sure we haven't refreshed the /// credential while the request was in flight. If it has already refreshed, then we don't need to trigger an /// additional refresh. If it hasn't refreshed, then we need to refresh. /// /// Now that it is understood how the result of this method is used in the refresh lifecyle, let's walk through how /// to implement it. You should return `true` in this method if the `URLRequest` is authenticated in a way that /// matches the values in the `Credential`. In the case of OAuth2, this would mean that the Bearer token in the /// `Authorization` header of the `URLRequest` matches the access token in the `Credential`. If it matches, then we /// know the `Credential` was used to authenticate the `URLRequest` and should return `true`. If the Bearer token /// did not match the access token, then you should return `false`. /// /// - Parameters: /// - urlRequest: The `URLRequest`. /// - credential: The `Credential`. /// /// - Returns: `true` if the `URLRequest` is authenticated with the `Credential`, `false` otherwise. func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: Credential) -> Bool } // MARK: - /// Represents various authentication failures that occur when using the `AuthenticationInterceptor`. All errors are /// still vended from Alamofire as `AFError` types. The `AuthenticationError` instances will be embedded within /// `AFError` `.requestAdaptationFailed` or `.requestRetryFailed` cases. public enum AuthenticationError: Error { /// The credential was missing so the request could not be authenticated. case missingCredential /// The credential was refreshed too many times within the `RefreshWindow`. case excessiveRefresh } // MARK: - /// The `AuthenticationInterceptor` class manages the queuing and threading complexity of authenticating requests. /// It relies on an `Authenticator` type to handle the actual `URLRequest` authentication and `Credential` refresh. public class AuthenticationInterceptor: RequestInterceptor where AuthenticatorType: Authenticator { // MARK: Typealiases /// Type of credential used to authenticate requests. public typealias Credential = AuthenticatorType.Credential // MARK: Helper Types /// Type that defines a time window used to identify excessive refresh calls. When enabled, prior to executing a /// refresh, the `AuthenticationInterceptor` compares the timestamp history of previous refresh calls against the /// `RefreshWindow`. If more refreshes have occurred within the refresh window than allowed, the refresh is /// cancelled and an `AuthorizationError.excessiveRefresh` error is thrown. public struct RefreshWindow { /// `TimeInterval` defining the duration of the time window before the current time in which the number of /// refresh attempts is compared against `maximumAttempts`. For example, if `interval` is 30 seconds, then the /// `RefreshWindow` represents the past 30 seconds. If more attempts occurred in the past 30 seconds than /// `maximumAttempts`, an `.excessiveRefresh` error will be thrown. public let interval: TimeInterval /// Total refresh attempts allowed within `interval` before throwing an `.excessiveRefresh` error. public let maximumAttempts: Int /// Creates a `RefreshWindow` instance from the specified `interval` and `maximumAttempts`. /// /// - Parameters: /// - interval: `TimeInterval` defining the duration of the time window before the current time. /// - maximumAttempts: The maximum attempts allowed within the `TimeInterval`. public init(interval: TimeInterval = 30.0, maximumAttempts: Int = 5) { self.interval = interval self.maximumAttempts = maximumAttempts } } private struct AdaptOperation { let urlRequest: URLRequest let session: Session let completion: (Result) -> Void } private enum AdaptResult { case adapt(Credential) case doNotAdapt(AuthenticationError) case adaptDeferred } private struct MutableState { var credential: Credential? var isRefreshing = false var refreshTimestamps: [TimeInterval] = [] var refreshWindow: RefreshWindow? var adaptOperations: [AdaptOperation] = [] var requestsToRetry: [(RetryResult) -> Void] = [] } // MARK: Properties /// The `Credential` used to authenticate requests. public var credential: Credential? { get { $mutableState.credential } set { $mutableState.credential = newValue } } let authenticator: AuthenticatorType let queue = DispatchQueue(label: "org.alamofire.authentication.inspector") @Protected private var mutableState: MutableState // MARK: Initialization /// Creates an `AuthenticationInterceptor` instance from the specified parameters. /// /// A `nil` `RefreshWindow` will result in the `AuthenticationInterceptor` not checking for excessive refresh calls. /// It is recommended to always use a `RefreshWindow` to avoid endless refresh cycles. /// /// - Parameters: /// - authenticator: The `Authenticator` type. /// - credential: The `Credential` if it exists. `nil` by default. /// - refreshWindow: The `RefreshWindow` used to identify excessive refresh calls. `RefreshWindow()` by default. public init(authenticator: AuthenticatorType, credential: Credential? = nil, refreshWindow: RefreshWindow? = RefreshWindow()) { self.authenticator = authenticator mutableState = MutableState(credential: credential, refreshWindow: refreshWindow) } // MARK: Adapt public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { let adaptResult: AdaptResult = $mutableState.write { mutableState in // Queue the adapt operation if a refresh is already in place. guard !mutableState.isRefreshing else { let operation = AdaptOperation(urlRequest: urlRequest, session: session, completion: completion) mutableState.adaptOperations.append(operation) return .adaptDeferred } // Throw missing credential error is the credential is missing. guard let credential = mutableState.credential else { let error = AuthenticationError.missingCredential return .doNotAdapt(error) } // Queue the adapt operation and trigger refresh operation if credential requires refresh. guard !credential.requiresRefresh else { let operation = AdaptOperation(urlRequest: urlRequest, session: session, completion: completion) mutableState.adaptOperations.append(operation) refresh(credential, for: session, insideLock: &mutableState) return .adaptDeferred } return .adapt(credential) } switch adaptResult { case let .adapt(credential): var authenticatedRequest = urlRequest authenticator.apply(credential, to: &authenticatedRequest) completion(.success(authenticatedRequest)) case let .doNotAdapt(adaptError): completion(.failure(adaptError)) case .adaptDeferred: // No-op: adapt operation captured during refresh. break } } // MARK: Retry public func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { // Do not attempt retry if there was not an original request and response from the server. guard let urlRequest = request.request, let response = request.response else { completion(.doNotRetry) return } // Do not attempt retry unless the `Authenticator` verifies failure was due to authentication error (i.e. 401 status code). guard authenticator.didRequest(urlRequest, with: response, failDueToAuthenticationError: error) else { completion(.doNotRetry) return } // Do not attempt retry if there is no credential. guard let credential = credential else { let error = AuthenticationError.missingCredential completion(.doNotRetryWithError(error)) return } // Retry the request if the `Authenticator` verifies it was authenticated with a previous credential. guard authenticator.isRequest(urlRequest, authenticatedWith: credential) else { completion(.retry) return } $mutableState.write { mutableState in mutableState.requestsToRetry.append(completion) guard !mutableState.isRefreshing else { return } refresh(credential, for: session, insideLock: &mutableState) } } // MARK: Refresh private func refresh(_ credential: Credential, for session: Session, insideLock mutableState: inout MutableState) { guard !isRefreshExcessive(insideLock: &mutableState) else { let error = AuthenticationError.excessiveRefresh handleRefreshFailure(error, insideLock: &mutableState) return } mutableState.refreshTimestamps.append(ProcessInfo.processInfo.systemUptime) mutableState.isRefreshing = true // Dispatch to queue to hop out of the lock in case authenticator.refresh is implemented synchronously. queue.async { self.authenticator.refresh(credential, for: session) { result in self.$mutableState.write { mutableState in switch result { case let .success(credential): self.handleRefreshSuccess(credential, insideLock: &mutableState) case let .failure(error): self.handleRefreshFailure(error, insideLock: &mutableState) } } } } } private func isRefreshExcessive(insideLock mutableState: inout MutableState) -> Bool { guard let refreshWindow = mutableState.refreshWindow else { return false } let refreshWindowMin = ProcessInfo.processInfo.systemUptime - refreshWindow.interval let refreshAttemptsWithinWindow = mutableState.refreshTimestamps.reduce(into: 0) { attempts, refreshTimestamp in guard refreshWindowMin <= refreshTimestamp else { return } attempts += 1 } let isRefreshExcessive = refreshAttemptsWithinWindow >= refreshWindow.maximumAttempts return isRefreshExcessive } private func handleRefreshSuccess(_ credential: Credential, insideLock mutableState: inout MutableState) { mutableState.credential = credential let adaptOperations = mutableState.adaptOperations let requestsToRetry = mutableState.requestsToRetry mutableState.adaptOperations.removeAll() mutableState.requestsToRetry.removeAll() mutableState.isRefreshing = false // Dispatch to queue to hop out of the mutable state lock queue.async { adaptOperations.forEach { self.adapt($0.urlRequest, for: $0.session, completion: $0.completion) } requestsToRetry.forEach { $0(.retry) } } } private func handleRefreshFailure(_ error: Error, insideLock mutableState: inout MutableState) { let adaptOperations = mutableState.adaptOperations let requestsToRetry = mutableState.requestsToRetry mutableState.adaptOperations.removeAll() mutableState.requestsToRetry.removeAll() mutableState.isRefreshing = false // Dispatch to queue to hop out of the mutable state lock queue.async { adaptOperations.forEach { $0.completion(.failure(error)) } requestsToRetry.forEach { $0(.doNotRetryWithError(error)) } } } } ================================================ FILE: JetChat/Pods/Alamofire/Source/CachedResponseHandler.swift ================================================ // // CachedResponseHandler.swift // // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// A type that handles whether the data task should store the HTTP response in the cache. public protocol CachedResponseHandler { /// Determines whether the HTTP response should be stored in the cache. /// /// The `completion` closure should be passed one of three possible options: /// /// 1. The cached response provided by the server (this is the most common use case). /// 2. A modified version of the cached response (you may want to modify it in some way before caching). /// 3. A `nil` value to prevent the cached response from being stored in the cache. /// /// - Parameters: /// - task: The data task whose request resulted in the cached response. /// - response: The cached response to potentially store in the cache. /// - completion: The closure to execute containing cached response, a modified response, or `nil`. func dataTask(_ task: URLSessionDataTask, willCacheResponse response: CachedURLResponse, completion: @escaping (CachedURLResponse?) -> Void) } // MARK: - /// `ResponseCacher` is a convenience `CachedResponseHandler` making it easy to cache, not cache, or modify a cached /// response. public struct ResponseCacher { /// Defines the behavior of the `ResponseCacher` type. public enum Behavior { /// Stores the cached response in the cache. case cache /// Prevents the cached response from being stored in the cache. case doNotCache /// Modifies the cached response before storing it in the cache. case modify((URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?) } /// Returns a `ResponseCacher` with a `.cache` `Behavior`. public static let cache = ResponseCacher(behavior: .cache) /// Returns a `ResponseCacher` with a `.doNotCache` `Behavior`. public static let doNotCache = ResponseCacher(behavior: .doNotCache) /// The `Behavior` of the `ResponseCacher`. public let behavior: Behavior /// Creates a `ResponseCacher` instance from the `Behavior`. /// /// - Parameter behavior: The `Behavior`. public init(behavior: Behavior) { self.behavior = behavior } } extension ResponseCacher: CachedResponseHandler { public func dataTask(_ task: URLSessionDataTask, willCacheResponse response: CachedURLResponse, completion: @escaping (CachedURLResponse?) -> Void) { switch behavior { case .cache: completion(response) case .doNotCache: completion(nil) case let .modify(closure): let response = closure(task, response) completion(response) } } } #if swift(>=5.5) extension CachedResponseHandler where Self == ResponseCacher { /// Provides a `ResponseCacher` which caches the response, if allowed. Equivalent to `ResponseCacher.cache`. public static var cache: ResponseCacher { .cache } /// Provides a `ResponseCacher` which does not cache the response. Equivalent to `ResponseCacher.doNotCache`. public static var doNotCache: ResponseCacher { .doNotCache } /// Creates a `ResponseCacher` which modifies the proposed `CachedURLResponse` using the provided closure. /// /// - Parameter closure: Closure used to modify the `CachedURLResponse`. /// - Returns: The `ResponseCacher`. public static func modify(using closure: @escaping ((URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)) -> ResponseCacher { ResponseCacher(behavior: .modify(closure)) } } #endif ================================================ FILE: JetChat/Pods/Alamofire/Source/Combine.swift ================================================ // // Combine.swift // // Copyright (c) 2020 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #if !((os(iOS) && (arch(i386) || arch(arm))) || os(Windows) || os(Linux)) import Combine import Dispatch import Foundation // MARK: - DataRequest / UploadRequest /// A Combine `Publisher` that publishes the `DataResponse` of the provided `DataRequest`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public struct DataResponsePublisher: Publisher { public typealias Output = DataResponse public typealias Failure = Never private typealias Handler = (@escaping (_ response: DataResponse) -> Void) -> DataRequest private let request: DataRequest private let responseHandler: Handler /// Creates an instance which will serialize responses using the provided `ResponseSerializer`. /// /// - Parameters: /// - request: `DataRequest` for which to publish the response. /// - queue: `DispatchQueue` on which the `DataResponse` value will be published. `.main` by default. /// - serializer: `ResponseSerializer` used to produce the published `DataResponse`. public init(_ request: DataRequest, queue: DispatchQueue, serializer: Serializer) where Value == Serializer.SerializedObject { self.request = request responseHandler = { request.response(queue: queue, responseSerializer: serializer, completionHandler: $0) } } /// Creates an instance which will serialize responses using the provided `DataResponseSerializerProtocol`. /// /// - Parameters: /// - request: `DataRequest` for which to publish the response. /// - queue: `DispatchQueue` on which the `DataResponse` value will be published. `.main` by default. /// - serializer: `DataResponseSerializerProtocol` used to produce the published `DataResponse`. public init(_ request: DataRequest, queue: DispatchQueue, serializer: Serializer) where Value == Serializer.SerializedObject { self.request = request responseHandler = { request.response(queue: queue, responseSerializer: serializer, completionHandler: $0) } } /// Publishes only the `Result` of the `DataResponse` value. /// /// - Returns: The `AnyPublisher` publishing the `Result` value. public func result() -> AnyPublisher, Never> { map(\.result).eraseToAnyPublisher() } /// Publishes the `Result` of the `DataResponse` as a single `Value` or fail with the `AFError` instance. /// /// - Returns: The `AnyPublisher` publishing the stream. public func value() -> AnyPublisher { setFailureType(to: AFError.self).flatMap(\.result.publisher).eraseToAnyPublisher() } public func receive(subscriber: S) where S: Subscriber, DataResponsePublisher.Failure == S.Failure, DataResponsePublisher.Output == S.Input { subscriber.receive(subscription: Inner(request: request, responseHandler: responseHandler, downstream: subscriber)) } private final class Inner: Subscription, Cancellable where Downstream.Input == Output { typealias Failure = Downstream.Failure @Protected private var downstream: Downstream? private let request: DataRequest private let responseHandler: Handler init(request: DataRequest, responseHandler: @escaping Handler, downstream: Downstream) { self.request = request self.responseHandler = responseHandler self.downstream = downstream } func request(_ demand: Subscribers.Demand) { assert(demand > 0) guard let downstream = downstream else { return } self.downstream = nil responseHandler { response in _ = downstream.receive(response) downstream.receive(completion: .finished) }.resume() } func cancel() { request.cancel() downstream = nil } } } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) extension DataResponsePublisher where Value == Data? { /// Creates an instance which publishes a `DataResponse` value without serialization. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public init(_ request: DataRequest, queue: DispatchQueue) { self.request = request responseHandler = { request.response(queue: queue, completionHandler: $0) } } } extension DataRequest { /// Creates a `DataResponsePublisher` for this instance using the given `ResponseSerializer` and `DispatchQueue`. /// /// - Parameters: /// - serializer: `ResponseSerializer` used to serialize response `Data`. /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. /// /// - Returns: The `DataResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishResponse(using serializer: Serializer, on queue: DispatchQueue = .main) -> DataResponsePublisher where Serializer.SerializedObject == T { DataResponsePublisher(self, queue: queue, serializer: serializer) } /// Creates a `DataResponsePublisher` for this instance and uses a `DataResponseSerializer` to serialize the /// response. /// /// - Parameters: /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. `PassthroughPreprocessor()` /// by default. /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by /// default. /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless of /// status code. `[.head]` by default. /// - Returns: The `DataResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishData(queue: DispatchQueue = .main, preprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) -> DataResponsePublisher { publishResponse(using: DataResponseSerializer(dataPreprocessor: preprocessor, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), on: queue) } /// Creates a `DataResponsePublisher` for this instance and uses a `StringResponseSerializer` to serialize the /// response. /// /// - Parameters: /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. `PassthroughPreprocessor()` /// by default. /// - encoding: `String.Encoding` to parse the response. `nil` by default, in which case the encoding /// will be determined by the server response, falling back to the default HTTP character /// set, `ISO-8859-1`. /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by /// default. /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless of /// status code. `[.head]` by default. /// /// - Returns: The `DataResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishString(queue: DispatchQueue = .main, preprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, encoding: String.Encoding? = nil, emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) -> DataResponsePublisher { publishResponse(using: StringResponseSerializer(dataPreprocessor: preprocessor, encoding: encoding, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), on: queue) } @_disfavoredOverload @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) @available(*, deprecated, message: "Renamed publishDecodable(type:queue:preprocessor:decoder:emptyResponseCodes:emptyRequestMethods).") public func publishDecodable(type: T.Type = T.self, queue: DispatchQueue = .main, preprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, decoder: DataDecoder = JSONDecoder(), emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, emptyResponseMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DataResponsePublisher { publishResponse(using: DecodableResponseSerializer(dataPreprocessor: preprocessor, decoder: decoder, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyResponseMethods), on: queue) } /// Creates a `DataResponsePublisher` for this instance and uses a `DecodableResponseSerializer` to serialize the /// response. /// /// - Parameters: /// - type: `Decodable` type to which to decode response `Data`. Inferred from the context by /// default. /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. /// `PassthroughPreprocessor()` by default. /// - decoder: `DataDecoder` instance used to decode response `Data`. `JSONDecoder()` by default. /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by /// default. /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless of /// status code. `[.head]` by default. /// /// - Returns: The `DataResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishDecodable(type: T.Type = T.self, queue: DispatchQueue = .main, preprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, decoder: DataDecoder = JSONDecoder(), emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DataResponsePublisher { publishResponse(using: DecodableResponseSerializer(dataPreprocessor: preprocessor, decoder: decoder, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), on: queue) } /// Creates a `DataResponsePublisher` for this instance which does not serialize the response before publishing. /// /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. /// /// - Returns: The `DataResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishUnserialized(queue: DispatchQueue = .main) -> DataResponsePublisher { DataResponsePublisher(self, queue: queue) } } // A Combine `Publisher` that publishes a sequence of `Stream` values received by the provided `DataStreamRequest`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public struct DataStreamPublisher: Publisher { public typealias Output = DataStreamRequest.Stream public typealias Failure = Never private typealias Handler = (@escaping DataStreamRequest.Handler) -> DataStreamRequest private let request: DataStreamRequest private let streamHandler: Handler /// Creates an instance which will serialize responses using the provided `DataStreamSerializer`. /// /// - Parameters: /// - request: `DataStreamRequest` for which to publish the response. /// - queue: `DispatchQueue` on which the `Stream` values will be published. `.main` by /// default. /// - serializer: `DataStreamSerializer` used to produce the published `Stream` values. public init(_ request: DataStreamRequest, queue: DispatchQueue, serializer: Serializer) where Value == Serializer.SerializedObject { self.request = request streamHandler = { request.responseStream(using: serializer, on: queue, stream: $0) } } /// Publishes only the `Result` of the `DataStreamRequest.Stream`'s `Event`s. /// /// - Returns: The `AnyPublisher` publishing the `Result` value. public func result() -> AnyPublisher, Never> { compactMap { stream in switch stream.event { case let .stream(result): return result // If the stream has completed with an error, send the error value downstream as a `.failure`. case let .complete(completion): return completion.error.map(Result.failure) } } .eraseToAnyPublisher() } /// Publishes the streamed values of the `DataStreamRequest.Stream` as a sequence of `Value` or fail with the /// `AFError` instance. /// /// - Returns: The `AnyPublisher` publishing the stream. public func value() -> AnyPublisher { result().setFailureType(to: AFError.self).flatMap(\.publisher).eraseToAnyPublisher() } public func receive(subscriber: S) where S: Subscriber, DataStreamPublisher.Failure == S.Failure, DataStreamPublisher.Output == S.Input { subscriber.receive(subscription: Inner(request: request, streamHandler: streamHandler, downstream: subscriber)) } private final class Inner: Subscription, Cancellable where Downstream.Input == Output { typealias Failure = Downstream.Failure @Protected private var downstream: Downstream? private let request: DataStreamRequest private let streamHandler: Handler init(request: DataStreamRequest, streamHandler: @escaping Handler, downstream: Downstream) { self.request = request self.streamHandler = streamHandler self.downstream = downstream } func request(_ demand: Subscribers.Demand) { assert(demand > 0) guard let downstream = downstream else { return } self.downstream = nil streamHandler { stream in _ = downstream.receive(stream) if case .complete = stream.event { downstream.receive(completion: .finished) } }.resume() } func cancel() { request.cancel() downstream = nil } } } extension DataStreamRequest { /// Creates a `DataStreamPublisher` for this instance using the given `DataStreamSerializer` and `DispatchQueue`. /// /// - Parameters: /// - serializer: `DataStreamSerializer` used to serialize the streamed `Data`. /// - queue: `DispatchQueue` on which the `DataRequest.Stream` values will be published. `.main` by default. /// - Returns: The `DataStreamPublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishStream(using serializer: Serializer, on queue: DispatchQueue = .main) -> DataStreamPublisher { DataStreamPublisher(self, queue: queue, serializer: serializer) } /// Creates a `DataStreamPublisher` for this instance which uses a `PassthroughStreamSerializer` to stream `Data` /// unserialized. /// /// - Parameters: /// - queue: `DispatchQueue` on which the `DataRequest.Stream` values will be published. `.main` by default. /// - Returns: The `DataStreamPublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishData(queue: DispatchQueue = .main) -> DataStreamPublisher { publishStream(using: PassthroughStreamSerializer(), on: queue) } /// Creates a `DataStreamPublisher` for this instance which uses a `StringStreamSerializer` to serialize stream /// `Data` values into `String` values. /// /// - Parameters: /// - queue: `DispatchQueue` on which the `DataRequest.Stream` values will be published. `.main` by default. /// - Returns: The `DataStreamPublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishString(queue: DispatchQueue = .main) -> DataStreamPublisher { publishStream(using: StringStreamSerializer(), on: queue) } /// Creates a `DataStreamPublisher` for this instance which uses a `DecodableStreamSerializer` with the provided /// parameters to serialize stream `Data` values into the provided type. /// /// - Parameters: /// - type: `Decodable` type to which to decode stream `Data`. Inferred from the context by default. /// - queue: `DispatchQueue` on which the `DataRequest.Stream` values will be published. `.main` by default. /// - decoder: `DataDecoder` instance used to decode stream `Data`. `JSONDecoder()` by default. /// - preprocessor: `DataPreprocessor` which filters incoming stream `Data` before serialization. /// `PassthroughPreprocessor()` by default. /// - Returns: The `DataStreamPublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishDecodable(type: T.Type = T.self, queue: DispatchQueue = .main, decoder: DataDecoder = JSONDecoder(), preprocessor: DataPreprocessor = PassthroughPreprocessor()) -> DataStreamPublisher { publishStream(using: DecodableStreamSerializer(decoder: decoder, dataPreprocessor: preprocessor), on: queue) } } /// A Combine `Publisher` that publishes the `DownloadResponse` of the provided `DownloadRequest`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public struct DownloadResponsePublisher: Publisher { public typealias Output = DownloadResponse public typealias Failure = Never private typealias Handler = (@escaping (_ response: DownloadResponse) -> Void) -> DownloadRequest private let request: DownloadRequest private let responseHandler: Handler /// Creates an instance which will serialize responses using the provided `ResponseSerializer`. /// /// - Parameters: /// - request: `DownloadRequest` for which to publish the response. /// - queue: `DispatchQueue` on which the `DownloadResponse` value will be published. `.main` by default. /// - serializer: `ResponseSerializer` used to produce the published `DownloadResponse`. public init(_ request: DownloadRequest, queue: DispatchQueue, serializer: Serializer) where Value == Serializer.SerializedObject { self.request = request responseHandler = { request.response(queue: queue, responseSerializer: serializer, completionHandler: $0) } } /// Creates an instance which will serialize responses using the provided `DownloadResponseSerializerProtocol` value. /// /// - Parameters: /// - request: `DownloadRequest` for which to publish the response. /// - queue: `DispatchQueue` on which the `DataResponse` value will be published. `.main` by default. /// - serializer: `DownloadResponseSerializerProtocol` used to produce the published `DownloadResponse`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public init(_ request: DownloadRequest, queue: DispatchQueue, serializer: Serializer) where Value == Serializer.SerializedObject { self.request = request responseHandler = { request.response(queue: queue, responseSerializer: serializer, completionHandler: $0) } } /// Publishes only the `Result` of the `DownloadResponse` value. /// /// - Returns: The `AnyPublisher` publishing the `Result` value. public func result() -> AnyPublisher, Never> { map(\.result).eraseToAnyPublisher() } /// Publishes the `Result` of the `DownloadResponse` as a single `Value` or fail with the `AFError` instance. /// /// - Returns: The `AnyPublisher` publishing the stream. public func value() -> AnyPublisher { setFailureType(to: AFError.self).flatMap(\.result.publisher).eraseToAnyPublisher() } public func receive(subscriber: S) where S: Subscriber, DownloadResponsePublisher.Failure == S.Failure, DownloadResponsePublisher.Output == S.Input { subscriber.receive(subscription: Inner(request: request, responseHandler: responseHandler, downstream: subscriber)) } private final class Inner: Subscription, Cancellable where Downstream.Input == Output { typealias Failure = Downstream.Failure @Protected private var downstream: Downstream? private let request: DownloadRequest private let responseHandler: Handler init(request: DownloadRequest, responseHandler: @escaping Handler, downstream: Downstream) { self.request = request self.responseHandler = responseHandler self.downstream = downstream } func request(_ demand: Subscribers.Demand) { assert(demand > 0) guard let downstream = downstream else { return } self.downstream = nil responseHandler { response in _ = downstream.receive(response) downstream.receive(completion: .finished) }.resume() } func cancel() { request.cancel() downstream = nil } } } extension DownloadRequest { /// Creates a `DownloadResponsePublisher` for this instance using the given `ResponseSerializer` and `DispatchQueue`. /// /// - Parameters: /// - serializer: `ResponseSerializer` used to serialize the response `Data` from disk. /// - queue: `DispatchQueue` on which the `DownloadResponse` will be published.`.main` by default. /// /// - Returns: The `DownloadResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishResponse(using serializer: Serializer, on queue: DispatchQueue = .main) -> DownloadResponsePublisher where Serializer.SerializedObject == T { DownloadResponsePublisher(self, queue: queue, serializer: serializer) } /// Creates a `DownloadResponsePublisher` for this instance using the given `DownloadResponseSerializerProtocol` and /// `DispatchQueue`. /// /// - Parameters: /// - serializer: `DownloadResponseSerializer` used to serialize the response `Data` from disk. /// - queue: `DispatchQueue` on which the `DownloadResponse` will be published.`.main` by default. /// /// - Returns: The `DownloadResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishResponse(using serializer: Serializer, on queue: DispatchQueue = .main) -> DownloadResponsePublisher where Serializer.SerializedObject == T { DownloadResponsePublisher(self, queue: queue, serializer: serializer) } /// Creates a `DownloadResponsePublisher` for this instance and uses a `URLResponseSerializer` to serialize the /// response. /// /// - Parameter queue: `DispatchQueue` on which the `DownloadResponse` will be published. `.main` by default. /// /// - Returns: The `DownloadResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishURL(queue: DispatchQueue = .main) -> DownloadResponsePublisher { publishResponse(using: URLResponseSerializer(), on: queue) } /// Creates a `DownloadResponsePublisher` for this instance and uses a `DataResponseSerializer` to serialize the /// response. /// /// - Parameters: /// - queue: `DispatchQueue` on which the `DownloadResponse` will be published. `.main` by default. /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. `PassthroughPreprocessor()` /// by default. /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by /// default. /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless of /// status code. `[.head]` by default. /// /// - Returns: The `DownloadResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishData(queue: DispatchQueue = .main, preprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) -> DownloadResponsePublisher { publishResponse(using: DataResponseSerializer(dataPreprocessor: preprocessor, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), on: queue) } /// Creates a `DownloadResponsePublisher` for this instance and uses a `StringResponseSerializer` to serialize the /// response. /// /// - Parameters: /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. `PassthroughPreprocessor()` /// by default. /// - encoding: `String.Encoding` to parse the response. `nil` by default, in which case the encoding /// will be determined by the server response, falling back to the default HTTP character /// set, `ISO-8859-1`. /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by /// default. /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless of /// status code. `[.head]` by default. /// /// - Returns: The `DownloadResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishString(queue: DispatchQueue = .main, preprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, encoding: String.Encoding? = nil, emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) -> DownloadResponsePublisher { publishResponse(using: StringResponseSerializer(dataPreprocessor: preprocessor, encoding: encoding, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), on: queue) } @_disfavoredOverload @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) @available(*, deprecated, message: "Renamed publishDecodable(type:queue:preprocessor:decoder:emptyResponseCodes:emptyRequestMethods).") public func publishDecodable(type: T.Type = T.self, queue: DispatchQueue = .main, preprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, decoder: DataDecoder = JSONDecoder(), emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, emptyResponseMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DownloadResponsePublisher { publishResponse(using: DecodableResponseSerializer(dataPreprocessor: preprocessor, decoder: decoder, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyResponseMethods), on: queue) } /// Creates a `DownloadResponsePublisher` for this instance and uses a `DecodableResponseSerializer` to serialize /// the response. /// /// - Parameters: /// - type: `Decodable` type to which to decode response `Data`. Inferred from the context by default. /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. /// `PassthroughPreprocessor()` by default. /// - decoder: `DataDecoder` instance used to decode response `Data`. `JSONDecoder()` by default. /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by /// default. /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless /// of status code. `[.head]` by default. /// /// - Returns: The `DownloadResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishDecodable(type: T.Type = T.self, queue: DispatchQueue = .main, preprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, decoder: DataDecoder = JSONDecoder(), emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DownloadResponsePublisher { publishResponse(using: DecodableResponseSerializer(dataPreprocessor: preprocessor, decoder: decoder, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), on: queue) } } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) extension DownloadResponsePublisher where Value == URL? { /// Creates an instance which publishes a `DownloadResponse` value without serialization. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public init(_ request: DownloadRequest, queue: DispatchQueue) { self.request = request responseHandler = { request.response(queue: queue, completionHandler: $0) } } } extension DownloadRequest { /// Creates a `DownloadResponsePublisher` for this instance which does not serialize the response before publishing. /// /// - Parameter queue: `DispatchQueue` on which the `DownloadResponse` will be published. `.main` by default. /// /// - Returns: The `DownloadResponsePublisher`. @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func publishUnserialized(on queue: DispatchQueue = .main) -> DownloadResponsePublisher { DownloadResponsePublisher(self, queue: queue) } } #endif ================================================ FILE: JetChat/Pods/Alamofire/Source/Concurrency.swift ================================================ // // Concurrency.swift // // Copyright (c) 2021 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #if compiler(>=5.6.0) && canImport(_Concurrency) import Foundation // MARK: - Request Event Streams @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension Request { /// Creates a `StreamOf` for the instance's upload progress. /// /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. /// /// - Returns: The `StreamOf`. public func uploadProgress(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in uploadProgress(queue: .singleEventQueue) { progress in continuation.yield(progress) } } } /// Creates a `StreamOf` for the instance's download progress. /// /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. /// /// - Returns: The `StreamOf`. public func downloadProgress(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in downloadProgress(queue: .singleEventQueue) { progress in continuation.yield(progress) } } } /// Creates a `StreamOf` for the `URLRequest`s produced for the instance. /// /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. /// /// - Returns: The `StreamOf`. public func urlRequests(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in onURLRequestCreation(on: .singleEventQueue) { request in continuation.yield(request) } } } /// Creates a `StreamOf` for the `URLSessionTask`s produced for the instance. /// /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. /// /// - Returns: The `StreamOf`. public func urlSessionTasks(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in onURLSessionTaskCreation(on: .singleEventQueue) { task in continuation.yield(task) } } } /// Creates a `StreamOf` for the cURL descriptions produced for the instance. /// /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. /// /// - Returns: The `StreamOf`. public func cURLDescriptions(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in cURLDescription(on: .singleEventQueue) { description in continuation.yield(description) } } } private func stream(of type: T.Type = T.self, bufferingPolicy: StreamOf.BufferingPolicy = .unbounded, yielder: @escaping (StreamOf.Continuation) -> Void) -> StreamOf { StreamOf(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in yielder(continuation) // Must come after serializers run in order to catch retry progress. onFinish { continuation.finish() } } } } // MARK: - DataTask /// Value used to `await` a `DataResponse` and associated values. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) public struct DataTask { /// `DataResponse` produced by the `DataRequest` and its response handler. public var response: DataResponse { get async { if shouldAutomaticallyCancel { return await withTaskCancellationHandler { self.cancel() } operation: { await task.value } } else { return await task.value } } } /// `Result` of any response serialization performed for the `response`. public var result: Result { get async { await response.result } } /// `Value` returned by the `response`. public var value: Value { get async throws { try await result.get() } } private let request: DataRequest private let task: Task, Never> private let shouldAutomaticallyCancel: Bool fileprivate init(request: DataRequest, task: Task, Never>, shouldAutomaticallyCancel: Bool) { self.request = request self.task = task self.shouldAutomaticallyCancel = shouldAutomaticallyCancel } /// Cancel the underlying `DataRequest` and `Task`. public func cancel() { task.cancel() } /// Resume the underlying `DataRequest`. public func resume() { request.resume() } /// Suspend the underlying `DataRequest`. public func suspend() { request.suspend() } } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension DataRequest { /// Creates a `DataTask` to `await` a `Data` value. /// /// - Parameters: /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DataTask`'s async /// properties. `false` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before completion. /// - emptyResponseCodes: HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// /// - Returns: The `DataTask`. public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = false, dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) -> DataTask { serializingResponse(using: DataResponseSerializer(dataPreprocessor: dataPreprocessor, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), automaticallyCancelling: shouldAutomaticallyCancel) } /// Creates a `DataTask` to `await` serialization of a `Decodable` value. /// /// - Parameters: /// - type: `Decodable` type to decode from response data. /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DataTask`'s async /// properties. `false` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer. /// `PassthroughPreprocessor()` by default. /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// /// - Returns: The `DataTask`. public func serializingDecodable(_ type: Value.Type = Value.self, automaticallyCancelling shouldAutomaticallyCancel: Bool = false, dataPreprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, decoder: DataDecoder = JSONDecoder(), emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DataTask { serializingResponse(using: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor, decoder: decoder, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), automaticallyCancelling: shouldAutomaticallyCancel) } /// Creates a `DataTask` to `await` serialization of a `String` value. /// /// - Parameters: /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DataTask`'s async /// properties. `false` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer. /// `PassthroughPreprocessor()` by default. /// - encoding: `String.Encoding` to use during serialization. Defaults to `nil`, in which case /// the encoding will be determined from the server response, falling back to the /// default HTTP character set, `ISO-8859-1`. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// /// - Returns: The `DataTask`. public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = false, dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, encoding: String.Encoding? = nil, emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) -> DataTask { serializingResponse(using: StringResponseSerializer(dataPreprocessor: dataPreprocessor, encoding: encoding, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), automaticallyCancelling: shouldAutomaticallyCancel) } /// Creates a `DataTask` to `await` serialization using the provided `ResponseSerializer` instance. /// /// - Parameters: /// - serializer: `ResponseSerializer` responsible for serializing the request, response, and data. /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DataTask`'s async /// properties. `false` by default. /// /// - Returns: The `DataTask`. public func serializingResponse(using serializer: Serializer, automaticallyCancelling shouldAutomaticallyCancel: Bool = false) -> DataTask { dataTask(automaticallyCancelling: shouldAutomaticallyCancel) { self.response(queue: .singleEventQueue, responseSerializer: serializer, completionHandler: $0) } } /// Creates a `DataTask` to `await` serialization using the provided `DataResponseSerializerProtocol` instance. /// /// - Parameters: /// - serializer: `DataResponseSerializerProtocol` responsible for serializing the request, /// response, and data. /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DataTask`'s async /// properties. `false` by default. /// /// - Returns: The `DataTask`. public func serializingResponse(using serializer: Serializer, automaticallyCancelling shouldAutomaticallyCancel: Bool = false) -> DataTask { dataTask(automaticallyCancelling: shouldAutomaticallyCancel) { self.response(queue: .singleEventQueue, responseSerializer: serializer, completionHandler: $0) } } private func dataTask(automaticallyCancelling shouldAutomaticallyCancel: Bool, forResponse onResponse: @escaping (@escaping (DataResponse) -> Void) -> Void) -> DataTask { let task = Task { await withTaskCancellationHandler { self.cancel() } operation: { await withCheckedContinuation { continuation in onResponse { continuation.resume(returning: $0) } } } } return DataTask(request: self, task: task, shouldAutomaticallyCancel: shouldAutomaticallyCancel) } } // MARK: - DownloadTask /// Value used to `await` a `DownloadResponse` and associated values. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) public struct DownloadTask { /// `DownloadResponse` produced by the `DownloadRequest` and its response handler. public var response: DownloadResponse { get async { if shouldAutomaticallyCancel { return await withTaskCancellationHandler { self.cancel() } operation: { await task.value } } else { return await task.value } } } /// `Result` of any response serialization performed for the `response`. public var result: Result { get async { await response.result } } /// `Value` returned by the `response`. public var value: Value { get async throws { try await result.get() } } private let task: Task, Never> private let request: DownloadRequest private let shouldAutomaticallyCancel: Bool fileprivate init(request: DownloadRequest, task: Task, Never>, shouldAutomaticallyCancel: Bool) { self.request = request self.task = task self.shouldAutomaticallyCancel = shouldAutomaticallyCancel } /// Cancel the underlying `DownloadRequest` and `Task`. public func cancel() { task.cancel() } /// Resume the underlying `DownloadRequest`. public func resume() { request.resume() } /// Suspend the underlying `DownloadRequest`. public func suspend() { request.suspend() } } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension DownloadRequest { /// Creates a `DownloadTask` to `await` a `Data` value. /// /// - Parameters: /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// properties. `false` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before completion. /// - emptyResponseCodes: HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// /// - Returns: The `DownloadTask`. public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = false, dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) -> DownloadTask { serializingDownload(using: DataResponseSerializer(dataPreprocessor: dataPreprocessor, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), automaticallyCancelling: shouldAutomaticallyCancel) } /// Creates a `DownloadTask` to `await` serialization of a `Decodable` value. /// /// - Note: This serializer reads the entire response into memory before parsing. /// /// - Parameters: /// - type: `Decodable` type to decode from response data. /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// properties. `false` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer. /// `PassthroughPreprocessor()` by default. /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// /// - Returns: The `DownloadTask`. public func serializingDecodable(_ type: Value.Type = Value.self, automaticallyCancelling shouldAutomaticallyCancel: Bool = false, dataPreprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, decoder: DataDecoder = JSONDecoder(), emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DownloadTask { serializingDownload(using: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor, decoder: decoder, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), automaticallyCancelling: shouldAutomaticallyCancel) } /// Creates a `DownloadTask` to `await` serialization of the downloaded file's `URL` on disk. /// /// - Parameters: /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// properties. `false` by default. /// /// - Returns: The `DownloadTask`. public func serializingDownloadedFileURL(automaticallyCancelling shouldAutomaticallyCancel: Bool = false) -> DownloadTask { serializingDownload(using: URLResponseSerializer(), automaticallyCancelling: shouldAutomaticallyCancel) } /// Creates a `DownloadTask` to `await` serialization of a `String` value. /// /// - Parameters: /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// properties. `false` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the /// serializer. `PassthroughPreprocessor()` by default. /// - encoding: `String.Encoding` to use during serialization. Defaults to `nil`, in which case /// the encoding will be determined from the server response, falling back to the /// default HTTP character set, `ISO-8859-1`. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// /// - Returns: The `DownloadTask`. public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = false, dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, encoding: String.Encoding? = nil, emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) -> DownloadTask { serializingDownload(using: StringResponseSerializer(dataPreprocessor: dataPreprocessor, encoding: encoding, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), automaticallyCancelling: shouldAutomaticallyCancel) } /// Creates a `DownloadTask` to `await` serialization using the provided `ResponseSerializer` instance. /// /// - Parameters: /// - serializer: `ResponseSerializer` responsible for serializing the request, response, and data. /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// properties. `false` by default. /// /// - Returns: The `DownloadTask`. public func serializingDownload(using serializer: Serializer, automaticallyCancelling shouldAutomaticallyCancel: Bool = false) -> DownloadTask { downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) { self.response(queue: .singleEventQueue, responseSerializer: serializer, completionHandler: $0) } } /// Creates a `DownloadTask` to `await` serialization using the provided `DownloadResponseSerializerProtocol` /// instance. /// /// - Parameters: /// - serializer: `DownloadResponseSerializerProtocol` responsible for serializing the request, /// response, and data. /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async /// properties. `false` by default. /// /// - Returns: The `DownloadTask`. public func serializingDownload(using serializer: Serializer, automaticallyCancelling shouldAutomaticallyCancel: Bool = false) -> DownloadTask { downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) { self.response(queue: .singleEventQueue, responseSerializer: serializer, completionHandler: $0) } } private func downloadTask(automaticallyCancelling shouldAutomaticallyCancel: Bool, forResponse onResponse: @escaping (@escaping (DownloadResponse) -> Void) -> Void) -> DownloadTask { let task = Task { await withTaskCancellationHandler { self.cancel() } operation: { await withCheckedContinuation { continuation in onResponse { continuation.resume(returning: $0) } } } } return DownloadTask(request: self, task: task, shouldAutomaticallyCancel: shouldAutomaticallyCancel) } } // MARK: - DataStreamTask @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) public struct DataStreamTask { // Type of created streams. public typealias Stream = StreamOf> private let request: DataStreamRequest fileprivate init(request: DataStreamRequest) { self.request = request } /// Creates a `Stream` of `Data` values from the underlying `DataStreamRequest`. /// /// - Parameters: /// - shouldAutomaticallyCancel: `Bool` indicating whether the underlying `DataStreamRequest` should be canceled /// which observation of the stream stops. `true` by default. /// - bufferingPolicy: ` BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. /// /// - Returns: The `Stream`. public func streamingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = true, bufferingPolicy: Stream.BufferingPolicy = .unbounded) -> Stream { createStream(automaticallyCancelling: shouldAutomaticallyCancel, bufferingPolicy: bufferingPolicy) { onStream in self.request.responseStream(on: .streamCompletionQueue(forRequestID: request.id), stream: onStream) } } /// Creates a `Stream` of `UTF-8` `String`s from the underlying `DataStreamRequest`. /// /// - Parameters: /// - shouldAutomaticallyCancel: `Bool` indicating whether the underlying `DataStreamRequest` should be canceled /// which observation of the stream stops. `true` by default. /// - bufferingPolicy: ` BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. /// - Returns: public func streamingStrings(automaticallyCancelling shouldAutomaticallyCancel: Bool = true, bufferingPolicy: Stream.BufferingPolicy = .unbounded) -> Stream { createStream(automaticallyCancelling: shouldAutomaticallyCancel, bufferingPolicy: bufferingPolicy) { onStream in self.request.responseStreamString(on: .streamCompletionQueue(forRequestID: request.id), stream: onStream) } } /// Creates a `Stream` of `Decodable` values from the underlying `DataStreamRequest`. /// /// - Parameters: /// - type: `Decodable` type to be serialized from stream payloads. /// - shouldAutomaticallyCancel: `Bool` indicating whether the underlying `DataStreamRequest` should be canceled /// which observation of the stream stops. `true` by default. /// - bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. /// /// - Returns: The `Stream`. public func streamingDecodables(_ type: T.Type = T.self, automaticallyCancelling shouldAutomaticallyCancel: Bool = true, bufferingPolicy: Stream.BufferingPolicy = .unbounded) -> Stream where T: Decodable { streamingResponses(serializedUsing: DecodableStreamSerializer(), automaticallyCancelling: shouldAutomaticallyCancel, bufferingPolicy: bufferingPolicy) } /// Creates a `Stream` of values using the provided `DataStreamSerializer` from the underlying `DataStreamRequest`. /// /// - Parameters: /// - serializer: `DataStreamSerializer` to use to serialize incoming `Data`. /// - shouldAutomaticallyCancel: `Bool` indicating whether the underlying `DataStreamRequest` should be canceled /// which observation of the stream stops. `true` by default. /// - bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. /// /// - Returns: The `Stream`. public func streamingResponses(serializedUsing serializer: Serializer, automaticallyCancelling shouldAutomaticallyCancel: Bool = true, bufferingPolicy: Stream.BufferingPolicy = .unbounded) -> Stream { createStream(automaticallyCancelling: shouldAutomaticallyCancel, bufferingPolicy: bufferingPolicy) { onStream in self.request.responseStream(using: serializer, on: .streamCompletionQueue(forRequestID: request.id), stream: onStream) } } private func createStream(automaticallyCancelling shouldAutomaticallyCancel: Bool = true, bufferingPolicy: Stream.BufferingPolicy = .unbounded, forResponse onResponse: @escaping (@escaping (DataStreamRequest.Stream) -> Void) -> Void) -> Stream { StreamOf(bufferingPolicy: bufferingPolicy) { guard shouldAutomaticallyCancel, request.isInitialized || request.isResumed || request.isSuspended else { return } cancel() } builder: { continuation in onResponse { stream in continuation.yield(stream) if case .complete = stream.event { continuation.finish() } } } } /// Cancel the underlying `DataStreamRequest`. public func cancel() { request.cancel() } /// Resume the underlying `DataStreamRequest`. public func resume() { request.resume() } /// Suspend the underlying `DataStreamRequest`. public func suspend() { request.suspend() } } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension DataStreamRequest { /// Creates a `DataStreamTask` used to `await` streams of serialized values. /// /// - Returns: The `DataStreamTask`. public func streamTask() -> DataStreamTask { DataStreamTask(request: self) } } extension DispatchQueue { fileprivate static let singleEventQueue = DispatchQueue(label: "org.alamofire.concurrencySingleEventQueue", attributes: .concurrent) fileprivate static func streamCompletionQueue(forRequestID id: UUID) -> DispatchQueue { DispatchQueue(label: "org.alamofire.concurrencyStreamCompletionQueue-\(id)", target: .singleEventQueue) } } /// An asynchronous sequence generated from an underlying `AsyncStream`. Only produced by Alamofire. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) public struct StreamOf: AsyncSequence { public typealias AsyncIterator = Iterator public typealias BufferingPolicy = AsyncStream.Continuation.BufferingPolicy fileprivate typealias Continuation = AsyncStream.Continuation private let bufferingPolicy: BufferingPolicy private let onTermination: (() -> Void)? private let builder: (Continuation) -> Void fileprivate init(bufferingPolicy: BufferingPolicy = .unbounded, onTermination: (() -> Void)? = nil, builder: @escaping (Continuation) -> Void) { self.bufferingPolicy = bufferingPolicy self.onTermination = onTermination self.builder = builder } public func makeAsyncIterator() -> Iterator { var continuation: AsyncStream.Continuation? let stream = AsyncStream { innerContinuation in continuation = innerContinuation builder(innerContinuation) } return Iterator(iterator: stream.makeAsyncIterator()) { continuation?.finish() self.onTermination?() } } public struct Iterator: AsyncIteratorProtocol { private final class Token { private let onDeinit: () -> Void init(onDeinit: @escaping () -> Void) { self.onDeinit = onDeinit } deinit { onDeinit() } } private var iterator: AsyncStream.AsyncIterator private let token: Token init(iterator: AsyncStream.AsyncIterator, onCancellation: @escaping () -> Void) { self.iterator = iterator token = Token(onDeinit: onCancellation) } public mutating func next() async -> Element? { await iterator.next() } } } #endif ================================================ FILE: JetChat/Pods/Alamofire/Source/DispatchQueue+Alamofire.swift ================================================ // // DispatchQueue+Alamofire.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Dispatch import Foundation extension DispatchQueue { /// Execute the provided closure after a `TimeInterval`. /// /// - Parameters: /// - delay: `TimeInterval` to delay execution. /// - closure: Closure to execute. func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) { asyncAfter(deadline: .now() + delay, execute: closure) } } ================================================ FILE: JetChat/Pods/Alamofire/Source/EventMonitor.swift ================================================ // // EventMonitor.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Protocol outlining the lifetime events inside Alamofire. It includes both events received from the various /// `URLSession` delegate protocols as well as various events from the lifetime of `Request` and its subclasses. public protocol EventMonitor { /// The `DispatchQueue` onto which Alamofire's root `CompositeEventMonitor` will dispatch events. `.main` by default. var queue: DispatchQueue { get } // MARK: - URLSession Events // MARK: URLSessionDelegate Events /// Event called during `URLSessionDelegate`'s `urlSession(_:didBecomeInvalidWithError:)` method. func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) // MARK: URLSessionTaskDelegate Events /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didReceive:completionHandler:)` method. func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)` method. func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:needNewBodyStream:)` method. func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)` method. func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest) /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didFinishCollecting:)` method. func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didCompleteWithError:)` method. func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:taskIsWaitingForConnectivity:)` method. @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) // MARK: URLSessionDataDelegate Events /// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:didReceive:)` method. func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) /// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:willCacheResponse:completionHandler:)` method. func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse) // MARK: URLSessionDownloadDelegate Events /// Event called during `URLSessionDownloadDelegate`'s `urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)` method. func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) /// Event called during `URLSessionDownloadDelegate`'s `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)` method. func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) /// Event called during `URLSessionDownloadDelegate`'s `urlSession(_:downloadTask:didFinishDownloadingTo:)` method. func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) // MARK: - Request Events /// Event called when a `URLRequest` is first created for a `Request`. If a `RequestAdapter` is active, the /// `URLRequest` will be adapted before being issued. func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) /// Event called when the attempt to create a `URLRequest` from a `Request`'s original `URLRequestConvertible` value fails. func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) /// Event called when a `RequestAdapter` adapts the `Request`'s initial `URLRequest`. func request(_ request: Request, didAdaptInitialRequest initialRequest: URLRequest, to adaptedRequest: URLRequest) /// Event called when a `RequestAdapter` fails to adapt the `Request`'s initial `URLRequest`. func request(_ request: Request, didFailToAdaptURLRequest initialRequest: URLRequest, withError error: AFError) /// Event called when a final `URLRequest` is created for a `Request`. func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) /// Event called when a `URLSessionTask` subclass instance is created for a `Request`. func request(_ request: Request, didCreateTask task: URLSessionTask) /// Event called when a `Request` receives a `URLSessionTaskMetrics` value. func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) /// Event called when a `Request` fails due to an error created by Alamofire. e.g. When certificate pinning fails. func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) /// Event called when a `Request`'s task completes, possibly with an error. A `Request` may receive this event /// multiple times if it is retried. func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) /// Event called when a `Request` is about to be retried. func requestIsRetrying(_ request: Request) /// Event called when a `Request` finishes and response serializers are being called. func requestDidFinish(_ request: Request) /// Event called when a `Request` receives a `resume` call. func requestDidResume(_ request: Request) /// Event called when a `Request`'s associated `URLSessionTask` is resumed. func request(_ request: Request, didResumeTask task: URLSessionTask) /// Event called when a `Request` receives a `suspend` call. func requestDidSuspend(_ request: Request) /// Event called when a `Request`'s associated `URLSessionTask` is suspended. func request(_ request: Request, didSuspendTask task: URLSessionTask) /// Event called when a `Request` receives a `cancel` call. func requestDidCancel(_ request: Request) /// Event called when a `Request`'s associated `URLSessionTask` is cancelled. func request(_ request: Request, didCancelTask task: URLSessionTask) // MARK: DataRequest Events /// Event called when a `DataRequest` calls a `Validation`. func request(_ request: DataRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, data: Data?, withResult result: Request.ValidationResult) /// Event called when a `DataRequest` creates a `DataResponse` value without calling a `ResponseSerializer`. func request(_ request: DataRequest, didParseResponse response: DataResponse) /// Event called when a `DataRequest` calls a `ResponseSerializer` and creates a generic `DataResponse`. func request(_ request: DataRequest, didParseResponse response: DataResponse) // MARK: DataStreamRequest Events /// Event called when a `DataStreamRequest` calls a `Validation` closure. /// /// - Parameters: /// - request: `DataStreamRequest` which is calling the `Validation`. /// - urlRequest: `URLRequest` of the request being validated. /// - response: `HTTPURLResponse` of the request being validated. /// - result: Produced `ValidationResult`. func request(_ request: DataStreamRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, withResult result: Request.ValidationResult) /// Event called when a `DataStreamSerializer` produces a value from streamed `Data`. /// /// - Parameters: /// - request: `DataStreamRequest` for which the value was serialized. /// - result: `Result` of the serialization attempt. func request(_ request: DataStreamRequest, didParseStream result: Result) // MARK: UploadRequest Events /// Event called when an `UploadRequest` creates its `Uploadable` value, indicating the type of upload it represents. func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) /// Event called when an `UploadRequest` failed to create its `Uploadable` value due to an error. func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) /// Event called when an `UploadRequest` provides the `InputStream` from its `Uploadable` value. This only occurs if /// the `InputStream` does not wrap a `Data` value or file `URL`. func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) // MARK: DownloadRequest Events /// Event called when a `DownloadRequest`'s `URLSessionDownloadTask` finishes and the temporary file has been moved. func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) /// Event called when a `DownloadRequest`'s `Destination` closure is called and creates the destination URL the /// downloaded file will be moved to. func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) /// Event called when a `DownloadRequest` calls a `Validation`. func request(_ request: DownloadRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, fileURL: URL?, withResult result: Request.ValidationResult) /// Event called when a `DownloadRequest` creates a `DownloadResponse` without calling a `ResponseSerializer`. func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) /// Event called when a `DownloadRequest` calls a `DownloadResponseSerializer` and creates a generic `DownloadResponse` func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) } extension EventMonitor { /// The default queue on which `CompositeEventMonitor`s will call the `EventMonitor` methods. `.main` by default. public var queue: DispatchQueue { .main } // MARK: Default Implementations public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {} public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) {} public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {} public func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) {} public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest) {} public func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {} public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {} public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {} public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {} public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse) {} public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {} public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {} public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {} public func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) {} public func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) {} public func request(_ request: Request, didAdaptInitialRequest initialRequest: URLRequest, to adaptedRequest: URLRequest) {} public func request(_ request: Request, didFailToAdaptURLRequest initialRequest: URLRequest, withError error: AFError) {} public func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) {} public func request(_ request: Request, didCreateTask task: URLSessionTask) {} public func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) {} public func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) {} public func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) {} public func requestIsRetrying(_ request: Request) {} public func requestDidFinish(_ request: Request) {} public func requestDidResume(_ request: Request) {} public func request(_ request: Request, didResumeTask task: URLSessionTask) {} public func requestDidSuspend(_ request: Request) {} public func request(_ request: Request, didSuspendTask task: URLSessionTask) {} public func requestDidCancel(_ request: Request) {} public func request(_ request: Request, didCancelTask task: URLSessionTask) {} public func request(_ request: DataRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, data: Data?, withResult result: Request.ValidationResult) {} public func request(_ request: DataRequest, didParseResponse response: DataResponse) {} public func request(_ request: DataRequest, didParseResponse response: DataResponse) {} public func request(_ request: DataStreamRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, withResult result: Request.ValidationResult) {} public func request(_ request: DataStreamRequest, didParseStream result: Result) {} public func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) {} public func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) {} public func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) {} public func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) {} public func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) {} public func request(_ request: DownloadRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, fileURL: URL?, withResult result: Request.ValidationResult) {} public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) {} public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) {} } /// An `EventMonitor` which can contain multiple `EventMonitor`s and calls their methods on their queues. public final class CompositeEventMonitor: EventMonitor { public let queue = DispatchQueue(label: "org.alamofire.compositeEventMonitor", qos: .utility) let monitors: [EventMonitor] init(monitors: [EventMonitor]) { self.monitors = monitors } func performEvent(_ event: @escaping (EventMonitor) -> Void) { queue.async { for monitor in self.monitors { monitor.queue.async { event(monitor) } } } } public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { performEvent { $0.urlSession(session, didBecomeInvalidWithError: error) } } public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) { performEvent { $0.urlSession(session, task: task, didReceive: challenge) } } public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { performEvent { $0.urlSession(session, task: task, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend) } } public func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) { performEvent { $0.urlSession(session, taskNeedsNewBodyStream: task) } } public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest) { performEvent { $0.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request) } } public func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { performEvent { $0.urlSession(session, task: task, didFinishCollecting: metrics) } } public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { performEvent { $0.urlSession(session, task: task, didCompleteWithError: error) } } @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) { performEvent { $0.urlSession(session, taskIsWaitingForConnectivity: task) } } public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { performEvent { $0.urlSession(session, dataTask: dataTask, didReceive: data) } } public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse) { performEvent { $0.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse) } } public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) { performEvent { $0.urlSession(session, downloadTask: downloadTask, didResumeAtOffset: fileOffset, expectedTotalBytes: expectedTotalBytes) } } public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { performEvent { $0.urlSession(session, downloadTask: downloadTask, didWriteData: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite) } } public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { performEvent { $0.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location) } } public func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) { performEvent { $0.request(request, didCreateInitialURLRequest: urlRequest) } } public func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) { performEvent { $0.request(request, didFailToCreateURLRequestWithError: error) } } public func request(_ request: Request, didAdaptInitialRequest initialRequest: URLRequest, to adaptedRequest: URLRequest) { performEvent { $0.request(request, didAdaptInitialRequest: initialRequest, to: adaptedRequest) } } public func request(_ request: Request, didFailToAdaptURLRequest initialRequest: URLRequest, withError error: AFError) { performEvent { $0.request(request, didFailToAdaptURLRequest: initialRequest, withError: error) } } public func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) { performEvent { $0.request(request, didCreateURLRequest: urlRequest) } } public func request(_ request: Request, didCreateTask task: URLSessionTask) { performEvent { $0.request(request, didCreateTask: task) } } public func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) { performEvent { $0.request(request, didGatherMetrics: metrics) } } public func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) { performEvent { $0.request(request, didFailTask: task, earlyWithError: error) } } public func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) { performEvent { $0.request(request, didCompleteTask: task, with: error) } } public func requestIsRetrying(_ request: Request) { performEvent { $0.requestIsRetrying(request) } } public func requestDidFinish(_ request: Request) { performEvent { $0.requestDidFinish(request) } } public func requestDidResume(_ request: Request) { performEvent { $0.requestDidResume(request) } } public func request(_ request: Request, didResumeTask task: URLSessionTask) { performEvent { $0.request(request, didResumeTask: task) } } public func requestDidSuspend(_ request: Request) { performEvent { $0.requestDidSuspend(request) } } public func request(_ request: Request, didSuspendTask task: URLSessionTask) { performEvent { $0.request(request, didSuspendTask: task) } } public func requestDidCancel(_ request: Request) { performEvent { $0.requestDidCancel(request) } } public func request(_ request: Request, didCancelTask task: URLSessionTask) { performEvent { $0.request(request, didCancelTask: task) } } public func request(_ request: DataRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, data: Data?, withResult result: Request.ValidationResult) { performEvent { $0.request(request, didValidateRequest: urlRequest, response: response, data: data, withResult: result) } } public func request(_ request: DataRequest, didParseResponse response: DataResponse) { performEvent { $0.request(request, didParseResponse: response) } } public func request(_ request: DataRequest, didParseResponse response: DataResponse) { performEvent { $0.request(request, didParseResponse: response) } } public func request(_ request: DataStreamRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, withResult result: Request.ValidationResult) { performEvent { $0.request(request, didValidateRequest: urlRequest, response: response, withResult: result) } } public func request(_ request: DataStreamRequest, didParseStream result: Result) { performEvent { $0.request(request, didParseStream: result) } } public func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) { performEvent { $0.request(request, didCreateUploadable: uploadable) } } public func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) { performEvent { $0.request(request, didFailToCreateUploadableWithError: error) } } public func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) { performEvent { $0.request(request, didProvideInputStream: stream) } } public func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) { performEvent { $0.request(request, didFinishDownloadingUsing: task, with: result) } } public func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) { performEvent { $0.request(request, didCreateDestinationURL: url) } } public func request(_ request: DownloadRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, fileURL: URL?, withResult result: Request.ValidationResult) { performEvent { $0.request(request, didValidateRequest: urlRequest, response: response, fileURL: fileURL, withResult: result) } } public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) { performEvent { $0.request(request, didParseResponse: response) } } public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) { performEvent { $0.request(request, didParseResponse: response) } } } /// `EventMonitor` that allows optional closures to be set to receive events. open class ClosureEventMonitor: EventMonitor { /// Closure called on the `urlSession(_:didBecomeInvalidWithError:)` event. open var sessionDidBecomeInvalidWithError: ((URLSession, Error?) -> Void)? /// Closure called on the `urlSession(_:task:didReceive:completionHandler:)`. open var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> Void)? /// Closure that receives `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)` event. open var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)? /// Closure called on the `urlSession(_:task:needNewBodyStream:)` event. open var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> Void)? /// Closure called on the `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)` event. open var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> Void)? /// Closure called on the `urlSession(_:task:didFinishCollecting:)` event. open var taskDidFinishCollectingMetrics: ((URLSession, URLSessionTask, URLSessionTaskMetrics) -> Void)? /// Closure called on the `urlSession(_:task:didCompleteWithError:)` event. open var taskDidComplete: ((URLSession, URLSessionTask, Error?) -> Void)? /// Closure called on the `urlSession(_:taskIsWaitingForConnectivity:)` event. open var taskIsWaitingForConnectivity: ((URLSession, URLSessionTask) -> Void)? /// Closure that receives the `urlSession(_:dataTask:didReceive:)` event. open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)? /// Closure called on the `urlSession(_:dataTask:willCacheResponse:completionHandler:)` event. open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> Void)? /// Closure called on the `urlSession(_:downloadTask:didFinishDownloadingTo:)` event. open var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> Void)? /// Closure called on the `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)` /// event. open var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? /// Closure called on the `urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)` event. open var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)? // MARK: - Request Events /// Closure called on the `request(_:didCreateInitialURLRequest:)` event. open var requestDidCreateInitialURLRequest: ((Request, URLRequest) -> Void)? /// Closure called on the `request(_:didFailToCreateURLRequestWithError:)` event. open var requestDidFailToCreateURLRequestWithError: ((Request, AFError) -> Void)? /// Closure called on the `request(_:didAdaptInitialRequest:to:)` event. open var requestDidAdaptInitialRequestToAdaptedRequest: ((Request, URLRequest, URLRequest) -> Void)? /// Closure called on the `request(_:didFailToAdaptURLRequest:withError:)` event. open var requestDidFailToAdaptURLRequestWithError: ((Request, URLRequest, AFError) -> Void)? /// Closure called on the `request(_:didCreateURLRequest:)` event. open var requestDidCreateURLRequest: ((Request, URLRequest) -> Void)? /// Closure called on the `request(_:didCreateTask:)` event. open var requestDidCreateTask: ((Request, URLSessionTask) -> Void)? /// Closure called on the `request(_:didGatherMetrics:)` event. open var requestDidGatherMetrics: ((Request, URLSessionTaskMetrics) -> Void)? /// Closure called on the `request(_:didFailTask:earlyWithError:)` event. open var requestDidFailTaskEarlyWithError: ((Request, URLSessionTask, AFError) -> Void)? /// Closure called on the `request(_:didCompleteTask:with:)` event. open var requestDidCompleteTaskWithError: ((Request, URLSessionTask, AFError?) -> Void)? /// Closure called on the `requestIsRetrying(_:)` event. open var requestIsRetrying: ((Request) -> Void)? /// Closure called on the `requestDidFinish(_:)` event. open var requestDidFinish: ((Request) -> Void)? /// Closure called on the `requestDidResume(_:)` event. open var requestDidResume: ((Request) -> Void)? /// Closure called on the `request(_:didResumeTask:)` event. open var requestDidResumeTask: ((Request, URLSessionTask) -> Void)? /// Closure called on the `requestDidSuspend(_:)` event. open var requestDidSuspend: ((Request) -> Void)? /// Closure called on the `request(_:didSuspendTask:)` event. open var requestDidSuspendTask: ((Request, URLSessionTask) -> Void)? /// Closure called on the `requestDidCancel(_:)` event. open var requestDidCancel: ((Request) -> Void)? /// Closure called on the `request(_:didCancelTask:)` event. open var requestDidCancelTask: ((Request, URLSessionTask) -> Void)? /// Closure called on the `request(_:didValidateRequest:response:data:withResult:)` event. open var requestDidValidateRequestResponseDataWithResult: ((DataRequest, URLRequest?, HTTPURLResponse, Data?, Request.ValidationResult) -> Void)? /// Closure called on the `request(_:didParseResponse:)` event. open var requestDidParseResponse: ((DataRequest, DataResponse) -> Void)? /// Closure called on the `request(_:didValidateRequest:response:withResult:)` event. open var requestDidValidateRequestResponseWithResult: ((DataStreamRequest, URLRequest?, HTTPURLResponse, Request.ValidationResult) -> Void)? /// Closure called on the `request(_:didCreateUploadable:)` event. open var requestDidCreateUploadable: ((UploadRequest, UploadRequest.Uploadable) -> Void)? /// Closure called on the `request(_:didFailToCreateUploadableWithError:)` event. open var requestDidFailToCreateUploadableWithError: ((UploadRequest, AFError) -> Void)? /// Closure called on the `request(_:didProvideInputStream:)` event. open var requestDidProvideInputStream: ((UploadRequest, InputStream) -> Void)? /// Closure called on the `request(_:didFinishDownloadingUsing:with:)` event. open var requestDidFinishDownloadingUsingTaskWithResult: ((DownloadRequest, URLSessionTask, Result) -> Void)? /// Closure called on the `request(_:didCreateDestinationURL:)` event. open var requestDidCreateDestinationURL: ((DownloadRequest, URL) -> Void)? /// Closure called on the `request(_:didValidateRequest:response:temporaryURL:destinationURL:withResult:)` event. open var requestDidValidateRequestResponseFileURLWithResult: ((DownloadRequest, URLRequest?, HTTPURLResponse, URL?, Request.ValidationResult) -> Void)? /// Closure called on the `request(_:didParseResponse:)` event. open var requestDidParseDownloadResponse: ((DownloadRequest, DownloadResponse) -> Void)? public let queue: DispatchQueue /// Creates an instance using the provided queue. /// /// - Parameter queue: `DispatchQueue` on which events will fired. `.main` by default. public init(queue: DispatchQueue = .main) { self.queue = queue } open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { sessionDidBecomeInvalidWithError?(session, error) } open func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) { taskDidReceiveChallenge?(session, task, challenge) } open func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { taskDidSendBodyData?(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) } open func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) { taskNeedNewBodyStream?(session, task) } open func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest) { taskWillPerformHTTPRedirection?(session, task, response, request) } open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { taskDidFinishCollectingMetrics?(session, task, metrics) } open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { taskDidComplete?(session, task, error) } open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) { taskIsWaitingForConnectivity?(session, task) } open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { dataTaskDidReceiveData?(session, dataTask, data) } open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse) { dataTaskWillCacheResponse?(session, dataTask, proposedResponse) } open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) { downloadTaskDidResumeAtOffset?(session, downloadTask, fileOffset, expectedTotalBytes) } open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { downloadTaskDidWriteData?(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) } open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { downloadTaskDidFinishDownloadingToURL?(session, downloadTask, location) } // MARK: Request Events open func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) { requestDidCreateInitialURLRequest?(request, urlRequest) } open func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) { requestDidFailToCreateURLRequestWithError?(request, error) } open func request(_ request: Request, didAdaptInitialRequest initialRequest: URLRequest, to adaptedRequest: URLRequest) { requestDidAdaptInitialRequestToAdaptedRequest?(request, initialRequest, adaptedRequest) } open func request(_ request: Request, didFailToAdaptURLRequest initialRequest: URLRequest, withError error: AFError) { requestDidFailToAdaptURLRequestWithError?(request, initialRequest, error) } open func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) { requestDidCreateURLRequest?(request, urlRequest) } open func request(_ request: Request, didCreateTask task: URLSessionTask) { requestDidCreateTask?(request, task) } open func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) { requestDidGatherMetrics?(request, metrics) } open func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) { requestDidFailTaskEarlyWithError?(request, task, error) } open func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) { requestDidCompleteTaskWithError?(request, task, error) } open func requestIsRetrying(_ request: Request) { requestIsRetrying?(request) } open func requestDidFinish(_ request: Request) { requestDidFinish?(request) } open func requestDidResume(_ request: Request) { requestDidResume?(request) } public func request(_ request: Request, didResumeTask task: URLSessionTask) { requestDidResumeTask?(request, task) } open func requestDidSuspend(_ request: Request) { requestDidSuspend?(request) } public func request(_ request: Request, didSuspendTask task: URLSessionTask) { requestDidSuspendTask?(request, task) } open func requestDidCancel(_ request: Request) { requestDidCancel?(request) } public func request(_ request: Request, didCancelTask task: URLSessionTask) { requestDidCancelTask?(request, task) } open func request(_ request: DataRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, data: Data?, withResult result: Request.ValidationResult) { requestDidValidateRequestResponseDataWithResult?(request, urlRequest, response, data, result) } open func request(_ request: DataRequest, didParseResponse response: DataResponse) { requestDidParseResponse?(request, response) } public func request(_ request: DataStreamRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, withResult result: Request.ValidationResult) { requestDidValidateRequestResponseWithResult?(request, urlRequest, response, result) } open func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) { requestDidCreateUploadable?(request, uploadable) } open func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) { requestDidFailToCreateUploadableWithError?(request, error) } open func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) { requestDidProvideInputStream?(request, stream) } open func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) { requestDidFinishDownloadingUsingTaskWithResult?(request, task, result) } open func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) { requestDidCreateDestinationURL?(request, url) } open func request(_ request: DownloadRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, fileURL: URL?, withResult result: Request.ValidationResult) { requestDidValidateRequestResponseFileURLWithResult?(request, urlRequest, response, fileURL, result) } open func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) { requestDidParseDownloadResponse?(request, response) } } ================================================ FILE: JetChat/Pods/Alamofire/Source/HTTPHeaders.swift ================================================ // // HTTPHeaders.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// An order-preserving and case-insensitive representation of HTTP headers. public struct HTTPHeaders { private var headers: [HTTPHeader] = [] /// Creates an empty instance. public init() {} /// Creates an instance from an array of `HTTPHeader`s. Duplicate case-insensitive names are collapsed into the last /// name and value encountered. public init(_ headers: [HTTPHeader]) { self.init() headers.forEach { update($0) } } /// Creates an instance from a `[String: String]`. Duplicate case-insensitive names are collapsed into the last name /// and value encountered. public init(_ dictionary: [String: String]) { self.init() dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) } } /// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`. /// /// - Parameters: /// - name: The `HTTPHeader` name. /// - value: The `HTTPHeader value. public mutating func add(name: String, value: String) { update(HTTPHeader(name: name, value: value)) } /// Case-insensitively updates or appends the provided `HTTPHeader` into the instance. /// /// - Parameter header: The `HTTPHeader` to update or append. public mutating func add(_ header: HTTPHeader) { update(header) } /// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`. /// /// - Parameters: /// - name: The `HTTPHeader` name. /// - value: The `HTTPHeader value. public mutating func update(name: String, value: String) { update(HTTPHeader(name: name, value: value)) } /// Case-insensitively updates or appends the provided `HTTPHeader` into the instance. /// /// - Parameter header: The `HTTPHeader` to update or append. public mutating func update(_ header: HTTPHeader) { guard let index = headers.index(of: header.name) else { headers.append(header) return } headers.replaceSubrange(index...index, with: [header]) } /// Case-insensitively removes an `HTTPHeader`, if it exists, from the instance. /// /// - Parameter name: The name of the `HTTPHeader` to remove. public mutating func remove(name: String) { guard let index = headers.index(of: name) else { return } headers.remove(at: index) } /// Sort the current instance by header name, case insensitively. public mutating func sort() { headers.sort { $0.name.lowercased() < $1.name.lowercased() } } /// Returns an instance sorted by header name. /// /// - Returns: A copy of the current instance sorted by name. public func sorted() -> HTTPHeaders { var headers = self headers.sort() return headers } /// Case-insensitively find a header's value by name. /// /// - Parameter name: The name of the header to search for, case-insensitively. /// /// - Returns: The value of header, if it exists. public func value(for name: String) -> String? { guard let index = headers.index(of: name) else { return nil } return headers[index].value } /// Case-insensitively access the header with the given name. /// /// - Parameter name: The name of the header. public subscript(_ name: String) -> String? { get { value(for: name) } set { if let value = newValue { update(name: name, value: value) } else { remove(name: name) } } } /// The dictionary representation of all headers. /// /// This representation does not preserve the current order of the instance. public var dictionary: [String: String] { let namesAndValues = headers.map { ($0.name, $0.value) } return Dictionary(namesAndValues, uniquingKeysWith: { _, last in last }) } } extension HTTPHeaders: ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (String, String)...) { self.init() elements.forEach { update(name: $0.0, value: $0.1) } } } extension HTTPHeaders: ExpressibleByArrayLiteral { public init(arrayLiteral elements: HTTPHeader...) { self.init(elements) } } extension HTTPHeaders: Sequence { public func makeIterator() -> IndexingIterator<[HTTPHeader]> { headers.makeIterator() } } extension HTTPHeaders: Collection { public var startIndex: Int { headers.startIndex } public var endIndex: Int { headers.endIndex } public subscript(position: Int) -> HTTPHeader { headers[position] } public func index(after i: Int) -> Int { headers.index(after: i) } } extension HTTPHeaders: CustomStringConvertible { public var description: String { headers.map(\.description) .joined(separator: "\n") } } // MARK: - HTTPHeader /// A representation of a single HTTP header's name / value pair. public struct HTTPHeader: Hashable { /// Name of the header. public let name: String /// Value of the header. public let value: String /// Creates an instance from the given `name` and `value`. /// /// - Parameters: /// - name: The name of the header. /// - value: The value of the header. public init(name: String, value: String) { self.name = name self.value = value } } extension HTTPHeader: CustomStringConvertible { public var description: String { "\(name): \(value)" } } extension HTTPHeader { /// Returns an `Accept` header. /// /// - Parameter value: The `Accept` value. /// - Returns: The header. public static func accept(_ value: String) -> HTTPHeader { HTTPHeader(name: "Accept", value: value) } /// Returns an `Accept-Charset` header. /// /// - Parameter value: The `Accept-Charset` value. /// - Returns: The header. public static func acceptCharset(_ value: String) -> HTTPHeader { HTTPHeader(name: "Accept-Charset", value: value) } /// Returns an `Accept-Language` header. /// /// Alamofire offers a default Accept-Language header that accumulates and encodes the system's preferred languages. /// Use `HTTPHeader.defaultAcceptLanguage`. /// /// - Parameter value: The `Accept-Language` value. /// /// - Returns: The header. public static func acceptLanguage(_ value: String) -> HTTPHeader { HTTPHeader(name: "Accept-Language", value: value) } /// Returns an `Accept-Encoding` header. /// /// Alamofire offers a default accept encoding value that provides the most common values. Use /// `HTTPHeader.defaultAcceptEncoding`. /// /// - Parameter value: The `Accept-Encoding` value. /// /// - Returns: The header public static func acceptEncoding(_ value: String) -> HTTPHeader { HTTPHeader(name: "Accept-Encoding", value: value) } /// Returns a `Basic` `Authorization` header using the `username` and `password` provided. /// /// - Parameters: /// - username: The username of the header. /// - password: The password of the header. /// /// - Returns: The header. public static func authorization(username: String, password: String) -> HTTPHeader { let credential = Data("\(username):\(password)".utf8).base64EncodedString() return authorization("Basic \(credential)") } /// Returns a `Bearer` `Authorization` header using the `bearerToken` provided /// /// - Parameter bearerToken: The bearer token. /// /// - Returns: The header. public static func authorization(bearerToken: String) -> HTTPHeader { authorization("Bearer \(bearerToken)") } /// Returns an `Authorization` header. /// /// Alamofire provides built-in methods to produce `Authorization` headers. For a Basic `Authorization` header use /// `HTTPHeader.authorization(username:password:)`. For a Bearer `Authorization` header, use /// `HTTPHeader.authorization(bearerToken:)`. /// /// - Parameter value: The `Authorization` value. /// /// - Returns: The header. public static func authorization(_ value: String) -> HTTPHeader { HTTPHeader(name: "Authorization", value: value) } /// Returns a `Content-Disposition` header. /// /// - Parameter value: The `Content-Disposition` value. /// /// - Returns: The header. public static func contentDisposition(_ value: String) -> HTTPHeader { HTTPHeader(name: "Content-Disposition", value: value) } /// Returns a `Content-Type` header. /// /// All Alamofire `ParameterEncoding`s and `ParameterEncoder`s set the `Content-Type` of the request, so it may not be necessary to manually /// set this value. /// /// - Parameter value: The `Content-Type` value. /// /// - Returns: The header. public static func contentType(_ value: String) -> HTTPHeader { HTTPHeader(name: "Content-Type", value: value) } /// Returns a `User-Agent` header. /// /// - Parameter value: The `User-Agent` value. /// /// - Returns: The header. public static func userAgent(_ value: String) -> HTTPHeader { HTTPHeader(name: "User-Agent", value: value) } } extension Array where Element == HTTPHeader { /// Case-insensitively finds the index of an `HTTPHeader` with the provided name, if it exists. func index(of name: String) -> Int? { let lowercasedName = name.lowercased() return firstIndex { $0.name.lowercased() == lowercasedName } } } // MARK: - Defaults extension HTTPHeaders { /// The default set of `HTTPHeaders` used by Alamofire. Includes `Accept-Encoding`, `Accept-Language`, and /// `User-Agent`. public static let `default`: HTTPHeaders = [.defaultAcceptEncoding, .defaultAcceptLanguage, .defaultUserAgent] } extension HTTPHeader { /// Returns Alamofire's default `Accept-Encoding` header, appropriate for the encodings supported by particular OS /// versions. /// /// See the [Accept-Encoding HTTP header documentation](https://tools.ietf.org/html/rfc7230#section-4.2.3) . public static let defaultAcceptEncoding: HTTPHeader = { let encodings: [String] if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *) { encodings = ["br", "gzip", "deflate"] } else { encodings = ["gzip", "deflate"] } return .acceptEncoding(encodings.qualityEncoded()) }() /// Returns Alamofire's default `Accept-Language` header, generated by querying `Locale` for the user's /// `preferredLanguages`. /// /// See the [Accept-Language HTTP header documentation](https://tools.ietf.org/html/rfc7231#section-5.3.5). public static let defaultAcceptLanguage: HTTPHeader = .acceptLanguage(Locale.preferredLanguages.prefix(6).qualityEncoded()) /// Returns Alamofire's default `User-Agent` header. /// /// See the [User-Agent header documentation](https://tools.ietf.org/html/rfc7231#section-5.5.3). /// /// Example: `iOS Example/1.0 (org.alamofire.iOS-Example; build:1; iOS 13.0.0) Alamofire/5.0.0` public static let defaultUserAgent: HTTPHeader = { let info = Bundle.main.infoDictionary let executable = (info?["CFBundleExecutable"] as? String) ?? (ProcessInfo.processInfo.arguments.first?.split(separator: "/").last.map(String.init)) ?? "Unknown" let bundle = info?["CFBundleIdentifier"] as? String ?? "Unknown" let appVersion = info?["CFBundleShortVersionString"] as? String ?? "Unknown" let appBuild = info?["CFBundleVersion"] as? String ?? "Unknown" let osNameVersion: String = { let version = ProcessInfo.processInfo.operatingSystemVersion let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" let osName: String = { #if os(iOS) #if targetEnvironment(macCatalyst) return "macOS(Catalyst)" #else return "iOS" #endif #elseif os(watchOS) return "watchOS" #elseif os(tvOS) return "tvOS" #elseif os(macOS) return "macOS" #elseif os(Linux) return "Linux" #elseif os(Windows) return "Windows" #else return "Unknown" #endif }() return "\(osName) \(versionString)" }() let alamofireVersion = "Alamofire/\(version)" let userAgent = "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)" return .userAgent(userAgent) }() } extension Collection where Element == String { func qualityEncoded() -> String { enumerated().map { index, encoding in let quality = 1.0 - (Double(index) * 0.1) return "\(encoding);q=\(quality)" }.joined(separator: ", ") } } // MARK: - System Type Extensions extension URLRequest { /// Returns `allHTTPHeaderFields` as `HTTPHeaders`. public var headers: HTTPHeaders { get { allHTTPHeaderFields.map(HTTPHeaders.init) ?? HTTPHeaders() } set { allHTTPHeaderFields = newValue.dictionary } } } extension HTTPURLResponse { /// Returns `allHeaderFields` as `HTTPHeaders`. public var headers: HTTPHeaders { (allHeaderFields as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() } } extension URLSessionConfiguration { /// Returns `httpAdditionalHeaders` as `HTTPHeaders`. public var headers: HTTPHeaders { get { (httpAdditionalHeaders as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() } set { httpAdditionalHeaders = newValue.dictionary } } } ================================================ FILE: JetChat/Pods/Alamofire/Source/HTTPMethod.swift ================================================ // // HTTPMethod.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // /// Type representing HTTP methods. Raw `String` value is stored and compared case-sensitively, so /// `HTTPMethod.get != HTTPMethod(rawValue: "get")`. /// /// See https://tools.ietf.org/html/rfc7231#section-4.3 public struct HTTPMethod: RawRepresentable, Equatable, Hashable { /// `CONNECT` method. public static let connect = HTTPMethod(rawValue: "CONNECT") /// `DELETE` method. public static let delete = HTTPMethod(rawValue: "DELETE") /// `GET` method. public static let get = HTTPMethod(rawValue: "GET") /// `HEAD` method. public static let head = HTTPMethod(rawValue: "HEAD") /// `OPTIONS` method. public static let options = HTTPMethod(rawValue: "OPTIONS") /// `PATCH` method. public static let patch = HTTPMethod(rawValue: "PATCH") /// `POST` method. public static let post = HTTPMethod(rawValue: "POST") /// `PUT` method. public static let put = HTTPMethod(rawValue: "PUT") /// `QUERY` method. public static let query = HTTPMethod(rawValue: "QUERY") /// `TRACE` method. public static let trace = HTTPMethod(rawValue: "TRACE") public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue } } ================================================ FILE: JetChat/Pods/Alamofire/Source/MultipartFormData.swift ================================================ // // MultipartFormData.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation #if os(iOS) || os(watchOS) || os(tvOS) import MobileCoreServices #elseif os(macOS) import CoreServices #endif /// Constructs `multipart/form-data` for uploads within an HTTP or HTTPS body. There are currently two ways to encode /// multipart form data. The first way is to encode the data directly in memory. This is very efficient, but can lead /// to memory issues if the dataset is too large. The second way is designed for larger datasets and will write all the /// data to a single file on disk with all the proper boundary segmentation. The second approach MUST be used for /// larger datasets such as video content, otherwise your app may run out of memory when trying to encode the dataset. /// /// For more information on `multipart/form-data` in general, please refer to the RFC-2388 and RFC-2045 specs as well /// and the w3 form documentation. /// /// - https://www.ietf.org/rfc/rfc2388.txt /// - https://www.ietf.org/rfc/rfc2045.txt /// - https://www.w3.org/TR/html401/interact/forms.html#h-17.13 open class MultipartFormData { // MARK: - Helper Types enum EncodingCharacters { static let crlf = "\r\n" } enum BoundaryGenerator { enum BoundaryType { case initial, encapsulated, final } static func randomBoundary() -> String { let first = UInt32.random(in: UInt32.min...UInt32.max) let second = UInt32.random(in: UInt32.min...UInt32.max) return String(format: "alamofire.boundary.%08x%08x", first, second) } static func boundaryData(forBoundaryType boundaryType: BoundaryType, boundary: String) -> Data { let boundaryText: String switch boundaryType { case .initial: boundaryText = "--\(boundary)\(EncodingCharacters.crlf)" case .encapsulated: boundaryText = "\(EncodingCharacters.crlf)--\(boundary)\(EncodingCharacters.crlf)" case .final: boundaryText = "\(EncodingCharacters.crlf)--\(boundary)--\(EncodingCharacters.crlf)" } return Data(boundaryText.utf8) } } class BodyPart { let headers: HTTPHeaders let bodyStream: InputStream let bodyContentLength: UInt64 var hasInitialBoundary = false var hasFinalBoundary = false init(headers: HTTPHeaders, bodyStream: InputStream, bodyContentLength: UInt64) { self.headers = headers self.bodyStream = bodyStream self.bodyContentLength = bodyContentLength } } // MARK: - Properties /// Default memory threshold used when encoding `MultipartFormData`, in bytes. public static let encodingMemoryThreshold: UInt64 = 10_000_000 /// The `Content-Type` header value containing the boundary used to generate the `multipart/form-data`. open lazy var contentType: String = "multipart/form-data; boundary=\(self.boundary)" /// The content length of all body parts used to generate the `multipart/form-data` not including the boundaries. public var contentLength: UInt64 { bodyParts.reduce(0) { $0 + $1.bodyContentLength } } /// The boundary used to separate the body parts in the encoded form data. public let boundary: String let fileManager: FileManager private var bodyParts: [BodyPart] private var bodyPartError: AFError? private let streamBufferSize: Int // MARK: - Lifecycle /// Creates an instance. /// /// - Parameters: /// - fileManager: `FileManager` to use for file operations, if needed. /// - boundary: Boundary `String` used to separate body parts. public init(fileManager: FileManager = .default, boundary: String? = nil) { self.fileManager = fileManager self.boundary = boundary ?? BoundaryGenerator.randomBoundary() bodyParts = [] // // The optimal read/write buffer size in bytes for input and output streams is 1024 (1KB). For more // information, please refer to the following article: // - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html // streamBufferSize = 1024 } // MARK: - Body Parts /// Creates a body part from the data and appends it to the instance. /// /// The body part data will be encoded using the following format: /// /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header) /// - `Content-Type: #{mimeType}` (HTTP Header) /// - Encoded file data /// - Multipart form boundary /// /// - Parameters: /// - data: `Data` to encoding into the instance. /// - name: Name to associate with the `Data` in the `Content-Disposition` HTTP header. /// - fileName: Filename to associate with the `Data` in the `Content-Disposition` HTTP header. /// - mimeType: MIME type to associate with the data in the `Content-Type` HTTP header. public func append(_ data: Data, withName name: String, fileName: String? = nil, mimeType: String? = nil) { let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) let stream = InputStream(data: data) let length = UInt64(data.count) append(stream, withLength: length, headers: headers) } /// Creates a body part from the file and appends it to the instance. /// /// The body part data will be encoded using the following format: /// /// - `Content-Disposition: form-data; name=#{name}; filename=#{generated filename}` (HTTP Header) /// - `Content-Type: #{generated mimeType}` (HTTP Header) /// - Encoded file data /// - Multipart form boundary /// /// The filename in the `Content-Disposition` HTTP header is generated from the last path component of the /// `fileURL`. The `Content-Type` HTTP header MIME type is generated by mapping the `fileURL` extension to the /// system associated MIME type. /// /// - Parameters: /// - fileURL: `URL` of the file whose content will be encoded into the instance. /// - name: Name to associate with the file content in the `Content-Disposition` HTTP header. public func append(_ fileURL: URL, withName name: String) { let fileName = fileURL.lastPathComponent let pathExtension = fileURL.pathExtension if !fileName.isEmpty && !pathExtension.isEmpty { let mime = mimeType(forPathExtension: pathExtension) append(fileURL, withName: name, fileName: fileName, mimeType: mime) } else { setBodyPartError(withReason: .bodyPartFilenameInvalid(in: fileURL)) } } /// Creates a body part from the file and appends it to the instance. /// /// The body part data will be encoded using the following format: /// /// - Content-Disposition: form-data; name=#{name}; filename=#{filename} (HTTP Header) /// - Content-Type: #{mimeType} (HTTP Header) /// - Encoded file data /// - Multipart form boundary /// /// - Parameters: /// - fileURL: `URL` of the file whose content will be encoded into the instance. /// - name: Name to associate with the file content in the `Content-Disposition` HTTP header. /// - fileName: Filename to associate with the file content in the `Content-Disposition` HTTP header. /// - mimeType: MIME type to associate with the file content in the `Content-Type` HTTP header. public func append(_ fileURL: URL, withName name: String, fileName: String, mimeType: String) { let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) //============================================================ // Check 1 - is file URL? //============================================================ guard fileURL.isFileURL else { setBodyPartError(withReason: .bodyPartURLInvalid(url: fileURL)) return } //============================================================ // Check 2 - is file URL reachable? //============================================================ #if !(os(Linux) || os(Windows)) do { let isReachable = try fileURL.checkPromisedItemIsReachable() guard isReachable else { setBodyPartError(withReason: .bodyPartFileNotReachable(at: fileURL)) return } } catch { setBodyPartError(withReason: .bodyPartFileNotReachableWithError(atURL: fileURL, error: error)) return } #endif //============================================================ // Check 3 - is file URL a directory? //============================================================ var isDirectory: ObjCBool = false let path = fileURL.path guard fileManager.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else { setBodyPartError(withReason: .bodyPartFileIsDirectory(at: fileURL)) return } //============================================================ // Check 4 - can the file size be extracted? //============================================================ let bodyContentLength: UInt64 do { guard let fileSize = try fileManager.attributesOfItem(atPath: path)[.size] as? NSNumber else { setBodyPartError(withReason: .bodyPartFileSizeNotAvailable(at: fileURL)) return } bodyContentLength = fileSize.uint64Value } catch { setBodyPartError(withReason: .bodyPartFileSizeQueryFailedWithError(forURL: fileURL, error: error)) return } //============================================================ // Check 5 - can a stream be created from file URL? //============================================================ guard let stream = InputStream(url: fileURL) else { setBodyPartError(withReason: .bodyPartInputStreamCreationFailed(for: fileURL)) return } append(stream, withLength: bodyContentLength, headers: headers) } /// Creates a body part from the stream and appends it to the instance. /// /// The body part data will be encoded using the following format: /// /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header) /// - `Content-Type: #{mimeType}` (HTTP Header) /// - Encoded stream data /// - Multipart form boundary /// /// - Parameters: /// - stream: `InputStream` to encode into the instance. /// - length: Length, in bytes, of the stream. /// - name: Name to associate with the stream content in the `Content-Disposition` HTTP header. /// - fileName: Filename to associate with the stream content in the `Content-Disposition` HTTP header. /// - mimeType: MIME type to associate with the stream content in the `Content-Type` HTTP header. public func append(_ stream: InputStream, withLength length: UInt64, name: String, fileName: String, mimeType: String) { let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) append(stream, withLength: length, headers: headers) } /// Creates a body part with the stream, length, and headers and appends it to the instance. /// /// The body part data will be encoded using the following format: /// /// - HTTP headers /// - Encoded stream data /// - Multipart form boundary /// /// - Parameters: /// - stream: `InputStream` to encode into the instance. /// - length: Length, in bytes, of the stream. /// - headers: `HTTPHeaders` for the body part. public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) { let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length) bodyParts.append(bodyPart) } // MARK: - Data Encoding /// Encodes all appended body parts into a single `Data` value. /// /// - Note: This method will load all the appended body parts into memory all at the same time. This method should /// only be used when the encoded data will have a small memory footprint. For large data cases, please use /// the `writeEncodedData(to:))` method. /// /// - Returns: The encoded `Data`, if encoding is successful. /// - Throws: An `AFError` if encoding encounters an error. public func encode() throws -> Data { if let bodyPartError = bodyPartError { throw bodyPartError } var encoded = Data() bodyParts.first?.hasInitialBoundary = true bodyParts.last?.hasFinalBoundary = true for bodyPart in bodyParts { let encodedData = try encode(bodyPart) encoded.append(encodedData) } return encoded } /// Writes all appended body parts to the given file `URL`. /// /// This process is facilitated by reading and writing with input and output streams, respectively. Thus, /// this approach is very memory efficient and should be used for large body part data. /// /// - Parameter fileURL: File `URL` to which to write the form data. /// - Throws: An `AFError` if encoding encounters an error. public func writeEncodedData(to fileURL: URL) throws { if let bodyPartError = bodyPartError { throw bodyPartError } if fileManager.fileExists(atPath: fileURL.path) { throw AFError.multipartEncodingFailed(reason: .outputStreamFileAlreadyExists(at: fileURL)) } else if !fileURL.isFileURL { throw AFError.multipartEncodingFailed(reason: .outputStreamURLInvalid(url: fileURL)) } guard let outputStream = OutputStream(url: fileURL, append: false) else { throw AFError.multipartEncodingFailed(reason: .outputStreamCreationFailed(for: fileURL)) } outputStream.open() defer { outputStream.close() } bodyParts.first?.hasInitialBoundary = true bodyParts.last?.hasFinalBoundary = true for bodyPart in bodyParts { try write(bodyPart, to: outputStream) } } // MARK: - Private - Body Part Encoding private func encode(_ bodyPart: BodyPart) throws -> Data { var encoded = Data() let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() encoded.append(initialData) let headerData = encodeHeaders(for: bodyPart) encoded.append(headerData) let bodyStreamData = try encodeBodyStream(for: bodyPart) encoded.append(bodyStreamData) if bodyPart.hasFinalBoundary { encoded.append(finalBoundaryData()) } return encoded } private func encodeHeaders(for bodyPart: BodyPart) -> Data { let headerText = bodyPart.headers.map { "\($0.name): \($0.value)\(EncodingCharacters.crlf)" } .joined() + EncodingCharacters.crlf return Data(headerText.utf8) } private func encodeBodyStream(for bodyPart: BodyPart) throws -> Data { let inputStream = bodyPart.bodyStream inputStream.open() defer { inputStream.close() } var encoded = Data() while inputStream.hasBytesAvailable { var buffer = [UInt8](repeating: 0, count: streamBufferSize) let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) if let error = inputStream.streamError { throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error)) } if bytesRead > 0 { encoded.append(buffer, count: bytesRead) } else { break } } guard UInt64(encoded.count) == bodyPart.bodyContentLength else { let error = AFError.UnexpectedInputStreamLength(bytesExpected: bodyPart.bodyContentLength, bytesRead: UInt64(encoded.count)) throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error)) } return encoded } // MARK: - Private - Writing Body Part to Output Stream private func write(_ bodyPart: BodyPart, to outputStream: OutputStream) throws { try writeInitialBoundaryData(for: bodyPart, to: outputStream) try writeHeaderData(for: bodyPart, to: outputStream) try writeBodyStream(for: bodyPart, to: outputStream) try writeFinalBoundaryData(for: bodyPart, to: outputStream) } private func writeInitialBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws { let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() return try write(initialData, to: outputStream) } private func writeHeaderData(for bodyPart: BodyPart, to outputStream: OutputStream) throws { let headerData = encodeHeaders(for: bodyPart) return try write(headerData, to: outputStream) } private func writeBodyStream(for bodyPart: BodyPart, to outputStream: OutputStream) throws { let inputStream = bodyPart.bodyStream inputStream.open() defer { inputStream.close() } while inputStream.hasBytesAvailable { var buffer = [UInt8](repeating: 0, count: streamBufferSize) let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) if let streamError = inputStream.streamError { throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError)) } if bytesRead > 0 { if buffer.count != bytesRead { buffer = Array(buffer[0.. 0, outputStream.hasSpaceAvailable { let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite) if let error = outputStream.streamError { throw AFError.multipartEncodingFailed(reason: .outputStreamWriteFailed(error: error)) } bytesToWrite -= bytesWritten if bytesToWrite > 0 { buffer = Array(buffer[bytesWritten.. HTTPHeaders { var disposition = "form-data; name=\"\(name)\"" if let fileName = fileName { disposition += "; filename=\"\(fileName)\"" } var headers: HTTPHeaders = [.contentDisposition(disposition)] if let mimeType = mimeType { headers.add(.contentType(mimeType)) } return headers } // MARK: - Private - Boundary Encoding private func initialBoundaryData() -> Data { BoundaryGenerator.boundaryData(forBoundaryType: .initial, boundary: boundary) } private func encapsulatedBoundaryData() -> Data { BoundaryGenerator.boundaryData(forBoundaryType: .encapsulated, boundary: boundary) } private func finalBoundaryData() -> Data { BoundaryGenerator.boundaryData(forBoundaryType: .final, boundary: boundary) } // MARK: - Private - Errors private func setBodyPartError(withReason reason: AFError.MultipartEncodingFailureReason) { guard bodyPartError == nil else { return } bodyPartError = AFError.multipartEncodingFailed(reason: reason) } } #if canImport(UniformTypeIdentifiers) import UniformTypeIdentifiers extension MultipartFormData { // MARK: - Private - Mime Type private func mimeType(forPathExtension pathExtension: String) -> String { if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) { return UTType(filenameExtension: pathExtension)?.preferredMIMEType ?? "application/octet-stream" } else { if let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(), let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() { return contentType as String } return "application/octet-stream" } } } #else extension MultipartFormData { // MARK: - Private - Mime Type private func mimeType(forPathExtension pathExtension: String) -> String { #if !(os(Linux) || os(Windows)) if let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(), let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() { return contentType as String } #endif return "application/octet-stream" } } #endif ================================================ FILE: JetChat/Pods/Alamofire/Source/MultipartUpload.swift ================================================ // // MultipartUpload.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Internal type which encapsulates a `MultipartFormData` upload. final class MultipartUpload { lazy var result = Result { try build() } @Protected private(set) var multipartFormData: MultipartFormData let encodingMemoryThreshold: UInt64 let request: URLRequestConvertible let fileManager: FileManager init(encodingMemoryThreshold: UInt64, request: URLRequestConvertible, multipartFormData: MultipartFormData) { self.encodingMemoryThreshold = encodingMemoryThreshold self.request = request fileManager = multipartFormData.fileManager self.multipartFormData = multipartFormData } func build() throws -> UploadRequest.Uploadable { let uploadable: UploadRequest.Uploadable if $multipartFormData.contentLength < encodingMemoryThreshold { let data = try $multipartFormData.read { try $0.encode() } uploadable = .data(data) } else { let tempDirectoryURL = fileManager.temporaryDirectory let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data") let fileName = UUID().uuidString let fileURL = directoryURL.appendingPathComponent(fileName) try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) do { try $multipartFormData.read { try $0.writeEncodedData(to: fileURL) } } catch { // Cleanup after attempted write if it fails. try? fileManager.removeItem(at: fileURL) throw error } uploadable = .file(fileURL, shouldRemove: true) } return uploadable } } extension MultipartUpload: UploadConvertible { func asURLRequest() throws -> URLRequest { var urlRequest = try request.asURLRequest() $multipartFormData.read { multipartFormData in urlRequest.headers.add(.contentType(multipartFormData.contentType)) } return urlRequest } func createUploadable() throws -> UploadRequest.Uploadable { try result.get() } } ================================================ FILE: JetChat/Pods/Alamofire/Source/NetworkReachabilityManager.swift ================================================ // // NetworkReachabilityManager.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #if !(os(watchOS) || os(Linux) || os(Windows)) import Foundation import SystemConfiguration /// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both cellular and /// WiFi network interfaces. /// /// Reachability can be used to determine background information about why a network operation failed, or to retry /// network requests when a connection is established. It should not be used to prevent a user from initiating a network /// request, as it's possible that an initial request may be required to establish reachability. open class NetworkReachabilityManager { /// Defines the various states of network reachability. public enum NetworkReachabilityStatus { /// It is unknown whether the network is reachable. case unknown /// The network is not reachable. case notReachable /// The network is reachable on the associated `ConnectionType`. case reachable(ConnectionType) init(_ flags: SCNetworkReachabilityFlags) { guard flags.isActuallyReachable else { self = .notReachable; return } var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi) if flags.isCellular { networkStatus = .reachable(.cellular) } self = networkStatus } /// Defines the various connection types detected by reachability flags. public enum ConnectionType { /// The connection type is either over Ethernet or WiFi. case ethernetOrWiFi /// The connection type is a cellular connection. case cellular } } /// A closure executed when the network reachability status changes. The closure takes a single argument: the /// network reachability status. public typealias Listener = (NetworkReachabilityStatus) -> Void /// Default `NetworkReachabilityManager` for the zero address and a `listenerQueue` of `.main`. public static let `default` = NetworkReachabilityManager() // MARK: - Properties /// Whether the network is currently reachable. open var isReachable: Bool { isReachableOnCellular || isReachableOnEthernetOrWiFi } /// Whether the network is currently reachable over the cellular interface. /// /// - Note: Using this property to decide whether to make a high or low bandwidth request is not recommended. /// Instead, set the `allowsCellularAccess` on any `URLRequest`s being issued. /// open var isReachableOnCellular: Bool { status == .reachable(.cellular) } /// Whether the network is currently reachable over Ethernet or WiFi interface. open var isReachableOnEthernetOrWiFi: Bool { status == .reachable(.ethernetOrWiFi) } /// `DispatchQueue` on which reachability will update. public let reachabilityQueue = DispatchQueue(label: "org.alamofire.reachabilityQueue") /// Flags of the current reachability type, if any. open var flags: SCNetworkReachabilityFlags? { var flags = SCNetworkReachabilityFlags() return (SCNetworkReachabilityGetFlags(reachability, &flags)) ? flags : nil } /// The current network reachability status. open var status: NetworkReachabilityStatus { flags.map(NetworkReachabilityStatus.init) ?? .unknown } /// Mutable state storage. struct MutableState { /// A closure executed when the network reachability status changes. var listener: Listener? /// `DispatchQueue` on which listeners will be called. var listenerQueue: DispatchQueue? /// Previously calculated status. var previousStatus: NetworkReachabilityStatus? } /// `SCNetworkReachability` instance providing notifications. private let reachability: SCNetworkReachability /// Protected storage for mutable state. @Protected private var mutableState = MutableState() // MARK: - Initialization /// Creates an instance with the specified host. /// /// - Note: The `host` value must *not* contain a scheme, just the hostname. /// /// - Parameters: /// - host: Host used to evaluate network reachability. Must *not* include the scheme (e.g. `https`). public convenience init?(host: String) { guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil } self.init(reachability: reachability) } /// Creates an instance that monitors the address 0.0.0.0. /// /// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing /// status of the device, both IPv4 and IPv6. public convenience init?() { var zero = sockaddr() zero.sa_len = UInt8(MemoryLayout.size) zero.sa_family = sa_family_t(AF_INET) guard let reachability = SCNetworkReachabilityCreateWithAddress(nil, &zero) else { return nil } self.init(reachability: reachability) } private init(reachability: SCNetworkReachability) { self.reachability = reachability } deinit { stopListening() } // MARK: - Listening /// Starts listening for changes in network reachability status. /// /// - Note: Stops and removes any existing listener. /// /// - Parameters: /// - queue: `DispatchQueue` on which to call the `listener` closure. `.main` by default. /// - listener: `Listener` closure called when reachability changes. /// /// - Returns: `true` if listening was started successfully, `false` otherwise. @discardableResult open func startListening(onQueue queue: DispatchQueue = .main, onUpdatePerforming listener: @escaping Listener) -> Bool { stopListening() $mutableState.write { state in state.listenerQueue = queue state.listener = listener } var context = SCNetworkReachabilityContext(version: 0, info: Unmanaged.passUnretained(self).toOpaque(), retain: nil, release: nil, copyDescription: nil) let callback: SCNetworkReachabilityCallBack = { _, flags, info in guard let info = info else { return } let instance = Unmanaged.fromOpaque(info).takeUnretainedValue() instance.notifyListener(flags) } let queueAdded = SCNetworkReachabilitySetDispatchQueue(reachability, reachabilityQueue) let callbackAdded = SCNetworkReachabilitySetCallback(reachability, callback, &context) // Manually call listener to give initial state, since the framework may not. if let currentFlags = flags { reachabilityQueue.async { self.notifyListener(currentFlags) } } return callbackAdded && queueAdded } /// Stops listening for changes in network reachability status. open func stopListening() { SCNetworkReachabilitySetCallback(reachability, nil, nil) SCNetworkReachabilitySetDispatchQueue(reachability, nil) $mutableState.write { state in state.listener = nil state.listenerQueue = nil state.previousStatus = nil } } // MARK: - Internal - Listener Notification /// Calls the `listener` closure of the `listenerQueue` if the computed status hasn't changed. /// /// - Note: Should only be called from the `reachabilityQueue`. /// /// - Parameter flags: `SCNetworkReachabilityFlags` to use to calculate the status. func notifyListener(_ flags: SCNetworkReachabilityFlags) { let newStatus = NetworkReachabilityStatus(flags) $mutableState.write { state in guard state.previousStatus != newStatus else { return } state.previousStatus = newStatus let listener = state.listener state.listenerQueue?.async { listener?(newStatus) } } } } // MARK: - extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {} extension SCNetworkReachabilityFlags { var isReachable: Bool { contains(.reachable) } var isConnectionRequired: Bool { contains(.connectionRequired) } var canConnectAutomatically: Bool { contains(.connectionOnDemand) || contains(.connectionOnTraffic) } var canConnectWithoutUserInteraction: Bool { canConnectAutomatically && !contains(.interventionRequired) } var isActuallyReachable: Bool { isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) } var isCellular: Bool { #if os(iOS) || os(tvOS) return contains(.isWWAN) #else return false #endif } /// Human readable `String` for all states, to help with debugging. var readableDescription: String { let W = isCellular ? "W" : "-" let R = isReachable ? "R" : "-" let c = isConnectionRequired ? "c" : "-" let t = contains(.transientConnection) ? "t" : "-" let i = contains(.interventionRequired) ? "i" : "-" let C = contains(.connectionOnTraffic) ? "C" : "-" let D = contains(.connectionOnDemand) ? "D" : "-" let l = contains(.isLocalAddress) ? "l" : "-" let d = contains(.isDirect) ? "d" : "-" let a = contains(.connectionAutomatic) ? "a" : "-" return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)\(a)" } } #endif ================================================ FILE: JetChat/Pods/Alamofire/Source/Notifications.swift ================================================ // // Notifications.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation extension Request { /// Posted when a `Request` is resumed. The `Notification` contains the resumed `Request`. public static let didResumeNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didResume") /// Posted when a `Request` is suspended. The `Notification` contains the suspended `Request`. public static let didSuspendNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didSuspend") /// Posted when a `Request` is cancelled. The `Notification` contains the cancelled `Request`. public static let didCancelNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCancel") /// Posted when a `Request` is finished. The `Notification` contains the completed `Request`. public static let didFinishNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didFinish") /// Posted when a `URLSessionTask` is resumed. The `Notification` contains the `Request` associated with the `URLSessionTask`. public static let didResumeTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didResumeTask") /// Posted when a `URLSessionTask` is suspended. The `Notification` contains the `Request` associated with the `URLSessionTask`. public static let didSuspendTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didSuspendTask") /// Posted when a `URLSessionTask` is cancelled. The `Notification` contains the `Request` associated with the `URLSessionTask`. public static let didCancelTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCancelTask") /// Posted when a `URLSessionTask` is completed. The `Notification` contains the `Request` associated with the `URLSessionTask`. public static let didCompleteTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCompleteTask") } // MARK: - extension Notification { /// The `Request` contained by the instance's `userInfo`, `nil` otherwise. public var request: Request? { userInfo?[String.requestKey] as? Request } /// Convenience initializer for a `Notification` containing a `Request` payload. /// /// - Parameters: /// - name: The name of the notification. /// - request: The `Request` payload. init(name: Notification.Name, request: Request) { self.init(name: name, object: nil, userInfo: [String.requestKey: request]) } } extension NotificationCenter { /// Convenience function for posting notifications with `Request` payloads. /// /// - Parameters: /// - name: The name of the notification. /// - request: The `Request` payload. func postNotification(named name: Notification.Name, with request: Request) { let notification = Notification(name: name, request: request) post(notification) } } extension String { /// User info dictionary key representing the `Request` associated with the notification. fileprivate static let requestKey = "org.alamofire.notification.key.request" } /// `EventMonitor` that provides Alamofire's notifications. public final class AlamofireNotifications: EventMonitor { public func requestDidResume(_ request: Request) { NotificationCenter.default.postNotification(named: Request.didResumeNotification, with: request) } public func requestDidSuspend(_ request: Request) { NotificationCenter.default.postNotification(named: Request.didSuspendNotification, with: request) } public func requestDidCancel(_ request: Request) { NotificationCenter.default.postNotification(named: Request.didCancelNotification, with: request) } public func requestDidFinish(_ request: Request) { NotificationCenter.default.postNotification(named: Request.didFinishNotification, with: request) } public func request(_ request: Request, didResumeTask task: URLSessionTask) { NotificationCenter.default.postNotification(named: Request.didResumeTaskNotification, with: request) } public func request(_ request: Request, didSuspendTask task: URLSessionTask) { NotificationCenter.default.postNotification(named: Request.didSuspendTaskNotification, with: request) } public func request(_ request: Request, didCancelTask task: URLSessionTask) { NotificationCenter.default.postNotification(named: Request.didCancelTaskNotification, with: request) } public func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) { NotificationCenter.default.postNotification(named: Request.didCompleteTaskNotification, with: request) } } ================================================ FILE: JetChat/Pods/Alamofire/Source/OperationQueue+Alamofire.swift ================================================ // // OperationQueue+Alamofire.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation extension OperationQueue { /// Creates an instance using the provided parameters. /// /// - Parameters: /// - qualityOfService: `QualityOfService` to be applied to the queue. `.default` by default. /// - maxConcurrentOperationCount: Maximum concurrent operations. /// `OperationQueue.defaultMaxConcurrentOperationCount` by default. /// - underlyingQueue: Underlying `DispatchQueue`. `nil` by default. /// - name: Name for the queue. `nil` by default. /// - startSuspended: Whether the queue starts suspended. `false` by default. convenience init(qualityOfService: QualityOfService = .default, maxConcurrentOperationCount: Int = OperationQueue.defaultMaxConcurrentOperationCount, underlyingQueue: DispatchQueue? = nil, name: String? = nil, startSuspended: Bool = false) { self.init() self.qualityOfService = qualityOfService self.maxConcurrentOperationCount = maxConcurrentOperationCount self.underlyingQueue = underlyingQueue self.name = name isSuspended = startSuspended } } ================================================ FILE: JetChat/Pods/Alamofire/Source/ParameterEncoder.swift ================================================ // // ParameterEncoder.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// A type that can encode any `Encodable` type into a `URLRequest`. public protocol ParameterEncoder { /// Encode the provided `Encodable` parameters into `request`. /// /// - Parameters: /// - parameters: The `Encodable` parameter value. /// - request: The `URLRequest` into which to encode the parameters. /// /// - Returns: A `URLRequest` with the result of the encoding. /// - Throws: An `Error` when encoding fails. For Alamofire provided encoders, this will be an instance of /// `AFError.parameterEncoderFailed` with an associated `ParameterEncoderFailureReason`. func encode(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest } /// A `ParameterEncoder` that encodes types as JSON body data. /// /// If no `Content-Type` header is already set on the provided `URLRequest`s, it's set to `application/json`. open class JSONParameterEncoder: ParameterEncoder { /// Returns an encoder with default parameters. public static var `default`: JSONParameterEncoder { JSONParameterEncoder() } /// Returns an encoder with `JSONEncoder.outputFormatting` set to `.prettyPrinted`. public static var prettyPrinted: JSONParameterEncoder { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted return JSONParameterEncoder(encoder: encoder) } /// Returns an encoder with `JSONEncoder.outputFormatting` set to `.sortedKeys`. @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) public static var sortedKeys: JSONParameterEncoder { let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys return JSONParameterEncoder(encoder: encoder) } /// `JSONEncoder` used to encode parameters. public let encoder: JSONEncoder /// Creates an instance with the provided `JSONEncoder`. /// /// - Parameter encoder: The `JSONEncoder`. `JSONEncoder()` by default. public init(encoder: JSONEncoder = JSONEncoder()) { self.encoder = encoder } open func encode(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest { guard let parameters = parameters else { return request } var request = request do { let data = try encoder.encode(parameters) request.httpBody = data if request.headers["Content-Type"] == nil { request.headers.update(.contentType("application/json")) } } catch { throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) } return request } } #if swift(>=5.5) extension ParameterEncoder where Self == JSONParameterEncoder { /// Provides a default `JSONParameterEncoder` instance. public static var json: JSONParameterEncoder { JSONParameterEncoder() } /// Creates a `JSONParameterEncoder` using the provided `JSONEncoder`. /// /// - Parameter encoder: `JSONEncoder` used to encode parameters. `JSONEncoder()` by default. /// - Returns: The `JSONParameterEncoder`. public static func json(encoder: JSONEncoder = JSONEncoder()) -> JSONParameterEncoder { JSONParameterEncoder(encoder: encoder) } } #endif /// A `ParameterEncoder` that encodes types as URL-encoded query strings to be set on the URL or as body data, depending /// on the `Destination` set. /// /// If no `Content-Type` header is already set on the provided `URLRequest`s, it will be set to /// `application/x-www-form-urlencoded; charset=utf-8`. /// /// Encoding behavior can be customized by passing an instance of `URLEncodedFormEncoder` to the initializer. open class URLEncodedFormParameterEncoder: ParameterEncoder { /// Defines where the URL-encoded string should be set for each `URLRequest`. public enum Destination { /// Applies the encoded query string to any existing query string for `.get`, `.head`, and `.delete` request. /// Sets it to the `httpBody` for all other methods. case methodDependent /// Applies the encoded query string to any existing query string from the `URLRequest`. case queryString /// Applies the encoded query string to the `httpBody` of the `URLRequest`. case httpBody /// Determines whether the URL-encoded string should be applied to the `URLRequest`'s `url`. /// /// - Parameter method: The `HTTPMethod`. /// /// - Returns: Whether the URL-encoded string should be applied to a `URL`. func encodesParametersInURL(for method: HTTPMethod) -> Bool { switch self { case .methodDependent: return [.get, .head, .delete].contains(method) case .queryString: return true case .httpBody: return false } } } /// Returns an encoder with default parameters. public static var `default`: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() } /// The `URLEncodedFormEncoder` to use. public let encoder: URLEncodedFormEncoder /// The `Destination` for the URL-encoded string. public let destination: Destination /// Creates an instance with the provided `URLEncodedFormEncoder` instance and `Destination` value. /// /// - Parameters: /// - encoder: The `URLEncodedFormEncoder`. `URLEncodedFormEncoder()` by default. /// - destination: The `Destination`. `.methodDependent` by default. public init(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(), destination: Destination = .methodDependent) { self.encoder = encoder self.destination = destination } open func encode(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest { guard let parameters = parameters else { return request } var request = request guard let url = request.url else { throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url)) } guard let method = request.method else { let rawValue = request.method?.rawValue ?? "nil" throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.httpMethod(rawValue: rawValue))) } if destination.encodesParametersInURL(for: method), var components = URLComponents(url: url, resolvingAgainstBaseURL: false) { let query: String = try Result { try encoder.encode(parameters) } .mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get() let newQueryString = [components.percentEncodedQuery, query].compactMap { $0 }.joinedWithAmpersands() components.percentEncodedQuery = newQueryString.isEmpty ? nil : newQueryString guard let newURL = components.url else { throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url)) } request.url = newURL } else { if request.headers["Content-Type"] == nil { request.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8")) } request.httpBody = try Result { try encoder.encode(parameters) } .mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get() } return request } } #if swift(>=5.5) extension ParameterEncoder where Self == URLEncodedFormParameterEncoder { /// Provides a default `URLEncodedFormParameterEncoder` instance. public static var urlEncodedForm: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() } /// Creates a `URLEncodedFormParameterEncoder` with the provided encoder and destination. /// /// - Parameters: /// - encoder: `URLEncodedFormEncoder` used to encode the parameters. `URLEncodedFormEncoder()` by default. /// - destination: `Destination` to which to encode the parameters. `.methodDependent` by default. /// - Returns: The `URLEncodedFormParameterEncoder`. public static func urlEncodedForm(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(), destination: URLEncodedFormParameterEncoder.Destination = .methodDependent) -> URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder(encoder: encoder, destination: destination) } } #endif ================================================ FILE: JetChat/Pods/Alamofire/Source/ParameterEncoding.swift ================================================ // // ParameterEncoding.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// A dictionary of parameters to apply to a `URLRequest`. public typealias Parameters = [String: Any] /// A type used to define how a set of parameters are applied to a `URLRequest`. public protocol ParameterEncoding { /// Creates a `URLRequest` by encoding parameters and applying them on the passed request. /// /// - Parameters: /// - urlRequest: `URLRequestConvertible` value onto which parameters will be encoded. /// - parameters: `Parameters` to encode onto the request. /// /// - Returns: The encoded `URLRequest`. /// - Throws: Any `Error` produced during parameter encoding. func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest } // MARK: - /// Creates a url-encoded query string to be set as or appended to any existing URL query string or set as the HTTP /// body of the URL request. Whether the query string is set or appended to any existing URL query string or set as /// the HTTP body depends on the destination of the encoding. /// /// The `Content-Type` HTTP header field of an encoded request with HTTP body is set to /// `application/x-www-form-urlencoded; charset=utf-8`. /// /// There is no published specification for how to encode collection types. By default the convention of appending /// `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for /// nested dictionary values (`foo[bar]=baz`) is used. Optionally, `ArrayEncoding` can be used to omit the /// square brackets appended to array keys. /// /// `BoolEncoding` can be used to configure how boolean values are encoded. The default behavior is to encode /// `true` as 1 and `false` as 0. public struct URLEncoding: ParameterEncoding { // MARK: Helper Types /// Defines whether the url-encoded query string is applied to the existing query string or HTTP body of the /// resulting URL request. public enum Destination { /// Applies encoded query string result to existing query string for `GET`, `HEAD` and `DELETE` requests and /// sets as the HTTP body for requests with any other HTTP method. case methodDependent /// Sets or appends encoded query string result to existing query string. case queryString /// Sets encoded query string result as the HTTP body of the URL request. case httpBody func encodesParametersInURL(for method: HTTPMethod) -> Bool { switch self { case .methodDependent: return [.get, .head, .delete].contains(method) case .queryString: return true case .httpBody: return false } } } /// Configures how `Array` parameters are encoded. public enum ArrayEncoding { /// An empty set of square brackets is appended to the key for every value. This is the default behavior. case brackets /// No brackets are appended. The key is encoded as is. case noBrackets /// Brackets containing the item index are appended. This matches the jQuery and Node.js behavior. case indexInBrackets func encode(key: String, atIndex index: Int) -> String { switch self { case .brackets: return "\(key)[]" case .noBrackets: return key case .indexInBrackets: return "\(key)[\(index)]" } } } /// Configures how `Bool` parameters are encoded. public enum BoolEncoding { /// Encode `true` as `1` and `false` as `0`. This is the default behavior. case numeric /// Encode `true` and `false` as string literals. case literal func encode(value: Bool) -> String { switch self { case .numeric: return value ? "1" : "0" case .literal: return value ? "true" : "false" } } } // MARK: Properties /// Returns a default `URLEncoding` instance with a `.methodDependent` destination. public static var `default`: URLEncoding { URLEncoding() } /// Returns a `URLEncoding` instance with a `.queryString` destination. public static var queryString: URLEncoding { URLEncoding(destination: .queryString) } /// Returns a `URLEncoding` instance with an `.httpBody` destination. public static var httpBody: URLEncoding { URLEncoding(destination: .httpBody) } /// The destination defining where the encoded query string is to be applied to the URL request. public let destination: Destination /// The encoding to use for `Array` parameters. public let arrayEncoding: ArrayEncoding /// The encoding to use for `Bool` parameters. public let boolEncoding: BoolEncoding // MARK: Initialization /// Creates an instance using the specified parameters. /// /// - Parameters: /// - destination: `Destination` defining where the encoded query string will be applied. `.methodDependent` by /// default. /// - arrayEncoding: `ArrayEncoding` to use. `.brackets` by default. /// - boolEncoding: `BoolEncoding` to use. `.numeric` by default. public init(destination: Destination = .methodDependent, arrayEncoding: ArrayEncoding = .brackets, boolEncoding: BoolEncoding = .numeric) { self.destination = destination self.arrayEncoding = arrayEncoding self.boolEncoding = boolEncoding } // MARK: Encoding public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { var urlRequest = try urlRequest.asURLRequest() guard let parameters = parameters else { return urlRequest } if let method = urlRequest.method, destination.encodesParametersInURL(for: method) { guard let url = urlRequest.url else { throw AFError.parameterEncodingFailed(reason: .missingURL) } if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty { let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters) urlComponents.percentEncodedQuery = percentEncodedQuery urlRequest.url = urlComponents.url } } else { if urlRequest.headers["Content-Type"] == nil { urlRequest.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8")) } urlRequest.httpBody = Data(query(parameters).utf8) } return urlRequest } /// Creates a percent-escaped, URL encoded query string components from the given key-value pair recursively. /// /// - Parameters: /// - key: Key of the query component. /// - value: Value of the query component. /// /// - Returns: The percent-escaped, URL encoded query string components. public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] { var components: [(String, String)] = [] switch value { case let dictionary as [String: Any]: for (nestedKey, value) in dictionary { components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value) } case let array as [Any]: for (index, value) in array.enumerated() { components += queryComponents(fromKey: arrayEncoding.encode(key: key, atIndex: index), value: value) } case let number as NSNumber: if number.isBool { components.append((escape(key), escape(boolEncoding.encode(value: number.boolValue)))) } else { components.append((escape(key), escape("\(number)"))) } case let bool as Bool: components.append((escape(key), escape(boolEncoding.encode(value: bool)))) default: components.append((escape(key), escape("\(value)"))) } return components } /// Creates a percent-escaped string following RFC 3986 for a query string key or value. /// /// - Parameter string: `String` to be percent-escaped. /// /// - Returns: The percent-escaped `String`. public func escape(_ string: String) -> String { string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string } private func query(_ parameters: [String: Any]) -> String { var components: [(String, String)] = [] for key in parameters.keys.sorted(by: <) { let value = parameters[key]! components += queryComponents(fromKey: key, value: value) } return components.map { "\($0)=\($1)" }.joined(separator: "&") } } // MARK: - /// Uses `JSONSerialization` to create a JSON representation of the parameters object, which is set as the body of the /// request. The `Content-Type` HTTP header field of an encoded request is set to `application/json`. public struct JSONEncoding: ParameterEncoding { // MARK: Properties /// Returns a `JSONEncoding` instance with default writing options. public static var `default`: JSONEncoding { JSONEncoding() } /// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options. public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) } /// The options for writing the parameters as JSON data. public let options: JSONSerialization.WritingOptions // MARK: Initialization /// Creates an instance using the specified `WritingOptions`. /// /// - Parameter options: `JSONSerialization.WritingOptions` to use. public init(options: JSONSerialization.WritingOptions = []) { self.options = options } // MARK: Encoding public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { var urlRequest = try urlRequest.asURLRequest() guard let parameters = parameters else { return urlRequest } do { let data = try JSONSerialization.data(withJSONObject: parameters, options: options) if urlRequest.headers["Content-Type"] == nil { urlRequest.headers.update(.contentType("application/json")) } urlRequest.httpBody = data } catch { throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) } return urlRequest } /// Encodes any JSON compatible object into a `URLRequest`. /// /// - Parameters: /// - urlRequest: `URLRequestConvertible` value into which the object will be encoded. /// - jsonObject: `Any` value (must be JSON compatible` to be encoded into the `URLRequest`. `nil` by default. /// /// - Returns: The encoded `URLRequest`. /// - Throws: Any `Error` produced during encoding. public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest { var urlRequest = try urlRequest.asURLRequest() guard let jsonObject = jsonObject else { return urlRequest } do { let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options) if urlRequest.headers["Content-Type"] == nil { urlRequest.headers.update(.contentType("application/json")) } urlRequest.httpBody = data } catch { throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) } return urlRequest } } // MARK: - extension NSNumber { fileprivate var isBool: Bool { // Use Obj-C type encoding to check whether the underlying type is a `Bool`, as it's guaranteed as part of // swift-corelibs-foundation, per [this discussion on the Swift forums](https://forums.swift.org/t/alamofire-on-linux-possible-but-not-release-ready/34553/22). String(cString: objCType) == "c" } } ================================================ FILE: JetChat/Pods/Alamofire/Source/Protected.swift ================================================ // // Protected.swift // // Copyright (c) 2014-2020 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation private protocol Lock { func lock() func unlock() } extension Lock { /// Executes a closure returning a value while acquiring the lock. /// /// - Parameter closure: The closure to run. /// /// - Returns: The value the closure generated. func around(_ closure: () throws -> T) rethrows -> T { lock(); defer { unlock() } return try closure() } /// Execute a closure while acquiring the lock. /// /// - Parameter closure: The closure to run. func around(_ closure: () throws -> Void) rethrows { lock(); defer { unlock() } try closure() } } #if os(Linux) || os(Windows) extension NSLock: Lock {} #endif #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) /// An `os_unfair_lock` wrapper. final class UnfairLock: Lock { private let unfairLock: os_unfair_lock_t init() { unfairLock = .allocate(capacity: 1) unfairLock.initialize(to: os_unfair_lock()) } deinit { unfairLock.deinitialize(count: 1) unfairLock.deallocate() } fileprivate func lock() { os_unfair_lock_lock(unfairLock) } fileprivate func unlock() { os_unfair_lock_unlock(unfairLock) } } #endif /// A thread-safe wrapper around a value. @propertyWrapper @dynamicMemberLookup final class Protected { #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) private let lock = UnfairLock() #elseif os(Linux) || os(Windows) private let lock = NSLock() #endif private var value: T init(_ value: T) { self.value = value } /// The contained value. Unsafe for anything more than direct read or write. var wrappedValue: T { get { lock.around { value } } set { lock.around { value = newValue } } } var projectedValue: Protected { self } init(wrappedValue: T) { value = wrappedValue } /// Synchronously read or transform the contained value. /// /// - Parameter closure: The closure to execute. /// /// - Returns: The return value of the closure passed. func read(_ closure: (T) throws -> U) rethrows -> U { try lock.around { try closure(self.value) } } /// Synchronously modify the protected value. /// /// - Parameter closure: The closure to execute. /// /// - Returns: The modified value. @discardableResult func write(_ closure: (inout T) throws -> U) rethrows -> U { try lock.around { try closure(&self.value) } } subscript(dynamicMember keyPath: WritableKeyPath) -> Property { get { lock.around { value[keyPath: keyPath] } } set { lock.around { value[keyPath: keyPath] = newValue } } } subscript(dynamicMember keyPath: KeyPath) -> Property { lock.around { value[keyPath: keyPath] } } } extension Protected where T == Request.MutableState { /// Attempts to transition to the passed `State`. /// /// - Parameter state: The `State` to attempt transition to. /// /// - Returns: Whether the transition occurred. func attemptToTransitionTo(_ state: Request.State) -> Bool { lock.around { guard value.state.canTransitionTo(state) else { return false } value.state = state return true } } /// Perform a closure while locked with the provided `Request.State`. /// /// - Parameter perform: The closure to perform while locked. func withState(perform: (Request.State) -> Void) { lock.around { perform(value.state) } } } ================================================ FILE: JetChat/Pods/Alamofire/Source/RedirectHandler.swift ================================================ // // RedirectHandler.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// A type that handles how an HTTP redirect response from a remote server should be redirected to the new request. public protocol RedirectHandler { /// Determines how the HTTP redirect response should be redirected to the new request. /// /// The `completion` closure should be passed one of three possible options: /// /// 1. The new request specified by the redirect (this is the most common use case). /// 2. A modified version of the new request (you may want to route it somewhere else). /// 3. A `nil` value to deny the redirect request and return the body of the redirect response. /// /// - Parameters: /// - task: The `URLSessionTask` whose request resulted in a redirect. /// - request: The `URLRequest` to the new location specified by the redirect response. /// - response: The `HTTPURLResponse` containing the server's response to the original request. /// - completion: The closure to execute containing the new `URLRequest`, a modified `URLRequest`, or `nil`. func task(_ task: URLSessionTask, willBeRedirectedTo request: URLRequest, for response: HTTPURLResponse, completion: @escaping (URLRequest?) -> Void) } // MARK: - /// `Redirector` is a convenience `RedirectHandler` making it easy to follow, not follow, or modify a redirect. public struct Redirector { /// Defines the behavior of the `Redirector` type. public enum Behavior { /// Follow the redirect as defined in the response. case follow /// Do not follow the redirect defined in the response. case doNotFollow /// Modify the redirect request defined in the response. case modify((URLSessionTask, URLRequest, HTTPURLResponse) -> URLRequest?) } /// Returns a `Redirector` with a `.follow` `Behavior`. public static let follow = Redirector(behavior: .follow) /// Returns a `Redirector` with a `.doNotFollow` `Behavior`. public static let doNotFollow = Redirector(behavior: .doNotFollow) /// The `Behavior` of the `Redirector`. public let behavior: Behavior /// Creates a `Redirector` instance from the `Behavior`. /// /// - Parameter behavior: The `Behavior`. public init(behavior: Behavior) { self.behavior = behavior } } // MARK: - extension Redirector: RedirectHandler { public func task(_ task: URLSessionTask, willBeRedirectedTo request: URLRequest, for response: HTTPURLResponse, completion: @escaping (URLRequest?) -> Void) { switch behavior { case .follow: completion(request) case .doNotFollow: completion(nil) case let .modify(closure): let request = closure(task, request, response) completion(request) } } } #if swift(>=5.5) extension RedirectHandler where Self == Redirector { /// Provides a `Redirector` which follows redirects. Equivalent to `Redirector.follow`. public static var follow: Redirector { .follow } /// Provides a `Redirector` which does not follow redirects. Equivalent to `Redirector.doNotFollow`. public static var doNotFollow: Redirector { .doNotFollow } /// Creates a `Redirector` which modifies the redirected `URLRequest` using the provided closure. /// /// - Parameter closure: Closure used to modify the redirect. /// - Returns: The `Redirector`. public static func modify(using closure: @escaping (URLSessionTask, URLRequest, HTTPURLResponse) -> URLRequest?) -> Redirector { Redirector(behavior: .modify(closure)) } } #endif ================================================ FILE: JetChat/Pods/Alamofire/Source/Request.swift ================================================ // // Request.swift // // Copyright (c) 2014-2020 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// `Request` is the common superclass of all Alamofire request types and provides common state, delegate, and callback /// handling. public class Request { /// State of the `Request`, with managed transitions between states set when calling `resume()`, `suspend()`, or /// `cancel()` on the `Request`. public enum State { /// Initial state of the `Request`. case initialized /// `State` set when `resume()` is called. Any tasks created for the `Request` will have `resume()` called on /// them in this state. case resumed /// `State` set when `suspend()` is called. Any tasks created for the `Request` will have `suspend()` called on /// them in this state. case suspended /// `State` set when `cancel()` is called. Any tasks created for the `Request` will have `cancel()` called on /// them. Unlike `resumed` or `suspended`, once in the `cancelled` state, the `Request` can no longer transition /// to any other state. case cancelled /// `State` set when all response serialization completion closures have been cleared on the `Request` and /// enqueued on their respective queues. case finished /// Determines whether `self` can be transitioned to the provided `State`. func canTransitionTo(_ state: State) -> Bool { switch (self, state) { case (.initialized, _): return true case (_, .initialized), (.cancelled, _), (.finished, _): return false case (.resumed, .cancelled), (.suspended, .cancelled), (.resumed, .suspended), (.suspended, .resumed): return true case (.suspended, .suspended), (.resumed, .resumed): return false case (_, .finished): return true } } } // MARK: - Initial State /// `UUID` providing a unique identifier for the `Request`, used in the `Hashable` and `Equatable` conformances. public let id: UUID /// The serial queue for all internal async actions. public let underlyingQueue: DispatchQueue /// The queue used for all serialization actions. By default it's a serial queue that targets `underlyingQueue`. public let serializationQueue: DispatchQueue /// `EventMonitor` used for event callbacks. public let eventMonitor: EventMonitor? /// The `Request`'s interceptor. public let interceptor: RequestInterceptor? /// The `Request`'s delegate. public private(set) weak var delegate: RequestDelegate? // MARK: - Mutable State /// Type encapsulating all mutable state that may need to be accessed from anything other than the `underlyingQueue`. struct MutableState { /// State of the `Request`. var state: State = .initialized /// `ProgressHandler` and `DispatchQueue` provided for upload progress callbacks. var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? /// `ProgressHandler` and `DispatchQueue` provided for download progress callbacks. var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? /// `RedirectHandler` provided for to handle request redirection. var redirectHandler: RedirectHandler? /// `CachedResponseHandler` provided to handle response caching. var cachedResponseHandler: CachedResponseHandler? /// Queue and closure called when the `Request` is able to create a cURL description of itself. var cURLHandler: (queue: DispatchQueue, handler: (String) -> Void)? /// Queue and closure called when the `Request` creates a `URLRequest`. var urlRequestHandler: (queue: DispatchQueue, handler: (URLRequest) -> Void)? /// Queue and closure called when the `Request` creates a `URLSessionTask`. var urlSessionTaskHandler: (queue: DispatchQueue, handler: (URLSessionTask) -> Void)? /// Response serialization closures that handle response parsing. var responseSerializers: [() -> Void] = [] /// Response serialization completion closures executed once all response serializers are complete. var responseSerializerCompletions: [() -> Void] = [] /// Whether response serializer processing is finished. var responseSerializerProcessingFinished = false /// `URLCredential` used for authentication challenges. var credential: URLCredential? /// All `URLRequest`s created by Alamofire on behalf of the `Request`. var requests: [URLRequest] = [] /// All `URLSessionTask`s created by Alamofire on behalf of the `Request`. var tasks: [URLSessionTask] = [] /// All `URLSessionTaskMetrics` values gathered by Alamofire on behalf of the `Request`. Should correspond /// exactly the the `tasks` created. var metrics: [URLSessionTaskMetrics] = [] /// Number of times any retriers provided retried the `Request`. var retryCount = 0 /// Final `AFError` for the `Request`, whether from various internal Alamofire calls or as a result of a `task`. var error: AFError? /// Whether the instance has had `finish()` called and is running the serializers. Should be replaced with a /// representation in the state machine in the future. var isFinishing = false /// Actions to run when requests are finished. Use for concurrency support. var finishHandlers: [() -> Void] = [] } /// Protected `MutableState` value that provides thread-safe access to state values. @Protected fileprivate var mutableState = MutableState() /// `State` of the `Request`. public var state: State { $mutableState.state } /// Returns whether `state` is `.initialized`. public var isInitialized: Bool { state == .initialized } /// Returns whether `state is `.resumed`. public var isResumed: Bool { state == .resumed } /// Returns whether `state` is `.suspended`. public var isSuspended: Bool { state == .suspended } /// Returns whether `state` is `.cancelled`. public var isCancelled: Bool { state == .cancelled } /// Returns whether `state` is `.finished`. public var isFinished: Bool { state == .finished } // MARK: Progress /// Closure type executed when monitoring the upload or download progress of a request. public typealias ProgressHandler = (Progress) -> Void /// `Progress` of the upload of the body of the executed `URLRequest`. Reset to `0` if the `Request` is retried. public let uploadProgress = Progress(totalUnitCount: 0) /// `Progress` of the download of any response data. Reset to `0` if the `Request` is retried. public let downloadProgress = Progress(totalUnitCount: 0) /// `ProgressHandler` called when `uploadProgress` is updated, on the provided `DispatchQueue`. private var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? { get { $mutableState.uploadProgressHandler } set { $mutableState.uploadProgressHandler = newValue } } /// `ProgressHandler` called when `downloadProgress` is updated, on the provided `DispatchQueue`. fileprivate var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? { get { $mutableState.downloadProgressHandler } set { $mutableState.downloadProgressHandler = newValue } } // MARK: Redirect Handling /// `RedirectHandler` set on the instance. public private(set) var redirectHandler: RedirectHandler? { get { $mutableState.redirectHandler } set { $mutableState.redirectHandler = newValue } } // MARK: Cached Response Handling /// `CachedResponseHandler` set on the instance. public private(set) var cachedResponseHandler: CachedResponseHandler? { get { $mutableState.cachedResponseHandler } set { $mutableState.cachedResponseHandler = newValue } } // MARK: URLCredential /// `URLCredential` used for authentication challenges. Created by calling one of the `authenticate` methods. public private(set) var credential: URLCredential? { get { $mutableState.credential } set { $mutableState.credential = newValue } } // MARK: Validators /// `Validator` callback closures that store the validation calls enqueued. @Protected fileprivate var validators: [() -> Void] = [] // MARK: URLRequests /// All `URLRequests` created on behalf of the `Request`, including original and adapted requests. public var requests: [URLRequest] { $mutableState.requests } /// First `URLRequest` created on behalf of the `Request`. May not be the first one actually executed. public var firstRequest: URLRequest? { requests.first } /// Last `URLRequest` created on behalf of the `Request`. public var lastRequest: URLRequest? { requests.last } /// Current `URLRequest` created on behalf of the `Request`. public var request: URLRequest? { lastRequest } /// `URLRequest`s from all of the `URLSessionTask`s executed on behalf of the `Request`. May be different from /// `requests` due to `URLSession` manipulation. public var performedRequests: [URLRequest] { $mutableState.read { $0.tasks.compactMap(\.currentRequest) } } // MARK: HTTPURLResponse /// `HTTPURLResponse` received from the server, if any. If the `Request` was retried, this is the response of the /// last `URLSessionTask`. public var response: HTTPURLResponse? { lastTask?.response as? HTTPURLResponse } // MARK: Tasks /// All `URLSessionTask`s created on behalf of the `Request`. public var tasks: [URLSessionTask] { $mutableState.tasks } /// First `URLSessionTask` created on behalf of the `Request`. public var firstTask: URLSessionTask? { tasks.first } /// Last `URLSessionTask` crated on behalf of the `Request`. public var lastTask: URLSessionTask? { tasks.last } /// Current `URLSessionTask` created on behalf of the `Request`. public var task: URLSessionTask? { lastTask } // MARK: Metrics /// All `URLSessionTaskMetrics` gathered on behalf of the `Request`. Should correspond to the `tasks` created. public var allMetrics: [URLSessionTaskMetrics] { $mutableState.metrics } /// First `URLSessionTaskMetrics` gathered on behalf of the `Request`. public var firstMetrics: URLSessionTaskMetrics? { allMetrics.first } /// Last `URLSessionTaskMetrics` gathered on behalf of the `Request`. public var lastMetrics: URLSessionTaskMetrics? { allMetrics.last } /// Current `URLSessionTaskMetrics` gathered on behalf of the `Request`. public var metrics: URLSessionTaskMetrics? { lastMetrics } // MARK: Retry Count /// Number of times the `Request` has been retried. public var retryCount: Int { $mutableState.retryCount } // MARK: Error /// `Error` returned from Alamofire internally, from the network request directly, or any validators executed. public fileprivate(set) var error: AFError? { get { $mutableState.error } set { $mutableState.error = newValue } } /// Default initializer for the `Request` superclass. /// /// - Parameters: /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets /// `underlyingQueue`, but can be passed another queue from a `Session`. /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request`. init(id: UUID = UUID(), underlyingQueue: DispatchQueue, serializationQueue: DispatchQueue, eventMonitor: EventMonitor?, interceptor: RequestInterceptor?, delegate: RequestDelegate) { self.id = id self.underlyingQueue = underlyingQueue self.serializationQueue = serializationQueue self.eventMonitor = eventMonitor self.interceptor = interceptor self.delegate = delegate } // MARK: - Internal Event API // All API must be called from underlyingQueue. /// Called when an initial `URLRequest` has been created on behalf of the instance. If a `RequestAdapter` is active, /// the `URLRequest` will be adapted before being issued. /// /// - Parameter request: The `URLRequest` created. func didCreateInitialURLRequest(_ request: URLRequest) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) $mutableState.write { $0.requests.append(request) } eventMonitor?.request(self, didCreateInitialURLRequest: request) } /// Called when initial `URLRequest` creation has failed, typically through a `URLRequestConvertible`. /// /// - Note: Triggers retry. /// /// - Parameter error: `AFError` thrown from the failed creation. func didFailToCreateURLRequest(with error: AFError) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) self.error = error eventMonitor?.request(self, didFailToCreateURLRequestWithError: error) callCURLHandlerIfNecessary() retryOrFinish(error: error) } /// Called when a `RequestAdapter` has successfully adapted a `URLRequest`. /// /// - Parameters: /// - initialRequest: The `URLRequest` that was adapted. /// - adaptedRequest: The `URLRequest` returned by the `RequestAdapter`. func didAdaptInitialRequest(_ initialRequest: URLRequest, to adaptedRequest: URLRequest) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) $mutableState.write { $0.requests.append(adaptedRequest) } eventMonitor?.request(self, didAdaptInitialRequest: initialRequest, to: adaptedRequest) } /// Called when a `RequestAdapter` fails to adapt a `URLRequest`. /// /// - Note: Triggers retry. /// /// - Parameters: /// - request: The `URLRequest` the adapter was called with. /// - error: The `AFError` returned by the `RequestAdapter`. func didFailToAdaptURLRequest(_ request: URLRequest, withError error: AFError) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) self.error = error eventMonitor?.request(self, didFailToAdaptURLRequest: request, withError: error) callCURLHandlerIfNecessary() retryOrFinish(error: error) } /// Final `URLRequest` has been created for the instance. /// /// - Parameter request: The `URLRequest` created. func didCreateURLRequest(_ request: URLRequest) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) $mutableState.read { state in state.urlRequestHandler?.queue.async { state.urlRequestHandler?.handler(request) } } eventMonitor?.request(self, didCreateURLRequest: request) callCURLHandlerIfNecessary() } /// Asynchronously calls any stored `cURLHandler` and then removes it from `mutableState`. private func callCURLHandlerIfNecessary() { $mutableState.write { mutableState in guard let cURLHandler = mutableState.cURLHandler else { return } cURLHandler.queue.async { cURLHandler.handler(self.cURLDescription()) } mutableState.cURLHandler = nil } } /// Called when a `URLSessionTask` is created on behalf of the instance. /// /// - Parameter task: The `URLSessionTask` created. func didCreateTask(_ task: URLSessionTask) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) $mutableState.write { state in state.tasks.append(task) guard let urlSessionTaskHandler = state.urlSessionTaskHandler else { return } urlSessionTaskHandler.queue.async { urlSessionTaskHandler.handler(task) } } eventMonitor?.request(self, didCreateTask: task) } /// Called when resumption is completed. func didResume() { dispatchPrecondition(condition: .onQueue(underlyingQueue)) eventMonitor?.requestDidResume(self) } /// Called when a `URLSessionTask` is resumed on behalf of the instance. /// /// - Parameter task: The `URLSessionTask` resumed. func didResumeTask(_ task: URLSessionTask) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) eventMonitor?.request(self, didResumeTask: task) } /// Called when suspension is completed. func didSuspend() { dispatchPrecondition(condition: .onQueue(underlyingQueue)) eventMonitor?.requestDidSuspend(self) } /// Called when a `URLSessionTask` is suspended on behalf of the instance. /// /// - Parameter task: The `URLSessionTask` suspended. func didSuspendTask(_ task: URLSessionTask) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) eventMonitor?.request(self, didSuspendTask: task) } /// Called when cancellation is completed, sets `error` to `AFError.explicitlyCancelled`. func didCancel() { dispatchPrecondition(condition: .onQueue(underlyingQueue)) error = error ?? AFError.explicitlyCancelled eventMonitor?.requestDidCancel(self) } /// Called when a `URLSessionTask` is cancelled on behalf of the instance. /// /// - Parameter task: The `URLSessionTask` cancelled. func didCancelTask(_ task: URLSessionTask) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) eventMonitor?.request(self, didCancelTask: task) } /// Called when a `URLSessionTaskMetrics` value is gathered on behalf of the instance. /// /// - Parameter metrics: The `URLSessionTaskMetrics` gathered. func didGatherMetrics(_ metrics: URLSessionTaskMetrics) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) $mutableState.write { $0.metrics.append(metrics) } eventMonitor?.request(self, didGatherMetrics: metrics) } /// Called when a `URLSessionTask` fails before it is finished, typically during certificate pinning. /// /// - Parameters: /// - task: The `URLSessionTask` which failed. /// - error: The early failure `AFError`. func didFailTask(_ task: URLSessionTask, earlyWithError error: AFError) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) self.error = error // Task will still complete, so didCompleteTask(_:with:) will handle retry. eventMonitor?.request(self, didFailTask: task, earlyWithError: error) } /// Called when a `URLSessionTask` completes. All tasks will eventually call this method. /// /// - Note: Response validation is synchronously triggered in this step. /// /// - Parameters: /// - task: The `URLSessionTask` which completed. /// - error: The `AFError` `task` may have completed with. If `error` has already been set on the instance, this /// value is ignored. func didCompleteTask(_ task: URLSessionTask, with error: AFError?) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) self.error = self.error ?? error validators.forEach { $0() } eventMonitor?.request(self, didCompleteTask: task, with: error) retryOrFinish(error: self.error) } /// Called when the `RequestDelegate` is going to retry this `Request`. Calls `reset()`. func prepareForRetry() { dispatchPrecondition(condition: .onQueue(underlyingQueue)) $mutableState.write { $0.retryCount += 1 } reset() eventMonitor?.requestIsRetrying(self) } /// Called to determine whether retry will be triggered for the particular error, or whether the instance should /// call `finish()`. /// /// - Parameter error: The possible `AFError` which may trigger retry. func retryOrFinish(error: AFError?) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) guard let error = error, let delegate = delegate else { finish(); return } delegate.retryResult(for: self, dueTo: error) { retryResult in switch retryResult { case .doNotRetry: self.finish() case let .doNotRetryWithError(retryError): self.finish(error: retryError.asAFError(orFailWith: "Received retryError was not already AFError")) case .retry, .retryWithDelay: delegate.retryRequest(self, withDelay: retryResult.delay) } } } /// Finishes this `Request` and starts the response serializers. /// /// - Parameter error: The possible `Error` with which the instance will finish. func finish(error: AFError? = nil) { dispatchPrecondition(condition: .onQueue(underlyingQueue)) guard !$mutableState.isFinishing else { return } $mutableState.isFinishing = true if let error = error { self.error = error } // Start response handlers processNextResponseSerializer() eventMonitor?.requestDidFinish(self) } /// Appends the response serialization closure to the instance. /// /// - Note: This method will also `resume` the instance if `delegate.startImmediately` returns `true`. /// /// - Parameter closure: The closure containing the response serialization call. func appendResponseSerializer(_ closure: @escaping () -> Void) { $mutableState.write { mutableState in mutableState.responseSerializers.append(closure) if mutableState.state == .finished { mutableState.state = .resumed } if mutableState.responseSerializerProcessingFinished { underlyingQueue.async { self.processNextResponseSerializer() } } if mutableState.state.canTransitionTo(.resumed) { underlyingQueue.async { if self.delegate?.startImmediately == true { self.resume() } } } } } /// Returns the next response serializer closure to execute if there's one left. /// /// - Returns: The next response serialization closure, if there is one. func nextResponseSerializer() -> (() -> Void)? { var responseSerializer: (() -> Void)? $mutableState.write { mutableState in let responseSerializerIndex = mutableState.responseSerializerCompletions.count if responseSerializerIndex < mutableState.responseSerializers.count { responseSerializer = mutableState.responseSerializers[responseSerializerIndex] } } return responseSerializer } /// Processes the next response serializer and calls all completions if response serialization is complete. func processNextResponseSerializer() { guard let responseSerializer = nextResponseSerializer() else { // Execute all response serializer completions and clear them var completions: [() -> Void] = [] $mutableState.write { mutableState in completions = mutableState.responseSerializerCompletions // Clear out all response serializers and response serializer completions in mutable state since the // request is complete. It's important to do this prior to calling the completion closures in case // the completions call back into the request triggering a re-processing of the response serializers. // An example of how this can happen is by calling cancel inside a response completion closure. mutableState.responseSerializers.removeAll() mutableState.responseSerializerCompletions.removeAll() if mutableState.state.canTransitionTo(.finished) { mutableState.state = .finished } mutableState.responseSerializerProcessingFinished = true mutableState.isFinishing = false } completions.forEach { $0() } // Cleanup the request cleanup() return } serializationQueue.async { responseSerializer() } } /// Notifies the `Request` that the response serializer is complete. /// /// - Parameter completion: The completion handler provided with the response serializer, called when all serializers /// are complete. func responseSerializerDidComplete(completion: @escaping () -> Void) { $mutableState.write { $0.responseSerializerCompletions.append(completion) } processNextResponseSerializer() } /// Resets all task and response serializer related state for retry. func reset() { error = nil uploadProgress.totalUnitCount = 0 uploadProgress.completedUnitCount = 0 downloadProgress.totalUnitCount = 0 downloadProgress.completedUnitCount = 0 $mutableState.write { state in state.isFinishing = false state.responseSerializerCompletions = [] } } /// Called when updating the upload progress. /// /// - Parameters: /// - totalBytesSent: Total bytes sent so far. /// - totalBytesExpectedToSend: Total bytes expected to send. func updateUploadProgress(totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { uploadProgress.totalUnitCount = totalBytesExpectedToSend uploadProgress.completedUnitCount = totalBytesSent uploadProgressHandler?.queue.async { self.uploadProgressHandler?.handler(self.uploadProgress) } } /// Perform a closure on the current `state` while locked. /// /// - Parameter perform: The closure to perform. func withState(perform: (State) -> Void) { $mutableState.withState(perform: perform) } // MARK: Task Creation /// Called when creating a `URLSessionTask` for this `Request`. Subclasses must override. /// /// - Parameters: /// - request: `URLRequest` to use to create the `URLSessionTask`. /// - session: `URLSession` which creates the `URLSessionTask`. /// /// - Returns: The `URLSessionTask` created. func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { fatalError("Subclasses must override.") } // MARK: - Public API // These APIs are callable from any queue. // MARK: State /// Cancels the instance. Once cancelled, a `Request` can no longer be resumed or suspended. /// /// - Returns: The instance. @discardableResult public func cancel() -> Self { $mutableState.write { mutableState in guard mutableState.state.canTransitionTo(.cancelled) else { return } mutableState.state = .cancelled underlyingQueue.async { self.didCancel() } guard let task = mutableState.tasks.last, task.state != .completed else { underlyingQueue.async { self.finish() } return } // Resume to ensure metrics are gathered. task.resume() task.cancel() underlyingQueue.async { self.didCancelTask(task) } } return self } /// Suspends the instance. /// /// - Returns: The instance. @discardableResult public func suspend() -> Self { $mutableState.write { mutableState in guard mutableState.state.canTransitionTo(.suspended) else { return } mutableState.state = .suspended underlyingQueue.async { self.didSuspend() } guard let task = mutableState.tasks.last, task.state != .completed else { return } task.suspend() underlyingQueue.async { self.didSuspendTask(task) } } return self } /// Resumes the instance. /// /// - Returns: The instance. @discardableResult public func resume() -> Self { $mutableState.write { mutableState in guard mutableState.state.canTransitionTo(.resumed) else { return } mutableState.state = .resumed underlyingQueue.async { self.didResume() } guard let task = mutableState.tasks.last, task.state != .completed else { return } task.resume() underlyingQueue.async { self.didResumeTask(task) } } return self } // MARK: - Closure API /// Associates a credential using the provided values with the instance. /// /// - Parameters: /// - username: The username. /// - password: The password. /// - persistence: The `URLCredential.Persistence` for the created `URLCredential`. `.forSession` by default. /// /// - Returns: The instance. @discardableResult public func authenticate(username: String, password: String, persistence: URLCredential.Persistence = .forSession) -> Self { let credential = URLCredential(user: username, password: password, persistence: persistence) return authenticate(with: credential) } /// Associates the provided credential with the instance. /// /// - Parameter credential: The `URLCredential`. /// /// - Returns: The instance. @discardableResult public func authenticate(with credential: URLCredential) -> Self { $mutableState.credential = credential return self } /// Sets a closure to be called periodically during the lifecycle of the instance as data is read from the server. /// /// - Note: Only the last closure provided is used. /// /// - Parameters: /// - queue: The `DispatchQueue` to execute the closure on. `.main` by default. /// - closure: The closure to be executed periodically as data is read from the server. /// /// - Returns: The instance. @discardableResult public func downloadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self { $mutableState.downloadProgressHandler = (handler: closure, queue: queue) return self } /// Sets a closure to be called periodically during the lifecycle of the instance as data is sent to the server. /// /// - Note: Only the last closure provided is used. /// /// - Parameters: /// - queue: The `DispatchQueue` to execute the closure on. `.main` by default. /// - closure: The closure to be executed periodically as data is sent to the server. /// /// - Returns: The instance. @discardableResult public func uploadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self { $mutableState.uploadProgressHandler = (handler: closure, queue: queue) return self } // MARK: Redirects /// Sets the redirect handler for the instance which will be used if a redirect response is encountered. /// /// - Note: Attempting to set the redirect handler more than once is a logic error and will crash. /// /// - Parameter handler: The `RedirectHandler`. /// /// - Returns: The instance. @discardableResult public func redirect(using handler: RedirectHandler) -> Self { $mutableState.write { mutableState in precondition(mutableState.redirectHandler == nil, "Redirect handler has already been set.") mutableState.redirectHandler = handler } return self } // MARK: Cached Responses /// Sets the cached response handler for the `Request` which will be used when attempting to cache a response. /// /// - Note: Attempting to set the cache handler more than once is a logic error and will crash. /// /// - Parameter handler: The `CachedResponseHandler`. /// /// - Returns: The instance. @discardableResult public func cacheResponse(using handler: CachedResponseHandler) -> Self { $mutableState.write { mutableState in precondition(mutableState.cachedResponseHandler == nil, "Cached response handler has already been set.") mutableState.cachedResponseHandler = handler } return self } // MARK: - Lifetime APIs /// Sets a handler to be called when the cURL description of the request is available. /// /// - Note: When waiting for a `Request`'s `URLRequest` to be created, only the last `handler` will be called. /// /// - Parameters: /// - queue: `DispatchQueue` on which `handler` will be called. /// - handler: Closure to be called when the cURL description is available. /// /// - Returns: The instance. @discardableResult public func cURLDescription(on queue: DispatchQueue, calling handler: @escaping (String) -> Void) -> Self { $mutableState.write { mutableState in if mutableState.requests.last != nil { queue.async { handler(self.cURLDescription()) } } else { mutableState.cURLHandler = (queue, handler) } } return self } /// Sets a handler to be called when the cURL description of the request is available. /// /// - Note: When waiting for a `Request`'s `URLRequest` to be created, only the last `handler` will be called. /// /// - Parameter handler: Closure to be called when the cURL description is available. Called on the instance's /// `underlyingQueue` by default. /// /// - Returns: The instance. @discardableResult public func cURLDescription(calling handler: @escaping (String) -> Void) -> Self { $mutableState.write { mutableState in if mutableState.requests.last != nil { underlyingQueue.async { handler(self.cURLDescription()) } } else { mutableState.cURLHandler = (underlyingQueue, handler) } } return self } /// Sets a closure to called whenever Alamofire creates a `URLRequest` for this instance. /// /// - Note: This closure will be called multiple times if the instance adapts incoming `URLRequest`s or is retried. /// /// - Parameters: /// - queue: `DispatchQueue` on which `handler` will be called. `.main` by default. /// - handler: Closure to be called when a `URLRequest` is available. /// /// - Returns: The instance. @discardableResult public func onURLRequestCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLRequest) -> Void) -> Self { $mutableState.write { state in if let request = state.requests.last { queue.async { handler(request) } } state.urlRequestHandler = (queue, handler) } return self } /// Sets a closure to be called whenever the instance creates a `URLSessionTask`. /// /// - Note: This API should only be used to provide `URLSessionTask`s to existing API, like `NSFileProvider`. It /// **SHOULD NOT** be used to interact with tasks directly, as that may be break Alamofire features. /// Additionally, this closure may be called multiple times if the instance is retried. /// /// - Parameters: /// - queue: `DispatchQueue` on which `handler` will be called. `.main` by default. /// - handler: Closure to be called when the `URLSessionTask` is available. /// /// - Returns: The instance. @discardableResult public func onURLSessionTaskCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLSessionTask) -> Void) -> Self { $mutableState.write { state in if let task = state.tasks.last { queue.async { handler(task) } } state.urlSessionTaskHandler = (queue, handler) } return self } // MARK: Cleanup /// Adds a `finishHandler` closure to be called when the request completes. /// /// - Parameter closure: Closure to be called when the request finishes. func onFinish(perform finishHandler: @escaping () -> Void) { guard !isFinished else { finishHandler(); return } $mutableState.write { state in state.finishHandlers.append(finishHandler) } } /// Final cleanup step executed when the instance finishes response serialization. func cleanup() { delegate?.cleanup(after: self) let handlers = $mutableState.finishHandlers handlers.forEach { $0() } $mutableState.write { state in state.finishHandlers.removeAll() } } } // MARK: - Protocol Conformances extension Request: Equatable { public static func ==(lhs: Request, rhs: Request) -> Bool { lhs.id == rhs.id } } extension Request: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(id) } } extension Request: CustomStringConvertible { /// A textual representation of this instance, including the `HTTPMethod` and `URL` if the `URLRequest` has been /// created, as well as the response status code, if a response has been received. public var description: String { guard let request = performedRequests.last ?? lastRequest, let url = request.url, let method = request.httpMethod else { return "No request created yet." } let requestDescription = "\(method) \(url.absoluteString)" return response.map { "\(requestDescription) (\($0.statusCode))" } ?? requestDescription } } extension Request { /// cURL representation of the instance. /// /// - Returns: The cURL equivalent of the instance. public func cURLDescription() -> String { guard let request = lastRequest, let url = request.url, let host = url.host, let method = request.httpMethod else { return "$ curl command could not be created" } var components = ["$ curl -v"] components.append("-X \(method)") if let credentialStorage = delegate?.sessionConfiguration.urlCredentialStorage { let protectionSpace = URLProtectionSpace(host: host, port: url.port ?? 0, protocol: url.scheme, realm: host, authenticationMethod: NSURLAuthenticationMethodHTTPBasic) if let credentials = credentialStorage.credentials(for: protectionSpace)?.values { for credential in credentials { guard let user = credential.user, let password = credential.password else { continue } components.append("-u \(user):\(password)") } } else { if let credential = credential, let user = credential.user, let password = credential.password { components.append("-u \(user):\(password)") } } } if let configuration = delegate?.sessionConfiguration, configuration.httpShouldSetCookies { if let cookieStorage = configuration.httpCookieStorage, let cookies = cookieStorage.cookies(for: url), !cookies.isEmpty { let allCookies = cookies.map { "\($0.name)=\($0.value)" }.joined(separator: ";") components.append("-b \"\(allCookies)\"") } } var headers = HTTPHeaders() if let sessionHeaders = delegate?.sessionConfiguration.headers { for header in sessionHeaders where header.name != "Cookie" { headers[header.name] = header.value } } for header in request.headers where header.name != "Cookie" { headers[header.name] = header.value } for header in headers { let escapedValue = header.value.replacingOccurrences(of: "\"", with: "\\\"") components.append("-H \"\(header.name): \(escapedValue)\"") } if let httpBodyData = request.httpBody { let httpBody = String(decoding: httpBodyData, as: UTF8.self) var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"") escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"") components.append("-d \"\(escapedBody)\"") } components.append("\"\(url.absoluteString)\"") return components.joined(separator: " \\\n\t") } } /// Protocol abstraction for `Request`'s communication back to the `SessionDelegate`. public protocol RequestDelegate: AnyObject { /// `URLSessionConfiguration` used to create the underlying `URLSessionTask`s. var sessionConfiguration: URLSessionConfiguration { get } /// Determines whether the `Request` should automatically call `resume()` when adding the first response handler. var startImmediately: Bool { get } /// Notifies the delegate the `Request` has reached a point where it needs cleanup. /// /// - Parameter request: The `Request` to cleanup after. func cleanup(after request: Request) /// Asynchronously ask the delegate whether a `Request` will be retried. /// /// - Parameters: /// - request: `Request` which failed. /// - error: `Error` which produced the failure. /// - completion: Closure taking the `RetryResult` for evaluation. func retryResult(for request: Request, dueTo error: AFError, completion: @escaping (RetryResult) -> Void) /// Asynchronously retry the `Request`. /// /// - Parameters: /// - request: `Request` which will be retried. /// - timeDelay: `TimeInterval` after which the retry will be triggered. func retryRequest(_ request: Request, withDelay timeDelay: TimeInterval?) } // MARK: - Subclasses // MARK: - DataRequest /// `Request` subclass which handles in-memory `Data` download using `URLSessionDataTask`. public class DataRequest: Request { /// `URLRequestConvertible` value used to create `URLRequest`s for this instance. public let convertible: URLRequestConvertible /// `Data` read from the server so far. public var data: Data? { mutableData } /// Protected storage for the `Data` read by the instance. @Protected private var mutableData: Data? = nil /// Creates a `DataRequest` using the provided parameters. /// /// - Parameters: /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. /// - convertible: `URLRequestConvertible` value used to create `URLRequest`s for this instance. /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets /// `underlyingQueue`, but can be passed another queue from a `Session`. /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request`. init(id: UUID = UUID(), convertible: URLRequestConvertible, underlyingQueue: DispatchQueue, serializationQueue: DispatchQueue, eventMonitor: EventMonitor?, interceptor: RequestInterceptor?, delegate: RequestDelegate) { self.convertible = convertible super.init(id: id, underlyingQueue: underlyingQueue, serializationQueue: serializationQueue, eventMonitor: eventMonitor, interceptor: interceptor, delegate: delegate) } override func reset() { super.reset() mutableData = nil } /// Called when `Data` is received by this instance. /// /// - Note: Also calls `updateDownloadProgress`. /// /// - Parameter data: The `Data` received. func didReceive(data: Data) { if self.data == nil { mutableData = data } else { $mutableData.write { $0?.append(data) } } updateDownloadProgress() } override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { let copiedRequest = request return session.dataTask(with: copiedRequest) } /// Called to updated the `downloadProgress` of the instance. func updateDownloadProgress() { let totalBytesReceived = Int64(data?.count ?? 0) let totalBytesExpected = task?.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown downloadProgress.totalUnitCount = totalBytesExpected downloadProgress.completedUnitCount = totalBytesReceived downloadProgressHandler?.queue.async { self.downloadProgressHandler?.handler(self.downloadProgress) } } /// Validates the request, using the specified closure. /// /// - Note: If validation fails, subsequent calls to response handlers will have an associated error. /// /// - Parameter validation: `Validation` closure used to validate the response. /// /// - Returns: The instance. @discardableResult public func validate(_ validation: @escaping Validation) -> Self { let validator: () -> Void = { [unowned self] in guard self.error == nil, let response = self.response else { return } let result = validation(self.request, response, self.data) if case let .failure(error) = result { self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error))) } self.eventMonitor?.request(self, didValidateRequest: self.request, response: response, data: self.data, withResult: result) } $validators.write { $0.append(validator) } return self } } // MARK: - DataStreamRequest /// `Request` subclass which streams HTTP response `Data` through a `Handler` closure. public final class DataStreamRequest: Request { /// Closure type handling `DataStreamRequest.Stream` values. public typealias Handler = (Stream) throws -> Void /// Type encapsulating an `Event` as it flows through the stream, as well as a `CancellationToken` which can be used /// to stop the stream at any time. public struct Stream { /// Latest `Event` from the stream. public let event: Event /// Token used to cancel the stream. public let token: CancellationToken /// Cancel the ongoing stream by canceling the underlying `DataStreamRequest`. public func cancel() { token.cancel() } } /// Type representing an event flowing through the stream. Contains either the `Result` of processing streamed /// `Data` or the completion of the stream. public enum Event { /// Output produced every time the instance receives additional `Data`. The associated value contains the /// `Result` of processing the incoming `Data`. case stream(Result) /// Output produced when the instance has completed, whether due to stream end, cancellation, or an error. /// Associated `Completion` value contains the final state. case complete(Completion) } /// Value containing the state of a `DataStreamRequest` when the stream was completed. public struct Completion { /// Last `URLRequest` issued by the instance. public let request: URLRequest? /// Last `HTTPURLResponse` received by the instance. public let response: HTTPURLResponse? /// Last `URLSessionTaskMetrics` produced for the instance. public let metrics: URLSessionTaskMetrics? /// `AFError` produced for the instance, if any. public let error: AFError? } /// Type used to cancel an ongoing stream. public struct CancellationToken { weak var request: DataStreamRequest? init(_ request: DataStreamRequest) { self.request = request } /// Cancel the ongoing stream by canceling the underlying `DataStreamRequest`. public func cancel() { request?.cancel() } } /// `URLRequestConvertible` value used to create `URLRequest`s for this instance. public let convertible: URLRequestConvertible /// Whether or not the instance will be cancelled if stream parsing encounters an error. public let automaticallyCancelOnStreamError: Bool /// Internal mutable state specific to this type. struct StreamMutableState { /// `OutputStream` bound to the `InputStream` produced by `asInputStream`, if it has been called. var outputStream: OutputStream? /// Stream closures called as `Data` is received. var streams: [(_ data: Data) -> Void] = [] /// Number of currently executing streams. Used to ensure completions are only fired after all streams are /// enqueued. var numberOfExecutingStreams = 0 /// Completion calls enqueued while streams are still executing. var enqueuedCompletionEvents: [() -> Void] = [] } @Protected var streamMutableState = StreamMutableState() /// Creates a `DataStreamRequest` using the provided parameters. /// /// - Parameters: /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` /// by default. /// - convertible: `URLRequestConvertible` value used to create `URLRequest`s for this /// instance. /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance will be cancelled when an `Error` /// is thrown while serializing stream `Data`. /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default /// targets /// `underlyingQueue`, but can be passed another queue from a `Session`. /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. /// - delegate: `RequestDelegate` that provides an interface to actions not performed by /// the `Request`. init(id: UUID = UUID(), convertible: URLRequestConvertible, automaticallyCancelOnStreamError: Bool, underlyingQueue: DispatchQueue, serializationQueue: DispatchQueue, eventMonitor: EventMonitor?, interceptor: RequestInterceptor?, delegate: RequestDelegate) { self.convertible = convertible self.automaticallyCancelOnStreamError = automaticallyCancelOnStreamError super.init(id: id, underlyingQueue: underlyingQueue, serializationQueue: serializationQueue, eventMonitor: eventMonitor, interceptor: interceptor, delegate: delegate) } override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { let copiedRequest = request return session.dataTask(with: copiedRequest) } override func finish(error: AFError? = nil) { $streamMutableState.write { state in state.outputStream?.close() } super.finish(error: error) } func didReceive(data: Data) { $streamMutableState.write { state in #if !(os(Linux) || os(Windows)) if let stream = state.outputStream { underlyingQueue.async { var bytes = Array(data) stream.write(&bytes, maxLength: bytes.count) } } #endif state.numberOfExecutingStreams += state.streams.count let localState = state underlyingQueue.async { localState.streams.forEach { $0(data) } } } } /// Validates the `URLRequest` and `HTTPURLResponse` received for the instance using the provided `Validation` closure. /// /// - Parameter validation: `Validation` closure used to validate the request and response. /// /// - Returns: The `DataStreamRequest`. @discardableResult public func validate(_ validation: @escaping Validation) -> Self { let validator: () -> Void = { [unowned self] in guard self.error == nil, let response = self.response else { return } let result = validation(self.request, response) if case let .failure(error) = result { self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error))) } self.eventMonitor?.request(self, didValidateRequest: self.request, response: response, withResult: result) } $validators.write { $0.append(validator) } return self } #if !(os(Linux) || os(Windows)) /// Produces an `InputStream` that receives the `Data` received by the instance. /// /// - Note: The `InputStream` produced by this method must have `open()` called before being able to read `Data`. /// Additionally, this method will automatically call `resume()` on the instance, regardless of whether or /// not the creating session has `startRequestsImmediately` set to `true`. /// /// - Parameter bufferSize: Size, in bytes, of the buffer between the `OutputStream` and `InputStream`. /// /// - Returns: The `InputStream` bound to the internal `OutboundStream`. public func asInputStream(bufferSize: Int = 1024) -> InputStream? { defer { resume() } var inputStream: InputStream? $streamMutableState.write { state in Foundation.Stream.getBoundStreams(withBufferSize: bufferSize, inputStream: &inputStream, outputStream: &state.outputStream) state.outputStream?.open() } return inputStream } #endif func capturingError(from closure: () throws -> Void) { do { try closure() } catch { self.error = error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error))) cancel() } } func appendStreamCompletion(on queue: DispatchQueue, stream: @escaping Handler) { appendResponseSerializer { self.underlyingQueue.async { self.responseSerializerDidComplete { self.$streamMutableState.write { state in guard state.numberOfExecutingStreams == 0 else { state.enqueuedCompletionEvents.append { self.enqueueCompletion(on: queue, stream: stream) } return } self.enqueueCompletion(on: queue, stream: stream) } } } } } func enqueueCompletion(on queue: DispatchQueue, stream: @escaping Handler) { queue.async { do { let completion = Completion(request: self.request, response: self.response, metrics: self.metrics, error: self.error) try stream(.init(event: .complete(completion), token: .init(self))) } catch { // Ignore error, as errors on Completion can't be handled anyway. } } } } extension DataStreamRequest.Stream { /// Incoming `Result` values from `Event.stream`. public var result: Result? { guard case let .stream(result) = event else { return nil } return result } /// `Success` value of the instance, if any. public var value: Success? { guard case let .success(value) = result else { return nil } return value } /// `Failure` value of the instance, if any. public var error: Failure? { guard case let .failure(error) = result else { return nil } return error } /// `Completion` value of the instance, if any. public var completion: DataStreamRequest.Completion? { guard case let .complete(completion) = event else { return nil } return completion } } // MARK: - DownloadRequest /// `Request` subclass which downloads `Data` to a file on disk using `URLSessionDownloadTask`. public class DownloadRequest: Request { /// A set of options to be executed prior to moving a downloaded file from the temporary `URL` to the destination /// `URL`. public struct Options: OptionSet { /// Specifies that intermediate directories for the destination URL should be created. public static let createIntermediateDirectories = Options(rawValue: 1 << 0) /// Specifies that any previous file at the destination `URL` should be removed. public static let removePreviousFile = Options(rawValue: 1 << 1) public let rawValue: Int public init(rawValue: Int) { self.rawValue = rawValue } } // MARK: Destination /// A closure executed once a `DownloadRequest` has successfully completed in order to determine where to move the /// temporary file written to during the download process. The closure takes two arguments: the temporary file URL /// and the `HTTPURLResponse`, and returns two values: the file URL where the temporary file should be moved and /// the options defining how the file should be moved. /// /// - Note: Downloads from a local `file://` `URL`s do not use the `Destination` closure, as those downloads do not /// return an `HTTPURLResponse`. Instead the file is merely moved within the temporary directory. public typealias Destination = (_ temporaryURL: URL, _ response: HTTPURLResponse) -> (destinationURL: URL, options: Options) /// Creates a download file destination closure which uses the default file manager to move the temporary file to a /// file URL in the first available directory with the specified search path directory and search path domain mask. /// /// - Parameters: /// - directory: The search path directory. `.documentDirectory` by default. /// - domain: The search path domain mask. `.userDomainMask` by default. /// - options: `DownloadRequest.Options` used when moving the downloaded file to its destination. None by /// default. /// - Returns: The `Destination` closure. public class func suggestedDownloadDestination(for directory: FileManager.SearchPathDirectory = .documentDirectory, in domain: FileManager.SearchPathDomainMask = .userDomainMask, options: Options = []) -> Destination { { temporaryURL, response in let directoryURLs = FileManager.default.urls(for: directory, in: domain) let url = directoryURLs.first?.appendingPathComponent(response.suggestedFilename!) ?? temporaryURL return (url, options) } } /// Default `Destination` used by Alamofire to ensure all downloads persist. This `Destination` prepends /// `Alamofire_` to the automatically generated download name and moves it within the temporary directory. Files /// with this destination must be additionally moved if they should survive the system reclamation of temporary /// space. static let defaultDestination: Destination = { url, _ in (defaultDestinationURL(url), []) } /// Default `URL` creation closure. Creates a `URL` in the temporary directory with `Alamofire_` prepended to the /// provided file name. static let defaultDestinationURL: (URL) -> URL = { url in let filename = "Alamofire_\(url.lastPathComponent)" let destination = url.deletingLastPathComponent().appendingPathComponent(filename) return destination } // MARK: Downloadable /// Type describing the source used to create the underlying `URLSessionDownloadTask`. public enum Downloadable { /// Download should be started from the `URLRequest` produced by the associated `URLRequestConvertible` value. case request(URLRequestConvertible) /// Download should be started from the associated resume `Data` value. case resumeData(Data) } // MARK: Mutable State /// Type containing all mutable state for `DownloadRequest` instances. private struct DownloadRequestMutableState { /// Possible resume `Data` produced when cancelling the instance. var resumeData: Data? /// `URL` to which `Data` is being downloaded. var fileURL: URL? } /// Protected mutable state specific to `DownloadRequest`. @Protected private var mutableDownloadState = DownloadRequestMutableState() /// If the download is resumable and is eventually cancelled or fails, this value may be used to resume the download /// using the `download(resumingWith data:)` API. /// /// - Note: For more information about `resumeData`, see [Apple's documentation](https://developer.apple.com/documentation/foundation/urlsessiondownloadtask/1411634-cancel). public var resumeData: Data? { #if !(os(Linux) || os(Windows)) return $mutableDownloadState.resumeData ?? error?.downloadResumeData #else return $mutableDownloadState.resumeData #endif } /// If the download is successful, the `URL` where the file was downloaded. public var fileURL: URL? { $mutableDownloadState.fileURL } // MARK: Initial State /// `Downloadable` value used for this instance. public let downloadable: Downloadable /// The `Destination` to which the downloaded file is moved. let destination: Destination /// Creates a `DownloadRequest` using the provided parameters. /// /// - Parameters: /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. /// - downloadable: `Downloadable` value used to create `URLSessionDownloadTasks` for the instance. /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets /// `underlyingQueue`, but can be passed another queue from a `Session`. /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request` /// - destination: `Destination` closure used to move the downloaded file to its final location. init(id: UUID = UUID(), downloadable: Downloadable, underlyingQueue: DispatchQueue, serializationQueue: DispatchQueue, eventMonitor: EventMonitor?, interceptor: RequestInterceptor?, delegate: RequestDelegate, destination: @escaping Destination) { self.downloadable = downloadable self.destination = destination super.init(id: id, underlyingQueue: underlyingQueue, serializationQueue: serializationQueue, eventMonitor: eventMonitor, interceptor: interceptor, delegate: delegate) } override func reset() { super.reset() $mutableDownloadState.write { $0.resumeData = nil $0.fileURL = nil } } /// Called when a download has finished. /// /// - Parameters: /// - task: `URLSessionTask` that finished the download. /// - result: `Result` of the automatic move to `destination`. func didFinishDownloading(using task: URLSessionTask, with result: Result) { eventMonitor?.request(self, didFinishDownloadingUsing: task, with: result) switch result { case let .success(url): $mutableDownloadState.fileURL = url case let .failure(error): self.error = error } } /// Updates the `downloadProgress` using the provided values. /// /// - Parameters: /// - bytesWritten: Total bytes written so far. /// - totalBytesExpectedToWrite: Total bytes expected to write. func updateDownloadProgress(bytesWritten: Int64, totalBytesExpectedToWrite: Int64) { downloadProgress.totalUnitCount = totalBytesExpectedToWrite downloadProgress.completedUnitCount += bytesWritten downloadProgressHandler?.queue.async { self.downloadProgressHandler?.handler(self.downloadProgress) } } override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { session.downloadTask(with: request) } /// Creates a `URLSessionTask` from the provided resume data. /// /// - Parameters: /// - data: `Data` used to resume the download. /// - session: `URLSession` used to create the `URLSessionTask`. /// /// - Returns: The `URLSessionTask` created. public func task(forResumeData data: Data, using session: URLSession) -> URLSessionTask { session.downloadTask(withResumeData: data) } /// Cancels the instance. Once cancelled, a `DownloadRequest` can no longer be resumed or suspended. /// /// - Note: This method will NOT produce resume data. If you wish to cancel and produce resume data, use /// `cancel(producingResumeData:)` or `cancel(byProducingResumeData:)`. /// /// - Returns: The instance. @discardableResult override public func cancel() -> Self { cancel(producingResumeData: false) } /// Cancels the instance, optionally producing resume data. Once cancelled, a `DownloadRequest` can no longer be /// resumed or suspended. /// /// - Note: If `producingResumeData` is `true`, the `resumeData` property will be populated with any resume data, if /// available. /// /// - Returns: The instance. @discardableResult public func cancel(producingResumeData shouldProduceResumeData: Bool) -> Self { cancel(optionallyProducingResumeData: shouldProduceResumeData ? { _ in } : nil) } /// Cancels the instance while producing resume data. Once cancelled, a `DownloadRequest` can no longer be resumed /// or suspended. /// /// - Note: The resume data passed to the completion handler will also be available on the instance's `resumeData` /// property. /// /// - Parameter completionHandler: The completion handler that is called when the download has been successfully /// cancelled. It is not guaranteed to be called on a particular queue, so you may /// want use an appropriate queue to perform your work. /// /// - Returns: The instance. @discardableResult public func cancel(byProducingResumeData completionHandler: @escaping (_ data: Data?) -> Void) -> Self { cancel(optionallyProducingResumeData: completionHandler) } /// Internal implementation of cancellation that optionally takes a resume data handler. If no handler is passed, /// cancellation is performed without producing resume data. /// /// - Parameter completionHandler: Optional resume data handler. /// /// - Returns: The instance. private func cancel(optionallyProducingResumeData completionHandler: ((_ resumeData: Data?) -> Void)?) -> Self { $mutableState.write { mutableState in guard mutableState.state.canTransitionTo(.cancelled) else { return } mutableState.state = .cancelled underlyingQueue.async { self.didCancel() } guard let task = mutableState.tasks.last as? URLSessionDownloadTask, task.state != .completed else { underlyingQueue.async { self.finish() } return } if let completionHandler = completionHandler { // Resume to ensure metrics are gathered. task.resume() task.cancel { resumeData in self.$mutableDownloadState.resumeData = resumeData self.underlyingQueue.async { self.didCancelTask(task) } completionHandler(resumeData) } } else { // Resume to ensure metrics are gathered. task.resume() task.cancel() self.underlyingQueue.async { self.didCancelTask(task) } } } return self } /// Validates the request, using the specified closure. /// /// - Note: If validation fails, subsequent calls to response handlers will have an associated error. /// /// - Parameter validation: `Validation` closure to validate the response. /// /// - Returns: The instance. @discardableResult public func validate(_ validation: @escaping Validation) -> Self { let validator: () -> Void = { [unowned self] in guard self.error == nil, let response = self.response else { return } let result = validation(self.request, response, self.fileURL) if case let .failure(error) = result { self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error))) } self.eventMonitor?.request(self, didValidateRequest: self.request, response: response, fileURL: self.fileURL, withResult: result) } $validators.write { $0.append(validator) } return self } } // MARK: - UploadRequest /// `DataRequest` subclass which handles `Data` upload from memory, file, or stream using `URLSessionUploadTask`. public class UploadRequest: DataRequest { /// Type describing the origin of the upload, whether `Data`, file, or stream. public enum Uploadable { /// Upload from the provided `Data` value. case data(Data) /// Upload from the provided file `URL`, as well as a `Bool` determining whether the source file should be /// automatically removed once uploaded. case file(URL, shouldRemove: Bool) /// Upload from the provided `InputStream`. case stream(InputStream) } // MARK: Initial State /// The `UploadableConvertible` value used to produce the `Uploadable` value for this instance. public let upload: UploadableConvertible /// `FileManager` used to perform cleanup tasks, including the removal of multipart form encoded payloads written /// to disk. public let fileManager: FileManager // MARK: Mutable State /// `Uploadable` value used by the instance. public var uploadable: Uploadable? /// Creates an `UploadRequest` using the provided parameters. /// /// - Parameters: /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. /// - convertible: `UploadConvertible` value used to determine the type of upload to be performed. /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets /// `underlyingQueue`, but can be passed another queue from a `Session`. /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. /// - fileManager: `FileManager` used to perform cleanup tasks, including the removal of multipart form /// encoded payloads written to disk. /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request`. init(id: UUID = UUID(), convertible: UploadConvertible, underlyingQueue: DispatchQueue, serializationQueue: DispatchQueue, eventMonitor: EventMonitor?, interceptor: RequestInterceptor?, fileManager: FileManager, delegate: RequestDelegate) { upload = convertible self.fileManager = fileManager super.init(id: id, convertible: convertible, underlyingQueue: underlyingQueue, serializationQueue: serializationQueue, eventMonitor: eventMonitor, interceptor: interceptor, delegate: delegate) } /// Called when the `Uploadable` value has been created from the `UploadConvertible`. /// /// - Parameter uploadable: The `Uploadable` that was created. func didCreateUploadable(_ uploadable: Uploadable) { self.uploadable = uploadable eventMonitor?.request(self, didCreateUploadable: uploadable) } /// Called when the `Uploadable` value could not be created. /// /// - Parameter error: `AFError` produced by the failure. func didFailToCreateUploadable(with error: AFError) { self.error = error eventMonitor?.request(self, didFailToCreateUploadableWithError: error) retryOrFinish(error: error) } override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { guard let uploadable = uploadable else { fatalError("Attempting to create a URLSessionUploadTask when Uploadable value doesn't exist.") } switch uploadable { case let .data(data): return session.uploadTask(with: request, from: data) case let .file(url, _): return session.uploadTask(with: request, fromFile: url) case .stream: return session.uploadTask(withStreamedRequest: request) } } override func reset() { // Uploadable must be recreated on every retry. uploadable = nil super.reset() } /// Produces the `InputStream` from `uploadable`, if it can. /// /// - Note: Calling this method with a non-`.stream` `Uploadable` is a logic error and will crash. /// /// - Returns: The `InputStream`. func inputStream() -> InputStream { guard let uploadable = uploadable else { fatalError("Attempting to access the input stream but the uploadable doesn't exist.") } guard case let .stream(stream) = uploadable else { fatalError("Attempted to access the stream of an UploadRequest that wasn't created with one.") } eventMonitor?.request(self, didProvideInputStream: stream) return stream } override public func cleanup() { defer { super.cleanup() } guard let uploadable = uploadable, case let .file(url, shouldRemove) = uploadable, shouldRemove else { return } try? fileManager.removeItem(at: url) } } /// A type that can produce an `UploadRequest.Uploadable` value. public protocol UploadableConvertible { /// Produces an `UploadRequest.Uploadable` value from the instance. /// /// - Returns: The `UploadRequest.Uploadable`. /// - Throws: Any `Error` produced during creation. func createUploadable() throws -> UploadRequest.Uploadable } extension UploadRequest.Uploadable: UploadableConvertible { public func createUploadable() throws -> UploadRequest.Uploadable { self } } /// A type that can be converted to an upload, whether from an `UploadRequest.Uploadable` or `URLRequestConvertible`. public protocol UploadConvertible: UploadableConvertible & URLRequestConvertible {} ================================================ FILE: JetChat/Pods/Alamofire/Source/RequestInterceptor.swift ================================================ // // RequestInterceptor.swift // // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Stores all state associated with a `URLRequest` being adapted. public struct RequestAdapterState { /// The `UUID` of the `Request` associated with the `URLRequest` to adapt. public let requestID: UUID /// The `Session` associated with the `URLRequest` to adapt. public let session: Session } // MARK: - /// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary. public protocol RequestAdapter { /// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result. /// /// - Parameters: /// - urlRequest: The `URLRequest` to adapt. /// - session: The `Session` that will execute the `URLRequest`. /// - completion: The completion handler that must be called when adaptation is complete. func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) /// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result. /// /// - Parameters: /// - urlRequest: The `URLRequest` to adapt. /// - state: The `RequestAdapterState` associated with the `URLRequest`. /// - completion: The completion handler that must be called when adaptation is complete. func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result) -> Void) } extension RequestAdapter { public func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result) -> Void) { adapt(urlRequest, for: state.session, completion: completion) } } // MARK: - /// Outcome of determination whether retry is necessary. public enum RetryResult { /// Retry should be attempted immediately. case retry /// Retry should be attempted after the associated `TimeInterval`. case retryWithDelay(TimeInterval) /// Do not retry. case doNotRetry /// Do not retry due to the associated `Error`. case doNotRetryWithError(Error) } extension RetryResult { var retryRequired: Bool { switch self { case .retry, .retryWithDelay: return true default: return false } } var delay: TimeInterval? { switch self { case let .retryWithDelay(delay): return delay default: return nil } } var error: Error? { guard case let .doNotRetryWithError(error) = self else { return nil } return error } } /// A type that determines whether a request should be retried after being executed by the specified session manager /// and encountering an error. public protocol RequestRetrier { /// Determines whether the `Request` should be retried by calling the `completion` closure. /// /// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs /// to be retried. The one requirement is that the completion closure is called to ensure the request is properly /// cleaned up after. /// /// - Parameters: /// - request: `Request` that failed due to the provided `Error`. /// - session: `Session` that produced the `Request`. /// - error: `Error` encountered while executing the `Request`. /// - completion: Completion closure to be executed when a retry decision has been determined. func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) } // MARK: - /// Type that provides both `RequestAdapter` and `RequestRetrier` functionality. public protocol RequestInterceptor: RequestAdapter, RequestRetrier {} extension RequestInterceptor { public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { completion(.success(urlRequest)) } public func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { completion(.doNotRetry) } } /// `RequestAdapter` closure definition. public typealias AdaptHandler = (URLRequest, Session, _ completion: @escaping (Result) -> Void) -> Void /// `RequestRetrier` closure definition. public typealias RetryHandler = (Request, Session, Error, _ completion: @escaping (RetryResult) -> Void) -> Void // MARK: - /// Closure-based `RequestAdapter`. open class Adapter: RequestInterceptor { private let adaptHandler: AdaptHandler /// Creates an instance using the provided closure. /// /// - Parameter adaptHandler: `AdaptHandler` closure to be executed when handling request adaptation. public init(_ adaptHandler: @escaping AdaptHandler) { self.adaptHandler = adaptHandler } open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { adaptHandler(urlRequest, session, completion) } open func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result) -> Void) { adaptHandler(urlRequest, state.session, completion) } } #if swift(>=5.5) extension RequestAdapter where Self == Adapter { /// Creates an `Adapter` using the provided `AdaptHandler` closure. /// /// - Parameter closure: `AdaptHandler` to use to adapt the request. /// - Returns: The `Adapter`. public static func adapter(using closure: @escaping AdaptHandler) -> Adapter { Adapter(closure) } } #endif // MARK: - /// Closure-based `RequestRetrier`. open class Retrier: RequestInterceptor { private let retryHandler: RetryHandler /// Creates an instance using the provided closure. /// /// - Parameter retryHandler: `RetryHandler` closure to be executed when handling request retry. public init(_ retryHandler: @escaping RetryHandler) { self.retryHandler = retryHandler } open func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { retryHandler(request, session, error, completion) } } #if swift(>=5.5) extension RequestRetrier where Self == Retrier { /// Creates a `Retrier` using the provided `RetryHandler` closure. /// /// - Parameter closure: `RetryHandler` to use to retry the request. /// - Returns: The `Retrier`. public static func retrier(using closure: @escaping RetryHandler) -> Retrier { Retrier(closure) } } #endif // MARK: - /// `RequestInterceptor` which can use multiple `RequestAdapter` and `RequestRetrier` values. open class Interceptor: RequestInterceptor { /// All `RequestAdapter`s associated with the instance. These adapters will be run until one fails. public let adapters: [RequestAdapter] /// All `RequestRetrier`s associated with the instance. These retriers will be run one at a time until one triggers retry. public let retriers: [RequestRetrier] /// Creates an instance from `AdaptHandler` and `RetryHandler` closures. /// /// - Parameters: /// - adaptHandler: `AdaptHandler` closure to be used. /// - retryHandler: `RetryHandler` closure to be used. public init(adaptHandler: @escaping AdaptHandler, retryHandler: @escaping RetryHandler) { adapters = [Adapter(adaptHandler)] retriers = [Retrier(retryHandler)] } /// Creates an instance from `RequestAdapter` and `RequestRetrier` values. /// /// - Parameters: /// - adapter: `RequestAdapter` value to be used. /// - retrier: `RequestRetrier` value to be used. public init(adapter: RequestAdapter, retrier: RequestRetrier) { adapters = [adapter] retriers = [retrier] } /// Creates an instance from the arrays of `RequestAdapter` and `RequestRetrier` values. /// /// - Parameters: /// - adapters: `RequestAdapter` values to be used. /// - retriers: `RequestRetrier` values to be used. /// - interceptors: `RequestInterceptor`s to be used. public init(adapters: [RequestAdapter] = [], retriers: [RequestRetrier] = [], interceptors: [RequestInterceptor] = []) { self.adapters = adapters + interceptors self.retriers = retriers + interceptors } open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { adapt(urlRequest, for: session, using: adapters, completion: completion) } private func adapt(_ urlRequest: URLRequest, for session: Session, using adapters: [RequestAdapter], completion: @escaping (Result) -> Void) { var pendingAdapters = adapters guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return } let adapter = pendingAdapters.removeFirst() adapter.adapt(urlRequest, for: session) { result in switch result { case let .success(urlRequest): self.adapt(urlRequest, for: session, using: pendingAdapters, completion: completion) case .failure: completion(result) } } } open func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result) -> Void) { adapt(urlRequest, using: state, adapters: adapters, completion: completion) } private func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, adapters: [RequestAdapter], completion: @escaping (Result) -> Void) { var pendingAdapters = adapters guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return } let adapter = pendingAdapters.removeFirst() adapter.adapt(urlRequest, using: state) { result in switch result { case let .success(urlRequest): self.adapt(urlRequest, using: state, adapters: pendingAdapters, completion: completion) case .failure: completion(result) } } } open func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { retry(request, for: session, dueTo: error, using: retriers, completion: completion) } private func retry(_ request: Request, for session: Session, dueTo error: Error, using retriers: [RequestRetrier], completion: @escaping (RetryResult) -> Void) { var pendingRetriers = retriers guard !pendingRetriers.isEmpty else { completion(.doNotRetry); return } let retrier = pendingRetriers.removeFirst() retrier.retry(request, for: session, dueTo: error) { result in switch result { case .retry, .retryWithDelay, .doNotRetryWithError: completion(result) case .doNotRetry: // Only continue to the next retrier if retry was not triggered and no error was encountered self.retry(request, for: session, dueTo: error, using: pendingRetriers, completion: completion) } } } } #if swift(>=5.5) extension RequestInterceptor where Self == Interceptor { /// Creates an `Interceptor` using the provided `AdaptHandler` and `RetryHandler` closures. /// /// - Parameters: /// - adapter: `AdapterHandler`to use to adapt the request. /// - retrier: `RetryHandler` to use to retry the request. /// - Returns: The `Interceptor`. public static func interceptor(adapter: @escaping AdaptHandler, retrier: @escaping RetryHandler) -> Interceptor { Interceptor(adaptHandler: adapter, retryHandler: retrier) } /// Creates an `Interceptor` using the provided `RequestAdapter` and `RequestRetrier` instances. /// - Parameters: /// - adapter: `RequestAdapter` to use to adapt the request /// - retrier: `RequestRetrier` to use to retry the request. /// - Returns: The `Interceptor`. public static func interceptor(adapter: RequestAdapter, retrier: RequestRetrier) -> Interceptor { Interceptor(adapter: adapter, retrier: retrier) } /// Creates an `Interceptor` using the provided `RequestAdapter`s, `RequestRetrier`s, and `RequestInterceptor`s. /// - Parameters: /// - adapters: `RequestAdapter`s to use to adapt the request. These adapters will be run until one fails. /// - retriers: `RequestRetrier`s to use to retry the request. These retriers will be run one at a time until /// a retry is triggered. /// - interceptors: `RequestInterceptor`s to use to intercept the request. /// - Returns: The `Interceptor`. public static func interceptor(adapters: [RequestAdapter] = [], retriers: [RequestRetrier] = [], interceptors: [RequestInterceptor] = []) -> Interceptor { Interceptor(adapters: adapters, retriers: retriers, interceptors: interceptors) } } #endif ================================================ FILE: JetChat/Pods/Alamofire/Source/RequestTaskMap.swift ================================================ // // RequestTaskMap.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// A type that maintains a two way, one to one map of `URLSessionTask`s to `Request`s. struct RequestTaskMap { private typealias Events = (completed: Bool, metricsGathered: Bool) private var tasksToRequests: [URLSessionTask: Request] private var requestsToTasks: [Request: URLSessionTask] private var taskEvents: [URLSessionTask: Events] var requests: [Request] { Array(tasksToRequests.values) } init(tasksToRequests: [URLSessionTask: Request] = [:], requestsToTasks: [Request: URLSessionTask] = [:], taskEvents: [URLSessionTask: (completed: Bool, metricsGathered: Bool)] = [:]) { self.tasksToRequests = tasksToRequests self.requestsToTasks = requestsToTasks self.taskEvents = taskEvents } subscript(_ request: Request) -> URLSessionTask? { get { requestsToTasks[request] } set { guard let newValue = newValue else { guard let task = requestsToTasks[request] else { fatalError("RequestTaskMap consistency error: no task corresponding to request found.") } requestsToTasks.removeValue(forKey: request) tasksToRequests.removeValue(forKey: task) taskEvents.removeValue(forKey: task) return } requestsToTasks[request] = newValue tasksToRequests[newValue] = request taskEvents[newValue] = (completed: false, metricsGathered: false) } } subscript(_ task: URLSessionTask) -> Request? { get { tasksToRequests[task] } set { guard let newValue = newValue else { guard let request = tasksToRequests[task] else { fatalError("RequestTaskMap consistency error: no request corresponding to task found.") } tasksToRequests.removeValue(forKey: task) requestsToTasks.removeValue(forKey: request) taskEvents.removeValue(forKey: task) return } tasksToRequests[task] = newValue requestsToTasks[newValue] = task taskEvents[task] = (completed: false, metricsGathered: false) } } var count: Int { precondition(tasksToRequests.count == requestsToTasks.count, "RequestTaskMap.count invalid, requests.count: \(tasksToRequests.count) != tasks.count: \(requestsToTasks.count)") return tasksToRequests.count } var eventCount: Int { precondition(taskEvents.count == count, "RequestTaskMap.eventCount invalid, count: \(count) != taskEvents.count: \(taskEvents.count)") return taskEvents.count } var isEmpty: Bool { precondition(tasksToRequests.isEmpty == requestsToTasks.isEmpty, "RequestTaskMap.isEmpty invalid, requests.isEmpty: \(tasksToRequests.isEmpty) != tasks.isEmpty: \(requestsToTasks.isEmpty)") return tasksToRequests.isEmpty } var isEventsEmpty: Bool { precondition(taskEvents.isEmpty == isEmpty, "RequestTaskMap.isEventsEmpty invalid, isEmpty: \(isEmpty) != taskEvents.isEmpty: \(taskEvents.isEmpty)") return taskEvents.isEmpty } mutating func disassociateIfNecessaryAfterGatheringMetricsForTask(_ task: URLSessionTask) -> Bool { guard let events = taskEvents[task] else { fatalError("RequestTaskMap consistency error: no events corresponding to task found.") } switch (events.completed, events.metricsGathered) { case (_, true): fatalError("RequestTaskMap consistency error: duplicate metricsGatheredForTask call.") case (false, false): taskEvents[task] = (completed: false, metricsGathered: true); return false case (true, false): self[task] = nil; return true } } mutating func disassociateIfNecessaryAfterCompletingTask(_ task: URLSessionTask) -> Bool { guard let events = taskEvents[task] else { fatalError("RequestTaskMap consistency error: no events corresponding to task found.") } switch (events.completed, events.metricsGathered) { case (true, _): fatalError("RequestTaskMap consistency error: duplicate completionReceivedForTask call.") #if os(Linux) // Linux doesn't gather metrics, so unconditionally remove the reference and return true. default: self[task] = nil; return true #else case (false, false): if #available(macOS 10.12, iOS 10, watchOS 7, tvOS 10, *) { taskEvents[task] = (completed: true, metricsGathered: false); return false } else { // watchOS < 7 doesn't gather metrics, so unconditionally remove the reference and return true. self[task] = nil; return true } case (false, true): self[task] = nil; return true #endif } } } ================================================ FILE: JetChat/Pods/Alamofire/Source/Response.swift ================================================ // // Response.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Default type of `DataResponse` returned by Alamofire, with an `AFError` `Failure` type. public typealias AFDataResponse = DataResponse /// Default type of `DownloadResponse` returned by Alamofire, with an `AFError` `Failure` type. public typealias AFDownloadResponse = DownloadResponse /// Type used to store all values associated with a serialized response of a `DataRequest` or `UploadRequest`. public struct DataResponse { /// The URL request sent to the server. public let request: URLRequest? /// The server's response to the URL request. public let response: HTTPURLResponse? /// The data returned by the server. public let data: Data? /// The final metrics of the response. /// /// - Note: Due to `FB7624529`, collection of `URLSessionTaskMetrics` on watchOS is currently disabled.` /// public let metrics: URLSessionTaskMetrics? /// The time taken to serialize the response. public let serializationDuration: TimeInterval /// The result of response serialization. public let result: Result /// Returns the associated value of the result if it is a success, `nil` otherwise. public var value: Success? { result.success } /// Returns the associated error value if the result if it is a failure, `nil` otherwise. public var error: Failure? { result.failure } /// Creates a `DataResponse` instance with the specified parameters derived from the response serialization. /// /// - Parameters: /// - request: The `URLRequest` sent to the server. /// - response: The `HTTPURLResponse` from the server. /// - data: The `Data` returned by the server. /// - metrics: The `URLSessionTaskMetrics` of the `DataRequest` or `UploadRequest`. /// - serializationDuration: The duration taken by serialization. /// - result: The `Result` of response serialization. public init(request: URLRequest?, response: HTTPURLResponse?, data: Data?, metrics: URLSessionTaskMetrics?, serializationDuration: TimeInterval, result: Result) { self.request = request self.response = response self.data = data self.metrics = metrics self.serializationDuration = serializationDuration self.result = result } } // MARK: - extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible { /// The textual representation used when written to an output stream, which includes whether the result was a /// success or failure. public var description: String { "\(result)" } /// The debug textual representation used when written to an output stream, which includes (if available) a summary /// of the `URLRequest`, the request's headers and body (if decodable as a `String` below 100KB); the /// `HTTPURLResponse`'s status code, headers, and body; the duration of the network and serialization actions; and /// the `Result` of serialization. public var debugDescription: String { guard let urlRequest = request else { return "[Request]: None\n[Result]: \(result)" } let requestDescription = DebugDescription.description(of: urlRequest) let responseDescription = response.map { response in let responseBodyDescription = DebugDescription.description(for: data, headers: response.headers) return """ \(DebugDescription.description(of: response)) \(responseBodyDescription.indentingNewlines()) """ } ?? "[Response]: None" let networkDuration = metrics.map { "\($0.taskInterval.duration)s" } ?? "None" return """ \(requestDescription) \(responseDescription) [Network Duration]: \(networkDuration) [Serialization Duration]: \(serializationDuration)s [Result]: \(result) """ } } // MARK: - extension DataResponse { /// Evaluates the specified closure when the result of this `DataResponse` is a success, passing the unwrapped /// result value as a parameter. /// /// Use the `map` method with a closure that does not throw. For example: /// /// let possibleData: DataResponse = ... /// let possibleInt = possibleData.map { $0.count } /// /// - parameter transform: A closure that takes the success value of the instance's result. /// /// - returns: A `DataResponse` whose result wraps the value returned by the given closure. If this instance's /// result is a failure, returns a response wrapping the same failure. public func map(_ transform: (Success) -> NewSuccess) -> DataResponse { DataResponse(request: request, response: response, data: data, metrics: metrics, serializationDuration: serializationDuration, result: result.map(transform)) } /// Evaluates the given closure when the result of this `DataResponse` is a success, passing the unwrapped result /// value as a parameter. /// /// Use the `tryMap` method with a closure that may throw an error. For example: /// /// let possibleData: DataResponse = ... /// let possibleObject = possibleData.tryMap { /// try JSONSerialization.jsonObject(with: $0) /// } /// /// - parameter transform: A closure that takes the success value of the instance's result. /// /// - returns: A success or failure `DataResponse` depending on the result of the given closure. If this instance's /// result is a failure, returns the same failure. public func tryMap(_ transform: (Success) throws -> NewSuccess) -> DataResponse { DataResponse(request: request, response: response, data: data, metrics: metrics, serializationDuration: serializationDuration, result: result.tryMap(transform)) } /// Evaluates the specified closure when the `DataResponse` is a failure, passing the unwrapped error as a parameter. /// /// Use the `mapError` function with a closure that does not throw. For example: /// /// let possibleData: DataResponse = ... /// let withMyError = possibleData.mapError { MyError.error($0) } /// /// - Parameter transform: A closure that takes the error of the instance. /// /// - Returns: A `DataResponse` instance containing the result of the transform. public func mapError(_ transform: (Failure) -> NewFailure) -> DataResponse { DataResponse(request: request, response: response, data: data, metrics: metrics, serializationDuration: serializationDuration, result: result.mapError(transform)) } /// Evaluates the specified closure when the `DataResponse` is a failure, passing the unwrapped error as a parameter. /// /// Use the `tryMapError` function with a closure that may throw an error. For example: /// /// let possibleData: DataResponse = ... /// let possibleObject = possibleData.tryMapError { /// try someFailableFunction(taking: $0) /// } /// /// - Parameter transform: A throwing closure that takes the error of the instance. /// /// - Returns: A `DataResponse` instance containing the result of the transform. public func tryMapError(_ transform: (Failure) throws -> NewFailure) -> DataResponse { DataResponse(request: request, response: response, data: data, metrics: metrics, serializationDuration: serializationDuration, result: result.tryMapError(transform)) } } // MARK: - /// Used to store all data associated with a serialized response of a download request. public struct DownloadResponse { /// The URL request sent to the server. public let request: URLRequest? /// The server's response to the URL request. public let response: HTTPURLResponse? /// The final destination URL of the data returned from the server after it is moved. public let fileURL: URL? /// The resume data generated if the request was cancelled. public let resumeData: Data? /// The final metrics of the response. /// /// - Note: Due to `FB7624529`, collection of `URLSessionTaskMetrics` on watchOS is currently disabled.` /// public let metrics: URLSessionTaskMetrics? /// The time taken to serialize the response. public let serializationDuration: TimeInterval /// The result of response serialization. public let result: Result /// Returns the associated value of the result if it is a success, `nil` otherwise. public var value: Success? { result.success } /// Returns the associated error value if the result if it is a failure, `nil` otherwise. public var error: Failure? { result.failure } /// Creates a `DownloadResponse` instance with the specified parameters derived from response serialization. /// /// - Parameters: /// - request: The `URLRequest` sent to the server. /// - response: The `HTTPURLResponse` from the server. /// - fileURL: The final destination URL of the data returned from the server after it is moved. /// - resumeData: The resume `Data` generated if the request was cancelled. /// - metrics: The `URLSessionTaskMetrics` of the `DownloadRequest`. /// - serializationDuration: The duration taken by serialization. /// - result: The `Result` of response serialization. public init(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, resumeData: Data?, metrics: URLSessionTaskMetrics?, serializationDuration: TimeInterval, result: Result) { self.request = request self.response = response self.fileURL = fileURL self.resumeData = resumeData self.metrics = metrics self.serializationDuration = serializationDuration self.result = result } } // MARK: - extension DownloadResponse: CustomStringConvertible, CustomDebugStringConvertible { /// The textual representation used when written to an output stream, which includes whether the result was a /// success or failure. public var description: String { "\(result)" } /// The debug textual representation used when written to an output stream, which includes the URL request, the URL /// response, the temporary and destination URLs, the resume data, the durations of the network and serialization /// actions, and the response serialization result. public var debugDescription: String { guard let urlRequest = request else { return "[Request]: None\n[Result]: \(result)" } let requestDescription = DebugDescription.description(of: urlRequest) let responseDescription = response.map(DebugDescription.description(of:)) ?? "[Response]: None" let networkDuration = metrics.map { "\($0.taskInterval.duration)s" } ?? "None" let resumeDataDescription = resumeData.map { "\($0)" } ?? "None" return """ \(requestDescription) \(responseDescription) [File URL]: \(fileURL?.path ?? "None") [Resume Data]: \(resumeDataDescription) [Network Duration]: \(networkDuration) [Serialization Duration]: \(serializationDuration)s [Result]: \(result) """ } } // MARK: - extension DownloadResponse { /// Evaluates the given closure when the result of this `DownloadResponse` is a success, passing the unwrapped /// result value as a parameter. /// /// Use the `map` method with a closure that does not throw. For example: /// /// let possibleData: DownloadResponse = ... /// let possibleInt = possibleData.map { $0.count } /// /// - parameter transform: A closure that takes the success value of the instance's result. /// /// - returns: A `DownloadResponse` whose result wraps the value returned by the given closure. If this instance's /// result is a failure, returns a response wrapping the same failure. public func map(_ transform: (Success) -> NewSuccess) -> DownloadResponse { DownloadResponse(request: request, response: response, fileURL: fileURL, resumeData: resumeData, metrics: metrics, serializationDuration: serializationDuration, result: result.map(transform)) } /// Evaluates the given closure when the result of this `DownloadResponse` is a success, passing the unwrapped /// result value as a parameter. /// /// Use the `tryMap` method with a closure that may throw an error. For example: /// /// let possibleData: DownloadResponse = ... /// let possibleObject = possibleData.tryMap { /// try JSONSerialization.jsonObject(with: $0) /// } /// /// - parameter transform: A closure that takes the success value of the instance's result. /// /// - returns: A success or failure `DownloadResponse` depending on the result of the given closure. If this /// instance's result is a failure, returns the same failure. public func tryMap(_ transform: (Success) throws -> NewSuccess) -> DownloadResponse { DownloadResponse(request: request, response: response, fileURL: fileURL, resumeData: resumeData, metrics: metrics, serializationDuration: serializationDuration, result: result.tryMap(transform)) } /// Evaluates the specified closure when the `DownloadResponse` is a failure, passing the unwrapped error as a parameter. /// /// Use the `mapError` function with a closure that does not throw. For example: /// /// let possibleData: DownloadResponse = ... /// let withMyError = possibleData.mapError { MyError.error($0) } /// /// - Parameter transform: A closure that takes the error of the instance. /// /// - Returns: A `DownloadResponse` instance containing the result of the transform. public func mapError(_ transform: (Failure) -> NewFailure) -> DownloadResponse { DownloadResponse(request: request, response: response, fileURL: fileURL, resumeData: resumeData, metrics: metrics, serializationDuration: serializationDuration, result: result.mapError(transform)) } /// Evaluates the specified closure when the `DownloadResponse` is a failure, passing the unwrapped error as a parameter. /// /// Use the `tryMapError` function with a closure that may throw an error. For example: /// /// let possibleData: DownloadResponse = ... /// let possibleObject = possibleData.tryMapError { /// try someFailableFunction(taking: $0) /// } /// /// - Parameter transform: A throwing closure that takes the error of the instance. /// /// - Returns: A `DownloadResponse` instance containing the result of the transform. public func tryMapError(_ transform: (Failure) throws -> NewFailure) -> DownloadResponse { DownloadResponse(request: request, response: response, fileURL: fileURL, resumeData: resumeData, metrics: metrics, serializationDuration: serializationDuration, result: result.tryMapError(transform)) } } private enum DebugDescription { static func description(of request: URLRequest) -> String { let requestSummary = "\(request.httpMethod!) \(request)" let requestHeadersDescription = DebugDescription.description(for: request.headers) let requestBodyDescription = DebugDescription.description(for: request.httpBody, headers: request.headers) return """ [Request]: \(requestSummary) \(requestHeadersDescription.indentingNewlines()) \(requestBodyDescription.indentingNewlines()) """ } static func description(of response: HTTPURLResponse) -> String { """ [Response]: [Status Code]: \(response.statusCode) \(DebugDescription.description(for: response.headers).indentingNewlines()) """ } static func description(for headers: HTTPHeaders) -> String { guard !headers.isEmpty else { return "[Headers]: None" } let headerDescription = "\(headers.sorted())".indentingNewlines() return """ [Headers]: \(headerDescription) """ } static func description(for data: Data?, headers: HTTPHeaders, allowingPrintableTypes printableTypes: [String] = ["json", "xml", "text"], maximumLength: Int = 100_000) -> String { guard let data = data, !data.isEmpty else { return "[Body]: None" } guard data.count <= maximumLength, printableTypes.compactMap({ headers["Content-Type"]?.contains($0) }).contains(true) else { return "[Body]: \(data.count) bytes" } return """ [Body]: \(String(decoding: data, as: UTF8.self) .trimmingCharacters(in: .whitespacesAndNewlines) .indentingNewlines()) """ } } extension String { fileprivate func indentingNewlines(by spaceCount: Int = 4) -> String { let spaces = String(repeating: " ", count: spaceCount) return replacingOccurrences(of: "\n", with: "\n\(spaces)") } } ================================================ FILE: JetChat/Pods/Alamofire/Source/ResponseSerialization.swift ================================================ // // ResponseSerialization.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation // MARK: Protocols /// The type to which all data response serializers must conform in order to serialize a response. public protocol DataResponseSerializerProtocol { /// The type of serialized object to be created. associatedtype SerializedObject /// Serialize the response `Data` into the provided type.. /// /// - Parameters: /// - request: `URLRequest` which was used to perform the request, if any. /// - response: `HTTPURLResponse` received from the server, if any. /// - data: `Data` returned from the server, if any. /// - error: `Error` produced by Alamofire or the underlying `URLSession` during the request. /// /// - Returns: The `SerializedObject`. /// - Throws: Any `Error` produced during serialization. func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> SerializedObject } /// The type to which all download response serializers must conform in order to serialize a response. public protocol DownloadResponseSerializerProtocol { /// The type of serialized object to be created. associatedtype SerializedObject /// Serialize the downloaded response `Data` from disk into the provided type.. /// /// - Parameters: /// - request: `URLRequest` which was used to perform the request, if any. /// - response: `HTTPURLResponse` received from the server, if any. /// - fileURL: File `URL` to which the response data was downloaded. /// - error: `Error` produced by Alamofire or the underlying `URLSession` during the request. /// /// - Returns: The `SerializedObject`. /// - Throws: Any `Error` produced during serialization. func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: Error?) throws -> SerializedObject } /// A serializer that can handle both data and download responses. public protocol ResponseSerializer: DataResponseSerializerProtocol & DownloadResponseSerializerProtocol { /// `DataPreprocessor` used to prepare incoming `Data` for serialization. var dataPreprocessor: DataPreprocessor { get } /// `HTTPMethod`s for which empty response bodies are considered appropriate. var emptyRequestMethods: Set { get } /// HTTP response codes for which empty response bodies are considered appropriate. var emptyResponseCodes: Set { get } } /// Type used to preprocess `Data` before it handled by a serializer. public protocol DataPreprocessor { /// Process `Data` before it's handled by a serializer. /// - Parameter data: The raw `Data` to process. func preprocess(_ data: Data) throws -> Data } /// `DataPreprocessor` that returns passed `Data` without any transform. public struct PassthroughPreprocessor: DataPreprocessor { public init() {} public func preprocess(_ data: Data) throws -> Data { data } } /// `DataPreprocessor` that trims Google's typical `)]}',\n` XSSI JSON header. public struct GoogleXSSIPreprocessor: DataPreprocessor { public init() {} public func preprocess(_ data: Data) throws -> Data { (data.prefix(6) == Data(")]}',\n".utf8)) ? data.dropFirst(6) : data } } #if swift(>=5.5) extension DataPreprocessor where Self == PassthroughPreprocessor { /// Provides a `PassthroughPreprocessor` instance. public static var passthrough: PassthroughPreprocessor { PassthroughPreprocessor() } } extension DataPreprocessor where Self == GoogleXSSIPreprocessor { /// Provides a `GoogleXSSIPreprocessor` instance. public static var googleXSSI: GoogleXSSIPreprocessor { GoogleXSSIPreprocessor() } } #endif extension ResponseSerializer { /// Default `DataPreprocessor`. `PassthroughPreprocessor` by default. public static var defaultDataPreprocessor: DataPreprocessor { PassthroughPreprocessor() } /// Default `HTTPMethod`s for which empty response bodies are considered appropriate. `[.head]` by default. public static var defaultEmptyRequestMethods: Set { [.head] } /// HTTP response codes for which empty response bodies are considered appropriate. `[204, 205]` by default. public static var defaultEmptyResponseCodes: Set { [204, 205] } public var dataPreprocessor: DataPreprocessor { Self.defaultDataPreprocessor } public var emptyRequestMethods: Set { Self.defaultEmptyRequestMethods } public var emptyResponseCodes: Set { Self.defaultEmptyResponseCodes } /// Determines whether the `request` allows empty response bodies, if `request` exists. /// /// - Parameter request: `URLRequest` to evaluate. /// /// - Returns: `Bool` representing the outcome of the evaluation, or `nil` if `request` was `nil`. public func requestAllowsEmptyResponseData(_ request: URLRequest?) -> Bool? { request.flatMap(\.httpMethod) .flatMap(HTTPMethod.init) .map { emptyRequestMethods.contains($0) } } /// Determines whether the `response` allows empty response bodies, if `response` exists`. /// /// - Parameter response: `HTTPURLResponse` to evaluate. /// /// - Returns: `Bool` representing the outcome of the evaluation, or `nil` if `response` was `nil`. public func responseAllowsEmptyResponseData(_ response: HTTPURLResponse?) -> Bool? { response.map(\.statusCode) .map { emptyResponseCodes.contains($0) } } /// Determines whether `request` and `response` allow empty response bodies. /// /// - Parameters: /// - request: `URLRequest` to evaluate. /// - response: `HTTPURLResponse` to evaluate. /// /// - Returns: `true` if `request` or `response` allow empty bodies, `false` otherwise. public func emptyResponseAllowed(forRequest request: URLRequest?, response: HTTPURLResponse?) -> Bool { (requestAllowsEmptyResponseData(request) == true) || (responseAllowsEmptyResponseData(response) == true) } } /// By default, any serializer declared to conform to both types will get file serialization for free, as it just feeds /// the data read from disk into the data response serializer. extension DownloadResponseSerializerProtocol where Self: DataResponseSerializerProtocol { public func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: Error?) throws -> Self.SerializedObject { guard error == nil else { throw error! } guard let fileURL = fileURL else { throw AFError.responseSerializationFailed(reason: .inputFileNil) } let data: Data do { data = try Data(contentsOf: fileURL) } catch { throw AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL)) } do { return try serialize(request: request, response: response, data: data, error: error) } catch { throw error } } } // MARK: - Default extension DataRequest { /// Adds a handler to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is dispatched. `.main` by default. /// - completionHandler: The code to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func response(queue: DispatchQueue = .main, completionHandler: @escaping (AFDataResponse) -> Void) -> Self { appendResponseSerializer { // Start work that should be on the serialization queue. let result = AFResult(value: self.data, error: self.error) // End work that should be on the serialization queue. self.underlyingQueue.async { let response = DataResponse(request: self.request, response: self.response, data: self.data, metrics: self.metrics, serializationDuration: 0, result: result) self.eventMonitor?.request(self, didParseResponse: response) self.responseSerializerDidComplete { queue.async { completionHandler(response) } } } } return self } private func _response(queue: DispatchQueue = .main, responseSerializer: Serializer, completionHandler: @escaping (AFDataResponse) -> Void) -> Self { appendResponseSerializer { // Start work that should be on the serialization queue. let start = ProcessInfo.processInfo.systemUptime let result: AFResult = Result { try responseSerializer.serialize(request: self.request, response: self.response, data: self.data, error: self.error) }.mapError { error in error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error))) } let end = ProcessInfo.processInfo.systemUptime // End work that should be on the serialization queue. self.underlyingQueue.async { let response = DataResponse(request: self.request, response: self.response, data: self.data, metrics: self.metrics, serializationDuration: end - start, result: result) self.eventMonitor?.request(self, didParseResponse: response) guard let serializerError = result.failure, let delegate = self.delegate else { self.responseSerializerDidComplete { queue.async { completionHandler(response) } } return } delegate.retryResult(for: self, dueTo: serializerError) { retryResult in var didComplete: (() -> Void)? defer { if let didComplete = didComplete { self.responseSerializerDidComplete { queue.async { didComplete() } } } } switch retryResult { case .doNotRetry: didComplete = { completionHandler(response) } case let .doNotRetryWithError(retryError): let result: AFResult = .failure(retryError.asAFError(orFailWith: "Received retryError was not already AFError")) let response = DataResponse(request: self.request, response: self.response, data: self.data, metrics: self.metrics, serializationDuration: end - start, result: result) didComplete = { completionHandler(response) } case .retry, .retryWithDelay: delegate.retryRequest(self, withDelay: retryResult.delay) } } } } return self } /// Adds a handler to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is dispatched. `.main` by default /// - responseSerializer: The response serializer responsible for serializing the request, response, and data. /// - completionHandler: The code to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func response(queue: DispatchQueue = .main, responseSerializer: Serializer, completionHandler: @escaping (AFDataResponse) -> Void) -> Self { _response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler) } /// Adds a handler to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is dispatched. `.main` by default /// - responseSerializer: The response serializer responsible for serializing the request, response, and data. /// - completionHandler: The code to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func response(queue: DispatchQueue = .main, responseSerializer: Serializer, completionHandler: @escaping (AFDataResponse) -> Void) -> Self { _response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler) } } extension DownloadRequest { /// Adds a handler to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is dispatched. `.main` by default. /// - completionHandler: The code to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func response(queue: DispatchQueue = .main, completionHandler: @escaping (AFDownloadResponse) -> Void) -> Self { appendResponseSerializer { // Start work that should be on the serialization queue. let result = AFResult(value: self.fileURL, error: self.error) // End work that should be on the serialization queue. self.underlyingQueue.async { let response = DownloadResponse(request: self.request, response: self.response, fileURL: self.fileURL, resumeData: self.resumeData, metrics: self.metrics, serializationDuration: 0, result: result) self.eventMonitor?.request(self, didParseResponse: response) self.responseSerializerDidComplete { queue.async { completionHandler(response) } } } } return self } private func _response(queue: DispatchQueue = .main, responseSerializer: Serializer, completionHandler: @escaping (AFDownloadResponse) -> Void) -> Self { appendResponseSerializer { // Start work that should be on the serialization queue. let start = ProcessInfo.processInfo.systemUptime let result: AFResult = Result { try responseSerializer.serializeDownload(request: self.request, response: self.response, fileURL: self.fileURL, error: self.error) }.mapError { error in error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error))) } let end = ProcessInfo.processInfo.systemUptime // End work that should be on the serialization queue. self.underlyingQueue.async { let response = DownloadResponse(request: self.request, response: self.response, fileURL: self.fileURL, resumeData: self.resumeData, metrics: self.metrics, serializationDuration: end - start, result: result) self.eventMonitor?.request(self, didParseResponse: response) guard let serializerError = result.failure, let delegate = self.delegate else { self.responseSerializerDidComplete { queue.async { completionHandler(response) } } return } delegate.retryResult(for: self, dueTo: serializerError) { retryResult in var didComplete: (() -> Void)? defer { if let didComplete = didComplete { self.responseSerializerDidComplete { queue.async { didComplete() } } } } switch retryResult { case .doNotRetry: didComplete = { completionHandler(response) } case let .doNotRetryWithError(retryError): let result: AFResult = .failure(retryError.asAFError(orFailWith: "Received retryError was not already AFError")) let response = DownloadResponse(request: self.request, response: self.response, fileURL: self.fileURL, resumeData: self.resumeData, metrics: self.metrics, serializationDuration: end - start, result: result) didComplete = { completionHandler(response) } case .retry, .retryWithDelay: delegate.retryRequest(self, withDelay: retryResult.delay) } } } } return self } /// Adds a handler to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is dispatched. `.main` by default. /// - responseSerializer: The response serializer responsible for serializing the request, response, and data /// contained in the destination `URL`. /// - completionHandler: The code to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func response(queue: DispatchQueue = .main, responseSerializer: Serializer, completionHandler: @escaping (AFDownloadResponse) -> Void) -> Self { _response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler) } /// Adds a handler to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is dispatched. `.main` by default. /// - responseSerializer: The response serializer responsible for serializing the request, response, and data /// contained in the destination `URL`. /// - completionHandler: The code to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func response(queue: DispatchQueue = .main, responseSerializer: Serializer, completionHandler: @escaping (AFDownloadResponse) -> Void) -> Self { _response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler) } } // MARK: - URL /// A `DownloadResponseSerializerProtocol` that performs only `Error` checking and ensures that a downloaded `fileURL` /// is present. public struct URLResponseSerializer: DownloadResponseSerializerProtocol { /// Creates an instance. public init() {} public func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: Error?) throws -> URL { guard error == nil else { throw error! } guard let url = fileURL else { throw AFError.responseSerializationFailed(reason: .inputFileNil) } return url } } #if swift(>=5.5) extension DownloadResponseSerializerProtocol where Self == URLResponseSerializer { /// Provides a `URLResponseSerializer` instance. public static var url: URLResponseSerializer { URLResponseSerializer() } } #endif extension DownloadRequest { /// Adds a handler using a `URLResponseSerializer` to be called once the request is finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is called. `.main` by default. /// - completionHandler: A closure to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func responseURL(queue: DispatchQueue = .main, completionHandler: @escaping (AFDownloadResponse) -> Void) -> Self { response(queue: queue, responseSerializer: URLResponseSerializer(), completionHandler: completionHandler) } } // MARK: - Data /// A `ResponseSerializer` that performs minimal response checking and returns any response `Data` as-is. By default, a /// request returning `nil` or no data is considered an error. However, if the request has an `HTTPMethod` or the /// response has an HTTP status code valid for empty responses, then an empty `Data` value is returned. public final class DataResponseSerializer: ResponseSerializer { public let dataPreprocessor: DataPreprocessor public let emptyResponseCodes: Set public let emptyRequestMethods: Set /// Creates a `DataResponseSerializer` using the provided parameters. /// /// - Parameters: /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. public init(dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) { self.dataPreprocessor = dataPreprocessor self.emptyResponseCodes = emptyResponseCodes self.emptyRequestMethods = emptyRequestMethods } public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Data { guard error == nil else { throw error! } guard var data = data, !data.isEmpty else { guard emptyResponseAllowed(forRequest: request, response: response) else { throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) } return Data() } data = try dataPreprocessor.preprocess(data) return data } } #if swift(>=5.5) extension ResponseSerializer where Self == DataResponseSerializer { /// Provides a default `DataResponseSerializer` instance. public static var data: DataResponseSerializer { DataResponseSerializer() } /// Creates a `DataResponseSerializer` using the provided parameters. /// /// - Parameters: /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. /// /// - Returns: The `DataResponseSerializer`. public static func data(dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) -> DataResponseSerializer { DataResponseSerializer(dataPreprocessor: dataPreprocessor, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods) } } #endif extension DataRequest { /// Adds a handler using a `DataResponseSerializer` to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is called. `.main` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the /// `completionHandler`. `PassthroughPreprocessor()` by default. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - completionHandler: A closure to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func responseData(queue: DispatchQueue = .main, dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods, completionHandler: @escaping (AFDataResponse) -> Void) -> Self { response(queue: queue, responseSerializer: DataResponseSerializer(dataPreprocessor: dataPreprocessor, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), completionHandler: completionHandler) } } extension DownloadRequest { /// Adds a handler using a `DataResponseSerializer` to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is called. `.main` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the /// `completionHandler`. `PassthroughPreprocessor()` by default. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - completionHandler: A closure to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func responseData(queue: DispatchQueue = .main, dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods, completionHandler: @escaping (AFDownloadResponse) -> Void) -> Self { response(queue: queue, responseSerializer: DataResponseSerializer(dataPreprocessor: dataPreprocessor, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), completionHandler: completionHandler) } } // MARK: - String /// A `ResponseSerializer` that decodes the response data as a `String`. By default, a request returning `nil` or no /// data is considered an error. However, if the request has an `HTTPMethod` or the response has an HTTP status code /// valid for empty responses, then an empty `String` is returned. public final class StringResponseSerializer: ResponseSerializer { public let dataPreprocessor: DataPreprocessor /// Optional string encoding used to validate the response. public let encoding: String.Encoding? public let emptyResponseCodes: Set public let emptyRequestMethods: Set /// Creates an instance with the provided values. /// /// - Parameters: /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. /// - encoding: A string encoding. Defaults to `nil`, in which case the encoding will be determined /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. public init(dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, encoding: String.Encoding? = nil, emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) { self.dataPreprocessor = dataPreprocessor self.encoding = encoding self.emptyResponseCodes = emptyResponseCodes self.emptyRequestMethods = emptyRequestMethods } public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> String { guard error == nil else { throw error! } guard var data = data, !data.isEmpty else { guard emptyResponseAllowed(forRequest: request, response: response) else { throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) } return "" } data = try dataPreprocessor.preprocess(data) var convertedEncoding = encoding if let encodingName = response?.textEncodingName, convertedEncoding == nil { convertedEncoding = String.Encoding(ianaCharsetName: encodingName) } let actualEncoding = convertedEncoding ?? .isoLatin1 guard let string = String(data: data, encoding: actualEncoding) else { throw AFError.responseSerializationFailed(reason: .stringSerializationFailed(encoding: actualEncoding)) } return string } } #if swift(>=5.5) extension ResponseSerializer where Self == StringResponseSerializer { /// Provides a default `StringResponseSerializer` instance. public static var string: StringResponseSerializer { StringResponseSerializer() } /// Creates a `StringResponseSerializer` with the provided values. /// /// - Parameters: /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. /// - encoding: A string encoding. Defaults to `nil`, in which case the encoding will be determined /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. /// /// - Returns: The `StringResponseSerializer`. public static func string(dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, encoding: String.Encoding? = nil, emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) -> StringResponseSerializer { StringResponseSerializer(dataPreprocessor: dataPreprocessor, encoding: encoding, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods) } } #endif extension DataRequest { /// Adds a handler using a `StringResponseSerializer` to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is dispatched. `.main` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the /// `completionHandler`. `PassthroughPreprocessor()` by default. /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - completionHandler: A closure to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func responseString(queue: DispatchQueue = .main, dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, encoding: String.Encoding? = nil, emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods, completionHandler: @escaping (AFDataResponse) -> Void) -> Self { response(queue: queue, responseSerializer: StringResponseSerializer(dataPreprocessor: dataPreprocessor, encoding: encoding, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), completionHandler: completionHandler) } } extension DownloadRequest { /// Adds a handler using a `StringResponseSerializer` to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is dispatched. `.main` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the /// `completionHandler`. `PassthroughPreprocessor()` by default. /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - completionHandler: A closure to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func responseString(queue: DispatchQueue = .main, dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, encoding: String.Encoding? = nil, emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods, completionHandler: @escaping (AFDownloadResponse) -> Void) -> Self { response(queue: queue, responseSerializer: StringResponseSerializer(dataPreprocessor: dataPreprocessor, encoding: encoding, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), completionHandler: completionHandler) } } // MARK: - JSON /// A `ResponseSerializer` that decodes the response data using `JSONSerialization`. By default, a request returning /// `nil` or no data is considered an error. However, if the request has an `HTTPMethod` or the response has an /// HTTP status code valid for empty responses, then an `NSNull` value is returned. @available(*, deprecated, message: "JSONResponseSerializer deprecated and will be removed in Alamofire 6. Use DecodableResponseSerializer instead.") public final class JSONResponseSerializer: ResponseSerializer { public let dataPreprocessor: DataPreprocessor public let emptyResponseCodes: Set public let emptyRequestMethods: Set /// `JSONSerialization.ReadingOptions` used when serializing a response. public let options: JSONSerialization.ReadingOptions /// Creates an instance with the provided values. /// /// - Parameters: /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. /// - options: The options to use. `.allowFragments` by default. public init(dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = JSONResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = JSONResponseSerializer.defaultEmptyRequestMethods, options: JSONSerialization.ReadingOptions = .allowFragments) { self.dataPreprocessor = dataPreprocessor self.emptyResponseCodes = emptyResponseCodes self.emptyRequestMethods = emptyRequestMethods self.options = options } public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Any { guard error == nil else { throw error! } guard var data = data, !data.isEmpty else { guard emptyResponseAllowed(forRequest: request, response: response) else { throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) } return NSNull() } data = try dataPreprocessor.preprocess(data) do { return try JSONSerialization.jsonObject(with: data, options: options) } catch { throw AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)) } } } extension DataRequest { /// Adds a handler using a `JSONResponseSerializer` to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is dispatched. `.main` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the /// `completionHandler`. `PassthroughPreprocessor()` by default. /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - options: `JSONSerialization.ReadingOptions` used when parsing the response. `.allowFragments` /// by default. /// - completionHandler: A closure to be executed once the request has finished. /// /// - Returns: The request. @available(*, deprecated, message: "responseJSON deprecated and will be removed in Alamofire 6. Use responseDecodable instead.") @discardableResult public func responseJSON(queue: DispatchQueue = .main, dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = JSONResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = JSONResponseSerializer.defaultEmptyRequestMethods, options: JSONSerialization.ReadingOptions = .allowFragments, completionHandler: @escaping (AFDataResponse) -> Void) -> Self { response(queue: queue, responseSerializer: JSONResponseSerializer(dataPreprocessor: dataPreprocessor, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods, options: options), completionHandler: completionHandler) } } extension DownloadRequest { /// Adds a handler using a `JSONResponseSerializer` to be called once the request has finished. /// /// - Parameters: /// - queue: The queue on which the completion handler is dispatched. `.main` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the /// `completionHandler`. `PassthroughPreprocessor()` by default. /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - options: `JSONSerialization.ReadingOptions` used when parsing the response. `.allowFragments` /// by default. /// - completionHandler: A closure to be executed once the request has finished. /// /// - Returns: The request. @available(*, deprecated, message: "responseJSON deprecated and will be removed in Alamofire 6. Use responseDecodable instead.") @discardableResult public func responseJSON(queue: DispatchQueue = .main, dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor, emptyResponseCodes: Set = JSONResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = JSONResponseSerializer.defaultEmptyRequestMethods, options: JSONSerialization.ReadingOptions = .allowFragments, completionHandler: @escaping (AFDownloadResponse) -> Void) -> Self { response(queue: queue, responseSerializer: JSONResponseSerializer(dataPreprocessor: dataPreprocessor, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods, options: options), completionHandler: completionHandler) } } // MARK: - Empty /// Protocol representing an empty response. Use `T.emptyValue()` to get an instance. public protocol EmptyResponse { /// Empty value for the conforming type. /// /// - Returns: Value of `Self` to use for empty values. static func emptyValue() -> Self } /// Type representing an empty value. Use `Empty.value` to get the static instance. public struct Empty: Codable { /// Static `Empty` instance used for all `Empty` responses. public static let value = Empty() } extension Empty: EmptyResponse { public static func emptyValue() -> Empty { value } } // MARK: - DataDecoder Protocol /// Any type which can decode `Data` into a `Decodable` type. public protocol DataDecoder { /// Decode `Data` into the provided type. /// /// - Parameters: /// - type: The `Type` to be decoded. /// - data: The `Data` to be decoded. /// /// - Returns: The decoded value of type `D`. /// - Throws: Any error that occurs during decode. func decode(_ type: D.Type, from data: Data) throws -> D } /// `JSONDecoder` automatically conforms to `DataDecoder`. extension JSONDecoder: DataDecoder {} /// `PropertyListDecoder` automatically conforms to `DataDecoder`. extension PropertyListDecoder: DataDecoder {} // MARK: - Decodable /// A `ResponseSerializer` that decodes the response data as a generic value using any type that conforms to /// `DataDecoder`. By default, this is an instance of `JSONDecoder`. Additionally, a request returning `nil` or no data /// is considered an error. However, if the request has an `HTTPMethod` or the response has an HTTP status code valid /// for empty responses then an empty value will be returned. If the decoded type conforms to `EmptyResponse`, the /// type's `emptyValue()` will be returned. If the decoded type is `Empty`, the `.value` instance is returned. If the /// decoded type *does not* conform to `EmptyResponse` and isn't `Empty`, an error will be produced. public final class DecodableResponseSerializer: ResponseSerializer { public let dataPreprocessor: DataPreprocessor /// The `DataDecoder` instance used to decode responses. public let decoder: DataDecoder public let emptyResponseCodes: Set public let emptyRequestMethods: Set /// Creates an instance using the values provided. /// /// - Parameters: /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. /// - decoder: The `DataDecoder`. `JSONDecoder()` by default. /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. public init(dataPreprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, decoder: DataDecoder = JSONDecoder(), emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) { self.dataPreprocessor = dataPreprocessor self.decoder = decoder self.emptyResponseCodes = emptyResponseCodes self.emptyRequestMethods = emptyRequestMethods } public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> T { guard error == nil else { throw error! } guard var data = data, !data.isEmpty else { guard emptyResponseAllowed(forRequest: request, response: response) else { throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) } guard let emptyResponseType = T.self as? EmptyResponse.Type, let emptyValue = emptyResponseType.emptyValue() as? T else { throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)")) } return emptyValue } data = try dataPreprocessor.preprocess(data) do { return try decoder.decode(T.self, from: data) } catch { throw AFError.responseSerializationFailed(reason: .decodingFailed(error: error)) } } } #if swift(>=5.5) extension ResponseSerializer { /// Creates a `DecodableResponseSerializer` using the values provided. /// /// - Parameters: /// - type: `Decodable` type to decode from response data. /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. /// - decoder: The `DataDecoder`. `JSONDecoder()` by default. /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. /// /// - Returns: The `DecodableResponseSerializer`. public static func decodable(of type: T.Type, dataPreprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, decoder: DataDecoder = JSONDecoder(), emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DecodableResponseSerializer where Self == DecodableResponseSerializer { DecodableResponseSerializer(dataPreprocessor: dataPreprocessor, decoder: decoder, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods) } } #endif extension DataRequest { /// Adds a handler using a `DecodableResponseSerializer` to be called once the request has finished. /// /// - Parameters: /// - type: `Decodable` type to decode from response data. /// - queue: The queue on which the completion handler is dispatched. `.main` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the /// `completionHandler`. `PassthroughPreprocessor()` by default. /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - completionHandler: A closure to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func responseDecodable(of type: T.Type = T.self, queue: DispatchQueue = .main, dataPreprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, decoder: DataDecoder = JSONDecoder(), emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods, completionHandler: @escaping (AFDataResponse) -> Void) -> Self { response(queue: queue, responseSerializer: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor, decoder: decoder, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), completionHandler: completionHandler) } } extension DownloadRequest { /// Adds a handler using a `DecodableResponseSerializer` to be called once the request has finished. /// /// - Parameters: /// - type: `Decodable` type to decode from response data. /// - queue: The queue on which the completion handler is dispatched. `.main` by default. /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the /// `completionHandler`. `PassthroughPreprocessor()` by default. /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. /// - completionHandler: A closure to be executed once the request has finished. /// /// - Returns: The request. @discardableResult public func responseDecodable(of type: T.Type = T.self, queue: DispatchQueue = .main, dataPreprocessor: DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, decoder: DataDecoder = JSONDecoder(), emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods, completionHandler: @escaping (AFDownloadResponse) -> Void) -> Self { response(queue: queue, responseSerializer: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor, decoder: decoder, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods), completionHandler: completionHandler) } } // MARK: - DataStreamRequest /// A type which can serialize incoming `Data`. public protocol DataStreamSerializer { /// Type produced from the serialized `Data`. associatedtype SerializedObject /// Serializes incoming `Data` into a `SerializedObject` value. /// /// - Parameter data: `Data` to be serialized. /// /// - Throws: Any error produced during serialization. func serialize(_ data: Data) throws -> SerializedObject } /// `DataStreamSerializer` which uses the provided `DataPreprocessor` and `DataDecoder` to serialize the incoming `Data`. public struct DecodableStreamSerializer: DataStreamSerializer { /// `DataDecoder` used to decode incoming `Data`. public let decoder: DataDecoder /// `DataPreprocessor` incoming `Data` is passed through before being passed to the `DataDecoder`. public let dataPreprocessor: DataPreprocessor /// Creates an instance with the provided `DataDecoder` and `DataPreprocessor`. /// - Parameters: /// - decoder: ` DataDecoder` used to decode incoming `Data`. `JSONDecoder()` by default. /// - dataPreprocessor: `DataPreprocessor` used to process incoming `Data` before it's passed through the /// `decoder`. `PassthroughPreprocessor()` by default. public init(decoder: DataDecoder = JSONDecoder(), dataPreprocessor: DataPreprocessor = PassthroughPreprocessor()) { self.decoder = decoder self.dataPreprocessor = dataPreprocessor } public func serialize(_ data: Data) throws -> T { let processedData = try dataPreprocessor.preprocess(data) do { return try decoder.decode(T.self, from: processedData) } catch { throw AFError.responseSerializationFailed(reason: .decodingFailed(error: error)) } } } /// `DataStreamSerializer` which performs no serialization on incoming `Data`. public struct PassthroughStreamSerializer: DataStreamSerializer { /// Creates an instance. public init() {} public func serialize(_ data: Data) throws -> Data { data } } /// `DataStreamSerializer` which serializes incoming stream `Data` into `UTF8`-decoded `String` values. public struct StringStreamSerializer: DataStreamSerializer { /// Creates an instance. public init() {} public func serialize(_ data: Data) throws -> String { String(decoding: data, as: UTF8.self) } } #if swift(>=5.5) extension DataStreamSerializer { /// Creates a `DecodableStreamSerializer` instance with the provided `DataDecoder` and `DataPreprocessor`. /// /// - Parameters: /// - type: `Decodable` type to decode from stream data. /// - decoder: ` DataDecoder` used to decode incoming `Data`. `JSONDecoder()` by default. /// - dataPreprocessor: `DataPreprocessor` used to process incoming `Data` before it's passed through the /// `decoder`. `PassthroughPreprocessor()` by default. public static func decodable(of type: T.Type, decoder: DataDecoder = JSONDecoder(), dataPreprocessor: DataPreprocessor = PassthroughPreprocessor()) -> Self where Self == DecodableStreamSerializer { DecodableStreamSerializer(decoder: decoder, dataPreprocessor: dataPreprocessor) } } extension DataStreamSerializer where Self == PassthroughStreamSerializer { /// Provides a `PassthroughStreamSerializer` instance. public static var passthrough: PassthroughStreamSerializer { PassthroughStreamSerializer() } } extension DataStreamSerializer where Self == StringStreamSerializer { /// Provides a `StringStreamSerializer` instance. public static var string: StringStreamSerializer { StringStreamSerializer() } } #endif extension DataStreamRequest { /// Adds a `StreamHandler` which performs no parsing on incoming `Data`. /// /// - Parameters: /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. /// /// - Returns: The `DataStreamRequest`. @discardableResult public func responseStream(on queue: DispatchQueue = .main, stream: @escaping Handler) -> Self { let parser = { [unowned self] (data: Data) in queue.async { self.capturingError { try stream(.init(event: .stream(.success(data)), token: .init(self))) } self.updateAndCompleteIfPossible() } } $streamMutableState.write { $0.streams.append(parser) } appendStreamCompletion(on: queue, stream: stream) return self } /// Adds a `StreamHandler` which uses the provided `DataStreamSerializer` to process incoming `Data`. /// /// - Parameters: /// - serializer: `DataStreamSerializer` used to process incoming `Data`. Its work is done on the `serializationQueue`. /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. /// /// - Returns: The `DataStreamRequest`. @discardableResult public func responseStream(using serializer: Serializer, on queue: DispatchQueue = .main, stream: @escaping Handler) -> Self { let parser = { [unowned self] (data: Data) in self.serializationQueue.async { // Start work on serialization queue. let result = Result { try serializer.serialize(data) } .mapError { $0.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: $0))) } // End work on serialization queue. self.underlyingQueue.async { self.eventMonitor?.request(self, didParseStream: result) if result.isFailure, self.automaticallyCancelOnStreamError { self.cancel() } queue.async { self.capturingError { try stream(.init(event: .stream(result), token: .init(self))) } self.updateAndCompleteIfPossible() } } } } $streamMutableState.write { $0.streams.append(parser) } appendStreamCompletion(on: queue, stream: stream) return self } /// Adds a `StreamHandler` which parses incoming `Data` as a UTF8 `String`. /// /// - Parameters: /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. /// /// - Returns: The `DataStreamRequest`. @discardableResult public func responseStreamString(on queue: DispatchQueue = .main, stream: @escaping Handler) -> Self { let parser = { [unowned self] (data: Data) in self.serializationQueue.async { // Start work on serialization queue. let string = String(decoding: data, as: UTF8.self) // End work on serialization queue. self.underlyingQueue.async { self.eventMonitor?.request(self, didParseStream: .success(string)) queue.async { self.capturingError { try stream(.init(event: .stream(.success(string)), token: .init(self))) } self.updateAndCompleteIfPossible() } } } } $streamMutableState.write { $0.streams.append(parser) } appendStreamCompletion(on: queue, stream: stream) return self } private func updateAndCompleteIfPossible() { $streamMutableState.write { state in state.numberOfExecutingStreams -= 1 guard state.numberOfExecutingStreams == 0, !state.enqueuedCompletionEvents.isEmpty else { return } let completionEvents = state.enqueuedCompletionEvents self.underlyingQueue.async { completionEvents.forEach { $0() } } state.enqueuedCompletionEvents.removeAll() } } /// Adds a `StreamHandler` which parses incoming `Data` using the provided `DataDecoder`. /// /// - Parameters: /// - type: `Decodable` type to parse incoming `Data` into. /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. /// - decoder: `DataDecoder` used to decode the incoming `Data`. /// - preprocessor: `DataPreprocessor` used to process the incoming `Data` before it's passed to the `decoder`. /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. /// /// - Returns: The `DataStreamRequest`. @discardableResult public func responseStreamDecodable(of type: T.Type = T.self, on queue: DispatchQueue = .main, using decoder: DataDecoder = JSONDecoder(), preprocessor: DataPreprocessor = PassthroughPreprocessor(), stream: @escaping Handler) -> Self { responseStream(using: DecodableStreamSerializer(decoder: decoder, dataPreprocessor: preprocessor), stream: stream) } } ================================================ FILE: JetChat/Pods/Alamofire/Source/Result+Alamofire.swift ================================================ // // Result+Alamofire.swift // // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Default type of `Result` returned by Alamofire, with an `AFError` `Failure` type. public typealias AFResult = Result // MARK: - Internal APIs extension Result { /// Returns whether the instance is `.success`. var isSuccess: Bool { guard case .success = self else { return false } return true } /// Returns whether the instance is `.failure`. var isFailure: Bool { !isSuccess } /// Returns the associated value if the result is a success, `nil` otherwise. var success: Success? { guard case let .success(value) = self else { return nil } return value } /// Returns the associated error value if the result is a failure, `nil` otherwise. var failure: Failure? { guard case let .failure(error) = self else { return nil } return error } /// Initializes a `Result` from value or error. Returns `.failure` if the error is non-nil, `.success` otherwise. /// /// - Parameters: /// - value: A value. /// - error: An `Error`. init(value: Success, error: Failure?) { if let error = error { self = .failure(error) } else { self = .success(value) } } /// Evaluates the specified closure when the `Result` is a success, passing the unwrapped value as a parameter. /// /// Use the `tryMap` method with a closure that may throw an error. For example: /// /// let possibleData: Result = .success(Data(...)) /// let possibleObject = possibleData.tryMap { /// try JSONSerialization.jsonObject(with: $0) /// } /// /// - parameter transform: A closure that takes the success value of the instance. /// /// - returns: A `Result` containing the result of the given closure. If this instance is a failure, returns the /// same failure. func tryMap(_ transform: (Success) throws -> NewSuccess) -> Result { switch self { case let .success(value): do { return try .success(transform(value)) } catch { return .failure(error) } case let .failure(error): return .failure(error) } } /// Evaluates the specified closure when the `Result` is a failure, passing the unwrapped error as a parameter. /// /// Use the `tryMapError` function with a closure that may throw an error. For example: /// /// let possibleData: Result = .success(Data(...)) /// let possibleObject = possibleData.tryMapError { /// try someFailableFunction(taking: $0) /// } /// /// - Parameter transform: A throwing closure that takes the error of the instance. /// /// - Returns: A `Result` instance containing the result of the transform. If this instance is a success, returns /// the same success. func tryMapError(_ transform: (Failure) throws -> NewFailure) -> Result { switch self { case let .failure(error): do { return try .failure(transform(error)) } catch { return .failure(error) } case let .success(value): return .success(value) } } } ================================================ FILE: JetChat/Pods/Alamofire/Source/RetryPolicy.swift ================================================ // // RetryPolicy.swift // // Copyright (c) 2019-2020 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// A retry policy that retries requests using an exponential backoff for allowed HTTP methods and HTTP status codes /// as well as certain types of networking errors. open class RetryPolicy: RequestInterceptor { /// The default retry limit for retry policies. public static let defaultRetryLimit: UInt = 2 /// The default exponential backoff base for retry policies (must be a minimum of 2). public static let defaultExponentialBackoffBase: UInt = 2 /// The default exponential backoff scale for retry policies. public static let defaultExponentialBackoffScale: Double = 0.5 /// The default HTTP methods to retry. /// See [RFC 2616 - Section 9.1.2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) for more information. public static let defaultRetryableHTTPMethods: Set = [.delete, // [Delete](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7) - not always idempotent .get, // [GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3) - generally idempotent .head, // [HEAD](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4) - generally idempotent .options, // [OPTIONS](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2) - inherently idempotent .put, // [PUT](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) - not always idempotent .trace // [TRACE](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8) - inherently idempotent ] /// The default HTTP status codes to retry. /// See [RFC 2616 - Section 10](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10) for more information. public static let defaultRetryableHTTPStatusCodes: Set = [408, // [Request Timeout](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.9) 500, // [Internal Server Error](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1) 502, // [Bad Gateway](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.3) 503, // [Service Unavailable](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4) 504 // [Gateway Timeout](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.5) ] /// The default URL error codes to retry. public static let defaultRetryableURLErrorCodes: Set = [// [Security] App Transport Security disallowed a connection because there is no secure network connection. // - [Disabled] ATS settings do not change at runtime. // .appTransportSecurityRequiresSecureConnection, // [System] An app or app extension attempted to connect to a background session that is already connected to a // process. // - [Enabled] The other process could release the background session. .backgroundSessionInUseByAnotherProcess, // [System] The shared container identifier of the URL session configuration is needed but has not been set. // - [Disabled] Cannot change at runtime. // .backgroundSessionRequiresSharedContainer, // [System] The app is suspended or exits while a background data task is processing. // - [Enabled] App can be foregrounded or launched to recover. .backgroundSessionWasDisconnected, // [Network] The URL Loading system received bad data from the server. // - [Enabled] Server could return valid data when retrying. .badServerResponse, // [Resource] A malformed URL prevented a URL request from being initiated. // - [Disabled] URL was most likely constructed incorrectly. // .badURL, // [System] A connection was attempted while a phone call is active on a network that does not support // simultaneous phone and data communication (EDGE or GPRS). // - [Enabled] Phone call could be ended to allow request to recover. .callIsActive, // [Client] An asynchronous load has been canceled. // - [Disabled] Request was cancelled by the client. // .cancelled, // [File System] A download task couldn’t close the downloaded file on disk. // - [Disabled] File system error is unlikely to recover with retry. // .cannotCloseFile, // [Network] An attempt to connect to a host failed. // - [Enabled] Server or DNS lookup could recover during retry. .cannotConnectToHost, // [File System] A download task couldn’t create the downloaded file on disk because of an I/O failure. // - [Disabled] File system error is unlikely to recover with retry. // .cannotCreateFile, // [Data] Content data received during a connection request had an unknown content encoding. // - [Disabled] Server is unlikely to modify the content encoding during a retry. // .cannotDecodeContentData, // [Data] Content data received during a connection request could not be decoded for a known content encoding. // - [Disabled] Server is unlikely to modify the content encoding during a retry. // .cannotDecodeRawData, // [Network] The host name for a URL could not be resolved. // - [Enabled] Server or DNS lookup could recover during retry. .cannotFindHost, // [Network] A request to load an item only from the cache could not be satisfied. // - [Enabled] Cache could be populated during a retry. .cannotLoadFromNetwork, // [File System] A download task was unable to move a downloaded file on disk. // - [Disabled] File system error is unlikely to recover with retry. // .cannotMoveFile, // [File System] A download task was unable to open the downloaded file on disk. // - [Disabled] File system error is unlikely to recover with retry. // .cannotOpenFile, // [Data] A task could not parse a response. // - [Disabled] Invalid response is unlikely to recover with retry. // .cannotParseResponse, // [File System] A download task was unable to remove a downloaded file from disk. // - [Disabled] File system error is unlikely to recover with retry. // .cannotRemoveFile, // [File System] A download task was unable to write to the downloaded file on disk. // - [Disabled] File system error is unlikely to recover with retry. // .cannotWriteToFile, // [Security] A client certificate was rejected. // - [Disabled] Client certificate is unlikely to change with retry. // .clientCertificateRejected, // [Security] A client certificate was required to authenticate an SSL connection during a request. // - [Disabled] Client certificate is unlikely to be provided with retry. // .clientCertificateRequired, // [Data] The length of the resource data exceeds the maximum allowed. // - [Disabled] Resource will likely still exceed the length maximum on retry. // .dataLengthExceedsMaximum, // [System] The cellular network disallowed a connection. // - [Enabled] WiFi connection could be established during retry. .dataNotAllowed, // [Network] The host address could not be found via DNS lookup. // - [Enabled] DNS lookup could succeed during retry. .dnsLookupFailed, // [Data] A download task failed to decode an encoded file during the download. // - [Enabled] Server could correct the decoding issue with retry. .downloadDecodingFailedMidStream, // [Data] A download task failed to decode an encoded file after downloading. // - [Enabled] Server could correct the decoding issue with retry. .downloadDecodingFailedToComplete, // [File System] A file does not exist. // - [Disabled] File system error is unlikely to recover with retry. // .fileDoesNotExist, // [File System] A request for an FTP file resulted in the server responding that the file is not a plain file, // but a directory. // - [Disabled] FTP directory is not likely to change to a file during a retry. // .fileIsDirectory, // [Network] A redirect loop has been detected or the threshold for number of allowable redirects has been // exceeded (currently 16). // - [Disabled] The redirect loop is unlikely to be resolved within the retry window. // .httpTooManyRedirects, // [System] The attempted connection required activating a data context while roaming, but international roaming // is disabled. // - [Enabled] WiFi connection could be established during retry. .internationalRoamingOff, // [Connectivity] A client or server connection was severed in the middle of an in-progress load. // - [Enabled] A network connection could be established during retry. .networkConnectionLost, // [File System] A resource couldn’t be read because of insufficient permissions. // - [Disabled] Permissions are unlikely to be granted during retry. // .noPermissionsToReadFile, // [Connectivity] A network resource was requested, but an internet connection has not been established and // cannot be established automatically. // - [Enabled] A network connection could be established during retry. .notConnectedToInternet, // [Resource] A redirect was specified by way of server response code, but the server did not accompany this // code with a redirect URL. // - [Disabled] The redirect URL is unlikely to be supplied during a retry. // .redirectToNonExistentLocation, // [Client] A body stream is needed but the client did not provide one. // - [Disabled] The client will be unlikely to supply a body stream during retry. // .requestBodyStreamExhausted, // [Resource] A requested resource couldn’t be retrieved. // - [Disabled] The resource is unlikely to become available during the retry window. // .resourceUnavailable, // [Security] An attempt to establish a secure connection failed for reasons that can’t be expressed more // specifically. // - [Enabled] The secure connection could be established during a retry given the lack of specificity // provided by the error. .secureConnectionFailed, // [Security] A server certificate had a date which indicates it has expired, or is not yet valid. // - [Enabled] The server certificate could become valid within the retry window. .serverCertificateHasBadDate, // [Security] A server certificate was not signed by any root server. // - [Disabled] The server certificate is unlikely to change during the retry window. // .serverCertificateHasUnknownRoot, // [Security] A server certificate is not yet valid. // - [Enabled] The server certificate could become valid within the retry window. .serverCertificateNotYetValid, // [Security] A server certificate was signed by a root server that isn’t trusted. // - [Disabled] The server certificate is unlikely to become trusted within the retry window. // .serverCertificateUntrusted, // [Network] An asynchronous operation timed out. // - [Enabled] The request timed out for an unknown reason and should be retried. .timedOut // [System] The URL Loading System encountered an error that it can’t interpret. // - [Disabled] The error could not be interpreted and is unlikely to be recovered from during a retry. // .unknown, // [Resource] A properly formed URL couldn’t be handled by the framework. // - [Disabled] The URL is unlikely to change during a retry. // .unsupportedURL, // [Client] Authentication is required to access a resource. // - [Disabled] The user authentication is unlikely to be provided by retrying. // .userAuthenticationRequired, // [Client] An asynchronous request for authentication has been canceled by the user. // - [Disabled] The user cancelled authentication and explicitly took action to not retry. // .userCancelledAuthentication, // [Resource] A server reported that a URL has a non-zero content length, but terminated the network connection // gracefully without sending any data. // - [Disabled] The server is unlikely to provide data during the retry window. // .zeroByteResource, ] /// The total number of times the request is allowed to be retried. public let retryLimit: UInt /// The base of the exponential backoff policy (should always be greater than or equal to 2). public let exponentialBackoffBase: UInt /// The scale of the exponential backoff. public let exponentialBackoffScale: Double /// The HTTP methods that are allowed to be retried. public let retryableHTTPMethods: Set /// The HTTP status codes that are automatically retried by the policy. public let retryableHTTPStatusCodes: Set /// The URL error codes that are automatically retried by the policy. public let retryableURLErrorCodes: Set /// Creates a `RetryPolicy` from the specified parameters. /// /// - Parameters: /// - retryLimit: The total number of times the request is allowed to be retried. `2` by default. /// - exponentialBackoffBase: The base of the exponential backoff policy. `2` by default. /// - exponentialBackoffScale: The scale of the exponential backoff. `0.5` by default. /// - retryableHTTPMethods: The HTTP methods that are allowed to be retried. /// `RetryPolicy.defaultRetryableHTTPMethods` by default. /// - retryableHTTPStatusCodes: The HTTP status codes that are automatically retried by the policy. /// `RetryPolicy.defaultRetryableHTTPStatusCodes` by default. /// - retryableURLErrorCodes: The URL error codes that are automatically retried by the policy. /// `RetryPolicy.defaultRetryableURLErrorCodes` by default. public init(retryLimit: UInt = RetryPolicy.defaultRetryLimit, exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase, exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale, retryableHTTPMethods: Set = RetryPolicy.defaultRetryableHTTPMethods, retryableHTTPStatusCodes: Set = RetryPolicy.defaultRetryableHTTPStatusCodes, retryableURLErrorCodes: Set = RetryPolicy.defaultRetryableURLErrorCodes) { precondition(exponentialBackoffBase >= 2, "The `exponentialBackoffBase` must be a minimum of 2.") self.retryLimit = retryLimit self.exponentialBackoffBase = exponentialBackoffBase self.exponentialBackoffScale = exponentialBackoffScale self.retryableHTTPMethods = retryableHTTPMethods self.retryableHTTPStatusCodes = retryableHTTPStatusCodes self.retryableURLErrorCodes = retryableURLErrorCodes } open func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { if request.retryCount < retryLimit, shouldRetry(request: request, dueTo: error) { completion(.retryWithDelay(pow(Double(exponentialBackoffBase), Double(request.retryCount)) * exponentialBackoffScale)) } else { completion(.doNotRetry) } } /// Determines whether or not to retry the provided `Request`. /// /// - Parameters: /// - request: `Request` that failed due to the provided `Error`. /// - error: `Error` encountered while executing the `Request`. /// /// - Returns: `Bool` determining whether or not to retry the `Request`. open func shouldRetry(request: Request, dueTo error: Error) -> Bool { guard let httpMethod = request.request?.method, retryableHTTPMethods.contains(httpMethod) else { return false } if let statusCode = request.response?.statusCode, retryableHTTPStatusCodes.contains(statusCode) { return true } else { let errorCode = (error as? URLError)?.code let afErrorCode = (error.asAFError?.underlyingError as? URLError)?.code guard let code = errorCode ?? afErrorCode else { return false } return retryableURLErrorCodes.contains(code) } } } #if swift(>=5.5) extension RequestInterceptor where Self == RetryPolicy { /// Provides a default `RetryPolicy` instance. public static var retryPolicy: RetryPolicy { RetryPolicy() } /// Creates an `RetryPolicy` from the specified parameters. /// /// - Parameters: /// - retryLimit: The total number of times the request is allowed to be retried. `2` by default. /// - exponentialBackoffBase: The base of the exponential backoff policy. `2` by default. /// - exponentialBackoffScale: The scale of the exponential backoff. `0.5` by default. /// - retryableHTTPMethods: The HTTP methods that are allowed to be retried. /// `RetryPolicy.defaultRetryableHTTPMethods` by default. /// - retryableHTTPStatusCodes: The HTTP status codes that are automatically retried by the policy. /// `RetryPolicy.defaultRetryableHTTPStatusCodes` by default. /// - retryableURLErrorCodes: The URL error codes that are automatically retried by the policy. /// `RetryPolicy.defaultRetryableURLErrorCodes` by default. /// /// - Returns: The `RetryPolicy` public static func retryPolicy(retryLimit: UInt = RetryPolicy.defaultRetryLimit, exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase, exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale, retryableHTTPMethods: Set = RetryPolicy.defaultRetryableHTTPMethods, retryableHTTPStatusCodes: Set = RetryPolicy.defaultRetryableHTTPStatusCodes, retryableURLErrorCodes: Set = RetryPolicy.defaultRetryableURLErrorCodes) -> RetryPolicy { RetryPolicy(retryLimit: retryLimit, exponentialBackoffBase: exponentialBackoffBase, exponentialBackoffScale: exponentialBackoffScale, retryableHTTPMethods: retryableHTTPMethods, retryableHTTPStatusCodes: retryableHTTPStatusCodes, retryableURLErrorCodes: retryableURLErrorCodes) } } #endif // MARK: - /// A retry policy that automatically retries idempotent requests for network connection lost errors. For more /// information about retrying network connection lost errors, please refer to Apple's /// [technical document](https://developer.apple.com/library/content/qa/qa1941/_index.html). open class ConnectionLostRetryPolicy: RetryPolicy { /// Creates a `ConnectionLostRetryPolicy` instance from the specified parameters. /// /// - Parameters: /// - retryLimit: The total number of times the request is allowed to be retried. /// `RetryPolicy.defaultRetryLimit` by default. /// - exponentialBackoffBase: The base of the exponential backoff policy. /// `RetryPolicy.defaultExponentialBackoffBase` by default. /// - exponentialBackoffScale: The scale of the exponential backoff. /// `RetryPolicy.defaultExponentialBackoffScale` by default. /// - retryableHTTPMethods: The idempotent http methods to retry. /// `RetryPolicy.defaultRetryableHTTPMethods` by default. public init(retryLimit: UInt = RetryPolicy.defaultRetryLimit, exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase, exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale, retryableHTTPMethods: Set = RetryPolicy.defaultRetryableHTTPMethods) { super.init(retryLimit: retryLimit, exponentialBackoffBase: exponentialBackoffBase, exponentialBackoffScale: exponentialBackoffScale, retryableHTTPMethods: retryableHTTPMethods, retryableHTTPStatusCodes: [], retryableURLErrorCodes: [.networkConnectionLost]) } } #if swift(>=5.5) extension RequestInterceptor where Self == ConnectionLostRetryPolicy { /// Provides a default `ConnectionLostRetryPolicy` instance. public static var connectionLostRetryPolicy: ConnectionLostRetryPolicy { ConnectionLostRetryPolicy() } /// Creates a `ConnectionLostRetryPolicy` instance from the specified parameters. /// /// - Parameters: /// - retryLimit: The total number of times the request is allowed to be retried. /// `RetryPolicy.defaultRetryLimit` by default. /// - exponentialBackoffBase: The base of the exponential backoff policy. /// `RetryPolicy.defaultExponentialBackoffBase` by default. /// - exponentialBackoffScale: The scale of the exponential backoff. /// `RetryPolicy.defaultExponentialBackoffScale` by default. /// - retryableHTTPMethods: The idempotent http methods to retry. /// /// - Returns: The `ConnectionLostRetryPolicy`. public static func connectionLostRetryPolicy(retryLimit: UInt = RetryPolicy.defaultRetryLimit, exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase, exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale, retryableHTTPMethods: Set = RetryPolicy.defaultRetryableHTTPMethods) -> ConnectionLostRetryPolicy { ConnectionLostRetryPolicy(retryLimit: retryLimit, exponentialBackoffBase: exponentialBackoffBase, exponentialBackoffScale: exponentialBackoffScale, retryableHTTPMethods: retryableHTTPMethods) } } #endif ================================================ FILE: JetChat/Pods/Alamofire/Source/ServerTrustEvaluation.swift ================================================ // // ServerTrustPolicy.swift // // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Responsible for managing the mapping of `ServerTrustEvaluating` values to given hosts. open class ServerTrustManager { /// Determines whether all hosts for this `ServerTrustManager` must be evaluated. `true` by default. public let allHostsMustBeEvaluated: Bool /// The dictionary of policies mapped to a particular host. public let evaluators: [String: ServerTrustEvaluating] /// Initializes the `ServerTrustManager` instance with the given evaluators. /// /// Since different servers and web services can have different leaf certificates, intermediate and even root /// certificates, it is important to have the flexibility to specify evaluation policies on a per host basis. This /// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key /// pinning for host3 and disabling evaluation for host4. /// /// - Parameters: /// - allHostsMustBeEvaluated: The value determining whether all hosts for this instance must be evaluated. `true` /// by default. /// - evaluators: A dictionary of evaluators mapped to hosts. public init(allHostsMustBeEvaluated: Bool = true, evaluators: [String: ServerTrustEvaluating]) { self.allHostsMustBeEvaluated = allHostsMustBeEvaluated self.evaluators = evaluators } #if !(os(Linux) || os(Windows)) /// Returns the `ServerTrustEvaluating` value for the given host, if one is set. /// /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override /// this method and implement more complex mapping implementations such as wildcards. /// /// - Parameter host: The host to use when searching for a matching policy. /// /// - Returns: The `ServerTrustEvaluating` value for the given host if found, `nil` otherwise. /// - Throws: `AFError.serverTrustEvaluationFailed` if `allHostsMustBeEvaluated` is `true` and no matching /// evaluators are found. open func serverTrustEvaluator(forHost host: String) throws -> ServerTrustEvaluating? { guard let evaluator = evaluators[host] else { if allHostsMustBeEvaluated { throw AFError.serverTrustEvaluationFailed(reason: .noRequiredEvaluator(host: host)) } return nil } return evaluator } #endif } /// A protocol describing the API used to evaluate server trusts. public protocol ServerTrustEvaluating { #if os(Linux) || os(Windows) // Implement this once Linux/Windows has API for evaluating server trusts. #else /// Evaluates the given `SecTrust` value for the given `host`. /// /// - Parameters: /// - trust: The `SecTrust` value to evaluate. /// - host: The host for which to evaluate the `SecTrust` value. /// /// - Returns: A `Bool` indicating whether the evaluator considers the `SecTrust` value valid for `host`. func evaluate(_ trust: SecTrust, forHost host: String) throws #endif } // MARK: - Server Trust Evaluators #if !(os(Linux) || os(Windows)) /// An evaluator which uses the default server trust evaluation while allowing you to control whether to validate the /// host provided by the challenge. Applications are encouraged to always validate the host in production environments /// to guarantee the validity of the server's certificate chain. public final class DefaultTrustEvaluator: ServerTrustEvaluating { private let validateHost: Bool /// Creates a `DefaultTrustEvaluator`. /// /// - Parameter validateHost: Determines whether or not the evaluator should validate the host. `true` by default. public init(validateHost: Bool = true) { self.validateHost = validateHost } public func evaluate(_ trust: SecTrust, forHost host: String) throws { if validateHost { try trust.af.performValidation(forHost: host) } try trust.af.performDefaultValidation(forHost: host) } } /// An evaluator which Uses the default and revoked server trust evaluations allowing you to control whether to validate /// the host provided by the challenge as well as specify the revocation flags for testing for revoked certificates. /// Apple platforms did not start testing for revoked certificates automatically until iOS 10.1, macOS 10.12 and tvOS /// 10.1 which is demonstrated in our TLS tests. Applications are encouraged to always validate the host in production /// environments to guarantee the validity of the server's certificate chain. public final class RevocationTrustEvaluator: ServerTrustEvaluating { /// Represents the options to be use when evaluating the status of a certificate. /// Only Revocation Policy Constants are valid, and can be found in [Apple's documentation](https://developer.apple.com/documentation/security/certificate_key_and_trust_services/policies/1563600-revocation_policy_constants). public struct Options: OptionSet { /// Perform revocation checking using the CRL (Certification Revocation List) method. public static let crl = Options(rawValue: kSecRevocationCRLMethod) /// Consult only locally cached replies; do not use network access. public static let networkAccessDisabled = Options(rawValue: kSecRevocationNetworkAccessDisabled) /// Perform revocation checking using OCSP (Online Certificate Status Protocol). public static let ocsp = Options(rawValue: kSecRevocationOCSPMethod) /// Prefer CRL revocation checking over OCSP; by default, OCSP is preferred. public static let preferCRL = Options(rawValue: kSecRevocationPreferCRL) /// Require a positive response to pass the policy. If the flag is not set, revocation checking is done on a /// "best attempt" basis, where failure to reach the server is not considered fatal. public static let requirePositiveResponse = Options(rawValue: kSecRevocationRequirePositiveResponse) /// Perform either OCSP or CRL checking. The checking is performed according to the method(s) specified in the /// certificate and the value of `preferCRL`. public static let any = Options(rawValue: kSecRevocationUseAnyAvailableMethod) /// The raw value of the option. public let rawValue: CFOptionFlags /// Creates an `Options` value with the given `CFOptionFlags`. /// /// - Parameter rawValue: The `CFOptionFlags` value to initialize with. public init(rawValue: CFOptionFlags) { self.rawValue = rawValue } } private let performDefaultValidation: Bool private let validateHost: Bool private let options: Options /// Creates a `RevocationTrustEvaluator` using the provided parameters. /// /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates. /// /// - Parameters: /// - performDefaultValidation: Determines whether default validation should be performed in addition to /// evaluating the pinned certificates. `true` by default. /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to /// performing the default evaluation, even if `performDefaultValidation` is `false`. /// `true` by default. /// - options: The `Options` to use to check the revocation status of the certificate. `.any` by /// default. public init(performDefaultValidation: Bool = true, validateHost: Bool = true, options: Options = .any) { self.performDefaultValidation = performDefaultValidation self.validateHost = validateHost self.options = options } public func evaluate(_ trust: SecTrust, forHost host: String) throws { if performDefaultValidation { try trust.af.performDefaultValidation(forHost: host) } if validateHost { try trust.af.performValidation(forHost: host) } if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) { try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options)) } else { try trust.af.validate(policy: SecPolicy.af.revocation(options: options)) { status, result in AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options)) } } } } #if swift(>=5.5) extension ServerTrustEvaluating where Self == RevocationTrustEvaluator { /// Provides a default `RevocationTrustEvaluator` instance. public static var revocationChecking: RevocationTrustEvaluator { RevocationTrustEvaluator() } /// Creates a `RevocationTrustEvaluator` using the provided parameters. /// /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates. /// /// - Parameters: /// - performDefaultValidation: Determines whether default validation should be performed in addition to /// evaluating the pinned certificates. `true` by default. /// - validateHost: Determines whether or not the evaluator should validate the host, in addition /// to performing the default evaluation, even if `performDefaultValidation` is /// `false`. `true` by default. /// - options: The `Options` to use to check the revocation status of the certificate. `.any` /// by default. /// - Returns: The `RevocationTrustEvaluator`. public static func revocationChecking(performDefaultValidation: Bool = true, validateHost: Bool = true, options: RevocationTrustEvaluator.Options = .any) -> RevocationTrustEvaluator { RevocationTrustEvaluator(performDefaultValidation: performDefaultValidation, validateHost: validateHost, options: options) } } #endif /// Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned /// certificates match one of the server certificates. By validating both the certificate chain and host, certificate /// pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks. /// Applications are encouraged to always validate the host and require a valid certificate chain in production /// environments. public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating { private let certificates: [SecCertificate] private let acceptSelfSignedCertificates: Bool private let performDefaultValidation: Bool private let validateHost: Bool /// Creates a `PinnedCertificatesTrustEvaluator` from the provided parameters. /// /// - Parameters: /// - certificates: The certificates to use to evaluate the trust. All `cer`, `crt`, and `der` /// certificates in `Bundle.main` by default. /// - acceptSelfSignedCertificates: Adds the provided certificates as anchors for the trust evaluation, allowing /// self-signed certificates to pass. `false` by default. THIS SETTING SHOULD BE /// FALSE IN PRODUCTION! /// - performDefaultValidation: Determines whether default validation should be performed in addition to /// evaluating the pinned certificates. `true` by default. /// - validateHost: Determines whether or not the evaluator should validate the host, in addition /// to performing the default evaluation, even if `performDefaultValidation` is /// `false`. `true` by default. public init(certificates: [SecCertificate] = Bundle.main.af.certificates, acceptSelfSignedCertificates: Bool = false, performDefaultValidation: Bool = true, validateHost: Bool = true) { self.certificates = certificates self.acceptSelfSignedCertificates = acceptSelfSignedCertificates self.performDefaultValidation = performDefaultValidation self.validateHost = validateHost } public func evaluate(_ trust: SecTrust, forHost host: String) throws { guard !certificates.isEmpty else { throw AFError.serverTrustEvaluationFailed(reason: .noCertificatesFound) } if acceptSelfSignedCertificates { try trust.af.setAnchorCertificates(certificates) } if performDefaultValidation { try trust.af.performDefaultValidation(forHost: host) } if validateHost { try trust.af.performValidation(forHost: host) } let serverCertificatesData = Set(trust.af.certificateData) let pinnedCertificatesData = Set(certificates.af.data) let pinnedCertificatesInServerData = !serverCertificatesData.isDisjoint(with: pinnedCertificatesData) if !pinnedCertificatesInServerData { throw AFError.serverTrustEvaluationFailed(reason: .certificatePinningFailed(host: host, trust: trust, pinnedCertificates: certificates, serverCertificates: trust.af.certificates)) } } } #if swift(>=5.5) extension ServerTrustEvaluating where Self == PinnedCertificatesTrustEvaluator { /// Provides a default `PinnedCertificatesTrustEvaluator` instance. public static var pinnedCertificates: PinnedCertificatesTrustEvaluator { PinnedCertificatesTrustEvaluator() } /// Creates a `PinnedCertificatesTrustEvaluator` using the provided parameters. /// /// - Parameters: /// - certificates: The certificates to use to evaluate the trust. All `cer`, `crt`, and `der` /// certificates in `Bundle.main` by default. /// - acceptSelfSignedCertificates: Adds the provided certificates as anchors for the trust evaluation, allowing /// self-signed certificates to pass. `false` by default. THIS SETTING SHOULD BE /// FALSE IN PRODUCTION! /// - performDefaultValidation: Determines whether default validation should be performed in addition to /// evaluating the pinned certificates. `true` by default. /// - validateHost: Determines whether or not the evaluator should validate the host, in addition /// to performing the default evaluation, even if `performDefaultValidation` is /// `false`. `true` by default. public static func pinnedCertificates(certificates: [SecCertificate] = Bundle.main.af.certificates, acceptSelfSignedCertificates: Bool = false, performDefaultValidation: Bool = true, validateHost: Bool = true) -> PinnedCertificatesTrustEvaluator { PinnedCertificatesTrustEvaluator(certificates: certificates, acceptSelfSignedCertificates: acceptSelfSignedCertificates, performDefaultValidation: performDefaultValidation, validateHost: validateHost) } } #endif /// Uses the pinned public keys to validate the server trust. The server trust is considered valid if one of the pinned /// public keys match one of the server certificate public keys. By validating both the certificate chain and host, /// public key pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks. /// Applications are encouraged to always validate the host and require a valid certificate chain in production /// environments. public final class PublicKeysTrustEvaluator: ServerTrustEvaluating { private let keys: [SecKey] private let performDefaultValidation: Bool private let validateHost: Bool /// Creates a `PublicKeysTrustEvaluator` from the provided parameters. /// /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates. /// /// - Parameters: /// - keys: The `SecKey`s to use to validate public keys. Defaults to the public keys of all /// certificates included in the main bundle. /// - performDefaultValidation: Determines whether default validation should be performed in addition to /// evaluating the pinned certificates. `true` by default. /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to /// performing the default evaluation, even if `performDefaultValidation` is `false`. /// `true` by default. public init(keys: [SecKey] = Bundle.main.af.publicKeys, performDefaultValidation: Bool = true, validateHost: Bool = true) { self.keys = keys self.performDefaultValidation = performDefaultValidation self.validateHost = validateHost } public func evaluate(_ trust: SecTrust, forHost host: String) throws { guard !keys.isEmpty else { throw AFError.serverTrustEvaluationFailed(reason: .noPublicKeysFound) } if performDefaultValidation { try trust.af.performDefaultValidation(forHost: host) } if validateHost { try trust.af.performValidation(forHost: host) } let pinnedKeysInServerKeys: Bool = { for serverPublicKey in trust.af.publicKeys { for pinnedPublicKey in keys { if serverPublicKey == pinnedPublicKey { return true } } } return false }() if !pinnedKeysInServerKeys { throw AFError.serverTrustEvaluationFailed(reason: .publicKeyPinningFailed(host: host, trust: trust, pinnedKeys: keys, serverKeys: trust.af.publicKeys)) } } } #if swift(>=5.5) extension ServerTrustEvaluating where Self == PublicKeysTrustEvaluator { /// Provides a default `PublicKeysTrustEvaluator` instance. public static var publicKeys: PublicKeysTrustEvaluator { PublicKeysTrustEvaluator() } /// Creates a `PublicKeysTrustEvaluator` from the provided parameters. /// /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates. /// /// - Parameters: /// - keys: The `SecKey`s to use to validate public keys. Defaults to the public keys of all /// certificates included in the main bundle. /// - performDefaultValidation: Determines whether default validation should be performed in addition to /// evaluating the pinned certificates. `true` by default. /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to /// performing the default evaluation, even if `performDefaultValidation` is `false`. /// `true` by default. public static func publicKeys(keys: [SecKey] = Bundle.main.af.publicKeys, performDefaultValidation: Bool = true, validateHost: Bool = true) -> PublicKeysTrustEvaluator { PublicKeysTrustEvaluator(keys: keys, performDefaultValidation: performDefaultValidation, validateHost: validateHost) } } #endif /// Uses the provided evaluators to validate the server trust. The trust is only considered valid if all of the /// evaluators consider it valid. public final class CompositeTrustEvaluator: ServerTrustEvaluating { private let evaluators: [ServerTrustEvaluating] /// Creates a `CompositeTrustEvaluator` from the provided evaluators. /// /// - Parameter evaluators: The `ServerTrustEvaluating` values used to evaluate the server trust. public init(evaluators: [ServerTrustEvaluating]) { self.evaluators = evaluators } public func evaluate(_ trust: SecTrust, forHost host: String) throws { try evaluators.evaluate(trust, forHost: host) } } #if swift(>=5.5) extension ServerTrustEvaluating where Self == CompositeTrustEvaluator { /// Creates a `CompositeTrustEvaluator` from the provided evaluators. /// /// - Parameter evaluators: The `ServerTrustEvaluating` values used to evaluate the server trust. public static func composite(evaluators: [ServerTrustEvaluating]) -> CompositeTrustEvaluator { CompositeTrustEvaluator(evaluators: evaluators) } } #endif /// Disables all evaluation which in turn will always consider any server trust as valid. /// /// - Note: Instead of disabling server trust evaluation, it's a better idea to configure systems to properly trust test /// certificates, as outlined in [this Apple tech note](https://developer.apple.com/library/archive/qa/qa1948/_index.html). /// /// **THIS EVALUATOR SHOULD NEVER BE USED IN PRODUCTION!** @available(*, deprecated, renamed: "DisabledTrustEvaluator", message: "DisabledEvaluator has been renamed DisabledTrustEvaluator.") public typealias DisabledEvaluator = DisabledTrustEvaluator /// Disables all evaluation which in turn will always consider any server trust as valid. /// /// /// - Note: Instead of disabling server trust evaluation, it's a better idea to configure systems to properly trust test /// certificates, as outlined in [this Apple tech note](https://developer.apple.com/library/archive/qa/qa1948/_index.html). /// /// **THIS EVALUATOR SHOULD NEVER BE USED IN PRODUCTION!** public final class DisabledTrustEvaluator: ServerTrustEvaluating { /// Creates an instance. public init() {} public func evaluate(_ trust: SecTrust, forHost host: String) throws {} } // MARK: - Extensions extension Array where Element == ServerTrustEvaluating { #if os(Linux) || os(Windows) // Add this same convenience method for Linux/Windows. #else /// Evaluates the given `SecTrust` value for the given `host`. /// /// - Parameters: /// - trust: The `SecTrust` value to evaluate. /// - host: The host for which to evaluate the `SecTrust` value. /// /// - Returns: Whether or not the evaluator considers the `SecTrust` value valid for `host`. public func evaluate(_ trust: SecTrust, forHost host: String) throws { for evaluator in self { try evaluator.evaluate(trust, forHost: host) } } #endif } extension Bundle: AlamofireExtended {} extension AlamofireExtension where ExtendedType: Bundle { /// Returns all valid `cer`, `crt`, and `der` certificates in the bundle. public var certificates: [SecCertificate] { paths(forResourcesOfTypes: [".cer", ".CER", ".crt", ".CRT", ".der", ".DER"]).compactMap { path in guard let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData, let certificate = SecCertificateCreateWithData(nil, certificateData) else { return nil } return certificate } } /// Returns all public keys for the valid certificates in the bundle. public var publicKeys: [SecKey] { certificates.af.publicKeys } /// Returns all pathnames for the resources identified by the provided file extensions. /// /// - Parameter types: The filename extensions locate. /// /// - Returns: All pathnames for the given filename extensions. public func paths(forResourcesOfTypes types: [String]) -> [String] { Array(Set(types.flatMap { type.paths(forResourcesOfType: $0, inDirectory: nil) })) } } extension SecTrust: AlamofireExtended {} extension AlamofireExtension where ExtendedType == SecTrust { /// Evaluates `self` after applying the `SecPolicy` value provided. /// /// - Parameter policy: The `SecPolicy` to apply to `self` before evaluation. /// /// - Throws: Any `Error` from applying the `SecPolicy` or from evaluation. @available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) public func evaluate(afterApplying policy: SecPolicy) throws { try apply(policy: policy).af.evaluate() } /// Attempts to validate `self` using the `SecPolicy` provided and transforming any error produced using the closure passed. /// /// - Parameters: /// - policy: The `SecPolicy` used to evaluate `self`. /// - errorProducer: The closure used transform the failed `OSStatus` and `SecTrustResultType`. /// - Throws: Any `Error` from applying the `policy`, or the result of `errorProducer` if validation fails. @available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)") @available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate(afterApplying:)") @available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)") @available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate(afterApplying:)") public func validate(policy: SecPolicy, errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws { try apply(policy: policy).af.validate(errorProducer: errorProducer) } /// Applies a `SecPolicy` to `self`, throwing if it fails. /// /// - Parameter policy: The `SecPolicy`. /// /// - Returns: `self`, with the policy applied. /// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.policyApplicationFailed` reason. public func apply(policy: SecPolicy) throws -> SecTrust { let status = SecTrustSetPolicies(type, policy) guard status.af.isSuccess else { throw AFError.serverTrustEvaluationFailed(reason: .policyApplicationFailed(trust: type, policy: policy, status: status)) } return type } /// Evaluate `self`, throwing an `Error` if evaluation fails. /// /// - Throws: `AFError.serverTrustEvaluationFailed` with reason `.trustValidationFailed` and associated error from /// the underlying evaluation. @available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) public func evaluate() throws { var error: CFError? let evaluationSucceeded = SecTrustEvaluateWithError(type, &error) if !evaluationSucceeded { throw AFError.serverTrustEvaluationFailed(reason: .trustEvaluationFailed(error: error)) } } /// Validate `self`, passing any failure values through `errorProducer`. /// /// - Parameter errorProducer: The closure used to transform the failed `OSStatus` and `SecTrustResultType` into an /// `Error`. /// - Throws: The `Error` produced by the `errorProducer` closure. @available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate()") @available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate()") @available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate()") @available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate()") public func validate(errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws { var result = SecTrustResultType.invalid let status = SecTrustEvaluate(type, &result) guard status.af.isSuccess && result.af.isSuccess else { throw errorProducer(status, result) } } /// Sets a custom certificate chain on `self`, allowing full validation of a self-signed certificate and its chain. /// /// - Parameter certificates: The `SecCertificate`s to add to the chain. /// - Throws: Any error produced when applying the new certificate chain. public func setAnchorCertificates(_ certificates: [SecCertificate]) throws { // Add additional anchor certificates. let status = SecTrustSetAnchorCertificates(type, certificates as CFArray) guard status.af.isSuccess else { throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: status, certificates: certificates)) } // Trust only the set anchor certs. let onlyStatus = SecTrustSetAnchorCertificatesOnly(type, true) guard onlyStatus.af.isSuccess else { throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: onlyStatus, certificates: certificates)) } } /// The public keys contained in `self`. public var publicKeys: [SecKey] { certificates.af.publicKeys } #if swift(>=5.5) // Xcode 13 / 2021 SDKs. /// The `SecCertificate`s contained in `self`. public var certificates: [SecCertificate] { if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { return (SecTrustCopyCertificateChain(type) as? [SecCertificate]) ?? [] } else { return (0.. SecPolicy { SecPolicyCreateSSL(true, hostname as CFString) } /// Creates a `SecPolicy` which checks the revocation of certificates. /// /// - Parameter options: The `RevocationTrustEvaluator.Options` for evaluation. /// /// - Returns: The `SecPolicy`. /// - Throws: An `AFError.serverTrustEvaluationFailed` error with reason `.revocationPolicyCreationFailed` /// if the policy cannot be created. public static func revocation(options: RevocationTrustEvaluator.Options) throws -> SecPolicy { guard let policy = SecPolicyCreateRevocation(options.rawValue) else { throw AFError.serverTrustEvaluationFailed(reason: .revocationPolicyCreationFailed) } return policy } } extension Array: AlamofireExtended {} extension AlamofireExtension where ExtendedType == [SecCertificate] { /// All `Data` values for the contained `SecCertificate`s. public var data: [Data] { type.map { SecCertificateCopyData($0) as Data } } /// All public `SecKey` values for the contained `SecCertificate`s. public var publicKeys: [SecKey] { type.compactMap(\.af.publicKey) } } extension SecCertificate: AlamofireExtended {} extension AlamofireExtension where ExtendedType == SecCertificate { /// The public key for `self`, if it can be extracted. /// /// - Note: On 2020 OSes and newer, only RSA and ECDSA keys are supported. /// public var publicKey: SecKey? { let policy = SecPolicyCreateBasicX509() var trust: SecTrust? let trustCreationStatus = SecTrustCreateWithCertificates(type, policy, &trust) guard let createdTrust = trust, trustCreationStatus == errSecSuccess else { return nil } if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) { return SecTrustCopyKey(createdTrust) } else { return SecTrustCopyPublicKey(createdTrust) } } } extension OSStatus: AlamofireExtended {} extension AlamofireExtension where ExtendedType == OSStatus { /// Returns whether `self` is `errSecSuccess`. public var isSuccess: Bool { type == errSecSuccess } } extension SecTrustResultType: AlamofireExtended {} extension AlamofireExtension where ExtendedType == SecTrustResultType { /// Returns whether `self is `.unspecified` or `.proceed`. public var isSuccess: Bool { type == .unspecified || type == .proceed } } #endif ================================================ FILE: JetChat/Pods/Alamofire/Source/Session.swift ================================================ // // Session.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// `Session` creates and manages Alamofire's `Request` types during their lifetimes. It also provides common /// functionality for all `Request`s, including queuing, interception, trust management, redirect handling, and response /// cache handling. open class Session { /// Shared singleton instance used by all `AF.request` APIs. Cannot be modified. public static let `default` = Session() /// Underlying `URLSession` used to create `URLSessionTasks` for this instance, and for which this instance's /// `delegate` handles `URLSessionDelegate` callbacks. /// /// - Note: This instance should **NOT** be used to interact with the underlying `URLSessionTask`s. Doing so will /// break internal Alamofire logic that tracks those tasks. /// public let session: URLSession /// Instance's `SessionDelegate`, which handles the `URLSessionDelegate` methods and `Request` interaction. public let delegate: SessionDelegate /// Root `DispatchQueue` for all internal callbacks and state update. **MUST** be a serial queue. public let rootQueue: DispatchQueue /// Value determining whether this instance automatically calls `resume()` on all created `Request`s. public let startRequestsImmediately: Bool /// `DispatchQueue` on which `URLRequest`s are created asynchronously. By default this queue uses `rootQueue` as its /// `target`, but a separate queue can be used if request creation is determined to be a bottleneck. Always profile /// and test before introducing an additional queue. public let requestQueue: DispatchQueue /// `DispatchQueue` passed to all `Request`s on which they perform their response serialization. By default this /// queue uses `rootQueue` as its `target` but a separate queue can be used if response serialization is determined /// to be a bottleneck. Always profile and test before introducing an additional queue. public let serializationQueue: DispatchQueue /// `RequestInterceptor` used for all `Request` created by the instance. `RequestInterceptor`s can also be set on a /// per-`Request` basis, in which case the `Request`'s interceptor takes precedence over this value. public let interceptor: RequestInterceptor? /// `ServerTrustManager` instance used to evaluate all trust challenges and provide certificate and key pinning. public let serverTrustManager: ServerTrustManager? /// `RedirectHandler` instance used to provide customization for request redirection. public let redirectHandler: RedirectHandler? /// `CachedResponseHandler` instance used to provide customization of cached response handling. public let cachedResponseHandler: CachedResponseHandler? /// `CompositeEventMonitor` used to compose Alamofire's `defaultEventMonitors` and any passed `EventMonitor`s. public let eventMonitor: CompositeEventMonitor /// `EventMonitor`s included in all instances. `[AlamofireNotifications()]` by default. public let defaultEventMonitors: [EventMonitor] = [AlamofireNotifications()] /// Internal map between `Request`s and any `URLSessionTasks` that may be in flight for them. var requestTaskMap = RequestTaskMap() /// `Set` of currently active `Request`s. var activeRequests: Set = [] /// Completion events awaiting `URLSessionTaskMetrics`. var waitingCompletions: [URLSessionTask: () -> Void] = [:] /// Creates a `Session` from a `URLSession` and other parameters. /// /// - Note: When passing a `URLSession`, you must create the `URLSession` with a specific `delegateQueue` value and /// pass the `delegateQueue`'s `underlyingQueue` as the `rootQueue` parameter of this initializer. /// /// - Parameters: /// - session: Underlying `URLSession` for this instance. /// - delegate: `SessionDelegate` that handles `session`'s delegate callbacks as well as `Request` /// interaction. /// - rootQueue: Root `DispatchQueue` for all internal callbacks and state updates. **MUST** be a /// serial queue. /// - startRequestsImmediately: Determines whether this instance will automatically start all `Request`s. `true` /// by default. If set to `false`, all `Request`s created must have `.resume()` called. /// on them for them to start. /// - requestQueue: `DispatchQueue` on which to perform `URLRequest` creation. By default this queue /// will use the `rootQueue` as its `target`. A separate queue can be used if it's /// determined request creation is a bottleneck, but that should only be done after /// careful testing and profiling. `nil` by default. /// - serializationQueue: `DispatchQueue` on which to perform all response serialization. By default this /// queue will use the `rootQueue` as its `target`. A separate queue can be used if /// it's determined response serialization is a bottleneck, but that should only be /// done after careful testing and profiling. `nil` by default. /// - interceptor: `RequestInterceptor` to be used for all `Request`s created by this instance. `nil` /// by default. /// - serverTrustManager: `ServerTrustManager` to be used for all trust evaluations by this instance. `nil` /// by default. /// - redirectHandler: `RedirectHandler` to be used by all `Request`s created by this instance. `nil` by /// default. /// - cachedResponseHandler: `CachedResponseHandler` to be used by all `Request`s created by this instance. /// `nil` by default. /// - eventMonitors: Additional `EventMonitor`s used by the instance. Alamofire always adds a /// `AlamofireNotifications` `EventMonitor` to the array passed here. `[]` by default. public init(session: URLSession, delegate: SessionDelegate, rootQueue: DispatchQueue, startRequestsImmediately: Bool = true, requestQueue: DispatchQueue? = nil, serializationQueue: DispatchQueue? = nil, interceptor: RequestInterceptor? = nil, serverTrustManager: ServerTrustManager? = nil, redirectHandler: RedirectHandler? = nil, cachedResponseHandler: CachedResponseHandler? = nil, eventMonitors: [EventMonitor] = []) { precondition(session.configuration.identifier == nil, "Alamofire does not support background URLSessionConfigurations.") precondition(session.delegateQueue.underlyingQueue === rootQueue, "Session(session:) initializer must be passed the DispatchQueue used as the delegateQueue's underlyingQueue as rootQueue.") self.session = session self.delegate = delegate self.rootQueue = rootQueue self.startRequestsImmediately = startRequestsImmediately self.requestQueue = requestQueue ?? DispatchQueue(label: "\(rootQueue.label).requestQueue", target: rootQueue) self.serializationQueue = serializationQueue ?? DispatchQueue(label: "\(rootQueue.label).serializationQueue", target: rootQueue) self.interceptor = interceptor self.serverTrustManager = serverTrustManager self.redirectHandler = redirectHandler self.cachedResponseHandler = cachedResponseHandler eventMonitor = CompositeEventMonitor(monitors: defaultEventMonitors + eventMonitors) delegate.eventMonitor = eventMonitor delegate.stateProvider = self } /// Creates a `Session` from a `URLSessionConfiguration`. /// /// - Note: This initializer lets Alamofire handle the creation of the underlying `URLSession` and its /// `delegateQueue`, and is the recommended initializer for most uses. /// /// - Parameters: /// - configuration: `URLSessionConfiguration` to be used to create the underlying `URLSession`. Changes /// to this value after being passed to this initializer will have no effect. /// `URLSessionConfiguration.af.default` by default. /// - delegate: `SessionDelegate` that handles `session`'s delegate callbacks as well as `Request` /// interaction. `SessionDelegate()` by default. /// - rootQueue: Root `DispatchQueue` for all internal callbacks and state updates. **MUST** be a /// serial queue. `DispatchQueue(label: "org.alamofire.session.rootQueue")` by default. /// - startRequestsImmediately: Determines whether this instance will automatically start all `Request`s. `true` /// by default. If set to `false`, all `Request`s created must have `.resume()` called. /// on them for them to start. /// - requestQueue: `DispatchQueue` on which to perform `URLRequest` creation. By default this queue /// will use the `rootQueue` as its `target`. A separate queue can be used if it's /// determined request creation is a bottleneck, but that should only be done after /// careful testing and profiling. `nil` by default. /// - serializationQueue: `DispatchQueue` on which to perform all response serialization. By default this /// queue will use the `rootQueue` as its `target`. A separate queue can be used if /// it's determined response serialization is a bottleneck, but that should only be /// done after careful testing and profiling. `nil` by default. /// - interceptor: `RequestInterceptor` to be used for all `Request`s created by this instance. `nil` /// by default. /// - serverTrustManager: `ServerTrustManager` to be used for all trust evaluations by this instance. `nil` /// by default. /// - redirectHandler: `RedirectHandler` to be used by all `Request`s created by this instance. `nil` by /// default. /// - cachedResponseHandler: `CachedResponseHandler` to be used by all `Request`s created by this instance. /// `nil` by default. /// - eventMonitors: Additional `EventMonitor`s used by the instance. Alamofire always adds a /// `AlamofireNotifications` `EventMonitor` to the array passed here. `[]` by default. public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default, delegate: SessionDelegate = SessionDelegate(), rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"), startRequestsImmediately: Bool = true, requestQueue: DispatchQueue? = nil, serializationQueue: DispatchQueue? = nil, interceptor: RequestInterceptor? = nil, serverTrustManager: ServerTrustManager? = nil, redirectHandler: RedirectHandler? = nil, cachedResponseHandler: CachedResponseHandler? = nil, eventMonitors: [EventMonitor] = []) { precondition(configuration.identifier == nil, "Alamofire does not support background URLSessionConfigurations.") // Retarget the incoming rootQueue for safety, unless it's the main queue, which we know is safe. let serialRootQueue = (rootQueue === DispatchQueue.main) ? rootQueue : DispatchQueue(label: rootQueue.label, target: rootQueue) let delegateQueue = OperationQueue(maxConcurrentOperationCount: 1, underlyingQueue: serialRootQueue, name: "\(serialRootQueue.label).sessionDelegate") let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue) self.init(session: session, delegate: delegate, rootQueue: serialRootQueue, startRequestsImmediately: startRequestsImmediately, requestQueue: requestQueue, serializationQueue: serializationQueue, interceptor: interceptor, serverTrustManager: serverTrustManager, redirectHandler: redirectHandler, cachedResponseHandler: cachedResponseHandler, eventMonitors: eventMonitors) } deinit { finishRequestsForDeinit() session.invalidateAndCancel() } // MARK: - All Requests API /// Perform an action on all active `Request`s. /// /// - Note: The provided `action` closure is performed asynchronously, meaning that some `Request`s may complete and /// be unavailable by time it runs. Additionally, this action is performed on the instances's `rootQueue`, /// so care should be taken that actions are fast. Once the work on the `Request`s is complete, any /// additional work should be performed on another queue. /// /// - Parameters: /// - action: Closure to perform with all `Request`s. public func withAllRequests(perform action: @escaping (Set) -> Void) { rootQueue.async { action(self.activeRequests) } } /// Cancel all active `Request`s, optionally calling a completion handler when complete. /// /// - Note: This is an asynchronous operation and does not block the creation of future `Request`s. Cancelled /// `Request`s may not cancel immediately due internal work, and may not cancel at all if they are close to /// completion when cancelled. /// /// - Parameters: /// - queue: `DispatchQueue` on which the completion handler is run. `.main` by default. /// - completion: Closure to be called when all `Request`s have been cancelled. public func cancelAllRequests(completingOnQueue queue: DispatchQueue = .main, completion: (() -> Void)? = nil) { withAllRequests { requests in requests.forEach { $0.cancel() } queue.async { completion?() } } } // MARK: - DataRequest /// Closure which provides a `URLRequest` for mutation. public typealias RequestModifier = (inout URLRequest) throws -> Void struct RequestConvertible: URLRequestConvertible { let url: URLConvertible let method: HTTPMethod let parameters: Parameters? let encoding: ParameterEncoding let headers: HTTPHeaders? let requestModifier: RequestModifier? func asURLRequest() throws -> URLRequest { var request = try URLRequest(url: url, method: method, headers: headers) try requestModifier?(&request) return try encoding.encode(request, with: parameters) } } /// Creates a `DataRequest` from a `URLRequest` created using the passed components and a `RequestInterceptor`. /// /// - Parameters: /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. /// - parameters: `Parameters` (a.k.a. `[String: Any]`) value to be encoded into the `URLRequest`. `nil` by /// default. /// - encoding: `ParameterEncoding` to be used to encode the `parameters` value into the `URLRequest`. /// `URLEncoding.default` by default. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided /// parameters. `nil` by default. /// /// - Returns: The created `DataRequest`. open func request(_ convertible: URLConvertible, method: HTTPMethod = .get, parameters: Parameters? = nil, encoding: ParameterEncoding = URLEncoding.default, headers: HTTPHeaders? = nil, interceptor: RequestInterceptor? = nil, requestModifier: RequestModifier? = nil) -> DataRequest { let convertible = RequestConvertible(url: convertible, method: method, parameters: parameters, encoding: encoding, headers: headers, requestModifier: requestModifier) return request(convertible, interceptor: interceptor) } struct RequestEncodableConvertible: URLRequestConvertible { let url: URLConvertible let method: HTTPMethod let parameters: Parameters? let encoder: ParameterEncoder let headers: HTTPHeaders? let requestModifier: RequestModifier? func asURLRequest() throws -> URLRequest { var request = try URLRequest(url: url, method: method, headers: headers) try requestModifier?(&request) return try parameters.map { try encoder.encode($0, into: request) } ?? request } } /// Creates a `DataRequest` from a `URLRequest` created using the passed components, `Encodable` parameters, and a /// `RequestInterceptor`. /// /// - Parameters: /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. /// - parameters: `Encodable` value to be encoded into the `URLRequest`. `nil` by default. /// - encoder: `ParameterEncoder` to be used to encode the `parameters` value into the `URLRequest`. /// `URLEncodedFormParameterEncoder.default` by default. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from /// the provided parameters. `nil` by default. /// /// - Returns: The created `DataRequest`. open func request(_ convertible: URLConvertible, method: HTTPMethod = .get, parameters: Parameters? = nil, encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default, headers: HTTPHeaders? = nil, interceptor: RequestInterceptor? = nil, requestModifier: RequestModifier? = nil) -> DataRequest { let convertible = RequestEncodableConvertible(url: convertible, method: method, parameters: parameters, encoder: encoder, headers: headers, requestModifier: requestModifier) return request(convertible, interceptor: interceptor) } /// Creates a `DataRequest` from a `URLRequestConvertible` value and a `RequestInterceptor`. /// /// - Parameters: /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// /// - Returns: The created `DataRequest`. open func request(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil) -> DataRequest { let request = DataRequest(convertible: convertible, underlyingQueue: rootQueue, serializationQueue: serializationQueue, eventMonitor: eventMonitor, interceptor: interceptor, delegate: self) perform(request) return request } // MARK: - DataStreamRequest /// Creates a `DataStreamRequest` from the passed components, `Encodable` parameters, and `RequestInterceptor`. /// /// - Parameters: /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. /// - parameters: `Encodable` value to be encoded into the `URLRequest`. `nil` by default. /// - encoder: `ParameterEncoder` to be used to encode the `parameters` value into the /// `URLRequest`. /// `URLEncodedFormParameterEncoder.default` by default. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance should be canceled when an `Error` /// is thrown while serializing stream `Data`. `false` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` /// by default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from /// the provided parameters. `nil` by default. /// /// - Returns: The created `DataStream` request. open func streamRequest(_ convertible: URLConvertible, method: HTTPMethod = .get, parameters: Parameters? = nil, encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default, headers: HTTPHeaders? = nil, automaticallyCancelOnStreamError: Bool = false, interceptor: RequestInterceptor? = nil, requestModifier: RequestModifier? = nil) -> DataStreamRequest { let convertible = RequestEncodableConvertible(url: convertible, method: method, parameters: parameters, encoder: encoder, headers: headers, requestModifier: requestModifier) return streamRequest(convertible, automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, interceptor: interceptor) } /// Creates a `DataStreamRequest` from the passed components and `RequestInterceptor`. /// /// - Parameters: /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance should be canceled when an `Error` /// is thrown while serializing stream `Data`. `false` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` /// by default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from /// the provided parameters. `nil` by default. /// /// - Returns: The created `DataStream` request. open func streamRequest(_ convertible: URLConvertible, method: HTTPMethod = .get, headers: HTTPHeaders? = nil, automaticallyCancelOnStreamError: Bool = false, interceptor: RequestInterceptor? = nil, requestModifier: RequestModifier? = nil) -> DataStreamRequest { let convertible = RequestEncodableConvertible(url: convertible, method: method, parameters: Empty?.none, encoder: URLEncodedFormParameterEncoder.default, headers: headers, requestModifier: requestModifier) return streamRequest(convertible, automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, interceptor: interceptor) } /// Creates a `DataStreamRequest` from the passed `URLRequestConvertible` value and `RequestInterceptor`. /// /// - Parameters: /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance should be canceled when an `Error` /// is thrown while serializing stream `Data`. `false` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` /// by default. /// /// - Returns: The created `DataStreamRequest`. open func streamRequest(_ convertible: URLRequestConvertible, automaticallyCancelOnStreamError: Bool = false, interceptor: RequestInterceptor? = nil) -> DataStreamRequest { let request = DataStreamRequest(convertible: convertible, automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, underlyingQueue: rootQueue, serializationQueue: serializationQueue, eventMonitor: eventMonitor, interceptor: interceptor, delegate: self) perform(request) return request } // MARK: - DownloadRequest /// Creates a `DownloadRequest` using a `URLRequest` created using the passed components, `RequestInterceptor`, and /// `Destination`. /// /// - Parameters: /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. /// - parameters: `Parameters` (a.k.a. `[String: Any]`) value to be encoded into the `URLRequest`. `nil` by /// default. /// - encoding: `ParameterEncoding` to be used to encode the `parameters` value into the `URLRequest`. /// Defaults to `URLEncoding.default`. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided /// parameters. `nil` by default. /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file /// should be moved. `nil` by default. /// /// - Returns: The created `DownloadRequest`. open func download(_ convertible: URLConvertible, method: HTTPMethod = .get, parameters: Parameters? = nil, encoding: ParameterEncoding = URLEncoding.default, headers: HTTPHeaders? = nil, interceptor: RequestInterceptor? = nil, requestModifier: RequestModifier? = nil, to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { let convertible = RequestConvertible(url: convertible, method: method, parameters: parameters, encoding: encoding, headers: headers, requestModifier: requestModifier) return download(convertible, interceptor: interceptor, to: destination) } /// Creates a `DownloadRequest` from a `URLRequest` created using the passed components, `Encodable` parameters, and /// a `RequestInterceptor`. /// /// - Parameters: /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. /// - parameters: Value conforming to `Encodable` to be encoded into the `URLRequest`. `nil` by default. /// - encoder: `ParameterEncoder` to be used to encode the `parameters` value into the `URLRequest`. /// Defaults to `URLEncodedFormParameterEncoder.default`. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided /// parameters. `nil` by default. /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file /// should be moved. `nil` by default. /// /// - Returns: The created `DownloadRequest`. open func download(_ convertible: URLConvertible, method: HTTPMethod = .get, parameters: Parameters? = nil, encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default, headers: HTTPHeaders? = nil, interceptor: RequestInterceptor? = nil, requestModifier: RequestModifier? = nil, to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { let convertible = RequestEncodableConvertible(url: convertible, method: method, parameters: parameters, encoder: encoder, headers: headers, requestModifier: requestModifier) return download(convertible, interceptor: interceptor, to: destination) } /// Creates a `DownloadRequest` from a `URLRequestConvertible` value, a `RequestInterceptor`, and a `Destination`. /// /// - Parameters: /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file /// should be moved. `nil` by default. /// /// - Returns: The created `DownloadRequest`. open func download(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil, to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { let request = DownloadRequest(downloadable: .request(convertible), underlyingQueue: rootQueue, serializationQueue: serializationQueue, eventMonitor: eventMonitor, interceptor: interceptor, delegate: self, destination: destination ?? DownloadRequest.defaultDestination) perform(request) return request } /// Creates a `DownloadRequest` from the `resumeData` produced from a previously cancelled `DownloadRequest`, as /// well as a `RequestInterceptor`, and a `Destination`. /// /// - Note: If `destination` is not specified, the download will be moved to a temporary location determined by /// Alamofire. The file will not be deleted until the system purges the temporary files. /// /// - Note: On some versions of all Apple platforms (iOS 10 - 10.2, macOS 10.12 - 10.12.2, tvOS 10 - 10.1, watchOS 3 - 3.1.1), /// `resumeData` is broken on background URL session configurations. There's an underlying bug in the `resumeData` /// generation logic where the data is written incorrectly and will always fail to resume the download. For more /// information about the bug and possible workarounds, please refer to the [this Stack Overflow post](http://stackoverflow.com/a/39347461/1342462). /// /// - Parameters: /// - data: The resume data from a previously cancelled `DownloadRequest` or `URLSessionDownloadTask`. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file /// should be moved. `nil` by default. /// /// - Returns: The created `DownloadRequest`. open func download(resumingWith data: Data, interceptor: RequestInterceptor? = nil, to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { let request = DownloadRequest(downloadable: .resumeData(data), underlyingQueue: rootQueue, serializationQueue: serializationQueue, eventMonitor: eventMonitor, interceptor: interceptor, delegate: self, destination: destination ?? DownloadRequest.defaultDestination) perform(request) return request } // MARK: - UploadRequest struct ParameterlessRequestConvertible: URLRequestConvertible { let url: URLConvertible let method: HTTPMethod let headers: HTTPHeaders? let requestModifier: RequestModifier? func asURLRequest() throws -> URLRequest { var request = try URLRequest(url: url, method: method, headers: headers) try requestModifier?(&request) return request } } struct Upload: UploadConvertible { let request: URLRequestConvertible let uploadable: UploadableConvertible func createUploadable() throws -> UploadRequest.Uploadable { try uploadable.createUploadable() } func asURLRequest() throws -> URLRequest { try request.asURLRequest() } } // MARK: Data /// Creates an `UploadRequest` for the given `Data`, `URLRequest` components, and `RequestInterceptor`. /// /// - Parameters: /// - data: The `Data` to upload. /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by /// default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided /// parameters. `nil` by default. /// /// - Returns: The created `UploadRequest`. open func upload(_ data: Data, to convertible: URLConvertible, method: HTTPMethod = .post, headers: HTTPHeaders? = nil, interceptor: RequestInterceptor? = nil, fileManager: FileManager = .default, requestModifier: RequestModifier? = nil) -> UploadRequest { let convertible = ParameterlessRequestConvertible(url: convertible, method: method, headers: headers, requestModifier: requestModifier) return upload(data, with: convertible, interceptor: interceptor, fileManager: fileManager) } /// Creates an `UploadRequest` for the given `Data` using the `URLRequestConvertible` value and `RequestInterceptor`. /// /// - Parameters: /// - data: The `Data` to upload. /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by /// default. /// /// - Returns: The created `UploadRequest`. open func upload(_ data: Data, with convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil, fileManager: FileManager = .default) -> UploadRequest { upload(.data(data), with: convertible, interceptor: interceptor, fileManager: fileManager) } // MARK: File /// Creates an `UploadRequest` for the file at the given file `URL`, using a `URLRequest` from the provided /// components and `RequestInterceptor`. /// /// - Parameters: /// - fileURL: The `URL` of the file to upload. /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `UploadRequest`. `nil` by default. /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by /// default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided /// parameters. `nil` by default. /// /// - Returns: The created `UploadRequest`. open func upload(_ fileURL: URL, to convertible: URLConvertible, method: HTTPMethod = .post, headers: HTTPHeaders? = nil, interceptor: RequestInterceptor? = nil, fileManager: FileManager = .default, requestModifier: RequestModifier? = nil) -> UploadRequest { let convertible = ParameterlessRequestConvertible(url: convertible, method: method, headers: headers, requestModifier: requestModifier) return upload(fileURL, with: convertible, interceptor: interceptor, fileManager: fileManager) } /// Creates an `UploadRequest` for the file at the given file `URL` using the `URLRequestConvertible` value and /// `RequestInterceptor`. /// /// - Parameters: /// - fileURL: The `URL` of the file to upload. /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by /// default. /// /// - Returns: The created `UploadRequest`. open func upload(_ fileURL: URL, with convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil, fileManager: FileManager = .default) -> UploadRequest { upload(.file(fileURL, shouldRemove: false), with: convertible, interceptor: interceptor, fileManager: fileManager) } // MARK: InputStream /// Creates an `UploadRequest` from the `InputStream` provided using a `URLRequest` from the provided components and /// `RequestInterceptor`. /// /// - Parameters: /// - stream: The `InputStream` that provides the data to upload. /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by /// default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided /// parameters. `nil` by default. /// /// - Returns: The created `UploadRequest`. open func upload(_ stream: InputStream, to convertible: URLConvertible, method: HTTPMethod = .post, headers: HTTPHeaders? = nil, interceptor: RequestInterceptor? = nil, fileManager: FileManager = .default, requestModifier: RequestModifier? = nil) -> UploadRequest { let convertible = ParameterlessRequestConvertible(url: convertible, method: method, headers: headers, requestModifier: requestModifier) return upload(stream, with: convertible, interceptor: interceptor, fileManager: fileManager) } /// Creates an `UploadRequest` from the provided `InputStream` using the `URLRequestConvertible` value and /// `RequestInterceptor`. /// /// - Parameters: /// - stream: The `InputStream` that provides the data to upload. /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by /// default. /// /// - Returns: The created `UploadRequest`. open func upload(_ stream: InputStream, with convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil, fileManager: FileManager = .default) -> UploadRequest { upload(.stream(stream), with: convertible, interceptor: interceptor, fileManager: fileManager) } // MARK: MultipartFormData /// Creates an `UploadRequest` for the multipart form data built using a closure and sent using the provided /// `URLRequest` components and `RequestInterceptor`. /// /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be /// used for larger payloads such as video content. /// /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding /// technique was used. /// /// - Parameters: /// - multipartFormData: `MultipartFormData` building closure. /// - url: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by /// default. /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - fileManager: `FileManager` to be used if the form data exceeds the memory threshold and is /// written to disk before being uploaded. `.default` instance by default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the /// provided parameters. `nil` by default. /// /// - Returns: The created `UploadRequest`. open func upload(multipartFormData: @escaping (MultipartFormData) -> Void, to url: URLConvertible, usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, method: HTTPMethod = .post, headers: HTTPHeaders? = nil, interceptor: RequestInterceptor? = nil, fileManager: FileManager = .default, requestModifier: RequestModifier? = nil) -> UploadRequest { let convertible = ParameterlessRequestConvertible(url: url, method: method, headers: headers, requestModifier: requestModifier) let formData = MultipartFormData(fileManager: fileManager) multipartFormData(formData) return upload(multipartFormData: formData, with: convertible, usingThreshold: encodingMemoryThreshold, interceptor: interceptor, fileManager: fileManager) } /// Creates an `UploadRequest` using a `MultipartFormData` building closure, the provided `URLRequestConvertible` /// value, and a `RequestInterceptor`. /// /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be /// used for larger payloads such as video content. /// /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding /// technique was used. /// /// - Parameters: /// - multipartFormData: `MultipartFormData` building closure. /// - request: `URLRequestConvertible` value to be used to create the `URLRequest`. /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by /// default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - fileManager: `FileManager` to be used if the form data exceeds the memory threshold and is /// written to disk before being uploaded. `.default` instance by default. /// /// - Returns: The created `UploadRequest`. open func upload(multipartFormData: @escaping (MultipartFormData) -> Void, with request: URLRequestConvertible, usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, interceptor: RequestInterceptor? = nil, fileManager: FileManager = .default) -> UploadRequest { let formData = MultipartFormData(fileManager: fileManager) multipartFormData(formData) return upload(multipartFormData: formData, with: request, usingThreshold: encodingMemoryThreshold, interceptor: interceptor, fileManager: fileManager) } /// Creates an `UploadRequest` for the prebuilt `MultipartFormData` value using the provided `URLRequest` components /// and `RequestInterceptor`. /// /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be /// used for larger payloads such as video content. /// /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding /// technique was used. /// /// - Parameters: /// - multipartFormData: `MultipartFormData` instance to upload. /// - url: `URLConvertible` value to be used as the `URLRequest`'s `URL`. /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by /// default. /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - fileManager: `FileManager` to be used if the form data exceeds the memory threshold and is /// written to disk before being uploaded. `.default` instance by default. /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the /// provided parameters. `nil` by default. /// /// - Returns: The created `UploadRequest`. open func upload(multipartFormData: MultipartFormData, to url: URLConvertible, usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, method: HTTPMethod = .post, headers: HTTPHeaders? = nil, interceptor: RequestInterceptor? = nil, fileManager: FileManager = .default, requestModifier: RequestModifier? = nil) -> UploadRequest { let convertible = ParameterlessRequestConvertible(url: url, method: method, headers: headers, requestModifier: requestModifier) let multipartUpload = MultipartUpload(encodingMemoryThreshold: encodingMemoryThreshold, request: convertible, multipartFormData: multipartFormData) return upload(multipartUpload, interceptor: interceptor, fileManager: fileManager) } /// Creates an `UploadRequest` for the prebuilt `MultipartFormData` value using the providing `URLRequestConvertible` /// value and `RequestInterceptor`. /// /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be /// used for larger payloads such as video content. /// /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding /// technique was used. /// /// - Parameters: /// - multipartFormData: `MultipartFormData` instance to upload. /// - request: `URLRequestConvertible` value to be used to create the `URLRequest`. /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by /// default. /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by /// default. /// /// - Returns: The created `UploadRequest`. open func upload(multipartFormData: MultipartFormData, with request: URLRequestConvertible, usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, interceptor: RequestInterceptor? = nil, fileManager: FileManager = .default) -> UploadRequest { let multipartUpload = MultipartUpload(encodingMemoryThreshold: encodingMemoryThreshold, request: request, multipartFormData: multipartFormData) return upload(multipartUpload, interceptor: interceptor, fileManager: fileManager) } // MARK: - Internal API // MARK: Uploadable func upload(_ uploadable: UploadRequest.Uploadable, with convertible: URLRequestConvertible, interceptor: RequestInterceptor?, fileManager: FileManager) -> UploadRequest { let uploadable = Upload(request: convertible, uploadable: uploadable) return upload(uploadable, interceptor: interceptor, fileManager: fileManager) } func upload(_ upload: UploadConvertible, interceptor: RequestInterceptor?, fileManager: FileManager) -> UploadRequest { let request = UploadRequest(convertible: upload, underlyingQueue: rootQueue, serializationQueue: serializationQueue, eventMonitor: eventMonitor, interceptor: interceptor, fileManager: fileManager, delegate: self) perform(request) return request } // MARK: Perform /// Starts performing the provided `Request`. /// /// - Parameter request: The `Request` to perform. func perform(_ request: Request) { rootQueue.async { guard !request.isCancelled else { return } self.activeRequests.insert(request) self.requestQueue.async { // Leaf types must come first, otherwise they will cast as their superclass. switch request { case let r as UploadRequest: self.performUploadRequest(r) // UploadRequest must come before DataRequest due to subtype relationship. case let r as DataRequest: self.performDataRequest(r) case let r as DownloadRequest: self.performDownloadRequest(r) case let r as DataStreamRequest: self.performDataStreamRequest(r) default: fatalError("Attempted to perform unsupported Request subclass: \(type(of: request))") } } } } func performDataRequest(_ request: DataRequest) { dispatchPrecondition(condition: .onQueue(requestQueue)) performSetupOperations(for: request, convertible: request.convertible) } func performDataStreamRequest(_ request: DataStreamRequest) { dispatchPrecondition(condition: .onQueue(requestQueue)) performSetupOperations(for: request, convertible: request.convertible) } func performUploadRequest(_ request: UploadRequest) { dispatchPrecondition(condition: .onQueue(requestQueue)) performSetupOperations(for: request, convertible: request.convertible) { do { let uploadable = try request.upload.createUploadable() self.rootQueue.async { request.didCreateUploadable(uploadable) } return true } catch { self.rootQueue.async { request.didFailToCreateUploadable(with: error.asAFError(or: .createUploadableFailed(error: error))) } return false } } } func performDownloadRequest(_ request: DownloadRequest) { dispatchPrecondition(condition: .onQueue(requestQueue)) switch request.downloadable { case let .request(convertible): performSetupOperations(for: request, convertible: convertible) case let .resumeData(resumeData): rootQueue.async { self.didReceiveResumeData(resumeData, for: request) } } } func performSetupOperations(for request: Request, convertible: URLRequestConvertible, shouldCreateTask: @escaping () -> Bool = { true }) { dispatchPrecondition(condition: .onQueue(requestQueue)) let initialRequest: URLRequest do { initialRequest = try convertible.asURLRequest() try initialRequest.validate() } catch { rootQueue.async { request.didFailToCreateURLRequest(with: error.asAFError(or: .createURLRequestFailed(error: error))) } return } rootQueue.async { request.didCreateInitialURLRequest(initialRequest) } guard !request.isCancelled else { return } guard let adapter = adapter(for: request) else { guard shouldCreateTask() else { return } rootQueue.async { self.didCreateURLRequest(initialRequest, for: request) } return } let adapterState = RequestAdapterState(requestID: request.id, session: self) adapter.adapt(initialRequest, using: adapterState) { result in do { let adaptedRequest = try result.get() try adaptedRequest.validate() self.rootQueue.async { request.didAdaptInitialRequest(initialRequest, to: adaptedRequest) } guard shouldCreateTask() else { return } self.rootQueue.async { self.didCreateURLRequest(adaptedRequest, for: request) } } catch { self.rootQueue.async { request.didFailToAdaptURLRequest(initialRequest, withError: .requestAdaptationFailed(error: error)) } } } } // MARK: - Task Handling func didCreateURLRequest(_ urlRequest: URLRequest, for request: Request) { dispatchPrecondition(condition: .onQueue(rootQueue)) request.didCreateURLRequest(urlRequest) guard !request.isCancelled else { return } let task = request.task(for: urlRequest, using: session) requestTaskMap[request] = task request.didCreateTask(task) updateStatesForTask(task, request: request) } func didReceiveResumeData(_ data: Data, for request: DownloadRequest) { dispatchPrecondition(condition: .onQueue(rootQueue)) guard !request.isCancelled else { return } let task = request.task(forResumeData: data, using: session) requestTaskMap[request] = task request.didCreateTask(task) updateStatesForTask(task, request: request) } func updateStatesForTask(_ task: URLSessionTask, request: Request) { dispatchPrecondition(condition: .onQueue(rootQueue)) request.withState { state in switch state { case .initialized, .finished: // Do nothing. break case .resumed: task.resume() rootQueue.async { request.didResumeTask(task) } case .suspended: task.suspend() rootQueue.async { request.didSuspendTask(task) } case .cancelled: // Resume to ensure metrics are gathered. task.resume() task.cancel() rootQueue.async { request.didCancelTask(task) } } } } // MARK: - Adapters and Retriers func adapter(for request: Request) -> RequestAdapter? { if let requestInterceptor = request.interceptor, let sessionInterceptor = interceptor { return Interceptor(adapters: [requestInterceptor, sessionInterceptor]) } else { return request.interceptor ?? interceptor } } func retrier(for request: Request) -> RequestRetrier? { if let requestInterceptor = request.interceptor, let sessionInterceptor = interceptor { return Interceptor(retriers: [requestInterceptor, sessionInterceptor]) } else { return request.interceptor ?? interceptor } } // MARK: - Invalidation func finishRequestsForDeinit() { requestTaskMap.requests.forEach { request in rootQueue.async { request.finish(error: AFError.sessionDeinitialized) } } } } // MARK: - RequestDelegate extension Session: RequestDelegate { public var sessionConfiguration: URLSessionConfiguration { session.configuration } public var startImmediately: Bool { startRequestsImmediately } public func cleanup(after request: Request) { activeRequests.remove(request) } public func retryResult(for request: Request, dueTo error: AFError, completion: @escaping (RetryResult) -> Void) { guard let retrier = retrier(for: request) else { rootQueue.async { completion(.doNotRetry) } return } retrier.retry(request, for: self, dueTo: error) { retryResult in self.rootQueue.async { guard let retryResultError = retryResult.error else { completion(retryResult); return } let retryError = AFError.requestRetryFailed(retryError: retryResultError, originalError: error) completion(.doNotRetryWithError(retryError)) } } } public func retryRequest(_ request: Request, withDelay timeDelay: TimeInterval?) { rootQueue.async { let retry: () -> Void = { guard !request.isCancelled else { return } request.prepareForRetry() self.perform(request) } if let retryDelay = timeDelay { self.rootQueue.after(retryDelay) { retry() } } else { retry() } } } } // MARK: - SessionStateProvider extension Session: SessionStateProvider { func request(for task: URLSessionTask) -> Request? { dispatchPrecondition(condition: .onQueue(rootQueue)) return requestTaskMap[task] } func didGatherMetricsForTask(_ task: URLSessionTask) { dispatchPrecondition(condition: .onQueue(rootQueue)) let didDisassociate = requestTaskMap.disassociateIfNecessaryAfterGatheringMetricsForTask(task) if didDisassociate { waitingCompletions[task]?() waitingCompletions[task] = nil } } func didCompleteTask(_ task: URLSessionTask, completion: @escaping () -> Void) { dispatchPrecondition(condition: .onQueue(rootQueue)) let didDisassociate = requestTaskMap.disassociateIfNecessaryAfterCompletingTask(task) if didDisassociate { completion() } else { waitingCompletions[task] = completion } } func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential? { dispatchPrecondition(condition: .onQueue(rootQueue)) return requestTaskMap[task]?.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: protectionSpace) } func cancelRequestsForSessionInvalidation(with error: Error?) { dispatchPrecondition(condition: .onQueue(rootQueue)) requestTaskMap.requests.forEach { $0.finish(error: AFError.sessionInvalidated(error: error)) } } } ================================================ FILE: JetChat/Pods/Alamofire/Source/SessionDelegate.swift ================================================ // // SessionDelegate.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Class which implements the various `URLSessionDelegate` methods to connect various Alamofire features. open class SessionDelegate: NSObject { private let fileManager: FileManager weak var stateProvider: SessionStateProvider? var eventMonitor: EventMonitor? /// Creates an instance from the given `FileManager`. /// /// - Parameter fileManager: `FileManager` to use for underlying file management, such as moving downloaded files. /// `.default` by default. public init(fileManager: FileManager = .default) { self.fileManager = fileManager } /// Internal method to find and cast requests while maintaining some integrity checking. /// /// - Parameters: /// - task: The `URLSessionTask` for which to find the associated `Request`. /// - type: The `Request` subclass type to cast any `Request` associate with `task`. func request(for task: URLSessionTask, as type: R.Type) -> R? { guard let provider = stateProvider else { assertionFailure("StateProvider is nil.") return nil } return provider.request(for: task) as? R } } /// Type which provides various `Session` state values. protocol SessionStateProvider: AnyObject { var serverTrustManager: ServerTrustManager? { get } var redirectHandler: RedirectHandler? { get } var cachedResponseHandler: CachedResponseHandler? { get } func request(for task: URLSessionTask) -> Request? func didGatherMetricsForTask(_ task: URLSessionTask) func didCompleteTask(_ task: URLSessionTask, completion: @escaping () -> Void) func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential? func cancelRequestsForSessionInvalidation(with error: Error?) } // MARK: URLSessionDelegate extension SessionDelegate: URLSessionDelegate { open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { eventMonitor?.urlSession(session, didBecomeInvalidWithError: error) stateProvider?.cancelRequestsForSessionInvalidation(with: error) } } // MARK: URLSessionTaskDelegate extension SessionDelegate: URLSessionTaskDelegate { /// Result of a `URLAuthenticationChallenge` evaluation. typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: AFError?) open func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { eventMonitor?.urlSession(session, task: task, didReceive: challenge) let evaluation: ChallengeEvaluation switch challenge.protectionSpace.authenticationMethod { case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate: evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task) #if !(os(Linux) || os(Windows)) case NSURLAuthenticationMethodServerTrust: evaluation = attemptServerTrustAuthentication(with: challenge) case NSURLAuthenticationMethodClientCertificate: evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task) #endif default: evaluation = (.performDefaultHandling, nil, nil) } if let error = evaluation.error { stateProvider?.request(for: task)?.didFailTask(task, earlyWithError: error) } completionHandler(evaluation.disposition, evaluation.credential) } #if !(os(Linux) || os(Windows)) /// Evaluates the server trust `URLAuthenticationChallenge` received. /// /// - Parameter challenge: The `URLAuthenticationChallenge`. /// /// - Returns: The `ChallengeEvaluation`. func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation { let host = challenge.protectionSpace.host guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, let trust = challenge.protectionSpace.serverTrust else { return (.performDefaultHandling, nil, nil) } do { guard let evaluator = try stateProvider?.serverTrustManager?.serverTrustEvaluator(forHost: host) else { return (.performDefaultHandling, nil, nil) } try evaluator.evaluate(trust, forHost: host) return (.useCredential, URLCredential(trust: trust), nil) } catch { return (.cancelAuthenticationChallenge, nil, error.asAFError(or: .serverTrustEvaluationFailed(reason: .customEvaluationFailed(error: error)))) } } #endif /// Evaluates the credential-based authentication `URLAuthenticationChallenge` received for `task`. /// /// - Parameters: /// - challenge: The `URLAuthenticationChallenge`. /// - task: The `URLSessionTask` which received the challenge. /// /// - Returns: The `ChallengeEvaluation`. func attemptCredentialAuthentication(for challenge: URLAuthenticationChallenge, belongingTo task: URLSessionTask) -> ChallengeEvaluation { guard challenge.previousFailureCount == 0 else { return (.rejectProtectionSpace, nil, nil) } guard let credential = stateProvider?.credential(for: task, in: challenge.protectionSpace) else { return (.performDefaultHandling, nil, nil) } return (.useCredential, credential, nil) } open func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { eventMonitor?.urlSession(session, task: task, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend) stateProvider?.request(for: task)?.updateUploadProgress(totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend) } open func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { eventMonitor?.urlSession(session, taskNeedsNewBodyStream: task) guard let request = request(for: task, as: UploadRequest.self) else { assertionFailure("needNewBodyStream did not find UploadRequest.") completionHandler(nil) return } completionHandler(request.inputStream()) } open func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { eventMonitor?.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request) if let redirectHandler = stateProvider?.request(for: task)?.redirectHandler ?? stateProvider?.redirectHandler { redirectHandler.task(task, willBeRedirectedTo: request, for: response, completion: completionHandler) } else { completionHandler(request) } } open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics) stateProvider?.request(for: task)?.didGatherMetrics(metrics) stateProvider?.didGatherMetricsForTask(task) } open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { eventMonitor?.urlSession(session, task: task, didCompleteWithError: error) let request = stateProvider?.request(for: task) stateProvider?.didCompleteTask(task) { request?.didCompleteTask(task, with: error.map { $0.asAFError(or: .sessionTaskFailed(error: $0)) }) } } @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) { eventMonitor?.urlSession(session, taskIsWaitingForConnectivity: task) } } // MARK: URLSessionDataDelegate extension SessionDelegate: URLSessionDataDelegate { open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data) if let request = request(for: dataTask, as: DataRequest.self) { request.didReceive(data: data) } else if let request = request(for: dataTask, as: DataStreamRequest.self) { request.didReceive(data: data) } else { assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive") return } } open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) { eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse) if let handler = stateProvider?.request(for: dataTask)?.cachedResponseHandler ?? stateProvider?.cachedResponseHandler { handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler) } else { completionHandler(proposedResponse) } } } // MARK: URLSessionDownloadDelegate extension SessionDelegate: URLSessionDownloadDelegate { open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) { eventMonitor?.urlSession(session, downloadTask: downloadTask, didResumeAtOffset: fileOffset, expectedTotalBytes: expectedTotalBytes) guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else { assertionFailure("downloadTask did not find DownloadRequest.") return } downloadRequest.updateDownloadProgress(bytesWritten: fileOffset, totalBytesExpectedToWrite: expectedTotalBytes) } open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { eventMonitor?.urlSession(session, downloadTask: downloadTask, didWriteData: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite) guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else { assertionFailure("downloadTask did not find DownloadRequest.") return } downloadRequest.updateDownloadProgress(bytesWritten: bytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite) } open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { eventMonitor?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location) guard let request = request(for: downloadTask, as: DownloadRequest.self) else { assertionFailure("downloadTask did not find DownloadRequest.") return } let (destination, options): (URL, DownloadRequest.Options) if let response = request.response { (destination, options) = request.destination(location, response) } else { // If there's no response this is likely a local file download, so generate the temporary URL directly. (destination, options) = (DownloadRequest.defaultDestinationURL(location), []) } eventMonitor?.request(request, didCreateDestinationURL: destination) do { if options.contains(.removePreviousFile), fileManager.fileExists(atPath: destination.path) { try fileManager.removeItem(at: destination) } if options.contains(.createIntermediateDirectories) { let directory = destination.deletingLastPathComponent() try fileManager.createDirectory(at: directory, withIntermediateDirectories: true) } try fileManager.moveItem(at: location, to: destination) request.didFinishDownloading(using: downloadTask, with: .success(destination)) } catch { request.didFinishDownloading(using: downloadTask, with: .failure(.downloadedFileMoveFailed(error: error, source: location, destination: destination))) } } } ================================================ FILE: JetChat/Pods/Alamofire/Source/StringEncoding+Alamofire.swift ================================================ // // StringEncoding+Alamofire.swift // // Copyright (c) 2020 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation extension String.Encoding { /// Creates an encoding from the IANA charset name. /// /// - Notes: These mappings match those [provided by CoreFoundation](https://opensource.apple.com/source/CF/CF-476.18/CFStringUtilities.c.auto.html) /// /// - Parameter name: IANA charset name. init?(ianaCharsetName name: String) { switch name.lowercased() { case "utf-8": self = .utf8 case "iso-8859-1": self = .isoLatin1 case "unicode-1-1", "iso-10646-ucs-2", "utf-16": self = .utf16 case "utf-16be": self = .utf16BigEndian case "utf-16le": self = .utf16LittleEndian case "utf-32": self = .utf32 case "utf-32be": self = .utf32BigEndian case "utf-32le": self = .utf32LittleEndian default: return nil } } } ================================================ FILE: JetChat/Pods/Alamofire/Source/URLConvertible+URLRequestConvertible.swift ================================================ // // URLConvertible+URLRequestConvertible.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// Types adopting the `URLConvertible` protocol can be used to construct `URL`s, which can then be used to construct /// `URLRequests`. public protocol URLConvertible { /// Returns a `URL` from the conforming instance or throws. /// /// - Returns: The `URL` created from the instance. /// - Throws: Any error thrown while creating the `URL`. func asURL() throws -> URL } extension String: URLConvertible { /// Returns a `URL` if `self` can be used to initialize a `URL` instance, otherwise throws. /// /// - Returns: The `URL` initialized with `self`. /// - Throws: An `AFError.invalidURL` instance. public func asURL() throws -> URL { guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) } return url } } extension URL: URLConvertible { /// Returns `self`. public func asURL() throws -> URL { self } } extension URLComponents: URLConvertible { /// Returns a `URL` if the `self`'s `url` is not nil, otherwise throws. /// /// - Returns: The `URL` from the `url` property. /// - Throws: An `AFError.invalidURL` instance. public func asURL() throws -> URL { guard let url = url else { throw AFError.invalidURL(url: self) } return url } } // MARK: - /// Types adopting the `URLRequestConvertible` protocol can be used to safely construct `URLRequest`s. public protocol URLRequestConvertible { /// Returns a `URLRequest` or throws if an `Error` was encountered. /// /// - Returns: A `URLRequest`. /// - Throws: Any error thrown while constructing the `URLRequest`. func asURLRequest() throws -> URLRequest } extension URLRequestConvertible { /// The `URLRequest` returned by discarding any `Error` encountered. public var urlRequest: URLRequest? { try? asURLRequest() } } extension URLRequest: URLRequestConvertible { /// Returns `self`. public func asURLRequest() throws -> URLRequest { self } } // MARK: - extension URLRequest { /// Creates an instance with the specified `url`, `method`, and `headers`. /// /// - Parameters: /// - url: The `URLConvertible` value. /// - method: The `HTTPMethod`. /// - headers: The `HTTPHeaders`, `nil` by default. /// - Throws: Any error thrown while converting the `URLConvertible` to a `URL`. public init(url: URLConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws { let url = try url.asURL() self.init(url: url) httpMethod = method.rawValue allHTTPHeaderFields = headers?.dictionary } } ================================================ FILE: JetChat/Pods/Alamofire/Source/URLEncodedFormEncoder.swift ================================================ // // URLEncodedFormEncoder.swift // // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation /// An object that encodes instances into URL-encoded query strings. /// /// There is no published specification for how to encode collection types. By default, the convention of appending /// `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for /// nested dictionary values (`foo[bar]=baz`) is used. Optionally, `ArrayEncoding` can be used to omit the /// square brackets appended to array keys. /// /// `BoolEncoding` can be used to configure how `Bool` values are encoded. The default behavior is to encode /// `true` as 1 and `false` as 0. /// /// `DateEncoding` can be used to configure how `Date` values are encoded. By default, the `.deferredToDate` /// strategy is used, which formats dates from their structure. /// /// `SpaceEncoding` can be used to configure how spaces are encoded. Modern encodings use percent replacement (`%20`), /// while older encodings may expect spaces to be replaced with `+`. /// /// This type is largely based on Vapor's [`url-encoded-form`](https://github.com/vapor/url-encoded-form) project. public final class URLEncodedFormEncoder { /// Encoding to use for `Array` values. public enum ArrayEncoding { /// An empty set of square brackets ("[]") are appended to the key for every value. This is the default encoding. case brackets /// No brackets are appended to the key and the key is encoded as is. case noBrackets /// Brackets containing the item index are appended. This matches the jQuery and Node.js behavior. case indexInBrackets /// Encodes the key according to the encoding. /// /// - Parameters: /// - key: The `key` to encode. /// - index: When this enum instance is `.indexInBrackets`, the `index` to encode. /// /// - Returns: The encoded key. func encode(_ key: String, atIndex index: Int) -> String { switch self { case .brackets: return "\(key)[]" case .noBrackets: return key case .indexInBrackets: return "\(key)[\(index)]" } } } /// Encoding to use for `Bool` values. public enum BoolEncoding { /// Encodes `true` as `1`, `false` as `0`. case numeric /// Encodes `true` as "true", `false` as "false". This is the default encoding. case literal /// Encodes the given `Bool` as a `String`. /// /// - Parameter value: The `Bool` to encode. /// /// - Returns: The encoded `String`. func encode(_ value: Bool) -> String { switch self { case .numeric: return value ? "1" : "0" case .literal: return value ? "true" : "false" } } } /// Encoding to use for `Data` values. public enum DataEncoding { /// Defers encoding to the `Data` type. case deferredToData /// Encodes `Data` as a Base64-encoded string. This is the default encoding. case base64 /// Encode the `Data` as a custom value encoded by the given closure. case custom((Data) throws -> String) /// Encodes `Data` according to the encoding. /// /// - Parameter data: The `Data` to encode. /// /// - Returns: The encoded `String`, or `nil` if the `Data` should be encoded according to its /// `Encodable` implementation. func encode(_ data: Data) throws -> String? { switch self { case .deferredToData: return nil case .base64: return data.base64EncodedString() case let .custom(encoding): return try encoding(data) } } } /// Encoding to use for `Date` values. public enum DateEncoding { /// ISO8601 and RFC3339 formatter. private static let iso8601Formatter: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.formatOptions = .withInternetDateTime return formatter }() /// Defers encoding to the `Date` type. This is the default encoding. case deferredToDate /// Encodes `Date`s as seconds since midnight UTC on January 1, 1970. case secondsSince1970 /// Encodes `Date`s as milliseconds since midnight UTC on January 1, 1970. case millisecondsSince1970 /// Encodes `Date`s according to the ISO8601 and RFC3339 standards. case iso8601 /// Encodes `Date`s using the given `DateFormatter`. case formatted(DateFormatter) /// Encodes `Date`s using the given closure. case custom((Date) throws -> String) /// Encodes the date according to the encoding. /// /// - Parameter date: The `Date` to encode. /// /// - Returns: The encoded `String`, or `nil` if the `Date` should be encoded according to its /// `Encodable` implementation. func encode(_ date: Date) throws -> String? { switch self { case .deferredToDate: return nil case .secondsSince1970: return String(date.timeIntervalSince1970) case .millisecondsSince1970: return String(date.timeIntervalSince1970 * 1000.0) case .iso8601: return DateEncoding.iso8601Formatter.string(from: date) case let .formatted(formatter): return formatter.string(from: date) case let .custom(closure): return try closure(date) } } } /// Encoding to use for keys. /// /// This type is derived from [`JSONEncoder`'s `KeyEncodingStrategy`](https://github.com/apple/swift/blob/6aa313b8dd5f05135f7f878eccc1db6f9fbe34ff/stdlib/public/Darwin/Foundation/JSONEncoder.swift#L128) /// and [`XMLEncoder`s `KeyEncodingStrategy`](https://github.com/MaxDesiatov/XMLCoder/blob/master/Sources/XMLCoder/Encoder/XMLEncoder.swift#L102). public enum KeyEncoding { /// Use the keys specified by each type. This is the default encoding. case useDefaultKeys /// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key. /// /// Capital characters are determined by testing membership in /// `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters` /// (Unicode General Categories Lu and Lt). /// The conversion to lower case uses `Locale.system`, also known as /// the ICU "root" locale. This means the result is consistent /// regardless of the current user's locale and language preferences. /// /// Converting from camel case to snake case: /// 1. Splits words at the boundary of lower-case to upper-case /// 2. Inserts `_` between words /// 3. Lowercases the entire string /// 4. Preserves starting and ending `_`. /// /// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`. /// /// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted. case convertToSnakeCase /// Same as convertToSnakeCase, but using `-` instead of `_`. /// For example `oneTwoThree` becomes `one-two-three`. case convertToKebabCase /// Capitalize the first letter only. /// For example `oneTwoThree` becomes `OneTwoThree`. case capitalized /// Uppercase all letters. /// For example `oneTwoThree` becomes `ONETWOTHREE`. case uppercased /// Lowercase all letters. /// For example `oneTwoThree` becomes `onetwothree`. case lowercased /// A custom encoding using the provided closure. case custom((String) -> String) func encode(_ key: String) -> String { switch self { case .useDefaultKeys: return key case .convertToSnakeCase: return convertToSnakeCase(key) case .convertToKebabCase: return convertToKebabCase(key) case .capitalized: return String(key.prefix(1).uppercased() + key.dropFirst()) case .uppercased: return key.uppercased() case .lowercased: return key.lowercased() case let .custom(encoding): return encoding(key) } } private func convertToSnakeCase(_ key: String) -> String { convert(key, usingSeparator: "_") } private func convertToKebabCase(_ key: String) -> String { convert(key, usingSeparator: "-") } private func convert(_ key: String, usingSeparator separator: String) -> String { guard !key.isEmpty else { return key } var words: [Range] = [] // The general idea of this algorithm is to split words on // transition from lower to upper case, then on transition of >1 // upper case characters to lowercase // // myProperty -> my_property // myURLProperty -> my_url_property // // It is assumed, per Swift naming conventions, that the first character of the key is lowercase. var wordStart = key.startIndex var searchRange = key.index(after: wordStart)..1 capital letters. Turn those into a word, stopping at the capital before the lower case character. let beforeLowerIndex = key.index(before: lowerCaseRange.lowerBound) words.append(upperCaseRange.lowerBound.. String { switch self { case .percentEscaped: return string.replacingOccurrences(of: " ", with: "%20") case .plusReplaced: return string.replacingOccurrences(of: " ", with: "+") } } } /// `URLEncodedFormEncoder` error. public enum Error: Swift.Error { /// An invalid root object was created by the encoder. Only keyed values are valid. case invalidRootObject(String) var localizedDescription: String { switch self { case let .invalidRootObject(object): return "URLEncodedFormEncoder requires keyed root object. Received \(object) instead." } } } /// Whether or not to sort the encoded key value pairs. /// /// - Note: This setting ensures a consistent ordering for all encodings of the same parameters. When set to `false`, /// encoded `Dictionary` values may have a different encoded order each time they're encoded due to /// ` Dictionary`'s random storage order, but `Encodable` types will maintain their encoded order. public let alphabetizeKeyValuePairs: Bool /// The `ArrayEncoding` to use. public let arrayEncoding: ArrayEncoding /// The `BoolEncoding` to use. public let boolEncoding: BoolEncoding /// THe `DataEncoding` to use. public let dataEncoding: DataEncoding /// The `DateEncoding` to use. public let dateEncoding: DateEncoding /// The `KeyEncoding` to use. public let keyEncoding: KeyEncoding /// The `SpaceEncoding` to use. public let spaceEncoding: SpaceEncoding /// The `CharacterSet` of allowed (non-escaped) characters. public var allowedCharacters: CharacterSet /// Creates an instance from the supplied parameters. /// /// - Parameters: /// - alphabetizeKeyValuePairs: Whether or not to sort the encoded key value pairs. `true` by default. /// - arrayEncoding: The `ArrayEncoding` to use. `.brackets` by default. /// - boolEncoding: The `BoolEncoding` to use. `.numeric` by default. /// - dataEncoding: The `DataEncoding` to use. `.base64` by default. /// - dateEncoding: The `DateEncoding` to use. `.deferredToDate` by default. /// - keyEncoding: The `KeyEncoding` to use. `.useDefaultKeys` by default. /// - spaceEncoding: The `SpaceEncoding` to use. `.percentEscaped` by default. /// - allowedCharacters: The `CharacterSet` of allowed (non-escaped) characters. `.afURLQueryAllowed` by /// default. public init(alphabetizeKeyValuePairs: Bool = true, arrayEncoding: ArrayEncoding = .brackets, boolEncoding: BoolEncoding = .numeric, dataEncoding: DataEncoding = .base64, dateEncoding: DateEncoding = .deferredToDate, keyEncoding: KeyEncoding = .useDefaultKeys, spaceEncoding: SpaceEncoding = .percentEscaped, allowedCharacters: CharacterSet = .afURLQueryAllowed) { self.alphabetizeKeyValuePairs = alphabetizeKeyValuePairs self.arrayEncoding = arrayEncoding self.boolEncoding = boolEncoding self.dataEncoding = dataEncoding self.dateEncoding = dateEncoding self.keyEncoding = keyEncoding self.spaceEncoding = spaceEncoding self.allowedCharacters = allowedCharacters } func encode(_ value: Encodable) throws -> URLEncodedFormComponent { let context = URLEncodedFormContext(.object([])) let encoder = _URLEncodedFormEncoder(context: context, boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) try value.encode(to: encoder) return context.component } /// Encodes the `value` as a URL form encoded `String`. /// /// - Parameter value: The `Encodable` value.` /// /// - Returns: The encoded `String`. /// - Throws: An `Error` or `EncodingError` instance if encoding fails. public func encode(_ value: Encodable) throws -> String { let component: URLEncodedFormComponent = try encode(value) guard case let .object(object) = component else { throw Error.invalidRootObject("\(component)") } let serializer = URLEncodedFormSerializer(alphabetizeKeyValuePairs: alphabetizeKeyValuePairs, arrayEncoding: arrayEncoding, keyEncoding: keyEncoding, spaceEncoding: spaceEncoding, allowedCharacters: allowedCharacters) let query = serializer.serialize(object) return query } /// Encodes the value as `Data`. This is performed by first creating an encoded `String` and then returning the /// `.utf8` data. /// /// - Parameter value: The `Encodable` value. /// /// - Returns: The encoded `Data`. /// /// - Throws: An `Error` or `EncodingError` instance if encoding fails. public func encode(_ value: Encodable) throws -> Data { let string: String = try encode(value) return Data(string.utf8) } } final class _URLEncodedFormEncoder { var codingPath: [CodingKey] // Returns an empty dictionary, as this encoder doesn't support userInfo. var userInfo: [CodingUserInfoKey: Any] { [:] } let context: URLEncodedFormContext private let boolEncoding: URLEncodedFormEncoder.BoolEncoding private let dataEncoding: URLEncodedFormEncoder.DataEncoding private let dateEncoding: URLEncodedFormEncoder.DateEncoding init(context: URLEncodedFormContext, codingPath: [CodingKey] = [], boolEncoding: URLEncodedFormEncoder.BoolEncoding, dataEncoding: URLEncodedFormEncoder.DataEncoding, dateEncoding: URLEncodedFormEncoder.DateEncoding) { self.context = context self.codingPath = codingPath self.boolEncoding = boolEncoding self.dataEncoding = dataEncoding self.dateEncoding = dateEncoding } } extension _URLEncodedFormEncoder: Encoder { func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { let container = _URLEncodedFormEncoder.KeyedContainer(context: context, codingPath: codingPath, boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) return KeyedEncodingContainer(container) } func unkeyedContainer() -> UnkeyedEncodingContainer { _URLEncodedFormEncoder.UnkeyedContainer(context: context, codingPath: codingPath, boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) } func singleValueContainer() -> SingleValueEncodingContainer { _URLEncodedFormEncoder.SingleValueContainer(context: context, codingPath: codingPath, boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) } } final class URLEncodedFormContext { var component: URLEncodedFormComponent init(_ component: URLEncodedFormComponent) { self.component = component } } enum URLEncodedFormComponent { typealias Object = [(key: String, value: URLEncodedFormComponent)] case string(String) case array([URLEncodedFormComponent]) case object(Object) /// Converts self to an `[URLEncodedFormData]` or returns `nil` if not convertible. var array: [URLEncodedFormComponent]? { switch self { case let .array(array): return array default: return nil } } /// Converts self to an `Object` or returns `nil` if not convertible. var object: Object? { switch self { case let .object(object): return object default: return nil } } /// Sets self to the supplied value at a given path. /// /// data.set(to: "hello", at: ["path", "to", "value"]) /// /// - parameters: /// - value: Value of `Self` to set at the supplied path. /// - path: `CodingKey` path to update with the supplied value. public mutating func set(to value: URLEncodedFormComponent, at path: [CodingKey]) { set(&self, to: value, at: path) } /// Recursive backing method to `set(to:at:)`. private func set(_ context: inout URLEncodedFormComponent, to value: URLEncodedFormComponent, at path: [CodingKey]) { guard !path.isEmpty else { context = value return } let end = path[0] var child: URLEncodedFormComponent switch path.count { case 1: child = value case 2...: if let index = end.intValue { let array = context.array ?? [] if array.count > index { child = array[index] } else { child = .array([]) } set(&child, to: value, at: Array(path[1...])) } else { child = context.object?.first { $0.key == end.stringValue }?.value ?? .object(.init()) set(&child, to: value, at: Array(path[1...])) } default: fatalError("Unreachable") } if let index = end.intValue { if var array = context.array { if array.count > index { array[index] = child } else { array.append(child) } context = .array(array) } else { context = .array([child]) } } else { if var object = context.object { if let index = object.firstIndex(where: { $0.key == end.stringValue }) { object[index] = (key: end.stringValue, value: child) } else { object.append((key: end.stringValue, value: child)) } context = .object(object) } else { context = .object([(key: end.stringValue, value: child)]) } } } } struct AnyCodingKey: CodingKey, Hashable { let stringValue: String let intValue: Int? init?(stringValue: String) { self.stringValue = stringValue intValue = nil } init?(intValue: Int) { stringValue = "\(intValue)" self.intValue = intValue } init(_ base: Key) where Key: CodingKey { if let intValue = base.intValue { self.init(intValue: intValue)! } else { self.init(stringValue: base.stringValue)! } } } extension _URLEncodedFormEncoder { final class KeyedContainer where Key: CodingKey { var codingPath: [CodingKey] private let context: URLEncodedFormContext private let boolEncoding: URLEncodedFormEncoder.BoolEncoding private let dataEncoding: URLEncodedFormEncoder.DataEncoding private let dateEncoding: URLEncodedFormEncoder.DateEncoding init(context: URLEncodedFormContext, codingPath: [CodingKey], boolEncoding: URLEncodedFormEncoder.BoolEncoding, dataEncoding: URLEncodedFormEncoder.DataEncoding, dateEncoding: URLEncodedFormEncoder.DateEncoding) { self.context = context self.codingPath = codingPath self.boolEncoding = boolEncoding self.dataEncoding = dataEncoding self.dateEncoding = dateEncoding } private func nestedCodingPath(for key: CodingKey) -> [CodingKey] { codingPath + [key] } } } extension _URLEncodedFormEncoder.KeyedContainer: KeyedEncodingContainerProtocol { func encodeNil(forKey key: Key) throws { let context = EncodingError.Context(codingPath: codingPath, debugDescription: "URLEncodedFormEncoder cannot encode nil values.") throw EncodingError.invalidValue("\(key): nil", context) } func encode(_ value: T, forKey key: Key) throws where T: Encodable { var container = nestedSingleValueEncoder(for: key) try container.encode(value) } func nestedSingleValueEncoder(for key: Key) -> SingleValueEncodingContainer { let container = _URLEncodedFormEncoder.SingleValueContainer(context: context, codingPath: nestedCodingPath(for: key), boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) return container } func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { let container = _URLEncodedFormEncoder.UnkeyedContainer(context: context, codingPath: nestedCodingPath(for: key), boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) return container } func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey: CodingKey { let container = _URLEncodedFormEncoder.KeyedContainer(context: context, codingPath: nestedCodingPath(for: key), boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) return KeyedEncodingContainer(container) } func superEncoder() -> Encoder { _URLEncodedFormEncoder(context: context, codingPath: codingPath, boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) } func superEncoder(forKey key: Key) -> Encoder { _URLEncodedFormEncoder(context: context, codingPath: nestedCodingPath(for: key), boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) } } extension _URLEncodedFormEncoder { final class SingleValueContainer { var codingPath: [CodingKey] private var canEncodeNewValue = true private let context: URLEncodedFormContext private let boolEncoding: URLEncodedFormEncoder.BoolEncoding private let dataEncoding: URLEncodedFormEncoder.DataEncoding private let dateEncoding: URLEncodedFormEncoder.DateEncoding init(context: URLEncodedFormContext, codingPath: [CodingKey], boolEncoding: URLEncodedFormEncoder.BoolEncoding, dataEncoding: URLEncodedFormEncoder.DataEncoding, dateEncoding: URLEncodedFormEncoder.DateEncoding) { self.context = context self.codingPath = codingPath self.boolEncoding = boolEncoding self.dataEncoding = dataEncoding self.dateEncoding = dateEncoding } private func checkCanEncode(value: Any?) throws { guard canEncodeNewValue else { let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Attempt to encode value through single value container when previously value already encoded.") throw EncodingError.invalidValue(value as Any, context) } } } } extension _URLEncodedFormEncoder.SingleValueContainer: SingleValueEncodingContainer { func encodeNil() throws { try checkCanEncode(value: nil) defer { canEncodeNewValue = false } let context = EncodingError.Context(codingPath: codingPath, debugDescription: "URLEncodedFormEncoder cannot encode nil values.") throw EncodingError.invalidValue("nil", context) } func encode(_ value: Bool) throws { try encode(value, as: String(boolEncoding.encode(value))) } func encode(_ value: String) throws { try encode(value, as: value) } func encode(_ value: Double) throws { try encode(value, as: String(value)) } func encode(_ value: Float) throws { try encode(value, as: String(value)) } func encode(_ value: Int) throws { try encode(value, as: String(value)) } func encode(_ value: Int8) throws { try encode(value, as: String(value)) } func encode(_ value: Int16) throws { try encode(value, as: String(value)) } func encode(_ value: Int32) throws { try encode(value, as: String(value)) } func encode(_ value: Int64) throws { try encode(value, as: String(value)) } func encode(_ value: UInt) throws { try encode(value, as: String(value)) } func encode(_ value: UInt8) throws { try encode(value, as: String(value)) } func encode(_ value: UInt16) throws { try encode(value, as: String(value)) } func encode(_ value: UInt32) throws { try encode(value, as: String(value)) } func encode(_ value: UInt64) throws { try encode(value, as: String(value)) } private func encode(_ value: T, as string: String) throws where T: Encodable { try checkCanEncode(value: value) defer { canEncodeNewValue = false } context.component.set(to: .string(string), at: codingPath) } func encode(_ value: T) throws where T: Encodable { switch value { case let date as Date: guard let string = try dateEncoding.encode(date) else { try attemptToEncode(value) return } try encode(value, as: string) case let data as Data: guard let string = try dataEncoding.encode(data) else { try attemptToEncode(value) return } try encode(value, as: string) case let decimal as Decimal: // Decimal's `Encodable` implementation returns an object, not a single value, so override it. try encode(value, as: String(describing: decimal)) default: try attemptToEncode(value) } } private func attemptToEncode(_ value: T) throws where T: Encodable { try checkCanEncode(value: value) defer { canEncodeNewValue = false } let encoder = _URLEncodedFormEncoder(context: context, codingPath: codingPath, boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) try value.encode(to: encoder) } } extension _URLEncodedFormEncoder { final class UnkeyedContainer { var codingPath: [CodingKey] var count = 0 var nestedCodingPath: [CodingKey] { codingPath + [AnyCodingKey(intValue: count)!] } private let context: URLEncodedFormContext private let boolEncoding: URLEncodedFormEncoder.BoolEncoding private let dataEncoding: URLEncodedFormEncoder.DataEncoding private let dateEncoding: URLEncodedFormEncoder.DateEncoding init(context: URLEncodedFormContext, codingPath: [CodingKey], boolEncoding: URLEncodedFormEncoder.BoolEncoding, dataEncoding: URLEncodedFormEncoder.DataEncoding, dateEncoding: URLEncodedFormEncoder.DateEncoding) { self.context = context self.codingPath = codingPath self.boolEncoding = boolEncoding self.dataEncoding = dataEncoding self.dateEncoding = dateEncoding } } } extension _URLEncodedFormEncoder.UnkeyedContainer: UnkeyedEncodingContainer { func encodeNil() throws { let context = EncodingError.Context(codingPath: codingPath, debugDescription: "URLEncodedFormEncoder cannot encode nil values.") throw EncodingError.invalidValue("nil", context) } func encode(_ value: T) throws where T: Encodable { var container = nestedSingleValueContainer() try container.encode(value) } func nestedSingleValueContainer() -> SingleValueEncodingContainer { defer { count += 1 } return _URLEncodedFormEncoder.SingleValueContainer(context: context, codingPath: nestedCodingPath, boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) } func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey: CodingKey { defer { count += 1 } let container = _URLEncodedFormEncoder.KeyedContainer(context: context, codingPath: nestedCodingPath, boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) return KeyedEncodingContainer(container) } func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { defer { count += 1 } return _URLEncodedFormEncoder.UnkeyedContainer(context: context, codingPath: nestedCodingPath, boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) } func superEncoder() -> Encoder { defer { count += 1 } return _URLEncodedFormEncoder(context: context, codingPath: codingPath, boolEncoding: boolEncoding, dataEncoding: dataEncoding, dateEncoding: dateEncoding) } } final class URLEncodedFormSerializer { private let alphabetizeKeyValuePairs: Bool private let arrayEncoding: URLEncodedFormEncoder.ArrayEncoding private let keyEncoding: URLEncodedFormEncoder.KeyEncoding private let spaceEncoding: URLEncodedFormEncoder.SpaceEncoding private let allowedCharacters: CharacterSet init(alphabetizeKeyValuePairs: Bool, arrayEncoding: URLEncodedFormEncoder.ArrayEncoding, keyEncoding: URLEncodedFormEncoder.KeyEncoding, spaceEncoding: URLEncodedFormEncoder.SpaceEncoding, allowedCharacters: CharacterSet) { self.alphabetizeKeyValuePairs = alphabetizeKeyValuePairs self.arrayEncoding = arrayEncoding self.keyEncoding = keyEncoding self.spaceEncoding = spaceEncoding self.allowedCharacters = allowedCharacters } func serialize(_ object: URLEncodedFormComponent.Object) -> String { var output: [String] = [] for (key, component) in object { let value = serialize(component, forKey: key) output.append(value) } output = alphabetizeKeyValuePairs ? output.sorted() : output return output.joinedWithAmpersands() } func serialize(_ component: URLEncodedFormComponent, forKey key: String) -> String { switch component { case let .string(string): return "\(escape(keyEncoding.encode(key)))=\(escape(string))" case let .array(array): return serialize(array, forKey: key) case let .object(object): return serialize(object, forKey: key) } } func serialize(_ object: URLEncodedFormComponent.Object, forKey key: String) -> String { var segments: [String] = object.map { subKey, value in let keyPath = "[\(subKey)]" return serialize(value, forKey: key + keyPath) } segments = alphabetizeKeyValuePairs ? segments.sorted() : segments return segments.joinedWithAmpersands() } func serialize(_ array: [URLEncodedFormComponent], forKey key: String) -> String { var segments: [String] = array.enumerated().map { index, component in let keyPath = arrayEncoding.encode(key, atIndex: index) return serialize(component, forKey: keyPath) } segments = alphabetizeKeyValuePairs ? segments.sorted() : segments return segments.joinedWithAmpersands() } func escape(_ query: String) -> String { var allowedCharactersWithSpace = allowedCharacters allowedCharactersWithSpace.insert(charactersIn: " ") let escapedQuery = query.addingPercentEncoding(withAllowedCharacters: allowedCharactersWithSpace) ?? query let spaceEncodedQuery = spaceEncoding.encode(escapedQuery) return spaceEncodedQuery } } extension Array where Element == String { func joinedWithAmpersands() -> String { joined(separator: "&") } } extension CharacterSet { /// Creates a CharacterSet from RFC 3986 allowed characters. /// /// RFC 3986 states that the following characters are "reserved" characters. /// /// - General Delimiters: ":", "#", "[", "]", "@", "?", "/" /// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" /// /// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow /// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" /// should be percent-escaped in the query string. public static let afURLQueryAllowed: CharacterSet = { let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 let subDelimitersToEncode = "!$&'()*+,;=" let encodableDelimiters = CharacterSet(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") return CharacterSet.urlQueryAllowed.subtracting(encodableDelimiters) }() } ================================================ FILE: JetChat/Pods/Alamofire/Source/URLRequest+Alamofire.swift ================================================ // // URLRequest+Alamofire.swift // // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation extension URLRequest { /// Returns the `httpMethod` as Alamofire's `HTTPMethod` type. public var method: HTTPMethod? { get { httpMethod.flatMap(HTTPMethod.init) } set { httpMethod = newValue?.rawValue } } public func validate() throws { if method == .get, let bodyData = httpBody { throw AFError.urlRequestValidationFailed(reason: .bodyDataInGETRequest(bodyData)) } } } ================================================ FILE: JetChat/Pods/Alamofire/Source/URLSessionConfiguration+Alamofire.swift ================================================ // // URLSessionConfiguration+Alamofire.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation extension URLSessionConfiguration: AlamofireExtended {} extension AlamofireExtension where ExtendedType: URLSessionConfiguration { /// Alamofire's default configuration. Same as `URLSessionConfiguration.default` but adds Alamofire default /// `Accept-Language`, `Accept-Encoding`, and `User-Agent` headers. public static var `default`: URLSessionConfiguration { let configuration = URLSessionConfiguration.default configuration.headers = .default return configuration } /// `.ephemeral` configuration with Alamofire's default `Accept-Language`, `Accept-Encoding`, and `User-Agent` /// headers. public static var ephemeral: URLSessionConfiguration { let configuration = URLSessionConfiguration.ephemeral configuration.headers = .default return configuration } } ================================================ FILE: JetChat/Pods/Alamofire/Source/Validation.swift ================================================ // // Validation.swift // // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation extension Request { // MARK: Helper Types fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason /// Used to represent whether a validation succeeded or failed. public typealias ValidationResult = Result fileprivate struct MIMEType { let type: String let subtype: String var isWildcard: Bool { type == "*" && subtype == "*" } init?(_ string: String) { let components: [String] = { let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines) let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)] return split.components(separatedBy: "/") }() if let type = components.first, let subtype = components.last { self.type = type self.subtype = subtype } else { return nil } } func matches(_ mime: MIMEType) -> Bool { switch (type, subtype) { case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"): return true default: return false } } } // MARK: Properties fileprivate var acceptableStatusCodes: Range { 200..<300 } fileprivate var acceptableContentTypes: [String] { if let accept = request?.value(forHTTPHeaderField: "Accept") { return accept.components(separatedBy: ",") } return ["*/*"] } // MARK: Status Code fileprivate func validate(statusCode acceptableStatusCodes: S, response: HTTPURLResponse) -> ValidationResult where S.Iterator.Element == Int { if acceptableStatusCodes.contains(response.statusCode) { return .success(()) } else { let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode) return .failure(AFError.responseValidationFailed(reason: reason)) } } // MARK: Content Type fileprivate func validate(contentType acceptableContentTypes: S, response: HTTPURLResponse, data: Data?) -> ValidationResult where S.Iterator.Element == String { guard let data = data, !data.isEmpty else { return .success(()) } return validate(contentType: acceptableContentTypes, response: response) } fileprivate func validate(contentType acceptableContentTypes: S, response: HTTPURLResponse) -> ValidationResult where S.Iterator.Element == String { guard let responseContentType = response.mimeType, let responseMIMEType = MIMEType(responseContentType) else { for contentType in acceptableContentTypes { if let mimeType = MIMEType(contentType), mimeType.isWildcard { return .success(()) } } let error: AFError = { let reason: ErrorReason = .missingContentType(acceptableContentTypes: acceptableContentTypes.sorted()) return AFError.responseValidationFailed(reason: reason) }() return .failure(error) } for contentType in acceptableContentTypes { if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) { return .success(()) } } let error: AFError = { let reason: ErrorReason = .unacceptableContentType(acceptableContentTypes: acceptableContentTypes.sorted(), responseContentType: responseContentType) return AFError.responseValidationFailed(reason: reason) }() return .failure(error) } } // MARK: - extension DataRequest { /// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the /// request was valid. public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult /// Validates that the response has a status code in the specified sequence. /// /// If validation fails, subsequent calls to response handlers will have an associated error. /// /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes. /// /// - Returns: The instance. @discardableResult public func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { validate { [unowned self] _, response, _ in self.validate(statusCode: acceptableStatusCodes, response: response) } } /// Validates that the response has a content type in the specified sequence. /// /// If validation fails, subsequent calls to response handlers will have an associated error. /// /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. /// /// - returns: The request. @discardableResult public func validate(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String { validate { [unowned self] _, response, data in self.validate(contentType: acceptableContentTypes(), response: response, data: data) } } /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content /// type matches any specified in the Accept HTTP header field. /// /// If validation fails, subsequent calls to response handlers will have an associated error. /// /// - returns: The request. @discardableResult public func validate() -> Self { let contentTypes: () -> [String] = { [unowned self] in self.acceptableContentTypes } return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) } } extension DataStreamRequest { /// A closure used to validate a request that takes a `URLRequest` and `HTTPURLResponse` and returns whether the /// request was valid. public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse) -> ValidationResult /// Validates that the response has a status code in the specified sequence. /// /// If validation fails, subsequent calls to response handlers will have an associated error. /// /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes. /// /// - Returns: The instance. @discardableResult public func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { validate { [unowned self] _, response in self.validate(statusCode: acceptableStatusCodes, response: response) } } /// Validates that the response has a content type in the specified sequence. /// /// If validation fails, subsequent calls to response handlers will have an associated error. /// /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. /// /// - returns: The request. @discardableResult public func validate(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String { validate { [unowned self] _, response in self.validate(contentType: acceptableContentTypes(), response: response) } } /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content /// type matches any specified in the Accept HTTP header field. /// /// If validation fails, subsequent calls to response handlers will have an associated error. /// /// - Returns: The instance. @discardableResult public func validate() -> Self { let contentTypes: () -> [String] = { [unowned self] in self.acceptableContentTypes } return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) } } // MARK: - extension DownloadRequest { /// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a /// destination URL, and returns whether the request was valid. public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse, _ fileURL: URL?) -> ValidationResult /// Validates that the response has a status code in the specified sequence. /// /// If validation fails, subsequent calls to response handlers will have an associated error. /// /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes. /// /// - Returns: The instance. @discardableResult public func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { validate { [unowned self] _, response, _ in self.validate(statusCode: acceptableStatusCodes, response: response) } } /// Validates that the response has a content type in the specified sequence. /// /// If validation fails, subsequent calls to response handlers will have an associated error. /// /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. /// /// - returns: The request. @discardableResult public func validate(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String { validate { [unowned self] _, response, fileURL in guard let validFileURL = fileURL else { return .failure(AFError.responseValidationFailed(reason: .dataFileNil)) } do { let data = try Data(contentsOf: validFileURL) return self.validate(contentType: acceptableContentTypes(), response: response, data: data) } catch { return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL))) } } } /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content /// type matches any specified in the Accept HTTP header field. /// /// If validation fails, subsequent calls to response handlers will have an associated error. /// /// - returns: The request. @discardableResult public func validate() -> Self { let contentTypes = { [unowned self] in self.acceptableContentTypes } return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) } } ================================================ FILE: JetChat/Pods/Alamofire.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 0A2051EA0914A2749DA2F7861A9CDD88 /* ParameterEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC2EA49A1B0E0517D5615D945A1D2F73 /* ParameterEncoder.swift */; }; 1DCDE46D0E94332E4C186BFFDD3BEEF5 /* AlamofireExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F3A8D6988BE763B29FD1CED8B0F0E8 /* AlamofireExtended.swift */; }; 254F24FBBEF34222F55E8D0D1A529F49 /* Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851203AC13D57B60943A3D829ABC69FD /* Combine.swift */; }; 2A14B156801E9B6CA78E0315974075F2 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4053832FEFD0F8B9CEDBB748646216 /* Notifications.swift */; }; 2DD69493543E21B33A15F68825B0128A /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71047F59C36C60ED4DAD9F8531193F48 /* NetworkReachabilityManager.swift */; }; 32E0F47AED3C9C20B71A0B74AE2729E7 /* CachedResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FBE7B560B5D1A1C1F6994ACB3908DD6 /* CachedResponseHandler.swift */; }; 34DC51C049DD492632AE5D4EECAD9692 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5722B8FD2185FDF5A63D5DDEBBE828F4 /* Response.swift */; }; 627FE23AB2A99CB197EB6FCB234FFA01 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8E4276DB5225716A45E6D73D7C8814A /* RedirectHandler.swift */; }; 6C56055FEDC2EAB4BAE62EDE8EC93B65 /* URLSessionConfiguration+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5E916AEF3F2D3302E9286BAEDADFDC /* URLSessionConfiguration+Alamofire.swift */; }; 6DB19E6A404DC6E48944226A29E63FAD /* Protected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A17CB1B3F1A646181434E02043AE4A9 /* Protected.swift */; }; 6FA1C2761AACC20A9D29E8D4BAAE3D38 /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E400B9C221A199141363B822034737D /* MultipartFormData.swift */; }; 71A044CFBDF31C2954EF267B619FCA7C /* RetryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A777786034AA3DAED36C544EBBB0BE17 /* RetryPolicy.swift */; }; 7B780DF98CD63FE86DED9791F2BAC9B0 /* StringEncoding+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0D76B0CA7C280DCCA3C50F6AE99B52C /* StringEncoding+Alamofire.swift */; }; 7C37932A7CCAD169552F3AA48CFD221E /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CE5B1A2C9408DD1774C9837A4E7CD4 /* EventMonitor.swift */; }; 86F5A7CC87ECAB730DB431A33C6EE8E6 /* ParameterEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3743BEF1080A8DC1D71A82857E48B9 /* ParameterEncoding.swift */; }; 8A7E7CD446ABD6F5192B57DFBCF0D5FC /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F5557764115A3E7B2B876F825517B2 /* Session.swift */; }; 8D745FB91C58D355FCDE815516E00BF9 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F31E760BA0B35046C6060C798E4BB78 /* Request.swift */; }; 9927815D3EFE3F4424DF16BA289BDAED /* HTTPHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E3DB87ACFBC8F3E440668B169BAFAD /* HTTPHeaders.swift */; }; 9F291CC20DD5B5ED32936B4E059F9BD7 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BB9776E628FF1183F277FAE29EDB624 /* Foundation.framework */; }; 9FEEE2982ECEC995F1F173C372F8F9B0 /* Result+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19498655818EC4ECAAD4D2F2BEB77015 /* Result+Alamofire.swift */; }; A2003C57F33B0E68E1E9B14FE318E049 /* Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237FD85C062BB32E9452655779BA7927 /* Concurrency.swift */; }; A3F0D4C2D120AAFBAC5BAAD373FC442C /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4E4C11DD81AE4B9C4158F354BD44233 /* CFNetwork.framework */; }; A626185897C70BBD0C959C23C91E0EAA /* OperationQueue+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86F8939933D19B4D3E1DE7B22CBEB43 /* OperationQueue+Alamofire.swift */; }; A6827CC573473B3E36C3225C6B7B46B4 /* URLConvertible+URLRequestConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624A417772BA4FAB25BBD7CF729BDE1C /* URLConvertible+URLRequestConvertible.swift */; }; AC29E8069D329D375A1167D4B97E3177 /* Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A737455D29EB95727684DB684B32BE01 /* Validation.swift */; }; B2CE5A8133B8BEBF6F840858BDD54213 /* URLRequest+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00571F6974B40513160B3F985877A862 /* URLRequest+Alamofire.swift */; }; B3E45649923766C92B927881F90AB7A8 /* MultipartUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217C3B6C84EF37B3D46A589894758024 /* MultipartUpload.swift */; }; B5680599181740E0B606BEA0EF8DCA5F /* ServerTrustEvaluation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3A7BC7B0B8E218B79A84051BB8C920 /* ServerTrustEvaluation.swift */; }; B76037809299E8C23917745EB1C0DEED /* AFError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5252984862448E92536EE6896FD3C064 /* AFError.swift */; }; B9848C08433A53F78613C0F32E20A498 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BA047D81D4B5A8D4719DD5FC0A26F84 /* HTTPMethod.swift */; }; BC628E85CF00F7E12EA23F7D41B15EC5 /* RequestInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090A5AB2922DFE6F567891A144E511CB /* RequestInterceptor.swift */; }; BE4E500440E5B200D02FB4F9CE49CBFB /* Alamofire-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = F314077ACB3DB8372B7F60ADE4C4CC77 /* Alamofire-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; BF959882BC087542FFBE4BCBA9B398F1 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581E730B4DCDE04EAE8CA4598A09C43A /* SessionDelegate.swift */; }; CF7A09FAFC24749C4593179931067C96 /* AuthenticationInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59D2016351214818AEF5C60F1597D5D /* AuthenticationInterceptor.swift */; }; D31636203169CF94112E33CA4A00EDDD /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BAF4CB5A5BEFA56133443DD410D169 /* Alamofire.swift */; }; D6D8BEE882D405DA3EB92BBBDBF3320B /* Alamofire-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 25C5EC1EB4BF380AD46721809B07F74A /* Alamofire-dummy.m */; }; DA2C6AF7B2E263F88512955FCBE876DE /* URLEncodedFormEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07D59B0C1AD14C5F94B3B5D2869C2FE /* URLEncodedFormEncoder.swift */; }; DC0EB849D4010EBF703DF476F24AF5DA /* DispatchQueue+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EBDB99DAE329B93042E6CE74AD11E9 /* DispatchQueue+Alamofire.swift */; }; E754DFB193485D3CB6B2E77CCF72FB4F /* RequestTaskMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AEAE3203FC92E08788AF90E5464072 /* RequestTaskMap.swift */; }; FED4316D33650896E39F10C97D1D6C1A /* ResponseSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F2E511750AE31D6B58EA876E0E91F67 /* ResponseSerialization.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 00571F6974B40513160B3F985877A862 /* URLRequest+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLRequest+Alamofire.swift"; path = "Source/URLRequest+Alamofire.swift"; sourceTree = ""; }; 090A5AB2922DFE6F567891A144E511CB /* RequestInterceptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestInterceptor.swift; path = Source/RequestInterceptor.swift; sourceTree = ""; }; 0BA047D81D4B5A8D4719DD5FC0A26F84 /* HTTPMethod.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HTTPMethod.swift; path = Source/HTTPMethod.swift; sourceTree = ""; }; 0F2E511750AE31D6B58EA876E0E91F67 /* ResponseSerialization.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ResponseSerialization.swift; path = Source/ResponseSerialization.swift; sourceTree = ""; }; 19498655818EC4ECAAD4D2F2BEB77015 /* Result+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Result+Alamofire.swift"; path = "Source/Result+Alamofire.swift"; sourceTree = ""; }; 1BB9776E628FF1183F277FAE29EDB624 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 1F31E760BA0B35046C6060C798E4BB78 /* Request.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Request.swift; path = Source/Request.swift; sourceTree = ""; }; 1F5E916AEF3F2D3302E9286BAEDADFDC /* URLSessionConfiguration+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLSessionConfiguration+Alamofire.swift"; path = "Source/URLSessionConfiguration+Alamofire.swift"; sourceTree = ""; }; 217C3B6C84EF37B3D46A589894758024 /* MultipartUpload.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartUpload.swift; path = Source/MultipartUpload.swift; sourceTree = ""; }; 237FD85C062BB32E9452655779BA7927 /* Concurrency.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Concurrency.swift; path = Source/Concurrency.swift; sourceTree = ""; }; 25C5EC1EB4BF380AD46721809B07F74A /* Alamofire-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Alamofire-dummy.m"; sourceTree = ""; }; 2C4053832FEFD0F8B9CEDBB748646216 /* Notifications.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Notifications.swift; path = Source/Notifications.swift; sourceTree = ""; }; 2E322595956F6D84C6BEEC8C4A741FE8 /* Alamofire.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.release.xcconfig; sourceTree = ""; }; 2F3A7BC7B0B8E218B79A84051BB8C920 /* ServerTrustEvaluation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServerTrustEvaluation.swift; path = Source/ServerTrustEvaluation.swift; sourceTree = ""; }; 2FBE7B560B5D1A1C1F6994ACB3908DD6 /* CachedResponseHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CachedResponseHandler.swift; path = Source/CachedResponseHandler.swift; sourceTree = ""; }; 31BAF4CB5A5BEFA56133443DD410D169 /* Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Alamofire.swift; path = Source/Alamofire.swift; sourceTree = ""; }; 3A17CB1B3F1A646181434E02043AE4A9 /* Protected.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Protected.swift; path = Source/Protected.swift; sourceTree = ""; }; 4EA985410A8D3BE11ADFBA9C644E4435 /* Alamofire-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Alamofire-Info.plist"; sourceTree = ""; }; 5252984862448E92536EE6896FD3C064 /* AFError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AFError.swift; path = Source/AFError.swift; sourceTree = ""; }; 5722B8FD2185FDF5A63D5DDEBBE828F4 /* Response.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Response.swift; path = Source/Response.swift; sourceTree = ""; }; 574703F845E1303C0F8F73A212883401 /* Alamofire-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-prefix.pch"; sourceTree = ""; }; 581E730B4DCDE04EAE8CA4598A09C43A /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Source/SessionDelegate.swift; sourceTree = ""; }; 624A417772BA4FAB25BBD7CF729BDE1C /* URLConvertible+URLRequestConvertible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLConvertible+URLRequestConvertible.swift"; path = "Source/URLConvertible+URLRequestConvertible.swift"; sourceTree = ""; }; 6C335C796159CBD3B48764946405CC49 /* Alamofire.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Alamofire.modulemap; sourceTree = ""; }; 6E400B9C221A199141363B822034737D /* MultipartFormData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartFormData.swift; path = Source/MultipartFormData.swift; sourceTree = ""; }; 71047F59C36C60ED4DAD9F8531193F48 /* NetworkReachabilityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkReachabilityManager.swift; path = Source/NetworkReachabilityManager.swift; sourceTree = ""; }; 75EBDB99DAE329B93042E6CE74AD11E9 /* DispatchQueue+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DispatchQueue+Alamofire.swift"; path = "Source/DispatchQueue+Alamofire.swift"; sourceTree = ""; }; 838A7EADD3693326F073BE27C1337F60 /* Alamofire.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.debug.xcconfig; sourceTree = ""; }; 851203AC13D57B60943A3D829ABC69FD /* Combine.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Combine.swift; path = Source/Combine.swift; sourceTree = ""; }; 96E3DB87ACFBC8F3E440668B169BAFAD /* HTTPHeaders.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HTTPHeaders.swift; path = Source/HTTPHeaders.swift; sourceTree = ""; }; 97F5557764115A3E7B2B876F825517B2 /* Session.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Session.swift; path = Source/Session.swift; sourceTree = ""; }; 98AEAE3203FC92E08788AF90E5464072 /* RequestTaskMap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestTaskMap.swift; path = Source/RequestTaskMap.swift; sourceTree = ""; }; A5F3A8D6988BE763B29FD1CED8B0F0E8 /* AlamofireExtended.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AlamofireExtended.swift; path = Source/AlamofireExtended.swift; sourceTree = ""; }; A737455D29EB95727684DB684B32BE01 /* Validation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Validation.swift; path = Source/Validation.swift; sourceTree = ""; }; A777786034AA3DAED36C544EBBB0BE17 /* RetryPolicy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RetryPolicy.swift; path = Source/RetryPolicy.swift; sourceTree = ""; }; A8E4276DB5225716A45E6D73D7C8814A /* RedirectHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedirectHandler.swift; path = Source/RedirectHandler.swift; sourceTree = ""; }; B0D76B0CA7C280DCCA3C50F6AE99B52C /* StringEncoding+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "StringEncoding+Alamofire.swift"; path = "Source/StringEncoding+Alamofire.swift"; sourceTree = ""; }; B86F8939933D19B4D3E1DE7B22CBEB43 /* OperationQueue+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "OperationQueue+Alamofire.swift"; path = "Source/OperationQueue+Alamofire.swift"; sourceTree = ""; }; CC2EA49A1B0E0517D5615D945A1D2F73 /* ParameterEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ParameterEncoder.swift; path = Source/ParameterEncoder.swift; sourceTree = ""; }; D063EE811069D306211D06E68BEF5C09 /* Alamofire */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Alamofire; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D07D59B0C1AD14C5F94B3B5D2869C2FE /* URLEncodedFormEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = URLEncodedFormEncoder.swift; path = Source/URLEncodedFormEncoder.swift; sourceTree = ""; }; D4CE5B1A2C9408DD1774C9837A4E7CD4 /* EventMonitor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EventMonitor.swift; path = Source/EventMonitor.swift; sourceTree = ""; }; D59D2016351214818AEF5C60F1597D5D /* AuthenticationInterceptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthenticationInterceptor.swift; path = Source/AuthenticationInterceptor.swift; sourceTree = ""; }; DD3743BEF1080A8DC1D71A82857E48B9 /* ParameterEncoding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ParameterEncoding.swift; path = Source/ParameterEncoding.swift; sourceTree = ""; }; E4E4C11DD81AE4B9C4158F354BD44233 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; F314077ACB3DB8372B7F60ADE4C4CC77 /* Alamofire-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-umbrella.h"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 272AA4ADCDF15FCEE0A7CD58EDFBD240 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( A3F0D4C2D120AAFBAC5BAAD373FC442C /* CFNetwork.framework in Frameworks */, 9F291CC20DD5B5ED32936B4E059F9BD7 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0D7BCD481FAEC42F606FACA5F1A46F0A /* Support Files */ = { isa = PBXGroup; children = ( 6C335C796159CBD3B48764946405CC49 /* Alamofire.modulemap */, 25C5EC1EB4BF380AD46721809B07F74A /* Alamofire-dummy.m */, 4EA985410A8D3BE11ADFBA9C644E4435 /* Alamofire-Info.plist */, 574703F845E1303C0F8F73A212883401 /* Alamofire-prefix.pch */, F314077ACB3DB8372B7F60ADE4C4CC77 /* Alamofire-umbrella.h */, 838A7EADD3693326F073BE27C1337F60 /* Alamofire.debug.xcconfig */, 2E322595956F6D84C6BEEC8C4A741FE8 /* Alamofire.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/Alamofire"; sourceTree = ""; }; 15DB8F8C32C4899ECF82C9A66D2947FC /* Products */ = { isa = PBXGroup; children = ( D063EE811069D306211D06E68BEF5C09 /* Alamofire */, ); name = Products; sourceTree = ""; }; 258F2A33F7C2551A44E6D268FE86814F /* Frameworks */ = { isa = PBXGroup; children = ( 8D32B23A300AC06BE36C248720F514AD /* iOS */, ); name = Frameworks; sourceTree = ""; }; 7FC35244BD082D1BE9DB3EBD155E705A /* Alamofire */ = { isa = PBXGroup; children = ( 5252984862448E92536EE6896FD3C064 /* AFError.swift */, 31BAF4CB5A5BEFA56133443DD410D169 /* Alamofire.swift */, A5F3A8D6988BE763B29FD1CED8B0F0E8 /* AlamofireExtended.swift */, D59D2016351214818AEF5C60F1597D5D /* AuthenticationInterceptor.swift */, 2FBE7B560B5D1A1C1F6994ACB3908DD6 /* CachedResponseHandler.swift */, 851203AC13D57B60943A3D829ABC69FD /* Combine.swift */, 237FD85C062BB32E9452655779BA7927 /* Concurrency.swift */, 75EBDB99DAE329B93042E6CE74AD11E9 /* DispatchQueue+Alamofire.swift */, D4CE5B1A2C9408DD1774C9837A4E7CD4 /* EventMonitor.swift */, 96E3DB87ACFBC8F3E440668B169BAFAD /* HTTPHeaders.swift */, 0BA047D81D4B5A8D4719DD5FC0A26F84 /* HTTPMethod.swift */, 6E400B9C221A199141363B822034737D /* MultipartFormData.swift */, 217C3B6C84EF37B3D46A589894758024 /* MultipartUpload.swift */, 71047F59C36C60ED4DAD9F8531193F48 /* NetworkReachabilityManager.swift */, 2C4053832FEFD0F8B9CEDBB748646216 /* Notifications.swift */, B86F8939933D19B4D3E1DE7B22CBEB43 /* OperationQueue+Alamofire.swift */, CC2EA49A1B0E0517D5615D945A1D2F73 /* ParameterEncoder.swift */, DD3743BEF1080A8DC1D71A82857E48B9 /* ParameterEncoding.swift */, 3A17CB1B3F1A646181434E02043AE4A9 /* Protected.swift */, A8E4276DB5225716A45E6D73D7C8814A /* RedirectHandler.swift */, 1F31E760BA0B35046C6060C798E4BB78 /* Request.swift */, 090A5AB2922DFE6F567891A144E511CB /* RequestInterceptor.swift */, 98AEAE3203FC92E08788AF90E5464072 /* RequestTaskMap.swift */, 5722B8FD2185FDF5A63D5DDEBBE828F4 /* Response.swift */, 0F2E511750AE31D6B58EA876E0E91F67 /* ResponseSerialization.swift */, 19498655818EC4ECAAD4D2F2BEB77015 /* Result+Alamofire.swift */, A777786034AA3DAED36C544EBBB0BE17 /* RetryPolicy.swift */, 2F3A7BC7B0B8E218B79A84051BB8C920 /* ServerTrustEvaluation.swift */, 97F5557764115A3E7B2B876F825517B2 /* Session.swift */, 581E730B4DCDE04EAE8CA4598A09C43A /* SessionDelegate.swift */, B0D76B0CA7C280DCCA3C50F6AE99B52C /* StringEncoding+Alamofire.swift */, 624A417772BA4FAB25BBD7CF729BDE1C /* URLConvertible+URLRequestConvertible.swift */, D07D59B0C1AD14C5F94B3B5D2869C2FE /* URLEncodedFormEncoder.swift */, 00571F6974B40513160B3F985877A862 /* URLRequest+Alamofire.swift */, 1F5E916AEF3F2D3302E9286BAEDADFDC /* URLSessionConfiguration+Alamofire.swift */, A737455D29EB95727684DB684B32BE01 /* Validation.swift */, 0D7BCD481FAEC42F606FACA5F1A46F0A /* Support Files */, ); name = Alamofire; path = Alamofire; sourceTree = ""; }; 8D32B23A300AC06BE36C248720F514AD /* iOS */ = { isa = PBXGroup; children = ( E4E4C11DD81AE4B9C4158F354BD44233 /* CFNetwork.framework */, 1BB9776E628FF1183F277FAE29EDB624 /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; F9CB9BECCE32DDA4A2CF383F40D304C6 = { isa = PBXGroup; children = ( 7FC35244BD082D1BE9DB3EBD155E705A /* Alamofire */, 258F2A33F7C2551A44E6D268FE86814F /* Frameworks */, 15DB8F8C32C4899ECF82C9A66D2947FC /* Products */, ); sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ D494003D09185C0D607CE4362679C417 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( BE4E500440E5B200D02FB4F9CE49CBFB /* Alamofire-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 81B7E9B7CD0CADA087A4BB042FBA92E7 /* Alamofire */ = { isa = PBXNativeTarget; buildConfigurationList = 964874FC92F964E1F19F56E6610AE60B /* Build configuration list for PBXNativeTarget "Alamofire" */; buildPhases = ( D494003D09185C0D607CE4362679C417 /* Headers */, 9AF650F4DCF2E22891B0DFAA21067F09 /* Sources */, 272AA4ADCDF15FCEE0A7CD58EDFBD240 /* Frameworks */, 1F8E566C12AFE3AACB8C2BD293001C1C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Alamofire; productName = Alamofire; productReference = D063EE811069D306211D06E68BEF5C09 /* Alamofire */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 56A9F5337958C3BFB5BA830E81895EE7 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 3C27EE8C2658DDA0F3E3BF447B623A73 /* Build configuration list for PBXProject "Alamofire" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = F9CB9BECCE32DDA4A2CF383F40D304C6; productRefGroup = 15DB8F8C32C4899ECF82C9A66D2947FC /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 81B7E9B7CD0CADA087A4BB042FBA92E7 /* Alamofire */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 1F8E566C12AFE3AACB8C2BD293001C1C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 9AF650F4DCF2E22891B0DFAA21067F09 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B76037809299E8C23917745EB1C0DEED /* AFError.swift in Sources */, D31636203169CF94112E33CA4A00EDDD /* Alamofire.swift in Sources */, D6D8BEE882D405DA3EB92BBBDBF3320B /* Alamofire-dummy.m in Sources */, 1DCDE46D0E94332E4C186BFFDD3BEEF5 /* AlamofireExtended.swift in Sources */, CF7A09FAFC24749C4593179931067C96 /* AuthenticationInterceptor.swift in Sources */, 32E0F47AED3C9C20B71A0B74AE2729E7 /* CachedResponseHandler.swift in Sources */, 254F24FBBEF34222F55E8D0D1A529F49 /* Combine.swift in Sources */, A2003C57F33B0E68E1E9B14FE318E049 /* Concurrency.swift in Sources */, DC0EB849D4010EBF703DF476F24AF5DA /* DispatchQueue+Alamofire.swift in Sources */, 7C37932A7CCAD169552F3AA48CFD221E /* EventMonitor.swift in Sources */, 9927815D3EFE3F4424DF16BA289BDAED /* HTTPHeaders.swift in Sources */, B9848C08433A53F78613C0F32E20A498 /* HTTPMethod.swift in Sources */, 6FA1C2761AACC20A9D29E8D4BAAE3D38 /* MultipartFormData.swift in Sources */, B3E45649923766C92B927881F90AB7A8 /* MultipartUpload.swift in Sources */, 2DD69493543E21B33A15F68825B0128A /* NetworkReachabilityManager.swift in Sources */, 2A14B156801E9B6CA78E0315974075F2 /* Notifications.swift in Sources */, A626185897C70BBD0C959C23C91E0EAA /* OperationQueue+Alamofire.swift in Sources */, 0A2051EA0914A2749DA2F7861A9CDD88 /* ParameterEncoder.swift in Sources */, 86F5A7CC87ECAB730DB431A33C6EE8E6 /* ParameterEncoding.swift in Sources */, 6DB19E6A404DC6E48944226A29E63FAD /* Protected.swift in Sources */, 627FE23AB2A99CB197EB6FCB234FFA01 /* RedirectHandler.swift in Sources */, 8D745FB91C58D355FCDE815516E00BF9 /* Request.swift in Sources */, BC628E85CF00F7E12EA23F7D41B15EC5 /* RequestInterceptor.swift in Sources */, E754DFB193485D3CB6B2E77CCF72FB4F /* RequestTaskMap.swift in Sources */, 34DC51C049DD492632AE5D4EECAD9692 /* Response.swift in Sources */, FED4316D33650896E39F10C97D1D6C1A /* ResponseSerialization.swift in Sources */, 9FEEE2982ECEC995F1F173C372F8F9B0 /* Result+Alamofire.swift in Sources */, 71A044CFBDF31C2954EF267B619FCA7C /* RetryPolicy.swift in Sources */, B5680599181740E0B606BEA0EF8DCA5F /* ServerTrustEvaluation.swift in Sources */, 8A7E7CD446ABD6F5192B57DFBCF0D5FC /* Session.swift in Sources */, BF959882BC087542FFBE4BCBA9B398F1 /* SessionDelegate.swift in Sources */, 7B780DF98CD63FE86DED9791F2BAC9B0 /* StringEncoding+Alamofire.swift in Sources */, A6827CC573473B3E36C3225C6B7B46B4 /* URLConvertible+URLRequestConvertible.swift in Sources */, DA2C6AF7B2E263F88512955FCBE876DE /* URLEncodedFormEncoder.swift in Sources */, B2CE5A8133B8BEBF6F840858BDD54213 /* URLRequest+Alamofire.swift in Sources */, 6C56055FEDC2EAB4BAE62EDE8EC93B65 /* URLSessionConfiguration+Alamofire.swift in Sources */, AC29E8069D329D375A1167D4B97E3177 /* Validation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ A6FB06411EC6D9B8E5472E8F236342DE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; A9206ED9640E588E5AA293F9D6A16221 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 838A7EADD3693326F073BE27C1337F60 /* Alamofire.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/Alamofire/Alamofire-prefix.pch"; INFOPLIST_FILE = "Target Support Files/Alamofire/Alamofire-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/Alamofire/Alamofire.modulemap"; PRODUCT_MODULE_NAME = Alamofire; PRODUCT_NAME = Alamofire; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.5; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; B5F7C111091F13E46146A8F199113257 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 2E322595956F6D84C6BEEC8C4A741FE8 /* Alamofire.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/Alamofire/Alamofire-prefix.pch"; INFOPLIST_FILE = "Target Support Files/Alamofire/Alamofire-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/Alamofire/Alamofire.modulemap"; PRODUCT_MODULE_NAME = Alamofire; PRODUCT_NAME = Alamofire; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.5; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; BDA87E95117989221CEE3597485B083F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3C27EE8C2658DDA0F3E3BF447B623A73 /* Build configuration list for PBXProject "Alamofire" */ = { isa = XCConfigurationList; buildConfigurations = ( A6FB06411EC6D9B8E5472E8F236342DE /* Debug */, BDA87E95117989221CEE3597485B083F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 964874FC92F964E1F19F56E6610AE60B /* Build configuration list for PBXNativeTarget "Alamofire" */ = { isa = XCConfigurationList; buildConfigurations = ( A9206ED9640E588E5AA293F9D6A16221 /* Debug */, B5F7C111091F13E46146A8F199113257 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 56A9F5337958C3BFB5BA830E81895EE7 /* Project object */; } ================================================ FILE: JetChat/Pods/FDFullscreenPopGesture/FDFullscreenPopGesture/UINavigationController+FDFullscreenPopGesture.h ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import /// "UINavigation+FDFullscreenPopGesture" extends UINavigationController's swipe- /// to-pop behavior in iOS 7+ by supporting fullscreen pan gesture. Instead of /// screen edge, you can now swipe from any place on the screen and the onboard /// interactive pop transition works seamlessly. /// /// Adding the implementation file of this category to your target will /// automatically patch UINavigationController with this feature. @interface UINavigationController (FDFullscreenPopGesture) /// The gesture recognizer that actually handles interactive pop. @property (nonatomic, strong, readonly) UIPanGestureRecognizer *fd_fullscreenPopGestureRecognizer; /// A view controller is able to control navigation bar's appearance by itself, /// rather than a global way, checking "fd_prefersNavigationBarHidden" property. /// Default to YES, disable it if you don't want so. @property (nonatomic, assign) BOOL fd_viewControllerBasedNavigationBarAppearanceEnabled; @end /// Allows any view controller to disable interactive pop gesture, which might /// be necessary when the view controller itself handles pan gesture in some /// cases. @interface UIViewController (FDFullscreenPopGesture) /// Whether the interactive pop gesture is disabled when contained in a navigation /// stack. @property (nonatomic, assign) BOOL fd_interactivePopDisabled; /// Indicate this view controller prefers its navigation bar hidden or not, /// checked when view controller based navigation bar's appearance is enabled. /// Default to NO, bars are more likely to show. @property (nonatomic, assign) BOOL fd_prefersNavigationBarHidden; @end ================================================ FILE: JetChat/Pods/FDFullscreenPopGesture/FDFullscreenPopGesture/UINavigationController+FDFullscreenPopGesture.m ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import "UINavigationController+FDFullscreenPopGesture.h" #import @interface _FDFullscreenPopGestureRecognizerDelegate : NSObject @property (nonatomic, weak) UINavigationController *navigationController; @end @implementation _FDFullscreenPopGestureRecognizerDelegate - (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer { // Ignore when no view controller is pushed into the navigation stack. if (self.navigationController.viewControllers.count <= 1) { return NO; } // Disable when the active view controller doesn't allow interactive pop. UIViewController *topViewController = self.navigationController.viewControllers.lastObject; if (topViewController.fd_interactivePopDisabled) { return NO; } // Ignore pan gesture when the navigation controller is currently in transition. if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) { return NO; } // Prevent calling the handler when the gesture begins in an opposite direction. CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view]; if (translation.x <= 0) { return NO; } return YES; } @end typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated); @interface UIViewController (FDFullscreenPopGesturePrivate) @property (nonatomic, copy) _FDViewControllerWillAppearInjectBlock fd_willAppearInjectBlock; @end @implementation UIViewController (FDFullscreenPopGesturePrivate) + (void)load { Method originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:)); Method swizzledMethod = class_getInstanceMethod(self, @selector(fd_viewWillAppear:)); method_exchangeImplementations(originalMethod, swizzledMethod); } - (void)fd_viewWillAppear:(BOOL)animated { // Forward to primary implementation. [self fd_viewWillAppear:animated]; if (self.fd_willAppearInjectBlock) { self.fd_willAppearInjectBlock(self, animated); } } - (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock { return objc_getAssociatedObject(self, _cmd); } - (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block { objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC); } @end @implementation UINavigationController (FDFullscreenPopGesture) + (void)load { // Inject "-pushViewController:animated:" Method originalMethod = class_getInstanceMethod(self, @selector(pushViewController:animated:)); Method swizzledMethod = class_getInstanceMethod(self, @selector(fd_pushViewController:animated:)); method_exchangeImplementations(originalMethod, swizzledMethod); } - (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated { if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.fd_fullscreenPopGestureRecognizer]) { // Add our own gesture recognizer to where the onboard screen edge pan gesture recognizer is attached to. [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.fd_fullscreenPopGestureRecognizer]; // Forward the gesture events to the private handler of the onboard gesture recognizer. NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets"]; id internalTarget = [internalTargets.firstObject valueForKey:@"target"]; SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:"); self.fd_fullscreenPopGestureRecognizer.delegate = self.fd_popGestureRecognizerDelegate; [self.fd_fullscreenPopGestureRecognizer addTarget:internalTarget action:internalAction]; // Disable the onboard gesture recognizer. self.interactivePopGestureRecognizer.enabled = NO; } // Handle perferred navigation bar appearance. [self fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController]; // Forward to primary implementation. [self fd_pushViewController:viewController animated:animated]; } - (void)fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController { if (!self.fd_viewControllerBasedNavigationBarAppearanceEnabled) { return; } __weak typeof(self) weakSelf = self; _FDViewControllerWillAppearInjectBlock block = ^(UIViewController *viewController, BOOL animated) { __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf) { [strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:animated]; } }; // Setup will appear inject block to appearing view controller. // Setup disappearing view controller as well, because not every view controller is added into // stack by pushing, maybe by "-setViewControllers:". appearingViewController.fd_willAppearInjectBlock = block; UIViewController *disappearingViewController = self.viewControllers.lastObject; if (disappearingViewController && !disappearingViewController.fd_willAppearInjectBlock) { disappearingViewController.fd_willAppearInjectBlock = block; } } - (_FDFullscreenPopGestureRecognizerDelegate *)fd_popGestureRecognizerDelegate { _FDFullscreenPopGestureRecognizerDelegate *delegate = objc_getAssociatedObject(self, _cmd); if (!delegate) { delegate = [[_FDFullscreenPopGestureRecognizerDelegate alloc] init]; delegate.navigationController = self; objc_setAssociatedObject(self, _cmd, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return delegate; } - (UIPanGestureRecognizer *)fd_fullscreenPopGestureRecognizer { UIPanGestureRecognizer *panGestureRecognizer = objc_getAssociatedObject(self, _cmd); if (!panGestureRecognizer) { panGestureRecognizer = [[UIPanGestureRecognizer alloc] init]; panGestureRecognizer.maximumNumberOfTouches = 1; objc_setAssociatedObject(self, _cmd, panGestureRecognizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return panGestureRecognizer; } - (BOOL)fd_viewControllerBasedNavigationBarAppearanceEnabled { NSNumber *number = objc_getAssociatedObject(self, _cmd); if (number) { return number.boolValue; } self.fd_viewControllerBasedNavigationBarAppearanceEnabled = YES; return YES; } - (void)setFd_viewControllerBasedNavigationBarAppearanceEnabled:(BOOL)enabled { SEL key = @selector(fd_viewControllerBasedNavigationBarAppearanceEnabled); objc_setAssociatedObject(self, key, @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end @implementation UIViewController (FDFullscreenPopGesture) - (BOOL)fd_interactivePopDisabled { return [objc_getAssociatedObject(self, _cmd) boolValue]; } - (void)setFd_interactivePopDisabled:(BOOL)disabled { objc_setAssociatedObject(self, @selector(fd_interactivePopDisabled), @(disabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (BOOL)fd_prefersNavigationBarHidden { return [objc_getAssociatedObject(self, _cmd) boolValue]; } - (void)setFd_prefersNavigationBarHidden:(BOOL)hidden { objc_setAssociatedObject(self, @selector(fd_prefersNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end ================================================ FILE: JetChat/Pods/FDFullscreenPopGesture/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/FDFullscreenPopGesture/README.md ================================================ # FDFullscreenPopGesture An UINavigationController's category to enable fullscreen pop gesture in an iOS7+ system style with AOP. # Overview ![snapshot](https://raw.githubusercontent.com/forkingdog/FDFullscreenPopGesture/master/Snapshots/snapshot0.gif) 这个扩展来自 @J_雨 同学的这个很天才的思路,他的文章地址:[http://www.jianshu.com/p/d39f7d22db6c](http://www.jianshu.com/p/d39f7d22db6c) # Usage **AOP**, just add 2 files and **no need** for any setups, all navigation controllers will be able to use fullscreen pop gesture automatically. To disable this pop gesture of a navigation controller: ``` objc navigationController.fd_fullscreenPopGestureRecognizer.enabled = NO; ``` To disable this pop gesture of a view controller: ``` objc viewController.fd_interactivePopDisabled = YES; ``` Require at least iOS **7.0**. # View Controller Based Navigation Bar Appearance It handles navigation bar transition properly when using fullscreen gesture to push or pop a view controller: - with bar -> without bar - without bar -> with bar - without bar -> without bar ![snapshot with bar states](https://raw.githubusercontent.com/forkingdog/FDFullscreenPopGesture/master/Snapshots/snapshot1.gif) This opmiziation is enabled by default, from now on you don't need to call **UINavigationController**'s `-setNavigationBarHidden:animated:` method, instead, use view controller's specific API to hide its bar: ``` objc - (void)viewDidLoad { [super viewDidLoad]; self.fd_prefersNavigationBarHidden = NO; } ``` And this property is **YES** by default. # Installation Use cocoapods ``` ruby pod 'FDFullscreenPopGesture', '1.1' ``` # Release Notes **1.1** - View controller based navigation bar appearance and transition. **1.0** - Fullscreen pop gesture. # License MIT ================================================ FILE: JetChat/Pods/FDFullscreenPopGesture.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 5D93002EF70CBE67D34305A810908957 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2811DD0C4F403A862FC617FEC2F6885F /* Foundation.framework */; }; 617CF3E9354339B5EBCA7247D220C38A /* UINavigationController+FDFullscreenPopGesture.m in Sources */ = {isa = PBXBuildFile; fileRef = 442410656F7B7C29C1EDA454EB5E2343 /* UINavigationController+FDFullscreenPopGesture.m */; }; 8EFBD71FE38A0D04B06250BE2AE064B7 /* UINavigationController+FDFullscreenPopGesture.h in Headers */ = {isa = PBXBuildFile; fileRef = F30A1FC2B45F5F84D07ED1DF369F6CE0 /* UINavigationController+FDFullscreenPopGesture.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA5F3AB14971F5906A19F38B1B040BCD /* FDFullscreenPopGesture-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = F92CAC636918E7E2EFBADA2B4F6015EB /* FDFullscreenPopGesture-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; FC8A200019C8B10E0CC39FB07E6FB7C2 /* FDFullscreenPopGesture-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CFCF7002D0DA3C01CD7ADA43409F360 /* FDFullscreenPopGesture-dummy.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2811DD0C4F403A862FC617FEC2F6885F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 442410656F7B7C29C1EDA454EB5E2343 /* UINavigationController+FDFullscreenPopGesture.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UINavigationController+FDFullscreenPopGesture.m"; path = "FDFullscreenPopGesture/UINavigationController+FDFullscreenPopGesture.m"; sourceTree = ""; }; 4A250EF9BD00E7210DA5117560B6D053 /* FDFullscreenPopGesture-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FDFullscreenPopGesture-Info.plist"; sourceTree = ""; }; 647138C6D5F4D7B77416F217E4115BB7 /* FDFullscreenPopGesture */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FDFullscreenPopGesture; path = FDFullscreenPopGesture.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7166D4F0E7815B12E4444BE344A12EFD /* FDFullscreenPopGesture.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FDFullscreenPopGesture.modulemap; sourceTree = ""; }; 8CD2EDE0491A752449362412A74882A2 /* FDFullscreenPopGesture.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FDFullscreenPopGesture.debug.xcconfig; sourceTree = ""; }; 8CFCF7002D0DA3C01CD7ADA43409F360 /* FDFullscreenPopGesture-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FDFullscreenPopGesture-dummy.m"; sourceTree = ""; }; B31F55AAFF6653EDBFB0532E3E78041D /* FDFullscreenPopGesture.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FDFullscreenPopGesture.release.xcconfig; sourceTree = ""; }; F30A1FC2B45F5F84D07ED1DF369F6CE0 /* UINavigationController+FDFullscreenPopGesture.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UINavigationController+FDFullscreenPopGesture.h"; path = "FDFullscreenPopGesture/UINavigationController+FDFullscreenPopGesture.h"; sourceTree = ""; }; F80D0671D125F6B1355456995A040F4D /* FDFullscreenPopGesture-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FDFullscreenPopGesture-prefix.pch"; sourceTree = ""; }; F92CAC636918E7E2EFBADA2B4F6015EB /* FDFullscreenPopGesture-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FDFullscreenPopGesture-umbrella.h"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ D1F7324A9E5004FCF3FFDF413BE2D28F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 5D93002EF70CBE67D34305A810908957 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 21CC3615D07A552BD0339CED13C768E0 /* iOS */ = { isa = PBXGroup; children = ( 2811DD0C4F403A862FC617FEC2F6885F /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; 36B6711F9529BB5DFBCF89662990DC79 = { isa = PBXGroup; children = ( D4CEADBFEEB4F8156DC2FE72C4A4F3A9 /* FDFullscreenPopGesture */, 506A2BACD5AD77047A7F08CF2A67A485 /* Frameworks */, E3E5A86431510652F9E2558AE1242A3B /* Products */, ); sourceTree = ""; }; 506A2BACD5AD77047A7F08CF2A67A485 /* Frameworks */ = { isa = PBXGroup; children = ( 21CC3615D07A552BD0339CED13C768E0 /* iOS */, ); name = Frameworks; sourceTree = ""; }; 5B5E5FF442A6392515F26E866DB9435E /* Support Files */ = { isa = PBXGroup; children = ( 7166D4F0E7815B12E4444BE344A12EFD /* FDFullscreenPopGesture.modulemap */, 8CFCF7002D0DA3C01CD7ADA43409F360 /* FDFullscreenPopGesture-dummy.m */, 4A250EF9BD00E7210DA5117560B6D053 /* FDFullscreenPopGesture-Info.plist */, F80D0671D125F6B1355456995A040F4D /* FDFullscreenPopGesture-prefix.pch */, F92CAC636918E7E2EFBADA2B4F6015EB /* FDFullscreenPopGesture-umbrella.h */, 8CD2EDE0491A752449362412A74882A2 /* FDFullscreenPopGesture.debug.xcconfig */, B31F55AAFF6653EDBFB0532E3E78041D /* FDFullscreenPopGesture.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/FDFullscreenPopGesture"; sourceTree = ""; }; D4CEADBFEEB4F8156DC2FE72C4A4F3A9 /* FDFullscreenPopGesture */ = { isa = PBXGroup; children = ( F30A1FC2B45F5F84D07ED1DF369F6CE0 /* UINavigationController+FDFullscreenPopGesture.h */, 442410656F7B7C29C1EDA454EB5E2343 /* UINavigationController+FDFullscreenPopGesture.m */, 5B5E5FF442A6392515F26E866DB9435E /* Support Files */, ); name = FDFullscreenPopGesture; path = FDFullscreenPopGesture; sourceTree = ""; }; E3E5A86431510652F9E2558AE1242A3B /* Products */ = { isa = PBXGroup; children = ( 647138C6D5F4D7B77416F217E4115BB7 /* FDFullscreenPopGesture */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 40FB73262556541BE31C68A371D768B2 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( CA5F3AB14971F5906A19F38B1B040BCD /* FDFullscreenPopGesture-umbrella.h in Headers */, 8EFBD71FE38A0D04B06250BE2AE064B7 /* UINavigationController+FDFullscreenPopGesture.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ A8BAA0D80184552CA0F0647BF03C3D1B /* FDFullscreenPopGesture */ = { isa = PBXNativeTarget; buildConfigurationList = C65CDAF9EE52F14CC151AF6E556D6FE0 /* Build configuration list for PBXNativeTarget "FDFullscreenPopGesture" */; buildPhases = ( 40FB73262556541BE31C68A371D768B2 /* Headers */, EBA71A78DF744C1BCA8203DC3E1EDD3E /* Sources */, D1F7324A9E5004FCF3FFDF413BE2D28F /* Frameworks */, B5EED544008394283215889E53A9A72A /* Resources */, ); buildRules = ( ); dependencies = ( ); name = FDFullscreenPopGesture; productName = FDFullscreenPopGesture; productReference = 647138C6D5F4D7B77416F217E4115BB7 /* FDFullscreenPopGesture */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ D866A8E7E3B7C15B8CA393D72C310963 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = DC8F4C97B07ED0D19369D41D6D324894 /* Build configuration list for PBXProject "FDFullscreenPopGesture" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 36B6711F9529BB5DFBCF89662990DC79; productRefGroup = E3E5A86431510652F9E2558AE1242A3B /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( A8BAA0D80184552CA0F0647BF03C3D1B /* FDFullscreenPopGesture */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ B5EED544008394283215889E53A9A72A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ EBA71A78DF744C1BCA8203DC3E1EDD3E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( FC8A200019C8B10E0CC39FB07E6FB7C2 /* FDFullscreenPopGesture-dummy.m in Sources */, 617CF3E9354339B5EBCA7247D220C38A /* UINavigationController+FDFullscreenPopGesture.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 5475041B13520804AB82F0B304AD3A49 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = B31F55AAFF6653EDBFB0532E3E78041D /* FDFullscreenPopGesture.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture-prefix.pch"; INFOPLIST_FILE = "Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture.modulemap"; PRODUCT_MODULE_NAME = FDFullscreenPopGesture; PRODUCT_NAME = FDFullscreenPopGesture; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 64A7D7F7C843687AA32404B3DFE7C981 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; 803A23C9436E2B759F5881F0C44B0ECE /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 8CD2EDE0491A752449362412A74882A2 /* FDFullscreenPopGesture.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture-prefix.pch"; INFOPLIST_FILE = "Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture.modulemap"; PRODUCT_MODULE_NAME = FDFullscreenPopGesture; PRODUCT_NAME = FDFullscreenPopGesture; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; D7DEE269AECDA48C238E51ABF0EBBFF4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ C65CDAF9EE52F14CC151AF6E556D6FE0 /* Build configuration list for PBXNativeTarget "FDFullscreenPopGesture" */ = { isa = XCConfigurationList; buildConfigurations = ( 803A23C9436E2B759F5881F0C44B0ECE /* Debug */, 5475041B13520804AB82F0B304AD3A49 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; DC8F4C97B07ED0D19369D41D6D324894 /* Build configuration list for PBXProject "FDFullscreenPopGesture" */ = { isa = XCConfigurationList; buildConfigurations = ( D7DEE269AECDA48C238E51ABF0EBBFF4 /* Debug */, 64A7D7F7C843687AA32404B3DFE7C981 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = D866A8E7E3B7C15B8CA393D72C310963 /* Project object */; } ================================================ FILE: JetChat/Pods/HandyJSON/LICENSE ================================================ Copyright 1999-2016 Alibaba Group Holding Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 1. reflection The MIT License (MIT) Copyright (c) 2016 Brad Hilton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 2. ObjectMapper The MIT License (MIT) Copyright (c) 2014 Hearst Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/HandyJSON/README.md ================================================ # HandyJSON HandyJSON is a framework written in Swift which to make converting model objects( **pure classes/structs** ) to and from JSON easy on iOS. Compared with others, the most significant feature of HandyJSON is that it does not require the objects inherit from NSObject(**not using KVC but reflection**), neither implements a 'mapping' function(**writing value to memory directly to achieve property assignment**). HandyJSON is totally depend on the memory layout rules infered from Swift runtime code. We are watching it and will follow every bit if it changes. [![Build Status](https://travis-ci.org/alibaba/HandyJSON.svg?branch=master)](https://travis-ci.org/alibaba/HandyJSON) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Cocoapods Version](https://img.shields.io/cocoapods/v/HandyJSON.svg?style=flat)](http://cocoadocs.org/docsets/HandyJSON) [![Cocoapods Platform](https://img.shields.io/cocoapods/p/HandyJSON.svg?style=flat)](http://cocoadocs.org/docsets/HandyJSON) [![Codecov branch](https://img.shields.io/codecov/c/github/alibaba/HandyJSON/master.svg?style=flat)](https://codecov.io/gh/alibaba/HandyJSON/branch/master) ## [中文文档](./README_cn.md) ## 交流群 群号: 581331250 ![交流群](qq_group.png) ## Sample Code ### Deserialization ```swift class BasicTypes: HandyJSON { var int: Int = 2 var doubleOptional: Double? var stringImplicitlyUnwrapped: String! required init() {} } let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}" if let object = BasicTypes.deserialize(from: jsonString) { print(object.int) print(object.doubleOptional!) print(object.stringImplicitlyUnwrapped) } ``` ### Serialization ```swift let object = BasicTypes() object.int = 1 object.doubleOptional = 1.1 object.stringImplicitlyUnwrapped = “hello" print(object.toJSON()!) // serialize to dictionary print(object.toJSONString()!) // serialize to JSON string print(object.toJSONString(prettyPrint: true)!) // serialize to pretty JSON string ``` # Content - [Features](#features) - [Requirements](#requirements) - [Installation](#installation) - [Cocoapods](#cocoapods) - [Carthage](#carthage) - [Manually](#manually) - [Deserialization](#deserialization) - [The Basics](#the-basics) - [Support Struct](#support-struct) - [Support Enum Property](#support-enum-property) - [Optional/ImplicitlyUnwrappedOptional/Collections/...](#optionalimplicitlyunwrappedoptionalcollections) - [Designated Path](#designated-path) - [Composition Object](#composition-object) - [Inheritance Object](#inheritance-object) - [JSON Array](#json-array) - [Mapping From Dictionary](#mapping-from-dictionary) - [Custom Mapping](#custom-mapping) - [Date/Data/URL/Decimal/Color](#datedataurldecimalcolor) - [Exclude Property](#exclude-property) - [Update Existing Model](#update-existing-model) - [Supported Property Type](#supported-property-type) - [Serialization](#serialization) - [The Basics](#the-basics) - [Mapping And Excluding](#mapping-and-excluding) - [FAQ](#faq) - [To Do](#to-do) # Features * Serialize/Deserialize Object/JSON to/From JSON/Object * Naturally use object property name for mapping, no need to specify a mapping relationship * Support almost all types in Swift, including enum * Support struct * Custom transformations * Type-Adaption, such as string json field maps to int property, int json field maps to string property An overview of types supported can be found at file: [BasicTypes.swift](./HandyJSONTest/BasicTypes.swift) # Requirements * iOS 8.0+/OSX 10.9+/watchOS 2.0+/tvOS 9.0+ * Swift 3.0+ / Swift 4.0+ / Swift 5.0+ # Installation **To use with Swift 5.0/5.1 ( Xcode 10.2+/11.0+ ), version == 5.0.1** **To use with Swift 4.2 ( Xcode 10 ), version == 4.2.0** **To use with Swift 4.0, version >= 4.1.1** **To use with Swift 3.x, version >= 1.8.0** For Legacy Swift2.x support, take a look at the [swift2 branch](https://github.com/alibaba/HandyJSON/tree/master_for_swift_2x). ## Cocoapods Add the following line to your `Podfile`: ``` pod 'HandyJSON', '~> 5.0.1' ``` Then, run the following command: ``` $ pod install ``` ## Carthage You can add a dependency on `HandyJSON` by adding the following line to your `Cartfile`: ``` github "alibaba/HandyJSON" ~> 5.0.1 ``` ## Manually You can integrate `HandyJSON` into your project manually by doing the following steps: * Open up `Terminal`, `cd` into your top-level project directory, and add `HandyJSON` as a submodule: ``` git init && git submodule add https://github.com/alibaba/HandyJSON.git ``` * Open the new `HandyJSON` folder, drag the `HandyJSON.xcodeproj` into the `Project Navigator` of your project. * Select your application project in the `Project Navigator`, open the `General` panel in the right window. * Click on the `+` button under the `Embedded Binaries` section. * You will see two different `HandyJSON.xcodeproj` folders each with four different versions of the HandyJSON.framework nested inside a Products folder. > It does not matter which Products folder you choose from, but it does matter which HandyJSON.framework you choose. * Select one of the four `HandyJSON.framework` which matches the platform your Application should run on. * Congratulations! # Deserialization ## The Basics To support deserialization from JSON, a class/struct need to conform to 'HandyJSON' protocol. It's truly protocol, not some class inherited from NSObject. To conform to 'HandyJSON', a class need to implement an empty initializer. ```swift class BasicTypes: HandyJSON { var int: Int = 2 var doubleOptional: Double? var stringImplicitlyUnwrapped: String! required init() {} } let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}" if let object = BasicTypes.deserialize(from: jsonString) { // … } ``` ## Support Struct For struct, since the compiler provide a default empty initializer, we use it for free. ```swift struct BasicTypes: HandyJSON { var int: Int = 2 var doubleOptional: Double? var stringImplicitlyUnwrapped: String! } let jsonString = "{\"doubleOptional\":1.1,\"stringImplicitlyUnwrapped\":\"hello\",\"int\":1}" if let object = BasicTypes.deserialize(from: jsonString) { // … } ``` But also notice that, if you have a designated initializer to override the default one in the struct, you should explicitly declare an empty one(no `required` modifier need). ## Support Enum Property To be convertable, An `enum` must conform to `HandyJSONEnum` protocol. Nothing special need to do now. ```swift enum AnimalType: String, HandyJSONEnum { case Cat = "cat" case Dog = "dog" case Bird = "bird" } struct Animal: HandyJSON { var name: String? var type: AnimalType? } let jsonString = "{\"type\":\"cat\",\"name\":\"Tom\"}" if let animal = Animal.deserialize(from: jsonString) { print(animal.type?.rawValue) } ``` ## Optional/ImplicitlyUnwrappedOptional/Collections/... 'HandyJSON' support classes/structs composed of `optional`, `implicitlyUnwrappedOptional`, `array`, `dictionary`, `objective-c base type`, `nested type` etc. properties. ```swift class BasicTypes: HandyJSON { var bool: Bool = true var intOptional: Int? var doubleImplicitlyUnwrapped: Double! var anyObjectOptional: Any? var arrayInt: Array = [] var arrayStringOptional: Array? var setInt: Set? var dictAnyObject: Dictionary = [:] var nsNumber = 2 var nsString: NSString? required init() {} } let object = BasicTypes() object.intOptional = 1 object.doubleImplicitlyUnwrapped = 1.1 object.anyObjectOptional = "StringValue" object.arrayInt = [1, 2] object.arrayStringOptional = ["a", "b"] object.setInt = [1, 2] object.dictAnyObject = ["key1": 1, "key2": "stringValue"] object.nsNumber = 2 object.nsString = "nsStringValue" let jsonString = object.toJSONString()! if let object = BasicTypes.deserialize(from: jsonString) { // ... } ``` ## Designated Path `HandyJSON` supports deserialization from designated path of JSON. ```swift class Cat: HandyJSON { var id: Int64! var name: String! required init() {} } let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}" if let cat = Cat.deserialize(from: jsonString, designatedPath: "data.cat") { print(cat.name) } ``` ## Composition Object Notice that all the properties of a class/struct need to deserialized should be type conformed to `HandyJSON`. ```swift class Component: HandyJSON { var aInt: Int? var aString: String? required init() {} } class Composition: HandyJSON { var aInt: Int? var comp1: Component? var comp2: Component? required init() {} } let jsonString = "{\"num\":12345,\"comp1\":{\"aInt\":1,\"aString\":\"aaaaa\"},\"comp2\":{\"aInt\":2,\"aString\":\"bbbbb\"}}" if let composition = Composition.deserialize(from: jsonString) { print(composition) } ``` ## Inheritance Object A subclass need deserialization, it's superclass need to conform to `HandyJSON`. ```swift class Animal: HandyJSON { var id: Int? var color: String? required init() {} } class Cat: Animal { var name: String? required init() {} } let jsonString = "{\"id\":12345,\"color\":\"black\",\"name\":\"cat\"}" if let cat = Cat.deserialize(from: jsonString) { print(cat) } ``` ## JSON Array If the first level of a JSON text is an array, we turn it to objects array. ```swift class Cat: HandyJSON { var name: String? var id: String? required init() {} } let jsonArrayString: String? = "[{\"name\":\"Bob\",\"id\":\"1\"}, {\"name\":\"Lily\",\"id\":\"2\"}, {\"name\":\"Lucy\",\"id\":\"3\"}]" if let cats = [Cat].deserialize(from: jsonArrayString) { cats.forEach({ (cat) in // ... }) } ``` ## Mapping From Dictionary `HandyJSON` support mapping swift dictionary to model. ```swift var dict = [String: Any]() dict["doubleOptional"] = 1.1 dict["stringImplicitlyUnwrapped"] = "hello" dict["int"] = 1 if let object = BasicTypes.deserialize(from: dict) { // ... } ``` ## Custom Mapping `HandyJSON` let you customize the key mapping to JSON fields, or parsing method of any property. All you need to do is implementing an optional `mapping` function, do things in it. We bring the transformer from [`ObjectMapper`](https://github.com/Hearst-DD/ObjectMapper). If you are familiar with it, it’s almost the same here. ```swift class Cat: HandyJSON { var id: Int64! var name: String! var parent: (String, String)? var friendName: String? required init() {} func mapping(mapper: HelpingMapper) { // specify 'cat_id' field in json map to 'id' property in object mapper <<< self.id <-- "cat_id" // specify 'parent' field in json parse as following to 'parent' property in object mapper <<< self.parent <-- TransformOf<(String, String), String>(fromJSON: { (rawString) -> (String, String)? in if let parentNames = rawString?.characters.split(separator: "/").map(String.init) { return (parentNames[0], parentNames[1]) } return nil }, toJSON: { (tuple) -> String? in if let _tuple = tuple { return "\(_tuple.0)/\(_tuple.1)" } return nil }) // specify 'friend.name' path field in json map to 'friendName' property mapper <<< self.friendName <-- "friend.name" } } let jsonString = "{\"cat_id\":12345,\"name\":\"Kitty\",\"parent\":\"Tom/Lily\",\"friend\":{\"id\":54321,\"name\":\"Lily\"}}" if let cat = Cat.deserialize(from: jsonString) { print(cat.id) print(cat.parent) print(cat.friendName) } ``` ## Date/Data/URL/Decimal/Color `HandyJSON` prepare some useful transformer for some none-basic type. ```swift class ExtendType: HandyJSON { var date: Date? var decimal: NSDecimalNumber? var url: URL? var data: Data? var color: UIColor? func mapping(mapper: HelpingMapper) { mapper <<< date <-- CustomDateFormatTransform(formatString: "yyyy-MM-dd") mapper <<< decimal <-- NSDecimalNumberTransform() mapper <<< url <-- URLTransform(shouldEncodeURLString: false) mapper <<< data <-- DataTransform() mapper <<< color <-- HexColorTransform() } public required init() {} } let object = ExtendType() object.date = Date() object.decimal = NSDecimalNumber(string: "1.23423414371298437124391243") object.url = URL(string: "https://www.aliyun.com") object.data = Data(base64Encoded: "aGVsbG8sIHdvcmxkIQ==") object.color = UIColor.blue print(object.toJSONString()!) // it prints: // {"date":"2017-09-11","decimal":"1.23423414371298437124391243","url":"https:\/\/www.aliyun.com","data":"aGVsbG8sIHdvcmxkIQ==","color":"0000FF"} let mappedObject = ExtendType.deserialize(from: object.toJSONString()!)! print(mappedObject.date) ... ``` ## Exclude Property If any non-basic property of a class/struct could not conform to `HandyJSON`/`HandyJSONEnum` or you just do not want to do the deserialization with it, you should exclude it in the mapping function. ```swift class NotHandyJSONType { var dummy: String? } class Cat: HandyJSON { var id: Int64! var name: String! var notHandyJSONTypeProperty: NotHandyJSONType? var basicTypeButNotWantedProperty: String? required init() {} func mapping(mapper: HelpingMapper) { mapper >>> self.notHandyJSONTypeProperty mapper >>> self.basicTypeButNotWantedProperty } } let jsonString = "{\"name\":\"cat\",\"id\":\"12345\"}" if let cat = Cat.deserialize(from: jsonString) { print(cat) } ``` ## Update Existing Model `HandyJSON` support updating an existing model with given json string or dictionary. ```swift class BasicTypes: HandyJSON { var int: Int = 2 var doubleOptional: Double? var stringImplicitlyUnwrapped: String! required init() {} } var object = BasicTypes() object.int = 1 object.doubleOptional = 1.1 let jsonString = "{\"doubleOptional\":2.2}" JSONDeserializer.update(object: &object, from: jsonString) print(object.int) print(object.doubleOptional) ``` ## Supported Property Type * `Int`/`Bool`/`Double`/`Float`/`String`/`NSNumber`/`NSString` * `RawRepresentable` enum * `NSArray/NSDictionary` * `Int8/Int16/Int32/Int64`/`UInt8/UInt16/UInt23/UInt64` * `Optional/ImplicitUnwrappedOptional` // T is one of the above types * `Array` // T is one of the above types * `Dictionary` // T is one of the above types * Nested of aboves # Serialization ## The Basics Now, a class/model which need to serialize to JSON should also conform to `HandyJSON` protocol. ```swift class BasicTypes: HandyJSON { var int: Int = 2 var doubleOptional: Double? var stringImplicitlyUnwrapped: String! required init() {} } let object = BasicTypes() object.int = 1 object.doubleOptional = 1.1 object.stringImplicitlyUnwrapped = “hello" print(object.toJSON()!) // serialize to dictionary print(object.toJSONString()!) // serialize to JSON string print(object.toJSONString(prettyPrint: true)!) // serialize to pretty JSON string ``` ## Mapping And Excluding It’s all like what we do on deserialization. A property which is excluded, it will not take part in neither deserialization nor serialization. And the mapper items define both the deserializing rules and serializing rules. Refer to the usage above. # FAQ ## Q: Why the mapping function is not working in the inheritance object? A: For some reason, you should define an empty mapping function in the super class(the root class if more than one layer), and override it in the subclass. It's the same with `didFinishMapping` function. ## Q: Why my didSet/willSet is not working? A: Since `HandyJSON` assign properties by writing value to memory directly, it doesn't trigger any observing function. You need to call the `didSet/willSet` logic explicitly after/before the deserialization. But since version `1.8.0`, `HandyJSON` handle dynamic properties by the `KVC` mechanism which will trigger the `KVO`. That means, if you do really need the `didSet/willSet`, you can define your model like follow: ```swift class BasicTypes: NSObject, HandyJSON { dynamic var int: Int = 0 { didSet { print("oldValue: ", oldValue) } willSet { print("newValue: ", newValue) } } public override required init() {} } ``` In this situation, `NSObject` and `dynamic` are both needed. And in versions since `1.8.0`, `HandyJSON` offer a `didFinishMapping` function to allow you to fill some observing logic. ```swift class BasicTypes: HandyJSON { var int: Int? required init() {} func didFinishMapping() { print("you can fill some observing logic here") } } ``` It may help. ## Q: How to support Enum property? It your enum conform to `RawRepresentable` protocol, please look into [Support Enum Property](#support-enum-property). Or use the `EnumTransform`: ```swift enum EnumType: String { case type1, type2 } class BasicTypes: HandyJSON { var type: EnumType? func mapping(mapper: HelpingMapper) { mapper <<< type <-- EnumTransform() } required init() {} } let object = BasicTypes() object.type = EnumType.type2 print(object.toJSONString()!) let mappedObject = BasicTypes.deserialize(from: object.toJSONString()!)! print(mappedObject.type) ``` Otherwise, you should implement your custom mapping function. ```swift enum EnumType { case type1, type2 } class BasicTypes: HandyJSON { var type: EnumType? func mapping(mapper: HelpingMapper) { mapper <<< type <-- TransformOf(fromJSON: { (rawString) -> EnumType? in if let _str = rawString { switch (_str) { case "type1": return EnumType.type1 case "type2": return EnumType.type2 default: return nil } } return nil }, toJSON: { (enumType) -> String? in if let _type = enumType { switch (_type) { case EnumType.type1: return "type1" case EnumType.type2: return "type2" } } return nil }) } required init() {} } ``` # Credit * [reflection](https://github.com/Zewo/Reflection): After the first version which used the swift mirror mechanism, HandyJSON had imported the reflection library and rewrote some code for class properties inspecting. * [ObjectMapper](https://github.com/tristanhimmelman/ObjectMapper): To make HandyJSON more compatible with the general style, the Mapper function support Transform which designed by ObjectMapper. And we import some testcases from ObjectMapper. # License HandyJSON is released under the Apache License, Version 2.0. See LICENSE for details. ================================================ FILE: JetChat/Pods/HandyJSON/Source/AnyExtensions.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // AnyExtension.swift // HandyJSON // // Created by zhouzhuo on 08/01/2017. // protocol AnyExtensions {} extension AnyExtensions { public static func isValueTypeOrSubtype(_ value: Any) -> Bool { return value is Self } public static func value(from storage: UnsafeRawPointer) -> Any { return storage.assumingMemoryBound(to: self).pointee } public static func write(_ value: Any, to storage: UnsafeMutableRawPointer) { guard let this = value as? Self else { return } storage.assumingMemoryBound(to: self).pointee = this } public static func takeValue(from anyValue: Any) -> Self? { return anyValue as? Self } } func extensions(of type: Any.Type) -> AnyExtensions.Type { struct Extensions : AnyExtensions {} var extensions: AnyExtensions.Type = Extensions.self withUnsafePointer(to: &extensions) { pointer in UnsafeMutableRawPointer(mutating: pointer).assumingMemoryBound(to: Any.Type.self).pointee = type } return extensions } func extensions(of value: Any) -> AnyExtensions { struct Extensions : AnyExtensions {} var extensions: AnyExtensions = Extensions() withUnsafePointer(to: &extensions) { pointer in UnsafeMutableRawPointer(mutating: pointer).assumingMemoryBound(to: Any.self).pointee = value } return extensions } /// Tests if `value` is `type` or a subclass of `type` func value(_ value: Any, is type: Any.Type) -> Bool { return extensions(of: type).isValueTypeOrSubtype(value) } /// Tests equality of any two existential types func == (lhs: Any.Type, rhs: Any.Type) -> Bool { return Metadata(type: lhs) == Metadata(type: rhs) } // MARK: AnyExtension + Storage extension AnyExtensions { mutating func storage() -> UnsafeRawPointer { if type(of: self) is AnyClass { let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque() return UnsafeRawPointer(opaquePointer) } else { return withUnsafePointer(to: &self) { pointer in return UnsafeRawPointer(pointer) } } } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/BuiltInBasicType.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Created by zhouzhuo on 7/7/16. // import Foundation protocol _BuiltInBasicType: _Transformable { static func _transform(from object: Any) -> Self? func _plainValue() -> Any? } // Suppport integer type protocol IntegerPropertyProtocol: FixedWidthInteger, _BuiltInBasicType { init?(_ text: String, radix: Int) init(_ number: NSNumber) } extension IntegerPropertyProtocol { static func _transform(from object: Any) -> Self? { switch object { case let str as String: return Self(str, radix: 10) case let num as NSNumber: return Self(num) default: return nil } } func _plainValue() -> Any? { return self } } extension Int: IntegerPropertyProtocol {} extension UInt: IntegerPropertyProtocol {} extension Int8: IntegerPropertyProtocol {} extension Int16: IntegerPropertyProtocol {} extension Int32: IntegerPropertyProtocol {} extension Int64: IntegerPropertyProtocol {} extension UInt8: IntegerPropertyProtocol {} extension UInt16: IntegerPropertyProtocol {} extension UInt32: IntegerPropertyProtocol {} extension UInt64: IntegerPropertyProtocol {} extension Bool: _BuiltInBasicType { static func _transform(from object: Any) -> Bool? { switch object { case let str as NSString: let lowerCase = str.lowercased if ["0", "false"].contains(lowerCase) { return false } if ["1", "true"].contains(lowerCase) { return true } return nil case let num as NSNumber: return num.boolValue default: return nil } } func _plainValue() -> Any? { return self } } // Support float type protocol FloatPropertyProtocol: _BuiltInBasicType, LosslessStringConvertible { init(_ number: NSNumber) } extension FloatPropertyProtocol { static func _transform(from object: Any) -> Self? { switch object { case let str as String: return Self(str) case let num as NSNumber: return Self(num) default: return nil } } func _plainValue() -> Any? { return self } } extension Float: FloatPropertyProtocol {} extension Double: FloatPropertyProtocol {} fileprivate let formatter: NumberFormatter = { let formatter = NumberFormatter() formatter.usesGroupingSeparator = false formatter.numberStyle = .decimal formatter.maximumFractionDigits = 16 return formatter }() extension String: _BuiltInBasicType { static func _transform(from object: Any) -> String? { switch object { case let str as String: return str case let num as NSNumber: // Boolean Type Inside if NSStringFromClass(type(of: num)) == "__NSCFBoolean" { if num.boolValue { return "true" } else { return "false" } } return formatter.string(from: num) case _ as NSNull: return nil default: return "\(object)" } } func _plainValue() -> Any? { return self } } // MARK: Optional Support extension Optional: _BuiltInBasicType { static func _transform(from object: Any) -> Optional? { if let value = (Wrapped.self as? _Transformable.Type)?.transform(from: object) as? Wrapped { return Optional(value) } else if let value = object as? Wrapped { return Optional(value) } return nil } func _getWrappedValue() -> Any? { return self.map( { (wrapped) -> Any in return wrapped as Any }) } func _plainValue() -> Any? { if let value = _getWrappedValue() { if let transformable = value as? _Transformable { return transformable.plainValue() } else { return value } } return nil } } // MARK: Collection Support : Array & Set extension Collection { static func _collectionTransform(from object: Any) -> [Iterator.Element]? { guard let arr = object as? [Any] else { InternalLogger.logDebug("Expect object to be an array but it's not") return nil } typealias Element = Iterator.Element var result: [Element] = [Element]() arr.forEach { (each) in if let element = (Element.self as? _Transformable.Type)?.transform(from: each) as? Element { result.append(element) } else if let element = each as? Element { result.append(element) } } return result } func _collectionPlainValue() -> Any? { typealias Element = Iterator.Element var result: [Any] = [Any]() self.forEach { (each) in if let transformable = each as? _Transformable, let transValue = transformable.plainValue() { result.append(transValue) } else { InternalLogger.logError("value: \(each) isn't transformable type!") } } return result } } extension Array: _BuiltInBasicType { static func _transform(from object: Any) -> [Element]? { return self._collectionTransform(from: object) } func _plainValue() -> Any? { return self._collectionPlainValue() } } extension Set: _BuiltInBasicType { static func _transform(from object: Any) -> Set? { if let arr = self._collectionTransform(from: object) { return Set(arr) } return nil } func _plainValue() -> Any? { return self._collectionPlainValue() } } // MARK: Dictionary Support extension Dictionary: _BuiltInBasicType { static func _transform(from object: Any) -> [Key: Value]? { guard let dict = object as? [String: Any] else { InternalLogger.logDebug("Expect object to be an NSDictionary but it's not") return nil } var result = [Key: Value]() for (key, value) in dict { if let sKey = key as? Key { if let nValue = (Value.self as? _Transformable.Type)?.transform(from: value) as? Value { result[sKey] = nValue } else if let nValue = value as? Value { result[sKey] = nValue } } } return result } func _plainValue() -> Any? { var result = [String: Any]() for (key, value) in self { if let key = key as? String { if let transformable = value as? _Transformable { if let transValue = transformable.plainValue() { result[key] = transValue } } } } return result } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/BuiltInBridgeType.swift ================================================ // // BuiltInBridgeType.swift // HandyJSON // // Created by zhouzhuo on 15/07/2017. // Copyright © 2017 aliyun. All rights reserved. // import Foundation protocol _BuiltInBridgeType: _Transformable { static func _transform(from object: Any) -> _BuiltInBridgeType? func _plainValue() -> Any? } extension NSString: _BuiltInBridgeType { static func _transform(from object: Any) -> _BuiltInBridgeType? { if let str = String.transform(from: object) { return NSString(string: str) } return nil } func _plainValue() -> Any? { return self } } extension NSNumber: _BuiltInBridgeType { static func _transform(from object: Any) -> _BuiltInBridgeType? { switch object { case let num as NSNumber: return num case let str as NSString: let lowercase = str.lowercased if lowercase == "true" { return NSNumber(booleanLiteral: true) } else if lowercase == "false" { return NSNumber(booleanLiteral: false) } else { // normal number let formatter = NumberFormatter() formatter.numberStyle = .decimal return formatter.number(from: str as String) } default: return nil } } func _plainValue() -> Any? { return self } } extension NSArray: _BuiltInBridgeType { static func _transform(from object: Any) -> _BuiltInBridgeType? { return object as? NSArray } func _plainValue() -> Any? { return (self as? Array)?.plainValue() } } extension NSDictionary: _BuiltInBridgeType { static func _transform(from object: Any) -> _BuiltInBridgeType? { return object as? NSDictionary } func _plainValue() -> Any? { return (self as? Dictionary)?.plainValue() } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/CBridge.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // CBridge.swift // HandyJSON // // Created by chantu on 2018/7/15. // Copyright © 2018 aliyun. All rights reserved. // import Foundation @_silgen_name("swift_getTypeByMangledNameInContext") public func _getTypeByMangledNameInContext( _ name: UnsafePointer, _ nameLength: Int, genericContext: UnsafeRawPointer?, genericArguments: UnsafeRawPointer?) -> Any.Type? @_silgen_name("swift_getTypeContextDescriptor") public func _swift_getTypeContextDescriptor(_ metadata: UnsafeRawPointer?) -> UnsafeRawPointer? ================================================ FILE: JetChat/Pods/HandyJSON/Source/Configuration.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Configuration.swift // HandyJSON // // Created by zhouzhuo on 08/01/2017. // public struct DeserializeOptions: OptionSet { public let rawValue: Int public static let caseInsensitive = DeserializeOptions(rawValue: 1 << 0) public static let defaultOptions: DeserializeOptions = [] public init(rawValue: Int) { self.rawValue = rawValue } } public enum DebugMode: Int { case verbose = 0 case debug = 1 case error = 2 case none = 3 } public struct HandyJSONConfiguration { private static var _mode = DebugMode.error public static var debugMode: DebugMode { get { return _mode } set { _mode = newValue } } public static var deserializeOptions: DeserializeOptions = .defaultOptions } ================================================ FILE: JetChat/Pods/HandyJSON/Source/ContextDescriptorType.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Created by zhouzhuo on 07/01/2017. // protocol ContextDescriptorType : MetadataType { var contextDescriptorOffsetLocation: Int { get } } extension ContextDescriptorType { var contextDescriptor: ContextDescriptorProtocol? { let pointer = UnsafePointer(self.pointer) let base = pointer.advanced(by: contextDescriptorOffsetLocation) if base.pointee == 0 { // swift class created dynamically in objc-runtime didn't have valid contextDescriptor return nil } if self.kind == .class { return ContextDescriptor<_ClassContextDescriptor>(pointer: relativePointer(base: base, offset: base.pointee - Int(bitPattern: base))) } else { return ContextDescriptor<_StructContextDescriptor>(pointer: relativePointer(base: base, offset: base.pointee - Int(bitPattern: base))) } } var contextDescriptorPointer: UnsafeRawPointer? { let pointer = UnsafePointer(self.pointer) let base = pointer.advanced(by: contextDescriptorOffsetLocation) if base.pointee == 0 { return nil } return UnsafeRawPointer(bitPattern: base.pointee) } // var genericArgumentVector: UnsafeRawPointer? { // let pointer = UnsafePointer(self.pointer) // let base = pointer.advanced(by: 19) // if base.pointee == 0 { // return nil // } // return UnsafeRawPointer(base) // } var mangledName: String { let pointer = UnsafePointer(self.pointer) let base = pointer.advanced(by: contextDescriptorOffsetLocation) let mangledNameAddress = base.pointee + 2 * 4 // 2 properties in front if let offset = contextDescriptor?.mangledName, let cString = UnsafePointer(bitPattern: mangledNameAddress + offset) { return String(cString: cString) } return "" } var numberOfFields: Int { return contextDescriptor?.numberOfFields ?? 0 } var fieldOffsets: [Int]? { guard let contextDescriptor = self.contextDescriptor else { return nil } let vectorOffset = contextDescriptor.fieldOffsetVector guard vectorOffset != 0 else { return nil } if self.kind == .class { return (0..(pointer)[vectorOffset + $0] } } else { return (0..(pointer)[vectorOffset * (is64BitPlatform ? 2 : 1) + $0]) } } } var reflectionFieldDescriptor: FieldDescriptor? { guard let contextDescriptor = self.contextDescriptor else { return nil } let pointer = UnsafePointer(self.pointer) let base = pointer.advanced(by: contextDescriptorOffsetLocation) let offset = contextDescriptor.reflectionFieldDescriptor let address = base.pointee + 4 * 4 // (4 properties in front) * (sizeof Int32) guard let fieldDescriptorPtr = UnsafePointer<_FieldDescriptor>(bitPattern: address + offset) else { return nil } return FieldDescriptor(pointer: fieldDescriptorPtr) } } protocol ContextDescriptorProtocol { var mangledName: Int { get } var numberOfFields: Int { get } var fieldOffsetVector: Int { get } var reflectionFieldDescriptor: Int { get } } struct ContextDescriptor: ContextDescriptorProtocol, PointerType { var pointer: UnsafePointer var mangledName: Int { return Int(pointer.pointee.mangledNameOffset) } var numberOfFields: Int { return Int(pointer.pointee.numberOfFields) } var fieldOffsetVector: Int { return Int(pointer.pointee.fieldOffsetVector) } var fieldTypesAccessor: Int { return Int(pointer.pointee.fieldTypesAccessor) } var reflectionFieldDescriptor: Int { return Int(pointer.pointee.reflectionFieldDescriptor) } } protocol _ContextDescriptorProtocol { var mangledNameOffset: Int32 { get } var numberOfFields: Int32 { get } var fieldOffsetVector: Int32 { get } var fieldTypesAccessor: Int32 { get } var reflectionFieldDescriptor: Int32 { get } } struct _StructContextDescriptor: _ContextDescriptorProtocol { var flags: Int32 var parent: Int32 var mangledNameOffset: Int32 var fieldTypesAccessor: Int32 var reflectionFieldDescriptor: Int32 var numberOfFields: Int32 var fieldOffsetVector: Int32 } struct _ClassContextDescriptor: _ContextDescriptorProtocol { var flags: Int32 var parent: Int32 var mangledNameOffset: Int32 var fieldTypesAccessor: Int32 var reflectionFieldDescriptor: Int32 var superClsRef: Int32 var metadataNegativeSizeInWords: Int32 var metadataPositiveSizeInWords: Int32 var numImmediateMembers: Int32 var numberOfFields: Int32 var fieldOffsetVector: Int32 } ================================================ FILE: JetChat/Pods/HandyJSON/Source/CustomDateFormatTransform.swift ================================================ // // CustomDateFormatTransform.swift // ObjectMapper // // Created by Dan McCracken on 3/8/15. // // The MIT License (MIT) // // Copyright (c) 2014-2016 Hearst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation open class CustomDateFormatTransform: DateFormatterTransform { public init(formatString: String) { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = formatString super.init(dateFormatter: formatter) } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/DataTransform.swift ================================================ // // DataTransform.swift // ObjectMapper // // Created by Yagrushkin, Evgeny on 8/30/16. // // The MIT License (MIT) // // Copyright (c) 2014-2016 Hearst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation open class DataTransform: TransformType { public typealias Object = Data public typealias JSON = String public init() {} open func transformFromJSON(_ value: Any?) -> Data? { guard let string = value as? String else{ return nil } return Data(base64Encoded: string) } open func transformToJSON(_ value: Data?) -> String? { guard let data = value else{ return nil } return data.base64EncodedString() } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/DateFormatterTransform.swift ================================================ // // DateFormatterTransform.swift // ObjectMapper // // Created by Tristan Himmelman on 2015-03-09. // // The MIT License (MIT) // // Copyright (c) 2014-2016 Hearst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation open class DateFormatterTransform: TransformType { public typealias Object = Date public typealias JSON = String public let dateFormatter: DateFormatter public init(dateFormatter: DateFormatter) { self.dateFormatter = dateFormatter } open func transformFromJSON(_ value: Any?) -> Date? { if let dateString = value as? String { return dateFormatter.date(from: dateString) } return nil } open func transformToJSON(_ value: Date?) -> String? { if let date = value { return dateFormatter.string(from: date) } return nil } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/DateTransform.swift ================================================ // // DateTransform.swift // ObjectMapper // // Created by Tristan Himmelman on 2014-10-13. // // The MIT License (MIT) // // Copyright (c) 2014-2016 Hearst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation open class DateTransform: TransformType { public typealias Object = Date public typealias JSON = Double public init() {} open func transformFromJSON(_ value: Any?) -> Date? { if let timeInt = value as? Double { return Date(timeIntervalSince1970: TimeInterval(timeInt)) } if let timeStr = value as? String { return Date(timeIntervalSince1970: TimeInterval(atof(timeStr))) } return nil } open func transformToJSON(_ value: Date?) -> Double? { if let date = value { return Double(date.timeIntervalSince1970) } return nil } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/Deserializer.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Created by zhouzhuo on 7/7/16. // import Foundation public extension HandyJSON { /// Finds the internal dictionary in `dict` as the `designatedPath` specified, and converts it to a Model /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer static func deserialize(from dict: NSDictionary?, designatedPath: String? = nil) -> Self? { return deserialize(from: dict as? [String: Any], designatedPath: designatedPath) } /// Finds the internal dictionary in `dict` as the `designatedPath` specified, and converts it to a Model /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer static func deserialize(from dict: [String: Any]?, designatedPath: String? = nil) -> Self? { return JSONDeserializer.deserializeFrom(dict: dict, designatedPath: designatedPath) } /// Finds the internal JSON field in `json` as the `designatedPath` specified, and converts it to a Model /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer static func deserialize(from json: String?, designatedPath: String? = nil) -> Self? { return JSONDeserializer.deserializeFrom(json: json, designatedPath: designatedPath) } } public extension Array where Element: HandyJSON { /// if the JSON field finded by `designatedPath` in `json` is representing a array, such as `[{...}, {...}, {...}]`, /// this method converts it to a Models array static func deserialize(from json: String?, designatedPath: String? = nil) -> [Element?]? { return JSONDeserializer.deserializeModelArrayFrom(json: json, designatedPath: designatedPath) } /// deserialize model array from NSArray static func deserialize(from array: NSArray?) -> [Element?]? { return JSONDeserializer.deserializeModelArrayFrom(array: array) } /// deserialize model array from array static func deserialize(from array: [Any]?) -> [Element?]? { return JSONDeserializer.deserializeModelArrayFrom(array: array) } } public class JSONDeserializer { /// Finds the internal dictionary in `dict` as the `designatedPath` specified, and map it to a Model /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer, or nil public static func deserializeFrom(dict: NSDictionary?, designatedPath: String? = nil) -> T? { return deserializeFrom(dict: dict as? [String: Any], designatedPath: designatedPath) } /// Finds the internal dictionary in `dict` as the `designatedPath` specified, and map it to a Model /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer, or nil public static func deserializeFrom(dict: [String: Any]?, designatedPath: String? = nil) -> T? { var targetDict = dict if let path = designatedPath { targetDict = getInnerObject(inside: targetDict, by: path) as? [String: Any] } if let _dict = targetDict { return T._transform(dict: _dict) as? T } return nil } /// Finds the internal JSON field in `json` as the `designatedPath` specified, and converts it to Model /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer, or nil public static func deserializeFrom(json: String?, designatedPath: String? = nil) -> T? { guard let _json = json else { return nil } do { let jsonObject = try JSONSerialization.jsonObject(with: _json.data(using: String.Encoding.utf8)!, options: .allowFragments) if let jsonDict = jsonObject as? NSDictionary { return self.deserializeFrom(dict: jsonDict, designatedPath: designatedPath) } } catch let error { InternalLogger.logError(error) } return nil } /// Finds the internal dictionary in `dict` as the `designatedPath` specified, and use it to reassign an exist model /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer, or nil public static func update(object: inout T, from dict: [String: Any]?, designatedPath: String? = nil) { var targetDict = dict if let path = designatedPath { targetDict = getInnerObject(inside: targetDict, by: path) as? [String: Any] } if let _dict = targetDict { T._transform(dict: _dict, to: &object) } } /// Finds the internal JSON field in `json` as the `designatedPath` specified, and use it to reassign an exist model /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer, or nil public static func update(object: inout T, from json: String?, designatedPath: String? = nil) { guard let _json = json else { return } do { let jsonObject = try JSONSerialization.jsonObject(with: _json.data(using: String.Encoding.utf8)!, options: .allowFragments) if let jsonDict = jsonObject as? [String: Any] { update(object: &object, from: jsonDict, designatedPath: designatedPath) } } catch let error { InternalLogger.logError(error) } } /// if the JSON field found by `designatedPath` in `json` is representing a array, such as `[{...}, {...}, {...}]`, /// this method converts it to a Models array public static func deserializeModelArrayFrom(json: String?, designatedPath: String? = nil) -> [T?]? { guard let _json = json else { return nil } do { let jsonObject = try JSONSerialization.jsonObject(with: _json.data(using: String.Encoding.utf8)!, options: .allowFragments) if let jsonArray = getInnerObject(inside: jsonObject, by: designatedPath) as? [Any] { return jsonArray.map({ (item) -> T? in return self.deserializeFrom(dict: item as? [String: Any]) }) } } catch let error { InternalLogger.logError(error) } return nil } /// mapping raw array to Models array public static func deserializeModelArrayFrom(array: NSArray?) -> [T?]? { return deserializeModelArrayFrom(array: array as? [Any]) } /// mapping raw array to Models array public static func deserializeModelArrayFrom(array: [Any]?) -> [T?]? { guard let _arr = array else { return nil } return _arr.map({ (item) -> T? in return self.deserializeFrom(dict: item as? NSDictionary) }) } } fileprivate func getInnerObject(inside object: Any?, by designatedPath: String?) -> Any? { var result: Any? = object var abort = false if let paths = designatedPath?.components(separatedBy: "."), paths.count > 0 { var next = object as? [String: Any] paths.forEach({ (seg) in if seg.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) == "" || abort { return } if let _next = next?[seg] { result = _next next = _next as? [String: Any] } else { abort = true } }) } return abort ? nil : result } ================================================ FILE: JetChat/Pods/HandyJSON/Source/EnumTransform.swift ================================================ // // EnumTransform.swift // ObjectMapper // // Created by Kaan Dedeoglu on 3/20/15. // // The MIT License (MIT) // // Copyright (c) 2014-2016 Hearst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation open class EnumTransform: TransformType { public typealias Object = T public typealias JSON = T.RawValue public init() {} open func transformFromJSON(_ value: Any?) -> T? { if let raw = value as? T.RawValue { return T(rawValue: raw) } return nil } open func transformToJSON(_ value: T?) -> T.RawValue? { if let obj = value { return obj.rawValue } return nil } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/EnumType.swift ================================================ // // EnumType.swift // HandyJSON // // Created by zhouzhuo on 16/07/2017. // Copyright © 2017 aliyun. All rights reserved. // import Foundation public protocol _RawEnumProtocol: _Transformable { static func _transform(from object: Any) -> Self? func _plainValue() -> Any? } extension RawRepresentable where Self: _RawEnumProtocol { public static func _transform(from object: Any) -> Self? { if let transformableType = RawValue.self as? _Transformable.Type { if let typedValue = transformableType.transform(from: object) { return Self(rawValue: typedValue as! RawValue) } } return nil } public func _plainValue() -> Any? { return self.rawValue } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/Export.swift ================================================ // // Export.swift // HandyJSON // // Created by zhouzhuo on 16/07/2017. // Copyright © 2017 aliyun. All rights reserved. // import Foundation public protocol HandyJSONCustomTransformable: _ExtendCustomBasicType {} public protocol HandyJSON: _ExtendCustomModelType {} public protocol HandyJSONEnum: _RawEnumProtocol {} ================================================ FILE: JetChat/Pods/HandyJSON/Source/ExtendCustomBasicType.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // ExtendCustomBasicType.swift // HandyJSON // // Created by zhouzhuo on 05/09/2017. // public protocol _ExtendCustomBasicType: _Transformable { static func _transform(from object: Any) -> Self? func _plainValue() -> Any? } ================================================ FILE: JetChat/Pods/HandyJSON/Source/ExtendCustomModelType.swift ================================================ // // ExtendCustomType.swift // HandyJSON // // Created by zhouzhuo on 16/07/2017. // Copyright © 2017 aliyun. All rights reserved. // import Foundation public protocol _ExtendCustomModelType: _Transformable { init() mutating func willStartMapping() mutating func mapping(mapper: HelpingMapper) mutating func didFinishMapping() } extension _ExtendCustomModelType { public mutating func willStartMapping() {} public mutating func mapping(mapper: HelpingMapper) {} public mutating func didFinishMapping() {} } fileprivate func convertKeyIfNeeded(dict: [String: Any]) -> [String: Any] { if HandyJSONConfiguration.deserializeOptions.contains(.caseInsensitive) { var newDict = [String: Any]() dict.forEach({ (kvPair) in let (key, value) = kvPair newDict[key.lowercased()] = value }) return newDict } return dict } fileprivate func getRawValueFrom(dict: [String: Any], property: PropertyInfo, mapper: HelpingMapper) -> Any? { let address = Int(bitPattern: property.address) if let mappingHandler = mapper.getMappingHandler(key: address) { if let mappingPaths = mappingHandler.mappingPaths, mappingPaths.count > 0 { for mappingPath in mappingPaths { if let _value = dict.findValueBy(path: mappingPath) { return _value } } return nil } } if HandyJSONConfiguration.deserializeOptions.contains(.caseInsensitive) { return dict[property.key.lowercased()] } return dict[property.key] } fileprivate func convertValue(rawValue: Any, property: PropertyInfo, mapper: HelpingMapper) -> Any? { if rawValue is NSNull { return nil } if let mappingHandler = mapper.getMappingHandler(key: Int(bitPattern: property.address)), let transformer = mappingHandler.assignmentClosure { return transformer(rawValue) } if let transformableType = property.type as? _Transformable.Type { return transformableType.transform(from: rawValue) } else { return extensions(of: property.type).takeValue(from: rawValue) } } fileprivate func assignProperty(convertedValue: Any, instance: _ExtendCustomModelType, property: PropertyInfo) { if property.bridged { (instance as! NSObject).setValue(convertedValue, forKey: property.key) } else { extensions(of: property.type).write(convertedValue, to: property.address) } } fileprivate func readAllChildrenFrom(mirror: Mirror) -> [(String, Any)] { var children = [(label: String?, value: Any)]() let mirrorChildrenCollection = AnyRandomAccessCollection(mirror.children)! children += mirrorChildrenCollection var currentMirror = mirror while let superclassChildren = currentMirror.superclassMirror?.children { let randomCollection = AnyRandomAccessCollection(superclassChildren)! children += randomCollection currentMirror = currentMirror.superclassMirror! } var result = [(String, Any)]() children.forEach { (child) in if let _label = child.label { result.append((_label, child.value)) } } return result } fileprivate func merge(children: [(String, Any)], propertyInfos: [PropertyInfo]) -> [String: (Any, PropertyInfo?)] { var infoDict = [String: PropertyInfo]() propertyInfos.forEach { (info) in infoDict[info.key] = info } var result = [String: (Any, PropertyInfo?)]() children.forEach { (child) in result[child.0] = (child.1, infoDict[child.0]) } return result } // this's a workaround before https://bugs.swift.org/browse/SR-5223 fixed extension NSObject { static func createInstance() -> NSObject { return self.init() } } extension _ExtendCustomModelType { static func _transform(from object: Any) -> Self? { if let dict = object as? [String: Any] { // nested object, transform recursively return self._transform(dict: dict) as? Self } return nil } static func _transform(dict: [String: Any]) -> _ExtendCustomModelType? { var instance: Self if let _nsType = Self.self as? NSObject.Type { instance = _nsType.createInstance() as! Self } else { instance = Self.init() } instance.willStartMapping() _transform(dict: dict, to: &instance) instance.didFinishMapping() return instance } static func _transform(dict: [String: Any], to instance: inout Self) { guard let properties = getProperties(forType: Self.self) else { InternalLogger.logDebug("Failed when try to get properties from type: \(type(of: Self.self))") return } // do user-specified mapping first let mapper = HelpingMapper() instance.mapping(mapper: mapper) // get head addr let rawPointer = instance.headPointer() InternalLogger.logVerbose("instance start at: ", Int(bitPattern: rawPointer)) // process dictionary let _dict = convertKeyIfNeeded(dict: dict) let instanceIsNsObject = instance.isNSObjectType() let bridgedPropertyList = instance.getBridgedPropertyList() for property in properties { let isBridgedProperty = instanceIsNsObject && bridgedPropertyList.contains(property.key) let propAddr = rawPointer.advanced(by: property.offset) InternalLogger.logVerbose(property.key, "address at: ", Int(bitPattern: propAddr)) if mapper.propertyExcluded(key: Int(bitPattern: propAddr)) { InternalLogger.logDebug("Exclude property: \(property.key)") continue } let propertyDetail = PropertyInfo(key: property.key, type: property.type, address: propAddr, bridged: isBridgedProperty) InternalLogger.logVerbose("field: ", property.key, " offset: ", property.offset, " isBridgeProperty: ", isBridgedProperty) if let rawValue = getRawValueFrom(dict: _dict, property: propertyDetail, mapper: mapper) { if let convertedValue = convertValue(rawValue: rawValue, property: propertyDetail, mapper: mapper) { assignProperty(convertedValue: convertedValue, instance: instance, property: propertyDetail) continue } } InternalLogger.logDebug("Property: \(property.key) hasn't been written in") } } } extension _ExtendCustomModelType { func _plainValue() -> Any? { return Self._serializeAny(object: self) } static func _serializeAny(object: _Transformable) -> Any? { let mirror = Mirror(reflecting: object) guard let displayStyle = mirror.displayStyle else { return object.plainValue() } // after filtered by protocols above, now we expect the type is pure struct/class switch displayStyle { case .class, .struct: let mapper = HelpingMapper() // do user-specified mapping first if !(object is _ExtendCustomModelType) { InternalLogger.logDebug("This model of type: \(type(of: object)) is not mappable but is class/struct type") return object } let children = readAllChildrenFrom(mirror: mirror) guard let properties = getProperties(forType: type(of: object)) else { InternalLogger.logError("Can not get properties info for type: \(type(of: object))") return nil } var mutableObject = object as! _ExtendCustomModelType let instanceIsNsObject = mutableObject.isNSObjectType() let head = mutableObject.headPointer() let bridgedProperty = mutableObject.getBridgedPropertyList() let propertyInfos = properties.map({ (desc) -> PropertyInfo in return PropertyInfo(key: desc.key, type: desc.type, address: head.advanced(by: desc.offset), bridged: instanceIsNsObject && bridgedProperty.contains(desc.key)) }) mutableObject.mapping(mapper: mapper) let requiredInfo = merge(children: children, propertyInfos: propertyInfos) return _serializeModelObject(instance: mutableObject, properties: requiredInfo, mapper: mapper) as Any default: return object.plainValue() } } static func _serializeModelObject(instance: _ExtendCustomModelType, properties: [String: (Any, PropertyInfo?)], mapper: HelpingMapper) -> [String: Any] { var dict = [String: Any]() for (key, property) in properties { var realKey = key var realValue = property.0 if let info = property.1 { if info.bridged, let _value = (instance as! NSObject).value(forKey: key) { realValue = _value } if mapper.propertyExcluded(key: Int(bitPattern: info.address)) { continue } if let mappingHandler = mapper.getMappingHandler(key: Int(bitPattern: info.address)) { // if specific key is set, replace the label if let mappingPaths = mappingHandler.mappingPaths, mappingPaths.count > 0 { // take the first path, last segment if more than one realKey = mappingPaths[0].segments.last! } if let transformer = mappingHandler.takeValueClosure { if let _transformedValue = transformer(realValue) { dict[realKey] = _transformedValue } continue } } } if let typedValue = realValue as? _Transformable { if let result = self._serializeAny(object: typedValue) { dict[realKey] = result continue } } InternalLogger.logDebug("The value for key: \(key) is not transformable type") } return dict } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/FieldDescriptor.swift ================================================ // // FieldDescriptor.swift // HandyJSON // // Created by chantu on 2019/1/31. // Copyright © 2019 aliyun. All rights reserved. // import Foundation enum FieldDescriptorKind : UInt16 { // Swift nominal types. case Struct = 0 case Class case Enum // Fixed-size multi-payload enums have a special descriptor format that // encodes spare bits. // // FIXME: Actually implement this. For now, a descriptor with this kind // just means we also have a builtin descriptor from which we get the // size and alignment. case MultiPayloadEnum // A Swift opaque protocol. There are no fields, just a record for the // type itself. case `Protocol` // A Swift class-bound protocol. case ClassProtocol // An Objective-C protocol, which may be imported or defined in Swift. case ObjCProtocol // An Objective-C class, which may be imported or defined in Swift. // In the former case, field type metadata is not emitted, and // must be obtained from the Objective-C runtime. case ObjCClass } struct FieldDescriptor: PointerType { var pointer: UnsafePointer<_FieldDescriptor> var fieldRecordSize: Int { return Int(pointer.pointee.fieldRecordSize) } var numFields: Int { return Int(pointer.pointee.numFields) } var fieldRecords: [FieldRecord] { return (0.. FieldRecord in return FieldRecord(pointer: UnsafePointer<_FieldRecord>(pointer + 1) + i) }) } } struct _FieldDescriptor { var mangledTypeNameOffset: Int32 var superClassOffset: Int32 var fieldDescriptorKind: FieldDescriptorKind var fieldRecordSize: Int16 var numFields: Int32 } struct FieldRecord: PointerType { var pointer: UnsafePointer<_FieldRecord> var fieldRecordFlags: Int { return Int(pointer.pointee.fieldRecordFlags) } var mangledTypeName: UnsafePointer? { let address = Int(bitPattern: pointer) + 1 * 4 let offset = Int(pointer.pointee.mangledTypeNameOffset) let cString = UnsafePointer(bitPattern: address + offset) return cString } var fieldName: String { let address = Int(bitPattern: pointer) + 2 * 4 let offset = Int(pointer.pointee.fieldNameOffset) if let cString = UnsafePointer(bitPattern: address + offset) { return String(cString: cString) } return "" } } struct _FieldRecord { var fieldRecordFlags: Int32 var mangledTypeNameOffset: Int32 var fieldNameOffset: Int32 } ================================================ FILE: JetChat/Pods/HandyJSON/Source/HandyJSON.h ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Created by zhouzhuo on 7/11/16. // @import Foundation; //! Project version number for HandyJSON. FOUNDATION_EXPORT double HandyJSONVersionNumber; //! Project version string for HandyJSON. FOUNDATION_EXPORT const unsigned char HandyJSONVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import ================================================ FILE: JetChat/Pods/HandyJSON/Source/HelpingMapper.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Created by zhouzhuo on 9/20/16. // import Foundation public typealias CustomMappingKeyValueTuple = (Int, MappingPropertyHandler) struct MappingPath { var segments: [String] static func buildFrom(rawPath: String) -> MappingPath { let regex = try! NSRegularExpression(pattern: "(? Any? { var currentDict: [String: Any]? = self var lastValue: Any? path.segments.forEach { (segment) in lastValue = currentDict?[segment] currentDict = currentDict?[segment] as? [String: Any] } return lastValue } } public class MappingPropertyHandler { var mappingPaths: [MappingPath]? var assignmentClosure: ((Any?) -> (Any?))? var takeValueClosure: ((Any?) -> (Any?))? public init(rawPaths: [String]?, assignmentClosure: ((Any?) -> (Any?))?, takeValueClosure: ((Any?) -> (Any?))?) { let mappingPaths = rawPaths?.map({ (rawPath) -> MappingPath in if HandyJSONConfiguration.deserializeOptions.contains(.caseInsensitive) { return MappingPath.buildFrom(rawPath: rawPath.lowercased()) } return MappingPath.buildFrom(rawPath: rawPath) }).filter({ (mappingPath) -> Bool in return mappingPath.segments.count > 0 }) if let count = mappingPaths?.count, count > 0 { self.mappingPaths = mappingPaths } self.assignmentClosure = assignmentClosure self.takeValueClosure = takeValueClosure } } public class HelpingMapper { private var mappingHandlers = [Int: MappingPropertyHandler]() private var excludeProperties = [Int]() internal func getMappingHandler(key: Int) -> MappingPropertyHandler? { return self.mappingHandlers[key] } internal func propertyExcluded(key: Int) -> Bool { return self.excludeProperties.contains(key) } public func specify(property: inout T, name: String) { self.specify(property: &property, name: name, converter: nil) } public func specify(property: inout T, converter: @escaping (String) -> T) { self.specify(property: &property, name: nil, converter: converter) } public func specify(property: inout T, name: String?, converter: ((String) -> T)?) { let pointer = withUnsafePointer(to: &property, { return $0 }) let key = Int(bitPattern: pointer) let names = (name == nil ? nil : [name!]) if let _converter = converter { let assignmentClosure = { (jsonValue: Any?) -> Any? in if let _value = jsonValue{ if let object = _value as? NSObject { if let str = String.transform(from: object){ return _converter(str) } } } return nil } self.mappingHandlers[key] = MappingPropertyHandler(rawPaths: names, assignmentClosure: assignmentClosure, takeValueClosure: nil) } else { self.mappingHandlers[key] = MappingPropertyHandler(rawPaths: names, assignmentClosure: nil, takeValueClosure: nil) } } public func exclude(property: inout T) { self._exclude(property: &property) } fileprivate func addCustomMapping(key: Int, mappingInfo: MappingPropertyHandler) { self.mappingHandlers[key] = mappingInfo } fileprivate func _exclude(property: inout T) { let pointer = withUnsafePointer(to: &property, { return $0 }) self.excludeProperties.append(Int(bitPattern: pointer)) } } infix operator <-- : LogicalConjunctionPrecedence public func <-- (property: inout T, name: String) -> CustomMappingKeyValueTuple { return property <-- [name] } public func <-- (property: inout T, names: [String]) -> CustomMappingKeyValueTuple { let pointer = withUnsafePointer(to: &property, { return $0 }) let key = Int(bitPattern: pointer) return (key, MappingPropertyHandler(rawPaths: names, assignmentClosure: nil, takeValueClosure: nil)) } // MARK: non-optional properties public func <-- (property: inout Transform.Object, transformer: Transform) -> CustomMappingKeyValueTuple { return property <-- (nil, transformer) } public func <-- (property: inout Transform.Object, transformer: (String?, Transform?)) -> CustomMappingKeyValueTuple { let names = (transformer.0 == nil ? [] : [transformer.0!]) return property <-- (names, transformer.1) } public func <-- (property: inout Transform.Object, transformer: ([String], Transform?)) -> CustomMappingKeyValueTuple { let pointer = withUnsafePointer(to: &property, { return $0 }) let key = Int(bitPattern: pointer) let assignmentClosure = { (jsonValue: Any?) -> Transform.Object? in return transformer.1?.transformFromJSON(jsonValue) } let takeValueClosure = { (objectValue: Any?) -> Any? in if let _value = objectValue as? Transform.Object { return transformer.1?.transformToJSON(_value) as Any } return nil } return (key, MappingPropertyHandler(rawPaths: transformer.0, assignmentClosure: assignmentClosure, takeValueClosure: takeValueClosure)) } // MARK: optional properties public func <-- (property: inout Transform.Object?, transformer: Transform) -> CustomMappingKeyValueTuple { return property <-- (nil, transformer) } public func <-- (property: inout Transform.Object?, transformer: (String?, Transform?)) -> CustomMappingKeyValueTuple { let names = (transformer.0 == nil ? [] : [transformer.0!]) return property <-- (names, transformer.1) } public func <-- (property: inout Transform.Object?, transformer: ([String], Transform?)) -> CustomMappingKeyValueTuple { let pointer = withUnsafePointer(to: &property, { return $0 }) let key = Int(bitPattern: pointer) let assignmentClosure = { (jsonValue: Any?) -> Any? in return transformer.1?.transformFromJSON(jsonValue) } let takeValueClosure = { (objectValue: Any?) -> Any? in if let _value = objectValue as? Transform.Object { return transformer.1?.transformToJSON(_value) as Any } return nil } return (key, MappingPropertyHandler(rawPaths: transformer.0, assignmentClosure: assignmentClosure, takeValueClosure: takeValueClosure)) } infix operator <<< : AssignmentPrecedence public func <<< (mapper: HelpingMapper, mapping: CustomMappingKeyValueTuple) { mapper.addCustomMapping(key: mapping.0, mappingInfo: mapping.1) } public func <<< (mapper: HelpingMapper, mappings: [CustomMappingKeyValueTuple]) { mappings.forEach { (mapping) in mapper.addCustomMapping(key: mapping.0, mappingInfo: mapping.1) } } infix operator >>> : AssignmentPrecedence public func >>> (mapper: HelpingMapper, property: inout T) { mapper._exclude(property: &property) } ================================================ FILE: JetChat/Pods/HandyJSON/Source/HexColorTransform.swift ================================================ // // HexColorTransform.swift // ObjectMapper // // Created by Vitaliy Kuzmenko on 10/10/16. // Copyright © 2016 hearst. All rights reserved. // #if os(iOS) || os(tvOS) || os(watchOS) import UIKit #else import Cocoa #endif open class HexColorTransform: TransformType { #if os(iOS) || os(tvOS) || os(watchOS) public typealias Object = UIColor #else public typealias Object = NSColor #endif public typealias JSON = String var prefix: Bool = false var alpha: Bool = false public init(prefixToJSON: Bool = false, alphaToJSON: Bool = false) { alpha = alphaToJSON prefix = prefixToJSON } open func transformFromJSON(_ value: Any?) -> Object? { if let rgba = value as? String { if rgba.hasPrefix("#") { let index = rgba.index(rgba.startIndex, offsetBy: 1) let hex = String(rgba[index...]) return getColor(hex: hex) } else { return getColor(hex: rgba) } } return nil } open func transformToJSON(_ value: Object?) -> JSON? { if let value = value { return hexString(color: value) } return nil } fileprivate func hexString(color: Object) -> String { let comps = color.cgColor.components! let r = Int(comps[0] * 255) let g = Int(comps[1] * 255) let b = Int(comps[2] * 255) let a = Int(comps[3] * 255) var hexString: String = "" if prefix { hexString = "#" } hexString += String(format: "%02X%02X%02X", r, g, b) if alpha { hexString += String(format: "%02X", a) } return hexString } fileprivate func getColor(hex: String) -> Object? { var red: CGFloat = 0.0 var green: CGFloat = 0.0 var blue: CGFloat = 0.0 var alpha: CGFloat = 1.0 let scanner = Scanner(string: hex) var hexValue: CUnsignedLongLong = 0 if scanner.scanHexInt64(&hexValue) { switch (hex.count) { case 3: red = CGFloat((hexValue & 0xF00) >> 8) / 15.0 green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0 blue = CGFloat(hexValue & 0x00F) / 15.0 case 4: red = CGFloat((hexValue & 0xF000) >> 12) / 15.0 green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0 blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0 alpha = CGFloat(hexValue & 0x000F) / 15.0 case 6: red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0 green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0 blue = CGFloat(hexValue & 0x0000FF) / 255.0 case 8: red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0 green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0 blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0 alpha = CGFloat(hexValue & 0x000000FF) / 255.0 default: // Invalid RGB string, number of characters after '#' should be either 3, 4, 6 or 8 return nil } } else { // "Scan hex error return nil } #if os(iOS) || os(tvOS) || os(watchOS) return UIColor(red: red, green: green, blue: blue, alpha: alpha) #else return NSColor(calibratedRed: red, green: green, blue: blue, alpha: alpha) #endif } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/ISO8601DateTransform.swift ================================================ // // ISO8601DateTransform.swift // ObjectMapper // // Created by Jean-Pierre Mouilleseaux on 21 Nov 2014. // // The MIT License (MIT) // // Copyright (c) 2014-2016 Hearst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation open class ISO8601DateTransform: DateFormatterTransform { public init() { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" super.init(dateFormatter: formatter) } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/Logger.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Logger.swift // HandyJSON // // Created by zhouzhuo on 08/01/2017. // struct InternalLogger { static func logError(_ items: Any..., separator: String = " ", terminator: String = "\n") { if HandyJSONConfiguration.debugMode.rawValue <= DebugMode.error.rawValue { print(items, separator: separator, terminator: terminator) } } static func logDebug(_ items: Any..., separator: String = " ", terminator: String = "\n") { if HandyJSONConfiguration.debugMode.rawValue <= DebugMode.debug.rawValue { print(items, separator: separator, terminator: terminator) } } static func logVerbose(_ items: Any..., separator: String = " ", terminator: String = "\n") { if HandyJSONConfiguration.debugMode.rawValue <= DebugMode.verbose.rawValue { print(items, separator: separator, terminator: terminator) } } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/MangledName.swift ================================================ // // MangledName.swift // HandyJSON // // Created by chantu on 2019/2/2. // Copyright © 2019 aliyun. All rights reserved. // import Foundation // mangled name might contain 0 but it is not the end, do not just use strlen func getMangledTypeNameSize(_ mangledName: UnsafePointer) -> Int { // TODO: should find the actually size return 256 } ================================================ FILE: JetChat/Pods/HandyJSON/Source/Measuable.swift ================================================ // // Measuable.swift // HandyJSON // // Created by zhouzhuo on 15/07/2017. // Copyright © 2017 aliyun. All rights reserved. // import Foundation typealias Byte = Int8 public protocol _Measurable {} extension _Measurable { // locate the head of a struct type object in memory mutating func headPointerOfStruct() -> UnsafeMutablePointer { return withUnsafeMutablePointer(to: &self) { return UnsafeMutableRawPointer($0).bindMemory(to: Byte.self, capacity: MemoryLayout.stride) } } // locating the head of a class type object in memory mutating func headPointerOfClass() -> UnsafeMutablePointer { let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque() let mutableTypedPointer = opaquePointer.bindMemory(to: Byte.self, capacity: MemoryLayout.stride) return UnsafeMutablePointer(mutableTypedPointer) } // locating the head of an object mutating func headPointer() -> UnsafeMutablePointer { if Self.self is AnyClass { return self.headPointerOfClass() } else { return self.headPointerOfStruct() } } func isNSObjectType() -> Bool { return (type(of: self) as? NSObject.Type) != nil } func getBridgedPropertyList() -> Set { if let anyClass = type(of: self) as? AnyClass { return _getBridgedPropertyList(anyClass: anyClass) } return [] } func _getBridgedPropertyList(anyClass: AnyClass) -> Set { if !(anyClass is HandyJSON.Type) { return [] } var propertyList = Set() if let superClass = class_getSuperclass(anyClass), superClass != NSObject.self { propertyList = propertyList.union(_getBridgedPropertyList(anyClass: superClass)) } let count = UnsafeMutablePointer.allocate(capacity: 1) if let props = class_copyPropertyList(anyClass, count) { for i in 0 ..< count.pointee { let name = String(cString: property_getName(props.advanced(by: Int(i)).pointee)) propertyList.insert(name) } free(props) } #if swift(>=4.1) count.deallocate() #else count.deallocate(capacity: 1) #endif return propertyList } // memory size occupy by self object static func size() -> Int { return MemoryLayout.size } // align static func align() -> Int { return MemoryLayout.alignment } // Returns the offset to the next integer that is greater than // or equal to Value and is a multiple of Align. Align must be // non-zero. static func offsetToAlignment(value: Int, align: Int) -> Int { let m = value % align return m == 0 ? 0 : (align - m) } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/Metadata.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Created by zhouzhuo on 07/01/2017. // struct _class_rw_t { var flags: Int32 var version: Int32 var ro: UInt // other fields we don't care // reference: include/swift/Remote/MetadataReader.h/readObjcRODataPtr func class_ro_t() -> UnsafePointer<_class_ro_t>? { var addr: UInt = self.ro if (self.ro & UInt(1)) != 0 { if let ptr = UnsafePointer(bitPattern: self.ro ^ 1) { addr = ptr.pointee } } return UnsafePointer<_class_ro_t>(bitPattern: addr) } } struct _class_ro_t { var flags: Int32 var instanceStart: Int32 var instanceSize: Int32 // other fields we don't care } // MARK: MetadataType protocol MetadataType : PointerType { static var kind: Metadata.Kind? { get } } extension MetadataType { var kind: Metadata.Kind { return Metadata.Kind(flag: UnsafePointer(pointer).pointee) } init?(anyType: Any.Type) { self.init(pointer: unsafeBitCast(anyType, to: UnsafePointer.self)) if let kind = type(of: self).kind, kind != self.kind { return nil } } } // MARK: Metadata struct Metadata : MetadataType { var pointer: UnsafePointer init(type: Any.Type) { self.init(pointer: unsafeBitCast(type, to: UnsafePointer.self)) } } struct _Metadata {} var is64BitPlatform: Bool { return MemoryLayout.size == MemoryLayout.size } // MARK: Metadata + Kind // include/swift/ABI/MetadataKind.def let MetadataKindIsNonHeap = 0x200 let MetadataKindIsRuntimePrivate = 0x100 let MetadataKindIsNonType = 0x400 extension Metadata { static let kind: Kind? = nil enum Kind { case `struct` case `enum` case optional case opaque case foreignClass case tuple case function case existential case metatype case objCClassWrapper case existentialMetatype case heapLocalVariable case heapGenericLocalVariable case errorObject case `class` // The kind only valid for non-class metadata init(flag: Int) { switch flag { case (0 | MetadataKindIsNonHeap): self = .struct case (1 | MetadataKindIsNonHeap): self = .enum case (2 | MetadataKindIsNonHeap): self = .optional case (3 | MetadataKindIsNonHeap): self = .foreignClass case (0 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap): self = .opaque case (1 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap): self = .tuple case (2 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap): self = .function case (3 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap): self = .existential case (4 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap): self = .metatype case (5 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap): self = .objCClassWrapper case (6 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap): self = .existentialMetatype case (0 | MetadataKindIsNonType): self = .heapLocalVariable case (0 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate): self = .heapGenericLocalVariable case (1 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate): self = .errorObject default: self = .class } } } } // MARK: Metadata + Class extension Metadata { struct Class : ContextDescriptorType { static let kind: Kind? = .class var pointer: UnsafePointer<_Metadata._Class> var isSwiftClass: Bool { get { // see include/swift/Runtime/Config.h macro SWIFT_CLASS_IS_SWIFT_MASK // it can be 1 or 2 depending on environment let lowbit = self.pointer.pointee.rodataPointer & 3 return lowbit != 0 } } var contextDescriptorOffsetLocation: Int { return is64BitPlatform ? 8 : 11 } var superclass: Class? { guard let superclass = pointer.pointee.superclass else { return nil } // If the superclass doesn't conform to handyjson/handyjsonenum protocol, // we should ignore the properties inside if !(superclass is HandyJSON.Type) && !(superclass is HandyJSONEnum.Type) { return nil } // ignore objc-runtime layer guard let metaclass = Metadata.Class(anyType: superclass) else { return nil } return metaclass } var vTableSize: Int { // memory size after ivar destroyer return Int(pointer.pointee.classObjectSize - pointer.pointee.classObjectAddressPoint) - (contextDescriptorOffsetLocation + 2) * MemoryLayout.size } // reference: https://github.com/apple/swift/blob/master/docs/ABI/TypeMetadata.rst#generic-argument-vector var genericArgumentVector: UnsafeRawPointer? { let pointer = UnsafePointer(self.pointer) var superVTableSize = 0 if let _superclass = self.superclass { superVTableSize = _superclass.vTableSize / MemoryLayout.size } let base = pointer.advanced(by: contextDescriptorOffsetLocation + 2 + superVTableSize) if base.pointee == 0 { return nil } return UnsafeRawPointer(base) } func _propertyDescriptionsAndStartPoint() -> ([Property.Description], Int32?)? { let instanceStart = pointer.pointee.class_rw_t()?.pointee.class_ro_t()?.pointee.instanceStart var result: [Property.Description] = [] if let fieldOffsets = self.fieldOffsets, let fieldRecords = self.reflectionFieldDescriptor?.fieldRecords { class NameAndType { var name: String? var type: Any.Type? } for i in 0.. 0 { return (superclassProperties.0 + result, superclassProperties.1) } return (result, instanceStart) } func propertyDescriptions() -> [Property.Description]? { let propsAndStp = _propertyDescriptionsAndStartPoint() if let firstInstanceStart = propsAndStp?.1, let firstProperty = propsAndStp?.0.first?.offset { return propsAndStp?.0.map({ (propertyDesc) -> Property.Description in let offset = propertyDesc.offset - firstProperty + Int(firstInstanceStart) return Property.Description(key: propertyDesc.key, type: propertyDesc.type, offset: offset) }) } else { return propsAndStp?.0 } } } } extension _Metadata { struct _Class { var kind: Int var superclass: Any.Type? var reserveword1: Int var reserveword2: Int var rodataPointer: UInt var classFlags: UInt32 var instanceAddressPoint: UInt32 var instanceSize: UInt32 var instanceAlignmentMask: UInt16 var runtimeReservedField: UInt16 var classObjectSize: UInt32 var classObjectAddressPoint: UInt32 var nominalTypeDescriptor: Int var ivarDestroyer: Int // other fields we don't care func class_rw_t() -> UnsafePointer<_class_rw_t>? { if MemoryLayout.size == MemoryLayout.size { let fast_data_mask: UInt64 = 0x00007ffffffffff8 let databits_t: UInt64 = UInt64(self.rodataPointer) return UnsafePointer<_class_rw_t>(bitPattern: UInt(databits_t & fast_data_mask)) } else { return UnsafePointer<_class_rw_t>(bitPattern: self.rodataPointer & 0xfffffffc) } } } } // MARK: Metadata + Struct extension Metadata { struct Struct : ContextDescriptorType { static let kind: Kind? = .struct var pointer: UnsafePointer<_Metadata._Struct> var contextDescriptorOffsetLocation: Int { return 1 } var genericArgumentOffsetLocation: Int { return 2 } var genericArgumentVector: UnsafeRawPointer? { let pointer = UnsafePointer(self.pointer) let base = pointer.advanced(by: genericArgumentOffsetLocation) if base.pointee == 0 { return nil } return UnsafeRawPointer(base) } func propertyDescriptions() -> [Property.Description]? { guard let fieldOffsets = self.fieldOffsets, let fieldRecords = self.reflectionFieldDescriptor?.fieldRecords else { return [] } var result: [Property.Description] = [] class NameAndType { var name: String? var type: Any.Type? } for i in 0.. var contextDescriptorOffsetLocation: Int { return is64BitPlatform ? 8 : 11 } var targetType: Any.Type? { get { return pointer.pointee.targetType } } } } extension _Metadata { struct _ObjcClassWrapper { var kind: Int var targetType: Any.Type? } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/NSDecimalNumberTransform.swift ================================================ // // TransformOf.swift // ObjectMapper // // Created by Tristan Himmelman on 8/22/16. // // The MIT License (MIT) // // Copyright (c) 2014-2016 Hearst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation open class NSDecimalNumberTransform: TransformType { public typealias Object = NSDecimalNumber public typealias JSON = String public init() {} open func transformFromJSON(_ value: Any?) -> NSDecimalNumber? { if let string = value as? String { return NSDecimalNumber(string: string) } if let double = value as? Double { return NSDecimalNumber(value: double) } return nil } open func transformToJSON(_ value: NSDecimalNumber?) -> String? { guard let value = value else { return nil } return value.description } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/OtherExtension.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // OtherExtension.swift // HandyJSON // // Created by zhouzhuo on 08/01/2017. // protocol UTF8Initializable { init?(validatingUTF8: UnsafePointer) } extension String : UTF8Initializable {} extension Array where Element : UTF8Initializable { init(utf8Strings: UnsafePointer) { var strings = [Element]() var pointer = utf8Strings while let string = Element(validatingUTF8: pointer) { strings.append(string) while pointer.pointee != 0 { pointer.advance() } pointer.advance() guard pointer.pointee != 0 else { break } } self = strings } } extension Strideable { mutating func advance() { self = advanced(by: 1) } } extension UnsafePointer { init(_ pointer: UnsafePointer) { self = UnsafeRawPointer(pointer).assumingMemoryBound(to: Pointee.self) } } func relativePointer(base: UnsafePointer, offset: U) -> UnsafePointer where U : FixedWidthInteger { return UnsafeRawPointer(base).advanced(by: Int(integer: offset)).assumingMemoryBound(to: V.self) } extension Int { fileprivate init(integer: T) { switch integer { case let value as Int: self = value case let value as Int32: self = Int(value) case let value as Int16: self = Int(value) case let value as Int8: self = Int(value) default: self = 0 } } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/PointerType.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Created by zhouzhuo on 07/01/2017. // protocol PointerType : Equatable { associatedtype Pointee var pointer: UnsafePointer { get set } } extension PointerType { init(pointer: UnsafePointer) { func cast(_ value: T) -> U { return unsafeBitCast(value, to: U.self) } self = cast(UnsafePointer(pointer)) } } func == (lhs: T, rhs: T) -> Bool { return lhs.pointer == rhs.pointer } ================================================ FILE: JetChat/Pods/HandyJSON/Source/Properties.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Created by zhouzhuo on 07/01/2017. // /// An instance property struct Property { let key: String let value: Any /// An instance property description struct Description { public let key: String public let type: Any.Type public let offset: Int public func write(_ value: Any, to storage: UnsafeMutableRawPointer) { return extensions(of: type).write(value, to: storage.advanced(by: offset)) } } } /// Retrieve properties for `instance` func getProperties(forInstance instance: Any) -> [Property]? { if let props = getProperties(forType: type(of: instance)) { var copy = extensions(of: instance) let storage = copy.storage() return props.map { nextProperty(description: $0, storage: storage) } } return nil } private func nextProperty(description: Property.Description, storage: UnsafeRawPointer) -> Property { return Property( key: description.key, value: extensions(of: description.type).value(from: storage.advanced(by: description.offset)) ) } /// Retrieve property descriptions for `type` func getProperties(forType type: Any.Type) -> [Property.Description]? { if let structDescriptor = Metadata.Struct(anyType: type) { return structDescriptor.propertyDescriptions() } else if let classDescriptor = Metadata.Class(anyType: type) { return classDescriptor.propertyDescriptions() } else if let objcClassDescriptor = Metadata.ObjcClassWrapper(anyType: type), let targetType = objcClassDescriptor.targetType { return getProperties(forType: targetType) } return nil } ================================================ FILE: JetChat/Pods/HandyJSON/Source/PropertyInfo.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // PropertyInfo.swift // HandyJSON // // Created by zhouzhuo on 20/08/2017. // struct PropertyInfo { let key: String let type: Any.Type let address: UnsafeMutableRawPointer let bridged: Bool } ================================================ FILE: JetChat/Pods/HandyJSON/Source/ReflectionHelper.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Helper.swift // HandyJSON // // Created by zhouzhuo on 07/01/2017. // struct ReflectionHelper { static func mutableStorage(instance: inout T) -> UnsafeMutableRawPointer { return UnsafeMutableRawPointer(mutating: storage(instance: &instance)) } static func storage(instance: inout T) -> UnsafeRawPointer { if type(of: instance) is AnyClass { let opaquePointer = Unmanaged.passUnretained(instance as AnyObject).toOpaque() return UnsafeRawPointer(opaquePointer) } else { return withUnsafePointer(to: &instance) { pointer in return UnsafeRawPointer(pointer) } } } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/Serializer.swift ================================================ /* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // JSONSerializer.swift // HandyJSON // // Created by zhouzhuo on 9/30/16. // import Foundation public extension HandyJSON { func toJSON() -> [String: Any]? { if let dict = Self._serializeAny(object: self) as? [String: Any] { return dict } return nil } func toJSONString(prettyPrint: Bool = false) -> String? { if let anyObject = self.toJSON() { if JSONSerialization.isValidJSONObject(anyObject) { do { let jsonData: Data if prettyPrint { jsonData = try JSONSerialization.data(withJSONObject: anyObject, options: [.prettyPrinted]) } else { jsonData = try JSONSerialization.data(withJSONObject: anyObject, options: []) } return String(data: jsonData, encoding: .utf8) } catch let error { InternalLogger.logError(error) } } else { InternalLogger.logDebug("\(anyObject)) is not a valid JSON Object") } } return nil } } public extension Collection where Iterator.Element: HandyJSON { func toJSON() -> [[String: Any]?] { return self.map{ $0.toJSON() } } func toJSONString(prettyPrint: Bool = false) -> String? { let anyArray = self.toJSON() if JSONSerialization.isValidJSONObject(anyArray) { do { let jsonData: Data if prettyPrint { jsonData = try JSONSerialization.data(withJSONObject: anyArray, options: [.prettyPrinted]) } else { jsonData = try JSONSerialization.data(withJSONObject: anyArray, options: []) } return String(data: jsonData, encoding: .utf8) } catch let error { InternalLogger.logError(error) } } else { InternalLogger.logDebug("\(self.toJSON()) is not a valid JSON Object") } return nil } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/TransformOf.swift ================================================ // // TransformOf.swift // ObjectMapper // // Created by Syo Ikeda on 1/23/15. // // The MIT License (MIT) // // Copyright (c) 2014-2016 Hearst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. open class TransformOf: TransformType { public typealias Object = ObjectType public typealias JSON = JSONType private let fromJSON: (JSONType?) -> ObjectType? private let toJSON: (ObjectType?) -> JSONType? public init(fromJSON: @escaping(JSONType?) -> ObjectType?, toJSON: @escaping(ObjectType?) -> JSONType?) { self.fromJSON = fromJSON self.toJSON = toJSON } open func transformFromJSON(_ value: Any?) -> ObjectType? { return fromJSON(value as? JSONType) } open func transformToJSON(_ value: ObjectType?) -> JSONType? { return toJSON(value) } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/TransformType.swift ================================================ // // TransformType.swift // ObjectMapper // // Created by Syo Ikeda on 2/4/15. // // The MIT License (MIT) // // Copyright (c) 2014-2016 Hearst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. public protocol TransformType { associatedtype Object associatedtype JSON func transformFromJSON(_ value: Any?) -> Object? func transformToJSON(_ value: Object?) -> JSON? } ================================================ FILE: JetChat/Pods/HandyJSON/Source/Transformable.swift ================================================ // // Transformable.swift // HandyJSON // // Created by zhouzhuo on 15/07/2017. // Copyright © 2017 aliyun. All rights reserved. // import Foundation public protocol _Transformable: _Measurable {} extension _Transformable { static func transform(from object: Any) -> Self? { if let typedObject = object as? Self { return typedObject } switch self { case let type as _ExtendCustomBasicType.Type: return type._transform(from: object) as? Self case let type as _BuiltInBridgeType.Type: return type._transform(from: object) as? Self case let type as _BuiltInBasicType.Type: return type._transform(from: object) as? Self case let type as _RawEnumProtocol.Type: return type._transform(from: object) as? Self case let type as _ExtendCustomModelType.Type: return type._transform(from: object) as? Self default: return nil } } func plainValue() -> Any? { switch self { case let rawValue as _ExtendCustomBasicType: return rawValue._plainValue() case let rawValue as _BuiltInBridgeType: return rawValue._plainValue() case let rawValue as _BuiltInBasicType: return rawValue._plainValue() case let rawValue as _RawEnumProtocol: return rawValue._plainValue() case let rawValue as _ExtendCustomModelType: return rawValue._plainValue() default: return nil } } } ================================================ FILE: JetChat/Pods/HandyJSON/Source/URLTransform.swift ================================================ // // URLTransform.swift // ObjectMapper // // Created by Tristan Himmelman on 2014-10-27. // // The MIT License (MIT) // // Copyright (c) 2014-2016 Hearst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation open class URLTransform: TransformType { public typealias Object = URL public typealias JSON = String private let shouldEncodeURLString: Bool /** Initializes the URLTransform with an option to encode URL strings before converting them to an NSURL - parameter shouldEncodeUrlString: when true (the default) the string is encoded before passing to `NSURL(string:)` - returns: an initialized transformer */ public init(shouldEncodeURLString: Bool = true) { self.shouldEncodeURLString = shouldEncodeURLString } open func transformFromJSON(_ value: Any?) -> URL? { guard let URLString = value as? String else { return nil } if !shouldEncodeURLString { return URL(string: URLString) } guard let escapedURLString = URLString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else { return nil } return URL(string: escapedURLString) } open func transformToJSON(_ value: URL?) -> String? { if let URL = value { return URL.absoluteString } return nil } } ================================================ FILE: JetChat/Pods/HandyJSON.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 00696F323C84DC5A2A692E2A6081431B /* ExtendCustomModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3491BBF499954AB6636B8F55E8C4C6E /* ExtendCustomModelType.swift */; }; 023BEA463461C03AACAC54594241FEF4 /* FieldDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B49F1B2BD503CAA8F9A36D988FC451 /* FieldDescriptor.swift */; }; 0B7136EF61C9B872D69C803E12475B7D /* ISO8601DateTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FDB3B6D22E961BB219F8BDFABD8A30 /* ISO8601DateTransform.swift */; }; 0C5FC2091B80694CE43702FDB1935D16 /* MangledName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A7E9CC917BBB8F027B5E1217CB5289 /* MangledName.swift */; }; 11A1EE6528044A9492F656B8E336DE3E /* NSDecimalNumberTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CF0AB7B5814FD58807F16AD7477946 /* NSDecimalNumberTransform.swift */; }; 158EE2069FBD8C0D25FDA58D236AA4FA /* Transformable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A449BF89ED5935EF41F23FD9241CF18E /* Transformable.swift */; }; 210ABF16C1578D9AE4B80158FC9CA60D /* DateTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60B450FE1326E0EDD6199A3812B42E33 /* DateTransform.swift */; }; 28E44676D2FC37473D3A2D02C400D4EF /* ContextDescriptorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90716A9F0DCBB3B9842D81086485D7C /* ContextDescriptorType.swift */; }; 2F1FE107E4E78ADE69CFCA860C4FB51C /* EnumType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB19464E2CFF35B5DCAB7A6EEF92F8A /* EnumType.swift */; }; 321D9C08BAF1F47F3B81F3E895C8A6A4 /* PointerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39658B165F58F89B8219335A5AB29D94 /* PointerType.swift */; }; 39496C96338AC1FAC0B78BB0A11849CB /* URLTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C057657067DC6ED8C209519862A6E2 /* URLTransform.swift */; }; 4304E3BBB2E55E660AF820EF350CD51C /* HandyJSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 271436BCA6901851B6F5224DF04A7762 /* HandyJSON.h */; settings = {ATTRIBUTES = (Public, ); }; }; 43BCAC5F6A6B5FFAC1EB192496674A68 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EE6558C01DAA76985025318D7FA98E /* Configuration.swift */; }; 46BC22AE63AA1D16D05B517FA75C2E41 /* OtherExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DB9918469A01AA3398D458A0107407E /* OtherExtension.swift */; }; 567BA2411FE9AE8DD133E14C50A4BD6A /* BuiltInBasicType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA5D4A9C73210F67DA4B0F770BA782F /* BuiltInBasicType.swift */; }; 574EC4B09A6B2478BD77BA15EB7E55F2 /* Deserializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675AB74000D9EFF37EB895DC9BA6AEEB /* Deserializer.swift */; }; 5B136F03D1C28E4D1CC0CF95E05FC8FF /* TransformType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CCD66189A92D6E1D299C79BD0553FE /* TransformType.swift */; }; 666E3BED23D56CF42278C558EE9CE3B7 /* HelpingMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB9CE32B57E35E85CB6AA88776279CD /* HelpingMapper.swift */; }; 667DD4C1C3268DE2A2F482C18B03E7B1 /* DataTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6844F26945ECA0ADDED805CB0BC1FDF5 /* DataTransform.swift */; }; 66FDDD98C57832F525ACDBBB20D26E3A /* AnyExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53670C692633AF453AD8CDE89CD418EA /* AnyExtensions.swift */; }; 682177F4A6B84F5FF991BDF0B3AA8DC7 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4AB48DDE41CE98F186217D90B47ABCB /* Foundation.framework */; }; 8A5DF3861C6A91E3A067A8C1612A43C8 /* BuiltInBridgeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F7210402545D2697D46EFA772D8415E /* BuiltInBridgeType.swift */; }; 92B64E0C27AFAC0A24B7A8FA4D82BB1F /* DateFormatterTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5486A4D33DCCAF9CA419B074FF4FA374 /* DateFormatterTransform.swift */; }; 98ACC7EEC41A956BA13EE380BF3BE0F6 /* EnumTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4693D867EBE9F8D3E4B9FAE77E13A912 /* EnumTransform.swift */; }; 9EA493AE4AC193DC8C604152A43C368E /* Serializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69E4EC48920B4CD6D7CC5373B0058C56 /* Serializer.swift */; }; 9FD9237F73E7F58165473926AD0E0246 /* CustomDateFormatTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE4DF09BA8A4AABB7B25FC32578B0EED /* CustomDateFormatTransform.swift */; }; A03A98D4BEBF7E5889A037B932859A1C /* Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28434F25E35DBDF84B75767B2FC3E8F5 /* Properties.swift */; }; A082236197E627DD46CC11619258C34F /* Measuable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D12D74EF5DBDE55070A46734F0E48E /* Measuable.swift */; }; A8D436679D69B9EFAB7F9D2856981FB1 /* CBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965BAF08925B7995BB93C7BE7DB1DB51 /* CBridge.swift */; }; AC365338CC7C0F758B174BF545AC6B26 /* HexColorTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = C54A62F37C73BAB0457624CF15AA618B /* HexColorTransform.swift */; }; B2D0FE3C2FC0DB243058D4B3A97F4018 /* TransformOf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 963A2E567D5152E51402C9A3CF3BF2F4 /* TransformOf.swift */; }; B36BC3C119B58CE55B72E3F1CF3D73BC /* HandyJSON-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A81F8C1BA9556BAD5F0E6B987AF200 /* HandyJSON-dummy.m */; }; C686DF7C40DFFA956462AF079A794148 /* PropertyInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6948ECEEFAE54BDA32235BCB6FAF3C /* PropertyInfo.swift */; }; D91EF6CFFBA81F50BD48C1A5D2D3F9E5 /* ExtendCustomBasicType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DD8000266FFC7C36E6684B7D2BBB35C /* ExtendCustomBasicType.swift */; }; E26B17499569EF4C189BEB857FF2A390 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81C1D35D903969E80B49A26ACDD60B7 /* Logger.swift */; }; EA5BCCF9F0C27D89D339D593024F1246 /* Export.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79371706431FD3415AF58F812970A1D1 /* Export.swift */; }; EF1CB9D2A303867DFB023F37A85A9A04 /* Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48A3BC5411FD321774EB0A254EA9AC57 /* Metadata.swift */; }; F1EE08FBEF2A2DB3A7933AAB8FA1C2E5 /* ReflectionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8CCB3BD1CE4EC17E30476642B07F4B /* ReflectionHelper.swift */; }; FACF98D89E80F92B56812F89FF669EEB /* HandyJSON-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = D12857D888B11734D18874A7AF00F096 /* HandyJSON-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 01B49F1B2BD503CAA8F9A36D988FC451 /* FieldDescriptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FieldDescriptor.swift; path = Source/FieldDescriptor.swift; sourceTree = ""; }; 04CB150BF8CCAF444D9CC4284A595D74 /* HandyJSON */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = HandyJSON; path = HandyJSON.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0C67BEAC6E62F220116F1CBE8F062D6D /* HandyJSON-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "HandyJSON-Info.plist"; sourceTree = ""; }; 18CCD66189A92D6E1D299C79BD0553FE /* TransformType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransformType.swift; path = Source/TransformType.swift; sourceTree = ""; }; 1FB19464E2CFF35B5DCAB7A6EEF92F8A /* EnumType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EnumType.swift; path = Source/EnumType.swift; sourceTree = ""; }; 271436BCA6901851B6F5224DF04A7762 /* HandyJSON.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = HandyJSON.h; path = Source/HandyJSON.h; sourceTree = ""; }; 28434F25E35DBDF84B75767B2FC3E8F5 /* Properties.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Properties.swift; path = Source/Properties.swift; sourceTree = ""; }; 2EA5D4A9C73210F67DA4B0F770BA782F /* BuiltInBasicType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BuiltInBasicType.swift; path = Source/BuiltInBasicType.swift; sourceTree = ""; }; 39658B165F58F89B8219335A5AB29D94 /* PointerType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PointerType.swift; path = Source/PointerType.swift; sourceTree = ""; }; 4693D867EBE9F8D3E4B9FAE77E13A912 /* EnumTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EnumTransform.swift; path = Source/EnumTransform.swift; sourceTree = ""; }; 48A3BC5411FD321774EB0A254EA9AC57 /* Metadata.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Metadata.swift; path = Source/Metadata.swift; sourceTree = ""; }; 50A81F8C1BA9556BAD5F0E6B987AF200 /* HandyJSON-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "HandyJSON-dummy.m"; sourceTree = ""; }; 53670C692633AF453AD8CDE89CD418EA /* AnyExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnyExtensions.swift; path = Source/AnyExtensions.swift; sourceTree = ""; }; 5486A4D33DCCAF9CA419B074FF4FA374 /* DateFormatterTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DateFormatterTransform.swift; path = Source/DateFormatterTransform.swift; sourceTree = ""; }; 54EE6558C01DAA76985025318D7FA98E /* Configuration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Configuration.swift; path = Source/Configuration.swift; sourceTree = ""; }; 60B450FE1326E0EDD6199A3812B42E33 /* DateTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DateTransform.swift; path = Source/DateTransform.swift; sourceTree = ""; }; 675AB74000D9EFF37EB895DC9BA6AEEB /* Deserializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Deserializer.swift; path = Source/Deserializer.swift; sourceTree = ""; }; 6844F26945ECA0ADDED805CB0BC1FDF5 /* DataTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DataTransform.swift; path = Source/DataTransform.swift; sourceTree = ""; }; 69E4EC48920B4CD6D7CC5373B0058C56 /* Serializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Serializer.swift; path = Source/Serializer.swift; sourceTree = ""; }; 78D12D74EF5DBDE55070A46734F0E48E /* Measuable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Measuable.swift; path = Source/Measuable.swift; sourceTree = ""; }; 79371706431FD3415AF58F812970A1D1 /* Export.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Export.swift; path = Source/Export.swift; sourceTree = ""; }; 7DD8000266FFC7C36E6684B7D2BBB35C /* ExtendCustomBasicType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExtendCustomBasicType.swift; path = Source/ExtendCustomBasicType.swift; sourceTree = ""; }; 87C057657067DC6ED8C209519862A6E2 /* URLTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = URLTransform.swift; path = Source/URLTransform.swift; sourceTree = ""; }; 8DB9918469A01AA3398D458A0107407E /* OtherExtension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OtherExtension.swift; path = Source/OtherExtension.swift; sourceTree = ""; }; 8F7210402545D2697D46EFA772D8415E /* BuiltInBridgeType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BuiltInBridgeType.swift; path = Source/BuiltInBridgeType.swift; sourceTree = ""; }; 963A2E567D5152E51402C9A3CF3BF2F4 /* TransformOf.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TransformOf.swift; path = Source/TransformOf.swift; sourceTree = ""; }; 965BAF08925B7995BB93C7BE7DB1DB51 /* CBridge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CBridge.swift; path = Source/CBridge.swift; sourceTree = ""; }; A3491BBF499954AB6636B8F55E8C4C6E /* ExtendCustomModelType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExtendCustomModelType.swift; path = Source/ExtendCustomModelType.swift; sourceTree = ""; }; A449BF89ED5935EF41F23FD9241CF18E /* Transformable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Transformable.swift; path = Source/Transformable.swift; sourceTree = ""; }; AA6948ECEEFAE54BDA32235BCB6FAF3C /* PropertyInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PropertyInfo.swift; path = Source/PropertyInfo.swift; sourceTree = ""; }; B4AB48DDE41CE98F186217D90B47ABCB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; B6890CA1ADA21871C286A255836E8D5D /* HandyJSON-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "HandyJSON-prefix.pch"; sourceTree = ""; }; BE4DF09BA8A4AABB7B25FC32578B0EED /* CustomDateFormatTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CustomDateFormatTransform.swift; path = Source/CustomDateFormatTransform.swift; sourceTree = ""; }; C2CF0AB7B5814FD58807F16AD7477946 /* NSDecimalNumberTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSDecimalNumberTransform.swift; path = Source/NSDecimalNumberTransform.swift; sourceTree = ""; }; C54A62F37C73BAB0457624CF15AA618B /* HexColorTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HexColorTransform.swift; path = Source/HexColorTransform.swift; sourceTree = ""; }; C90716A9F0DCBB3B9842D81086485D7C /* ContextDescriptorType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ContextDescriptorType.swift; path = Source/ContextDescriptorType.swift; sourceTree = ""; }; D12857D888B11734D18874A7AF00F096 /* HandyJSON-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "HandyJSON-umbrella.h"; sourceTree = ""; }; D4A7E9CC917BBB8F027B5E1217CB5289 /* MangledName.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MangledName.swift; path = Source/MangledName.swift; sourceTree = ""; }; D879D6209EA8264473ED4B2E0C8C143E /* HandyJSON.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = HandyJSON.release.xcconfig; sourceTree = ""; }; DD1836DF97F5ED4D25BF4BD84D6D4972 /* HandyJSON.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = HandyJSON.modulemap; sourceTree = ""; }; E9FDB3B6D22E961BB219F8BDFABD8A30 /* ISO8601DateTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ISO8601DateTransform.swift; path = Source/ISO8601DateTransform.swift; sourceTree = ""; }; EC8CCB3BD1CE4EC17E30476642B07F4B /* ReflectionHelper.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ReflectionHelper.swift; path = Source/ReflectionHelper.swift; sourceTree = ""; }; ECB9CE32B57E35E85CB6AA88776279CD /* HelpingMapper.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HelpingMapper.swift; path = Source/HelpingMapper.swift; sourceTree = ""; }; F77ED3CB99C3B3A4DF920D0D55D89621 /* HandyJSON.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = HandyJSON.debug.xcconfig; sourceTree = ""; }; F81C1D35D903969E80B49A26ACDD60B7 /* Logger.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Logger.swift; path = Source/Logger.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ BE8FCD7B74D1E92F00ED5DAEB2957A6A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 682177F4A6B84F5FF991BDF0B3AA8DC7 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 01B6C4C41760DE95A27FC5353B1D3BE9 /* HandyJSON */ = { isa = PBXGroup; children = ( 53670C692633AF453AD8CDE89CD418EA /* AnyExtensions.swift */, 2EA5D4A9C73210F67DA4B0F770BA782F /* BuiltInBasicType.swift */, 8F7210402545D2697D46EFA772D8415E /* BuiltInBridgeType.swift */, 965BAF08925B7995BB93C7BE7DB1DB51 /* CBridge.swift */, 54EE6558C01DAA76985025318D7FA98E /* Configuration.swift */, C90716A9F0DCBB3B9842D81086485D7C /* ContextDescriptorType.swift */, BE4DF09BA8A4AABB7B25FC32578B0EED /* CustomDateFormatTransform.swift */, 6844F26945ECA0ADDED805CB0BC1FDF5 /* DataTransform.swift */, 5486A4D33DCCAF9CA419B074FF4FA374 /* DateFormatterTransform.swift */, 60B450FE1326E0EDD6199A3812B42E33 /* DateTransform.swift */, 675AB74000D9EFF37EB895DC9BA6AEEB /* Deserializer.swift */, 4693D867EBE9F8D3E4B9FAE77E13A912 /* EnumTransform.swift */, 1FB19464E2CFF35B5DCAB7A6EEF92F8A /* EnumType.swift */, 79371706431FD3415AF58F812970A1D1 /* Export.swift */, 7DD8000266FFC7C36E6684B7D2BBB35C /* ExtendCustomBasicType.swift */, A3491BBF499954AB6636B8F55E8C4C6E /* ExtendCustomModelType.swift */, 01B49F1B2BD503CAA8F9A36D988FC451 /* FieldDescriptor.swift */, 271436BCA6901851B6F5224DF04A7762 /* HandyJSON.h */, ECB9CE32B57E35E85CB6AA88776279CD /* HelpingMapper.swift */, C54A62F37C73BAB0457624CF15AA618B /* HexColorTransform.swift */, E9FDB3B6D22E961BB219F8BDFABD8A30 /* ISO8601DateTransform.swift */, F81C1D35D903969E80B49A26ACDD60B7 /* Logger.swift */, D4A7E9CC917BBB8F027B5E1217CB5289 /* MangledName.swift */, 78D12D74EF5DBDE55070A46734F0E48E /* Measuable.swift */, 48A3BC5411FD321774EB0A254EA9AC57 /* Metadata.swift */, C2CF0AB7B5814FD58807F16AD7477946 /* NSDecimalNumberTransform.swift */, 8DB9918469A01AA3398D458A0107407E /* OtherExtension.swift */, 39658B165F58F89B8219335A5AB29D94 /* PointerType.swift */, 28434F25E35DBDF84B75767B2FC3E8F5 /* Properties.swift */, AA6948ECEEFAE54BDA32235BCB6FAF3C /* PropertyInfo.swift */, EC8CCB3BD1CE4EC17E30476642B07F4B /* ReflectionHelper.swift */, 69E4EC48920B4CD6D7CC5373B0058C56 /* Serializer.swift */, A449BF89ED5935EF41F23FD9241CF18E /* Transformable.swift */, 963A2E567D5152E51402C9A3CF3BF2F4 /* TransformOf.swift */, 18CCD66189A92D6E1D299C79BD0553FE /* TransformType.swift */, 87C057657067DC6ED8C209519862A6E2 /* URLTransform.swift */, 125D0BA97975F6EAB4C3FF04A760B062 /* Support Files */, ); name = HandyJSON; path = HandyJSON; sourceTree = ""; }; 125D0BA97975F6EAB4C3FF04A760B062 /* Support Files */ = { isa = PBXGroup; children = ( DD1836DF97F5ED4D25BF4BD84D6D4972 /* HandyJSON.modulemap */, 50A81F8C1BA9556BAD5F0E6B987AF200 /* HandyJSON-dummy.m */, 0C67BEAC6E62F220116F1CBE8F062D6D /* HandyJSON-Info.plist */, B6890CA1ADA21871C286A255836E8D5D /* HandyJSON-prefix.pch */, D12857D888B11734D18874A7AF00F096 /* HandyJSON-umbrella.h */, F77ED3CB99C3B3A4DF920D0D55D89621 /* HandyJSON.debug.xcconfig */, D879D6209EA8264473ED4B2E0C8C143E /* HandyJSON.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/HandyJSON"; sourceTree = ""; }; 26180FC30248C7E479B58B2440237BF1 /* Frameworks */ = { isa = PBXGroup; children = ( FB9AABD16B531D8EE08AFFBC1A8F1C08 /* iOS */, ); name = Frameworks; sourceTree = ""; }; 4BA2BBF2BBBA9159E5F60CF3F3A14F50 /* Products */ = { isa = PBXGroup; children = ( 04CB150BF8CCAF444D9CC4284A595D74 /* HandyJSON */, ); name = Products; sourceTree = ""; }; FB9AABD16B531D8EE08AFFBC1A8F1C08 /* iOS */ = { isa = PBXGroup; children = ( B4AB48DDE41CE98F186217D90B47ABCB /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; FE93C58F0D5C315330391D5683CD66B0 = { isa = PBXGroup; children = ( 26180FC30248C7E479B58B2440237BF1 /* Frameworks */, 01B6C4C41760DE95A27FC5353B1D3BE9 /* HandyJSON */, 4BA2BBF2BBBA9159E5F60CF3F3A14F50 /* Products */, ); sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 11928F6CF98C6C77EEB22D76B8FE2606 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 4304E3BBB2E55E660AF820EF350CD51C /* HandyJSON.h in Headers */, FACF98D89E80F92B56812F89FF669EEB /* HandyJSON-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 42160F46A22E1CA007076183E782CF4B /* HandyJSON */ = { isa = PBXNativeTarget; buildConfigurationList = 8191188948CBC46133944DA1260E9536 /* Build configuration list for PBXNativeTarget "HandyJSON" */; buildPhases = ( 11928F6CF98C6C77EEB22D76B8FE2606 /* Headers */, 4A89C4540FC39BDFF7057B4F7EA2FA23 /* Sources */, BE8FCD7B74D1E92F00ED5DAEB2957A6A /* Frameworks */, 88817298B68211F9CF1B067693C6018B /* Resources */, ); buildRules = ( ); dependencies = ( ); name = HandyJSON; productName = HandyJSON; productReference = 04CB150BF8CCAF444D9CC4284A595D74 /* HandyJSON */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 4B53B2B557184E3552D624DC9F1C6561 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 1C51737069F1310749529A47BF6938C4 /* Build configuration list for PBXProject "HandyJSON" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = FE93C58F0D5C315330391D5683CD66B0; productRefGroup = 4BA2BBF2BBBA9159E5F60CF3F3A14F50 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 42160F46A22E1CA007076183E782CF4B /* HandyJSON */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 88817298B68211F9CF1B067693C6018B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 4A89C4540FC39BDFF7057B4F7EA2FA23 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 66FDDD98C57832F525ACDBBB20D26E3A /* AnyExtensions.swift in Sources */, 567BA2411FE9AE8DD133E14C50A4BD6A /* BuiltInBasicType.swift in Sources */, 8A5DF3861C6A91E3A067A8C1612A43C8 /* BuiltInBridgeType.swift in Sources */, A8D436679D69B9EFAB7F9D2856981FB1 /* CBridge.swift in Sources */, 43BCAC5F6A6B5FFAC1EB192496674A68 /* Configuration.swift in Sources */, 28E44676D2FC37473D3A2D02C400D4EF /* ContextDescriptorType.swift in Sources */, 9FD9237F73E7F58165473926AD0E0246 /* CustomDateFormatTransform.swift in Sources */, 667DD4C1C3268DE2A2F482C18B03E7B1 /* DataTransform.swift in Sources */, 92B64E0C27AFAC0A24B7A8FA4D82BB1F /* DateFormatterTransform.swift in Sources */, 210ABF16C1578D9AE4B80158FC9CA60D /* DateTransform.swift in Sources */, 574EC4B09A6B2478BD77BA15EB7E55F2 /* Deserializer.swift in Sources */, 98ACC7EEC41A956BA13EE380BF3BE0F6 /* EnumTransform.swift in Sources */, 2F1FE107E4E78ADE69CFCA860C4FB51C /* EnumType.swift in Sources */, EA5BCCF9F0C27D89D339D593024F1246 /* Export.swift in Sources */, D91EF6CFFBA81F50BD48C1A5D2D3F9E5 /* ExtendCustomBasicType.swift in Sources */, 00696F323C84DC5A2A692E2A6081431B /* ExtendCustomModelType.swift in Sources */, 023BEA463461C03AACAC54594241FEF4 /* FieldDescriptor.swift in Sources */, B36BC3C119B58CE55B72E3F1CF3D73BC /* HandyJSON-dummy.m in Sources */, 666E3BED23D56CF42278C558EE9CE3B7 /* HelpingMapper.swift in Sources */, AC365338CC7C0F758B174BF545AC6B26 /* HexColorTransform.swift in Sources */, 0B7136EF61C9B872D69C803E12475B7D /* ISO8601DateTransform.swift in Sources */, E26B17499569EF4C189BEB857FF2A390 /* Logger.swift in Sources */, 0C5FC2091B80694CE43702FDB1935D16 /* MangledName.swift in Sources */, A082236197E627DD46CC11619258C34F /* Measuable.swift in Sources */, EF1CB9D2A303867DFB023F37A85A9A04 /* Metadata.swift in Sources */, 11A1EE6528044A9492F656B8E336DE3E /* NSDecimalNumberTransform.swift in Sources */, 46BC22AE63AA1D16D05B517FA75C2E41 /* OtherExtension.swift in Sources */, 321D9C08BAF1F47F3B81F3E895C8A6A4 /* PointerType.swift in Sources */, A03A98D4BEBF7E5889A037B932859A1C /* Properties.swift in Sources */, C686DF7C40DFFA956462AF079A794148 /* PropertyInfo.swift in Sources */, F1EE08FBEF2A2DB3A7933AAB8FA1C2E5 /* ReflectionHelper.swift in Sources */, 9EA493AE4AC193DC8C604152A43C368E /* Serializer.swift in Sources */, 158EE2069FBD8C0D25FDA58D236AA4FA /* Transformable.swift in Sources */, B2D0FE3C2FC0DB243058D4B3A97F4018 /* TransformOf.swift in Sources */, 5B136F03D1C28E4D1CC0CF95E05FC8FF /* TransformType.swift in Sources */, 39496C96338AC1FAC0B78BB0A11849CB /* URLTransform.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 0223F3B6B002101B41DA2C2931058917 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 76758648F90A98EC58E67DA79EE6D4FB /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = F77ED3CB99C3B3A4DF920D0D55D89621 /* HandyJSON.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/HandyJSON/HandyJSON-prefix.pch"; INFOPLIST_FILE = "Target Support Files/HandyJSON/HandyJSON-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/HandyJSON/HandyJSON.modulemap"; PRODUCT_MODULE_NAME = HandyJSON; PRODUCT_NAME = HandyJSON; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 833F5C0017504EB98F358B14676D0A86 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = D879D6209EA8264473ED4B2E0C8C143E /* HandyJSON.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/HandyJSON/HandyJSON-prefix.pch"; INFOPLIST_FILE = "Target Support Files/HandyJSON/HandyJSON-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/HandyJSON/HandyJSON.modulemap"; PRODUCT_MODULE_NAME = HandyJSON; PRODUCT_NAME = HandyJSON; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; B19C6FD615870E59B381CD4D8E72618B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1C51737069F1310749529A47BF6938C4 /* Build configuration list for PBXProject "HandyJSON" */ = { isa = XCConfigurationList; buildConfigurations = ( 0223F3B6B002101B41DA2C2931058917 /* Debug */, B19C6FD615870E59B381CD4D8E72618B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 8191188948CBC46133944DA1260E9536 /* Build configuration list for PBXNativeTarget "HandyJSON" */ = { isa = XCConfigurationList; buildConfigurations = ( 76758648F90A98EC58E67DA79EE6D4FB /* Debug */, 833F5C0017504EB98F358B14676D0A86 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 4B53B2B557184E3552D624DC9F1C6561 /* Project object */; } ================================================ FILE: JetChat/Pods/IGListDiffKit/LICENSE.md ================================================ MIT License Copyright (c) Facebook, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/IGListDiffKit/README.md ================================================

Build Status Coverage Status Pods Version Platforms Carthage Compatible

---------------- A data-driven `UICollectionView` framework for building fast and flexible lists. | | Main Features | ----------|----------------- 🙅 | Never call `performBatchUpdates(_:, completion:)` or `reloadData()` again 🏠 | Better architecture with reusable cells and components 🔠 | Create collections with multiple data types 🔑 | Decoupled diffing algorithm ✅ | Fully unit tested 🔍 | Customize your diffing behavior for your models 📱 | Simply `UICollectionView` at its core 🚀 | Extendable API 🐦 | Written in Objective-C with full Swift interop support `IGListKit` is built and maintained with ❤️ by [Instagram engineering](https://engineering.instagram.com/). We use the open source version `master` branch in the Instagram app. ## Requirements - Xcode 9.0+ - iOS 9.0+ - tvOS 9.0+ - macOS 10.11+ *(diffing algorithm components only)* - Interoperability with Swift 3.0+ ## Installation ### CocoaPods The preferred installation method is with [CocoaPods](https://cocoapods.org). Add the following to your `Podfile`: ```ruby pod 'IGListKit', '~> 4.0.0' ``` ### Carthage For [Carthage](https://github.com/Carthage/Carthage), add the following to your `Cartfile`: ```ogdl github "Instagram/IGListKit" ~> 4.0.0 ``` > For advanced usage, see our [Installation Guide](https://instagram.github.io/IGListKit/installation.html). ## Getting Started ```bash $ git clone https://github.com/Instagram/IGListKit.git $ cd IGListKit/ $ ./scripts/setup.sh ``` - Our [Getting Started guide](https://instagram.github.io/IGListKit/getting-started.html) - Ray Wenderlich's [IGListKit Tutorial: Better UICollectionViews](https://www.raywenderlich.com/147162/iglistkit-tutorial-better-uicollectionviews) - Our [example projects](https://github.com/Instagram/IGListKit/tree/master/Examples) - Ryan Nystrom's [talk at try! Swift NYC](https://realm.io/news/tryswift-ryan-nystrom-refactoring-at-scale-lessons-learned-rewriting-instagram-feed/) (Note: this talk was for an earlier version. Some APIs have changed.) - [Migrating an UITableView to IGListCollectionView](https://medium.com/cocoaacademymag/iglistkit-migrating-an-uitableview-to-iglistkitcollectionview-65a30cf9bac9), by Rodrigo Cavalcante - [Keeping data fresh in Buffer for iOS with AsyncDisplayKit, IGListKit & Pusher](https://overflow.buffer.com/2017/04/10/keeping-data-fresh-buffer-ios-asyncdisplaykit-iglistkit-pusher/), Andy Yates, Buffer ## Documentation You can find [the docs here](https://instagram.github.io/IGListKit). Documentation is generated with [jazzy](https://github.com/realm/jazzy) and hosted on [GitHub-Pages](https://pages.github.com). To regenerate docs, run `./scripts/build_docs.sh` from the root directory in the repo. ## Vision For the long-term goals and "vision" of `IGListKit`, please read our [Vision](https://github.com/Instagram/IGListKit/blob/master/Guides/VISION.md) doc. ## Contributing Please see the [CONTRIBUTING](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md) file for how to help. At Instagram, we sync the open source version of `IGListKit` daily, so we're always testing the latest changes. But that requires all changes be thoroughly tested and follow our style guide. We have a set of [starter tasks](https://github.com/Instagram/IGListKit/issues?q=is%3Aissue+is%3Aopen+label%3Astarter-task) that are great for beginners to jump in on and start contributing. ## License `IGListKit` is [MIT-licensed](./LICENSE). The files in the `/Examples/` directory are licensed under a separate license as specified in each file. Documentation is licensed [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/). ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListAssert.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #ifndef IGAssert #define IGAssert( condition, ... ) NSCAssert( (condition) , ##__VA_ARGS__) #endif // IGAssert #ifndef IGFailAssert #define IGFailAssert( ... ) IGAssert( (NO) , ##__VA_ARGS__) #endif // IGFailAssert #ifndef IGParameterAssert #define IGParameterAssert( condition ) IGAssert( (condition) , @"Invalid parameter not satisfying: %@", @#condition) #endif // IGParameterAssert #ifndef IGAssertMainThread #define IGAssertMainThread() IGAssert( ([NSThread isMainThread] == YES), @"Must be on the main thread") #endif // IGAssertMainThread ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListBatchUpdateData.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import NS_ASSUME_NONNULL_BEGIN /** An instance of `IGListBatchUpdateData` takes section indexes and item index paths and performs cleanup on init in order to perform a crash-free update via `-[UICollectionView performBatchUpdates:completion:]`. */ IGLK_SUBCLASSING_RESTRICTED NS_SWIFT_NAME(ListBatchUpdateData) @interface IGListBatchUpdateData : NSObject /** Section insert indexes. */ @property (nonatomic, strong, readonly) NSIndexSet *insertSections; /** Section delete indexes. */ @property (nonatomic, strong, readonly) NSIndexSet *deleteSections; /** Section moves. */ @property (nonatomic, strong, readonly) NSSet *moveSections; /** Item insert index paths. */ @property (nonatomic, strong, readonly) NSArray *insertIndexPaths; /** Item delete index paths. */ @property (nonatomic, strong, readonly) NSArray *deleteIndexPaths; /** Item update index paths. */ @property (nonatomic, strong, readonly) NSArray *updateIndexPaths; /** Item moves. */ @property (nonatomic, strong, readonly) NSArray *moveIndexPaths; /** Creates a new batch update object with section and item operations. @param insertSections Section indexes to insert. @param deleteSections Section indexes to delete. @param moveSections Section moves. @param insertIndexPaths Item index paths to insert. @param deleteIndexPaths Item index paths to delete. @param updateIndexPaths Item index paths to update. @param moveIndexPaths Item index paths to move. @param fixIndexPathImbalance When enabled, we remove duplicate NSIndexPath inserts to avoid insert/delete imbalance and a crash. @return A new batch update object. */ - (instancetype)initWithInsertSections:(NSIndexSet *)insertSections deleteSections:(NSIndexSet *)deleteSections moveSections:(NSSet *)moveSections insertIndexPaths:(NSArray *)insertIndexPaths deleteIndexPaths:(NSArray *)deleteIndexPaths updateIndexPaths:(NSArray *)updateIndexPaths moveIndexPaths:(NSArray *)moveIndexPaths fixIndexPathImbalance:(BOOL)fixIndexPathImbalance NS_DESIGNATED_INITIALIZER; /** Convenience initializer with fixIndexPathImbalance disabled. */ - (instancetype)initWithInsertSections:(NSIndexSet *)insertSections deleteSections:(NSIndexSet *)deleteSections moveSections:(NSSet *)moveSections insertIndexPaths:(NSArray *)insertIndexPaths deleteIndexPaths:(NSArray *)deleteIndexPaths updateIndexPaths:(NSArray *)updateIndexPaths moveIndexPaths:(NSArray *)moveIndexPaths; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListBatchUpdateData.mm ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListBatchUpdateData.h" #import #import #import // Plucks the given move from available moves and turns it into a delete + insert static void convertMoveToDeleteAndInsert(NSMutableSet *moves, IGListMoveIndex *move, NSMutableIndexSet *deletes, NSMutableIndexSet *inserts) { [moves removeObject:move]; // add a delete and insert respecting the move's from and to sections // delete + insert will result in reloading the entire section [deletes addIndex:move.from]; [inserts addIndex:move.to]; } @implementation IGListBatchUpdateData // Converts all section moves that have index path operations into a section delete + insert. + (void)_cleanIndexPathsWithMap:(const std::unordered_map &)map moves:(NSMutableSet *)moves indexPaths:(NSMutableArray *)indexPaths deletes:(NSMutableIndexSet *)deletes inserts:(NSMutableIndexSet *)inserts { for (NSInteger i = indexPaths.count - 1; i >= 0; i--) { NSIndexPath *path = indexPaths[i]; const auto it = map.find(path.section); if (it != map.end() && it->second != nil) { [indexPaths removeObjectAtIndex:i]; convertMoveToDeleteAndInsert(moves, it->second, deletes, inserts); } } } - (instancetype)initWithInsertSections:(NSIndexSet *)insertSections deleteSections:(NSIndexSet *)deleteSections moveSections:(NSSet *)moveSections insertIndexPaths:(NSArray *)insertIndexPaths deleteIndexPaths:(NSArray *)deleteIndexPaths updateIndexPaths:(NSArray *)updateIndexPaths moveIndexPaths:(NSArray *)moveIndexPaths { return [self initWithInsertSections:insertSections deleteSections:deleteSections moveSections:moveSections insertIndexPaths:insertIndexPaths deleteIndexPaths:deleteIndexPaths updateIndexPaths:updateIndexPaths moveIndexPaths:moveIndexPaths fixIndexPathImbalance:NO]; } /** Converts all section moves that are also reloaded, or have index path inserts, deletes, or reloads into a section delete + insert in order to avoid UICollectionView heap corruptions, exceptions, and animation/snapshot bugs. */ - (instancetype)initWithInsertSections:(nonnull NSIndexSet *)insertSections deleteSections:(nonnull NSIndexSet *)deleteSections moveSections:(nonnull NSSet *)moveSections insertIndexPaths:(nonnull NSArray *)insertIndexPaths deleteIndexPaths:(nonnull NSArray *)deleteIndexPaths updateIndexPaths:(nonnull NSArray *)updateIndexPaths moveIndexPaths:(nonnull NSArray *)moveIndexPaths fixIndexPathImbalance:(BOOL)fixIndexPathImbalance { IGParameterAssert(insertSections != nil); IGParameterAssert(deleteSections != nil); IGParameterAssert(moveSections != nil); IGParameterAssert(insertIndexPaths != nil); IGParameterAssert(deleteIndexPaths != nil); IGParameterAssert(updateIndexPaths != nil); IGParameterAssert(moveIndexPaths != nil); if (self = [super init]) { NSMutableSet *mMoveSections = [moveSections mutableCopy]; NSMutableIndexSet *mDeleteSections = [deleteSections mutableCopy]; NSMutableIndexSet *mInsertSections = [insertSections mutableCopy]; NSMutableSet *mMoveIndexPaths = [moveIndexPaths mutableCopy]; // these collections should NEVER be mutated during cleanup passes, otherwise sections that have multiple item // changes (e.g. a moved section that has a delete + reload on different index paths w/in the section) will only // convert one of the item changes into a section delete+insert. this will fail hard and be VERY difficult to // debug const NSInteger moveCount = [moveSections count]; std::unordered_map fromMap(moveCount); std::unordered_map toMap(moveCount); for (IGListMoveIndex *move in moveSections) { const NSInteger from = move.from; const NSInteger to = move.to; // if the move is already deleted or inserted, discard it because count-changing operations must match // with data source changes if ([deleteSections containsIndex:from] || [insertSections containsIndex:to]) { [mMoveSections removeObject:move]; } else { fromMap[from] = move; toMap[to] = move; } } // avoid a flaky UICollectionView bug when deleting from the same index path twice // exposes a possible data source inconsistency issue NSMutableArray *mDeleteIndexPaths = [[[NSSet setWithArray:deleteIndexPaths] allObjects] mutableCopy]; NSMutableArray *mInsertIndexPaths; if (fixIndexPathImbalance) { // Since we remove duplicate deletes (see above) we also need to remove inserts to keep the same insert/delete // balance. For example, if we reload (insert & delete) the same NSIndexPath twice, we would otherwise end up // with 2 inserts and 1 delete. mInsertIndexPaths = [[[NSSet setWithArray:insertIndexPaths] allObjects] mutableCopy]; } else { mInsertIndexPaths = [insertIndexPaths mutableCopy]; } // avoids a bug where a cell is animated twice and one of the snapshot cells is never removed from the hierarchy [IGListBatchUpdateData _cleanIndexPathsWithMap:fromMap moves:mMoveSections indexPaths:mDeleteIndexPaths deletes:mDeleteSections inserts:mInsertSections]; // prevents a bug where UICollectionView corrupts the heap memory when inserting into a section that is moved [IGListBatchUpdateData _cleanIndexPathsWithMap:toMap moves:mMoveSections indexPaths:mInsertIndexPaths deletes:mDeleteSections inserts:mInsertSections]; for (IGListMoveIndexPath *move in moveIndexPaths) { // if the section w/ an index path move is deleted, just drop the move if ([deleteSections containsIndex:move.from.section]) { [mMoveIndexPaths removeObject:move]; } // if a move is inside a section that is moved, convert the section move to a delete+insert const auto it = fromMap.find(move.from.section); if (it != fromMap.end() && it->second != nil) { IGListMoveIndex *sectionMove = it->second; [mMoveIndexPaths removeObject:move]; [mMoveSections removeObject:sectionMove]; [mDeleteSections addIndex:sectionMove.from]; [mInsertSections addIndex:sectionMove.to]; } } _deleteSections = [mDeleteSections copy]; _insertSections = [mInsertSections copy]; _moveSections = [mMoveSections copy]; _deleteIndexPaths = [mDeleteIndexPaths copy]; _insertIndexPaths = [mInsertIndexPaths copy]; _updateIndexPaths = [updateIndexPaths copy]; _moveIndexPaths = [mMoveIndexPaths copy]; } return self; } - (BOOL)isEqual:(id)object { if (object == self) { return YES; } if ([object isKindOfClass:[IGListBatchUpdateData class]]) { return ([self.insertSections isEqual:[object insertSections]] && [self.deleteSections isEqual:[object deleteSections]] && [self.moveSections isEqual:[object moveSections]] && [self.insertIndexPaths isEqual:[object insertIndexPaths]] && [self.deleteIndexPaths isEqual:[object deleteIndexPaths]] && [self.updateIndexPaths isEqual:[object updateIndexPaths]] && [self.moveIndexPaths isEqual:[object moveIndexPaths]]); } return NO; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p; deleteSections: %lu; insertSections: %lu; moveSections: %lu; deleteIndexPaths: %lu; insertIndexPaths: %lu; updateIndexPaths: %lu>", NSStringFromClass(self.class), self, (unsigned long)self.deleteSections.count, (unsigned long)self.insertSections.count, (unsigned long)self.moveSections.count, (unsigned long)self.deleteIndexPaths.count, (unsigned long)self.insertIndexPaths.count, (unsigned long)self.updateIndexPaths.count]; } @end ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListCompatibility.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #if TARGET_OS_EMBEDDED || TARGET_OS_SIMULATOR #import #else #import #endif ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListDiff.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import NS_ASSUME_NONNULL_BEGIN /** An option for how to do comparisons between similar objects. */ NS_SWIFT_NAME(ListDiffOption) typedef NS_ENUM(NSInteger, IGListDiffOption) { /** Compare objects using pointer personality. */ IGListDiffPointerPersonality, /** Compare objects using `-[IGListDiffable isEqualToDiffableObject:]`. */ IGListDiffEquality }; /** Creates a diff using indexes between two collections. @param oldArray The old objects to diff against. @param newArray The new objects. @param option An option on how to compare objects. @return A result object containing affected indexes. */ NS_SWIFT_NAME(ListDiff(oldArray:newArray:option:)) FOUNDATION_EXTERN IGListIndexSetResult *IGListDiff(NSArray> *_Nullable oldArray, NSArray> *_Nullable newArray, IGListDiffOption option); /** Creates a diff using index paths between two collections. @param fromSection The old section. @param toSection The new section. @param oldArray The old objects to diff against. @param newArray The new objects. @param option An option on how to compare objects. @return A result object containing affected indexes. */ NS_SWIFT_NAME(ListDiffPaths(fromSection:toSection:oldArray:newArray:option:)) FOUNDATION_EXTERN IGListIndexPathResult *IGListDiffPaths(NSInteger fromSection, NSInteger toSection, NSArray> *_Nullable oldArray, NSArray> *_Nullable newArray, IGListDiffOption option); NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListDiff.mm ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListDiff.h" #import #import #import #import #import #import "IGListIndexPathResultInternal.h" #import "IGListIndexSetResultInternal.h" #import "IGListMoveIndexInternal.h" #import "IGListMoveIndexPathInternal.h" using namespace std; /// Used to track data stats while diffing. struct IGListEntry { /// The number of times the data occurs in the old array NSInteger oldCounter = 0; /// The number of times the data occurs in the new array NSInteger newCounter = 0; /// The indexes of the data in the old array stack oldIndexes; /// Flag marking if the data has been updated between arrays by checking the isEqual: method BOOL updated = NO; }; /// Track both the entry and algorithm index. Default the index to NSNotFound struct IGListRecord { IGListEntry *entry; mutable NSInteger index; IGListRecord() { entry = NULL; index = NSNotFound; } }; static id IGListTableKey(__unsafe_unretained id object) { id key = [object diffIdentifier]; NSCAssert(key != nil, @"Cannot use a nil key for the diffIdentifier of object %@", object); return key; } struct IGListEqualID { bool operator()(const id a, const id b) const { return (a == b) || [a isEqual: b]; } }; struct IGListHashID { size_t operator()(const id o) const { return (size_t)[o hash]; } }; static void addIndexToMap(BOOL useIndexPaths, NSInteger section, NSInteger index, __unsafe_unretained id object, __unsafe_unretained NSMapTable *map) { id value; if (useIndexPaths) { value = [NSIndexPath indexPathForItem:index inSection:section]; } else { value = @(index); } [map setObject:value forKey:[object diffIdentifier]]; } static void addIndexToCollection(BOOL useIndexPaths, __unsafe_unretained id collection, NSInteger section, NSInteger index) { if (useIndexPaths) { NSIndexPath *path = [NSIndexPath indexPathForItem:index inSection:section]; [collection addObject:path]; } else { [collection addIndex:index]; } }; static NSArray *indexPathsAndPopulateMap(__unsafe_unretained NSArray> *array, NSInteger section, __unsafe_unretained NSMapTable *map) { NSMutableArray *paths = [NSMutableArray new]; [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSIndexPath *path = [NSIndexPath indexPathForItem:idx inSection:section]; [paths addObject:path]; [map setObject:path forKey:[obj diffIdentifier]]; }]; return paths; } static id IGListDiffing(BOOL returnIndexPaths, NSInteger fromSection, NSInteger toSection, NSArray> *oldArray, NSArray> *newArray, IGListDiffOption option, IGListExperiment experiments) { const NSInteger newCount = newArray.count; const NSInteger oldCount = oldArray.count; NSMapTable *oldMap = [NSMapTable strongToStrongObjectsMapTable]; NSMapTable *newMap = [NSMapTable strongToStrongObjectsMapTable]; // if no new objects, everything from the oldArray is deleted // take a shortcut and just build a delete-everything result if (newCount == 0) { if (returnIndexPaths) { return [[IGListIndexPathResult alloc] initWithInserts:[NSArray new] deletes:indexPathsAndPopulateMap(oldArray, fromSection, oldMap) updates:[NSArray new] moves:[NSArray new] oldIndexPathMap:oldMap newIndexPathMap:newMap]; } else { [oldArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { addIndexToMap(returnIndexPaths, fromSection, idx, obj, oldMap); }]; return [[IGListIndexSetResult alloc] initWithInserts:[NSIndexSet new] deletes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, oldCount)] updates:[NSIndexSet new] moves:[NSArray new] oldIndexMap:oldMap newIndexMap:newMap]; } } // if no old objects, everything from the newArray is inserted // take a shortcut and just build an insert-everything result if (oldCount == 0) { if (returnIndexPaths) { return [[IGListIndexPathResult alloc] initWithInserts:indexPathsAndPopulateMap(newArray, toSection, newMap) deletes:[NSArray new] updates:[NSArray new] moves:[NSArray new] oldIndexPathMap:oldMap newIndexPathMap:newMap]; } else { [newArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { addIndexToMap(returnIndexPaths, toSection, idx, obj, newMap); }]; return [[IGListIndexSetResult alloc] initWithInserts:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newCount)] deletes:[NSIndexSet new] updates:[NSIndexSet new] moves:[NSArray new] oldIndexMap:oldMap newIndexMap:newMap]; } } // symbol table uses the old/new array diffIdentifier as the key and IGListEntry as the value // using id as the key provided by https://lists.gnu.org/archive/html/discuss-gnustep/2011-07/msg00019.html unordered_map, IGListEntry, IGListHashID, IGListEqualID> table; // pass 1 // create an entry for every item in the new array // increment its new count for each occurence vector newResultsArray(newCount); for (NSInteger i = 0; i < newCount; i++) { id key = IGListTableKey(newArray[i]); IGListEntry &entry = table[key]; entry.newCounter++; // add NSNotFound for each occurence of the item in the new array entry.oldIndexes.push(NSNotFound); // note: the entry is just a pointer to the entry which is stack-allocated in the table newResultsArray[i].entry = &entry; } // pass 2 // update or create an entry for every item in the old array // increment its old count for each occurence // record the original index of the item in the old array // MUST be done in descending order to respect the oldIndexes stack construction vector oldResultsArray(oldCount); for (NSInteger i = oldCount - 1; i >= 0; i--) { id key = IGListTableKey(oldArray[i]); IGListEntry &entry = table[key]; entry.oldCounter++; // push the original indices where the item occurred onto the index stack entry.oldIndexes.push(i); // note: the entry is just a pointer to the entry which is stack-allocated in the table oldResultsArray[i].entry = &entry; } // pass 3 // handle data that occurs in both arrays for (NSInteger i = 0; i < newCount; i++) { IGListEntry *entry = newResultsArray[i].entry; // grab and pop the top original index. if the item was inserted this will be NSNotFound NSCAssert(!entry->oldIndexes.empty(), @"Old indexes is empty while iterating new item %li. Should have NSNotFound", (long)i); const NSInteger originalIndex = entry->oldIndexes.top(); entry->oldIndexes.pop(); if (originalIndex < oldCount) { const id n = newArray[i]; const id o = oldArray[originalIndex]; switch (option) { case IGListDiffPointerPersonality: // flag the entry as updated if the pointers are not the same if (n != o) { entry->updated = YES; } break; case IGListDiffEquality: // use -[IGListDiffable isEqualToDiffableObject:] between both version of data to see if anything has changed // skip the equality check if both indexes point to the same object if (n != o && ![n isEqualToDiffableObject:o]) { entry->updated = YES; } break; } } if (originalIndex != NSNotFound && entry->newCounter > 0 && entry->oldCounter > 0) { // if an item occurs in the new and old array, it is unique // assign the index of new and old records to the opposite index (reverse lookup) newResultsArray[i].index = originalIndex; oldResultsArray[originalIndex].index = i; } } // storage for final NSIndexPaths or indexes id mInserts, mMoves, mUpdates, mDeletes; if (returnIndexPaths) { mInserts = [NSMutableArray new]; mMoves = [NSMutableArray new]; mUpdates = [NSMutableArray new]; mDeletes = [NSMutableArray new]; } else { mInserts = [NSMutableIndexSet new]; mMoves = [NSMutableArray new]; mUpdates = [NSMutableIndexSet new]; mDeletes = [NSMutableIndexSet new]; } // track offsets from deleted items to calculate where items have moved vector deleteOffsets(oldCount), insertOffsets(newCount); NSInteger runningOffset = 0; // iterate old array records checking for deletes // incremement offset for each delete for (NSInteger i = 0; i < oldCount; i++) { deleteOffsets[i] = runningOffset; const IGListRecord record = oldResultsArray[i]; // if the record index in the new array doesn't exist, its a delete if (record.index == NSNotFound) { addIndexToCollection(returnIndexPaths, mDeletes, fromSection, i); runningOffset++; } addIndexToMap(returnIndexPaths, fromSection, i, oldArray[i], oldMap); } // reset and track offsets from inserted items to calculate where items have moved runningOffset = 0; for (NSInteger i = 0; i < newCount; i++) { insertOffsets[i] = runningOffset; const IGListRecord record = newResultsArray[i]; const NSInteger oldIndex = record.index; // add to inserts if the opposing index is NSNotFound if (record.index == NSNotFound) { addIndexToCollection(returnIndexPaths, mInserts, toSection, i); runningOffset++; } else { // note that an entry can be updated /and/ moved if (record.entry->updated) { addIndexToCollection(returnIndexPaths, mUpdates, fromSection, oldIndex); } // calculate the offset and determine if there was a move // if the indexes match, ignore the index const NSInteger insertOffset = insertOffsets[i]; const NSInteger deleteOffset = deleteOffsets[oldIndex]; if ((oldIndex - deleteOffset + insertOffset) != i) { id move; if (returnIndexPaths) { NSIndexPath *from = [NSIndexPath indexPathForItem:oldIndex inSection:fromSection]; NSIndexPath *to = [NSIndexPath indexPathForItem:i inSection:toSection]; move = [[IGListMoveIndexPath alloc] initWithFrom:from to:to]; } else { move = [[IGListMoveIndex alloc] initWithFrom:oldIndex to:i]; } [mMoves addObject:move]; } } addIndexToMap(returnIndexPaths, toSection, i, newArray[i], newMap); } NSCAssert((oldCount + [mInserts count] - [mDeletes count]) == newCount, @"Sanity check failed applying %lu inserts and %lu deletes to old count %li equaling new count %li", (unsigned long)[mInserts count], (unsigned long)[mDeletes count], (long)oldCount, (long)newCount); if (returnIndexPaths) { return [[IGListIndexPathResult alloc] initWithInserts:mInserts deletes:mDeletes updates:mUpdates moves:mMoves oldIndexPathMap:oldMap newIndexPathMap:newMap]; } else { return [[IGListIndexSetResult alloc] initWithInserts:mInserts deletes:mDeletes updates:mUpdates moves:mMoves oldIndexMap:oldMap newIndexMap:newMap]; } } IGListIndexSetResult *IGListDiff(NSArray > *oldArray, NSArray> *newArray, IGListDiffOption option) { return IGListDiffing(NO, 0, 0, oldArray, newArray, option, 0); } IGListIndexPathResult *IGListDiffPaths(NSInteger fromSection, NSInteger toSection, NSArray> *oldArray, NSArray> *newArray, IGListDiffOption option) { return IGListDiffing(YES, fromSection, toSection, oldArray, newArray, option, 0); } IGListIndexSetResult *IGListDiffExperiment(NSArray> *_Nullable oldArray, NSArray> *_Nullable newArray, IGListDiffOption option, IGListExperiment experiments) { return IGListDiffing(NO, 0, 0, oldArray, newArray, option, experiments); } IGListIndexPathResult *IGListDiffPathsExperiment(NSInteger fromSection, NSInteger toSection, NSArray> *_Nullable oldArray, NSArray> *_Nullable newArray, IGListDiffOption option, IGListExperiment experiments) { return IGListDiffing(YES, fromSection, toSection, oldArray, newArray, option, experiments); } ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListDiffKit.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import /** * Project version number for IGListKit. */ FOUNDATION_EXPORT double IGListKitVersionNumber; /** * Project version string for IGListKit. */ FOUNDATION_EXPORT const unsigned char IGListKitVersionString[]; #import #import #import #import #import #import #import #import #import #import #import ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListDiffable.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import /** The `IGListDiffable` protocol provides methods needed to compare the identity and equality of two objects. */ NS_SWIFT_NAME(ListDiffable) @protocol IGListDiffable /** Returns a key that uniquely identifies the object. @return A key that can be used to uniquely identify the object. @note Two objects may share the same identifier, but are not equal. A common pattern is to use the `NSObject` category for automatic conformance. However this means that objects will be identified on their pointer value so finding updates becomes impossible. @warning This value should never be mutated. */ - (nonnull id)diffIdentifier; /** Returns whether the receiver and a given object are equal. @param object The object to be compared to the receiver. @return `YES` if the receiver and object are equal, otherwise `NO`. */ - (BOOL)isEqualToDiffableObject:(nullable id)object; @end ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListExperiments.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import /** Bitmask-able options used for pre-release feature testing. */ NS_SWIFT_NAME(ListExperiment) typedef NS_OPTIONS (NSInteger, IGListExperiment) { /// Specifies no experiments. IGListExperimentNone = 1 << 1, /// Test updater diffing performed on a background queue. IGListExperimentBackgroundDiffing = 1 << 2, /// Test fallback to reloadData when "too many" update operations. IGListExperimentReloadDataFallback = 1 << 3, /// Test removing the layout pass when calling scrollToObject to avoid creating off-screen cells. IGListExperimentAvoidLayoutOnScrollToObject = 1 << 4, /// Test fixing a crash when inserting and deleting the same NSIndexPath multiple times. IGListExperimentFixIndexPathImbalance = 1 << 5, /// Test deferring object creation until just before diffing. IGListExperimentDeferredToObjectCreation = 1 << 6, /// Test getting collection view at update time. IGListExperimentGetCollectionViewAtUpdate = 1 << 7, /// Test invalidating layout when cell reloads/updates in IGListBindingSectionController. IGListExperimentInvalidateLayoutForUpdates = 1 << 8, /// Test using the collection view when asking for layout instead of accessing the data source. Only apply to IGListCollectionViewLayout. IGListExperimentUseCollectionViewInsteadOfDataSourceInLayout = 1 << 9 }; /** Check if an experiment is enabled in a bitmask. @param mask The bitmask of experiments. @param option The option to compare with. @return `YES` if the option is in the bitmask, otherwise `NO`. */ NS_SWIFT_NAME(ListExperimentEnabled(mask:option:)) static inline BOOL IGListExperimentEnabled(IGListExperiment mask, IGListExperiment option) { return (mask & option) != 0; } NS_ASSUME_NONNULL_BEGIN /** Performs an index diff with an experiment bitmask. @param oldArray The old array of objects. @param newArray The new array of objects. @param option Option to specify the type of diff. @param experiments Optional experiments. @return An index set result object contained the changed indexes. @see `IGListDiff()`. */ NS_SWIFT_NAME(ListDiffExperiment(oldArray:newArray:option:experiments:)) FOUNDATION_EXTERN IGListIndexSetResult *IGListDiffExperiment(NSArray> *_Nullable oldArray, NSArray> *_Nullable newArray, IGListDiffOption option, IGListExperiment experiments); /** Performs a index path diff with an experiment bitmask. @param fromSection The old section. @param toSection The new section. @param oldArray The old array of objects. @param newArray The new array of objects. @param option Option to specify the type of diff. @param experiments Optional experiments. @return An index path result object containing the changed indexPaths. @see `IGListDiffPaths()`. */ NS_SWIFT_NAME(ListDiffPathsExperiment(fromSection:toSection:oldArray:newArray:option:experiments:)) FOUNDATION_EXTERN IGListIndexPathResult *IGListDiffPathsExperiment(NSInteger fromSection, NSInteger toSection, NSArray> *_Nullable oldArray, NSArray> *_Nullable newArray, IGListDiffOption option, IGListExperiment experiments); NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListIndexPathResult.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import NS_ASSUME_NONNULL_BEGIN /** A result object returned when diffing with sections. */ NS_SWIFT_NAME(ListIndexPathResult) @interface IGListIndexPathResult : NSObject /** The index paths inserted into the new collection. */ @property (nonatomic, copy, readonly) NSArray *inserts; /** The index paths deleted from the old collection. */ @property (nonatomic, copy, readonly) NSArray *deletes; /** The index paths in the old collection that need updated. */ @property (nonatomic, copy, readonly) NSArray *updates; /** The moves from an index path in the old collection to an index path in the new collection. */ @property (nonatomic, copy, readonly) NSArray *moves; /** A Read-only boolean that indicates whether the result has any changes or not. `YES` if the result has changes, `NO` otherwise. */ @property (nonatomic, assign, readonly) BOOL hasChanges; /** Returns the index path of the object with the specified identifier *before* the diff. @param identifier The diff identifier of the object. @return The index path of the object before the diff, or `nil`. @see `-[IGListDiffable diffIdentifier]`. */ - (nullable NSIndexPath *)oldIndexPathForIdentifier:(id)identifier; /** Returns the index path of the object with the specified identifier *after* the diff. @param identifier The diff identifier of the object. @return The index path of the object after the diff, or `nil`. @see `-[IGListDiffable diffIdentifier]`. */ - (nullable NSIndexPath *)newIndexPathForIdentifier:(id)identifier; /** Creates a new result object with operations safe for use in `UITableView` and `UICollectionView` batch updates. */ - (IGListIndexPathResult *)resultForBatchUpdates; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListIndexPathResult.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListIndexPathResult.h" #import "IGListIndexPathResultInternal.h" @implementation IGListIndexPathResult { NSMapTable, NSIndexPath *> *_oldIndexPathMap; NSMapTable, NSIndexPath *> *_newIndexPathMap; } - (instancetype)initWithInserts:(NSArray *)inserts deletes:(NSArray *)deletes updates:(NSArray *)updates moves:(NSArray *)moves oldIndexPathMap:(NSMapTable, NSIndexPath *> *)oldIndexPathMap newIndexPathMap:(NSMapTable, NSIndexPath *> *)newIndexPathMap { if (self = [super init]) { _inserts = inserts; _deletes = deletes; _updates = updates; _moves = moves; _oldIndexPathMap = oldIndexPathMap; _newIndexPathMap = newIndexPathMap; } return self; } - (BOOL)hasChanges { return self.changeCount > 0; } - (NSInteger)changeCount { return self.inserts.count + self.deletes.count + self.updates.count + self.moves.count; } - (IGListIndexPathResult *)resultForBatchUpdates { NSMutableSet *deletes = [NSMutableSet setWithArray:self.deletes]; NSMutableSet *inserts = [NSMutableSet setWithArray:self.inserts]; NSMutableSet *filteredUpdates = [NSMutableSet setWithArray:self.updates]; NSArray *moves = self.moves; NSMutableArray *filteredMoves = [moves mutableCopy]; // convert move+update to delete+insert, respecting the from/to of the move const NSInteger moveCount = moves.count; for (NSInteger i = moveCount - 1; i >= 0; i--) { IGListMoveIndexPath *move = moves[i]; if ([filteredUpdates containsObject:move.from]) { [filteredMoves removeObjectAtIndex:i]; [filteredUpdates removeObject:move.from]; [deletes addObject:move.from]; [inserts addObject:move.to]; } } // iterate all new identifiers. if its index is updated, delete from the old index and insert the new index for (id key in [_oldIndexPathMap keyEnumerator]) { NSIndexPath *indexPath = [_oldIndexPathMap objectForKey:key]; if ([filteredUpdates containsObject:indexPath]) { [deletes addObject:indexPath]; [inserts addObject:(id)[_newIndexPathMap objectForKey:key]]; } } return [[IGListIndexPathResult alloc] initWithInserts:[inserts allObjects] deletes:[deletes allObjects] updates:[NSArray new] moves:filteredMoves oldIndexPathMap:_oldIndexPathMap newIndexPathMap:_newIndexPathMap]; } - (NSIndexPath *)oldIndexPathForIdentifier:(id)identifier { return [_oldIndexPathMap objectForKey:identifier]; } - (NSIndexPath *)newIndexPathForIdentifier:(id)identifier { return [_newIndexPathMap objectForKey:identifier]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p; %lu inserts; %lu deletes; %lu updates; %lu moves>", NSStringFromClass(self.class), self, (unsigned long)self.inserts.count, (unsigned long)self.deletes.count, (unsigned long)self.updates.count, (unsigned long)self.moves.count]; } @end ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListIndexSetResult.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import NS_ASSUME_NONNULL_BEGIN /** A result object returned when diffing with indexes. */ NS_SWIFT_NAME(ListIndexSetResult) @interface IGListIndexSetResult : NSObject /** The indexes inserted into the new collection. */ @property (nonatomic, strong, readonly) NSIndexSet *inserts; /** The indexes deleted from the old collection. */ @property (nonatomic, strong, readonly) NSIndexSet *deletes; /** The indexes in the old collection that need updated. */ @property (nonatomic, strong, readonly) NSIndexSet *updates; /** The moves from an index in the old collection to an index in the new collection. */ @property (nonatomic, copy, readonly) NSArray *moves; /** A Read-only boolean that indicates whether the result has any changes or not. `YES` if the result has changes, `NO` otherwise. */ @property (nonatomic, assign, readonly) BOOL hasChanges; /** Returns the index of the object with the specified identifier *before* the diff. @param identifier The diff identifier of the object. @return The index of the object before the diff, or `NSNotFound`. @see `-[IGListDiffable diffIdentifier]`. */ - (NSInteger)oldIndexForIdentifier:(id)identifier; /** Returns the index of the object with the specified identifier *after* the diff. @param identifier The diff identifier of the object. @return The index path of the object after the diff, or `NSNotFound`. @see `-[IGListDiffable diffIdentifier]`. */ - (NSInteger)newIndexForIdentifier:(id)identifier; /** Creates a new result object with operations safe for use in `UITableView` and `UICollectionView` batch updates. */ - (IGListIndexSetResult *)resultForBatchUpdates; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListIndexSetResult.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListIndexSetResult.h" #import "IGListIndexSetResultInternal.h" @implementation IGListIndexSetResult { NSMapTable, NSNumber *> *_oldIndexMap; NSMapTable, NSNumber *> *_newIndexMap; } - (instancetype)initWithInserts:(NSIndexSet *)inserts deletes:(NSIndexSet *)deletes updates:(NSIndexSet *)updates moves:(NSArray *)moves oldIndexMap:(NSMapTable, NSNumber *> *)oldIndexMap newIndexMap:(NSMapTable, NSNumber *> *)newIndexMap { if (self = [super init]) { _inserts = inserts; _deletes = deletes; _updates = updates; _moves = moves; _oldIndexMap = oldIndexMap; _newIndexMap = newIndexMap; } return self; } - (BOOL)hasChanges { return self.changeCount > 0; } - (NSInteger)changeCount { return self.inserts.count + self.deletes.count + self.updates.count + self.moves.count; } - (IGListIndexSetResult *)resultForBatchUpdates { NSMutableIndexSet *deletes = [self.deletes mutableCopy]; NSMutableIndexSet *inserts = [self.inserts mutableCopy]; NSMutableIndexSet *filteredUpdates = [self.updates mutableCopy]; NSArray *moves = self.moves; NSMutableArray *filteredMoves = [moves mutableCopy]; // convert all update+move to delete+insert const NSInteger moveCount = moves.count; for (NSInteger i = moveCount - 1; i >= 0; i--) { IGListMoveIndex *move = moves[i]; if ([filteredUpdates containsIndex:move.from]) { [filteredMoves removeObjectAtIndex:i]; [filteredUpdates removeIndex:move.from]; [deletes addIndex:move.from]; [inserts addIndex:move.to]; } } // iterate all new identifiers. if its index is updated, delete from the old index and insert the new index for (id key in [_oldIndexMap keyEnumerator]) { const NSInteger index = [[_oldIndexMap objectForKey:key] integerValue]; if ([filteredUpdates containsIndex:index]) { [deletes addIndex:index]; [inserts addIndex:[[_newIndexMap objectForKey:key] integerValue]]; } } return [[IGListIndexSetResult alloc] initWithInserts:inserts deletes:deletes updates:[NSIndexSet new] moves:filteredMoves oldIndexMap:_oldIndexMap newIndexMap:_newIndexMap]; } - (NSInteger)oldIndexForIdentifier:(id)identifier { NSNumber *index = [_oldIndexMap objectForKey:identifier]; return index == nil ? NSNotFound : [index integerValue]; } - (NSInteger)newIndexForIdentifier:(id)identifier { NSNumber *index = [_newIndexMap objectForKey:identifier]; return index == nil ? NSNotFound : [index integerValue]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p; %lu inserts; %lu deletes; %lu updates; %lu moves>", NSStringFromClass(self.class), self, (unsigned long)self.inserts.count, (unsigned long)self.deletes.count, (unsigned long)self.updates.count, (unsigned long)self.moves.count]; } @end ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListMacros.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #ifndef IGLK_SUBCLASSING_RESTRICTED #if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted) #define IGLK_SUBCLASSING_RESTRICTED __attribute__((objc_subclassing_restricted)) #else #define IGLK_SUBCLASSING_RESTRICTED #endif // #if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted) #endif // #ifndef IGLK_SUBCLASSING_RESTRICTED #ifndef IGLK_UNAVAILABLE #define IGLK_UNAVAILABLE(message) __attribute__((unavailable(message))) #endif // #ifndef IGLK_UNAVAILABLE #if IGLK_LOGGING_ENABLED #define IGLKLog( s, ... ) do { NSLog( @"IGListKit: %@", [NSString stringWithFormat: (s), ##__VA_ARGS__] ); } while(0) #else #define IGLKLog( s, ... ) #endif #ifndef IGLK_DEBUG_DESCRIPTION_ENABLED #define IGLK_DEBUG_DESCRIPTION_ENABLED DEBUG #endif // #ifndef IGLK_DEBUG_DESCRIPTION_ENABLED ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListMoveIndex.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import NS_ASSUME_NONNULL_BEGIN /** An object representing a move between indexes. */ NS_SWIFT_NAME(ListMoveIndex) @interface IGListMoveIndex : NSObject /** An index in the old collection. */ @property (nonatomic, assign, readonly) NSInteger from; /** An index in the new collection. */ @property (nonatomic, assign, readonly) NSInteger to; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListMoveIndex.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListMoveIndex.h" @implementation IGListMoveIndex - (instancetype)initWithFrom:(NSInteger)from to:(NSInteger)to { if (self = [super init]) { _from = from; _to = to; } return self; } - (NSUInteger)hash { return _from ^ _to; } - (BOOL)isEqual:(id)object { if (object == self) { return YES; } if ([object isKindOfClass:[IGListMoveIndex class]]) { const NSInteger f1 = self.from, f2 = [object from]; const NSInteger t1 = self.to, t2 = [object to]; return f1 == f2 && t1 == t2; } return NO; } - (NSComparisonResult)compare:(id)object { const NSInteger right = [object from]; const NSInteger left = [self from]; if (left == right) { return NSOrderedSame; } else if (left < right) { return NSOrderedAscending; } else { return NSOrderedDescending; } } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p; from: %li; to: %li;>", NSStringFromClass(self.class), self, (long)self.from, (long)self.to]; } @end ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListMoveIndexPath.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import NS_ASSUME_NONNULL_BEGIN /** An object representing a move between indexes. */ NS_SWIFT_NAME(ListMoveIndexPath) @interface IGListMoveIndexPath : NSObject /** An index path in the old collection. */ @property (nonatomic, strong, readonly) NSIndexPath *from; /** An index path in the new collection. */ @property (nonatomic, strong, readonly) NSIndexPath *to; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/IGListMoveIndexPath.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListMoveIndexPath.h" @implementation IGListMoveIndexPath - (instancetype)initWithFrom:(NSIndexPath *)from to:(NSIndexPath *)to { NSParameterAssert(from != nil); NSParameterAssert(to != nil); if (self = [super init]) { _from = from; _to = to; } return self; } - (NSUInteger)hash { return [_from hash] ^ [_to hash]; } - (BOOL)isEqual:(id)object { if (object == self) { return YES; } if ([object isKindOfClass:[IGListMoveIndexPath class]]) { NSIndexPath *f1 = self.from, *f2 = [object from]; NSIndexPath *t1 = self.to, *t2 = [object to]; return [f1 isEqual:f2] && [t1 isEqual:t2]; } return NO; } - (NSComparisonResult)compare:(id)object { return [[self from] compare:[object from]]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p; from: %@; to: %@;>", NSStringFromClass(self.class), self, self.from, self.to]; } @end ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/Internal/IGListIndexPathResultInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import NS_ASSUME_NONNULL_BEGIN @interface IGListIndexPathResult() - (instancetype)initWithInserts:(NSArray *)inserts deletes:(NSArray *)deletes updates:(NSArray *)updates moves:(NSArray *)moves oldIndexPathMap:(NSMapTable, NSIndexPath *> *)oldIndexPathMap newIndexPathMap:(NSMapTable, NSIndexPath *> *)newIndexPathMap; @property (nonatomic, assign, readonly) NSInteger changeCount; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/Internal/IGListIndexSetResultInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import NS_ASSUME_NONNULL_BEGIN @interface IGListIndexSetResult() - (instancetype)initWithInserts:(NSIndexSet *)inserts deletes:(NSIndexSet *)deletes updates:(NSIndexSet *)updates moves:(NSArray *)moves oldIndexMap:(NSMapTable, NSNumber *> *)oldIndexMap newIndexMap:(NSMapTable, NSNumber *> *)newIndexMap; @property (nonatomic, assign, readonly) NSInteger changeCount; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/Internal/IGListMoveIndexInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import NS_ASSUME_NONNULL_BEGIN @interface IGListMoveIndex () - (instancetype)initWithFrom:(NSInteger)from to:(NSInteger)to NS_DESIGNATED_INITIALIZER; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/Internal/IGListMoveIndexPathInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import NS_ASSUME_NONNULL_BEGIN @interface IGListMoveIndexPath () - (instancetype)initWithFrom:(NSIndexPath *)from to:(NSIndexPath *)to NS_DESIGNATED_INITIALIZER; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/NSNumber+IGListDiffable.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import /** This category provides default `IGListDiffable` conformance for `NSNumber`. */ @interface NSNumber (IGListDiffable) @end ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/NSNumber+IGListDiffable.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "NSNumber+IGListDiffable.h" @implementation NSNumber (IGListDiffable) - (id)diffIdentifier { return self; } - (BOOL)isEqualToDiffableObject:(id)object { return [self isEqual:object]; } @end ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/NSString+IGListDiffable.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import /** This category provides default `IGListDiffable` conformance for `NSString`. */ @interface NSString (IGListDiffable) @end ================================================ FILE: JetChat/Pods/IGListDiffKit/Source/IGListDiffKit/NSString+IGListDiffable.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "NSString+IGListDiffable.h" @implementation NSString (IGListDiffable) - (id)diffIdentifier { return self; } - (BOOL)isEqualToDiffableObject:(id)object { return [self isEqual:object]; } @end ================================================ FILE: JetChat/Pods/IGListDiffKit.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 0E7B5A4FA3947991B079FC8036B1D57E /* NSString+IGListDiffable.m in Sources */ = {isa = PBXBuildFile; fileRef = 891CC7C693988BFC9A8D26C0301B8648 /* NSString+IGListDiffable.m */; }; 1C0FEC4DBC92CA502EB97ABECD940922 /* NSNumber+IGListDiffable.h in Headers */ = {isa = PBXBuildFile; fileRef = 4500275AFBA8B77116EC51FBF9CDB67A /* NSNumber+IGListDiffable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1C41AA2C97C7743D2B5FACD8981DBD1D /* NSNumber+IGListDiffable.m in Sources */ = {isa = PBXBuildFile; fileRef = 563A74A4AAE0AE3F80D15DD13EAA1FD9 /* NSNumber+IGListDiffable.m */; }; 1CA3C8653E3F742C0FA38B7589F39B40 /* IGListDiffKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = CAF7C8ABD4F22C75D54B309648589D36 /* IGListDiffKit-dummy.m */; }; 21DE7B226CB11026BEB28EE188785E5D /* IGListIndexPathResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 85B69351814D2368E6A962E35AB63843 /* IGListIndexPathResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2E428040542468E65509083E3B6CF0F6 /* IGListCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = D305A8FA60FA27EF707A75D040E0DF36 /* IGListCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3BC80255D812EB19D1F0A5F4F9047F86 /* IGListIndexPathResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D040CE96D774B44BD2D25941B823775 /* IGListIndexPathResult.m */; }; 3C991924113297BCE709D5A164F250E4 /* IGListBatchUpdateData.mm in Sources */ = {isa = PBXBuildFile; fileRef = E9744EADF79114CCF14C8CC73294E313 /* IGListBatchUpdateData.mm */; }; 41559189D435B4EEDD5CFD198FDCF73F /* IGListBatchUpdateData.h in Headers */ = {isa = PBXBuildFile; fileRef = FEDBAE08ED0521773AEF7C6EE588C21F /* IGListBatchUpdateData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6664644E3E0B730609FB3B13926D871F /* IGListMoveIndexPathInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 52DC5E1716D36FEAF32C7C38B5EB7FC6 /* IGListMoveIndexPathInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 6BFE1372141D1D1C93E44E508CDB9E67 /* IGListMoveIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 529D8B78455D278363BA20251A9036A1 /* IGListMoveIndex.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6F44268A22C89E67711B2D9686BEC00C /* IGListMoveIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = 58A29333CCD6F15FBB167E1B9F9F0686 /* IGListMoveIndex.m */; }; 6FC4B2264ED4D460053E300C3E5BE109 /* IGListIndexPathResultInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1B1CD1A034AD3F64EF1A6B1E12871787 /* IGListIndexPathResultInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 71D7D70B6E8302C82CBCD8EACAC20D7C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D25C9CCFEB555036C7B8D4289B775FD3 /* Foundation.framework */; }; 805C1C2E0B9E4E74E69C9209453D901F /* IGListMoveIndexInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = CAE24744139B94E829E268A9D55522EF /* IGListMoveIndexInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 843D71F9C98B0BCF89B5927C99C66A71 /* IGListDiffable.h in Headers */ = {isa = PBXBuildFile; fileRef = 688D88734730651A988AA978538DA29B /* IGListDiffable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8774A64D1962DFA2002048AC7B10344E /* IGListMoveIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F07DA24694AA6DB43A506C9DEA41617 /* IGListMoveIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8800BD3FBF12AEC2C82020FB97363427 /* IGListDiffKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 50486C82FB8A45758EA656C470AAF32D /* IGListDiffKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8BF32B5178F06AB0468C2FA7D10D1134 /* NSString+IGListDiffable.h in Headers */ = {isa = PBXBuildFile; fileRef = D5879AB360AAD21A13724A3E132B2033 /* NSString+IGListDiffable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9431688AF26EBCB3A62B39BBE3AFDDC8 /* IGListDiff.mm in Sources */ = {isa = PBXBuildFile; fileRef = D34C93E79073FEB04582F4513327E0FC /* IGListDiff.mm */; }; B2545C4C4EA775048BF0ABF7994B9D1D /* IGListDiff.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A221EDAD4E2184FCCA736DAE49BED72 /* IGListDiff.h */; settings = {ATTRIBUTES = (Public, ); }; }; B484BDF8EDCA5419E87AFC9C84C51CED /* IGListMoveIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 898B5668B5F49B68967804ED76E9DA5C /* IGListMoveIndexPath.m */; }; B59E967EAC4DC977EA6C8A7D7E4F3371 /* IGListIndexSetResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A8AAAAF750EB65101D319B91D06A6B8 /* IGListIndexSetResult.m */; }; CED21D9D67576CD13A71FB3B5AF34B69 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66138C4701012355A8B43FDC990F9E79 /* UIKit.framework */; }; D1C38BED14869358A5B6E53FB461F6F7 /* IGListIndexSetResultInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DB29E6E4830E5DB57D6F1ED8C147E99 /* IGListIndexSetResultInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; E1D9BD1F669E7D4A6A466442B4A5F441 /* IGListAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D57CBA4A6A588D33F27392773D9F6DD /* IGListAssert.h */; settings = {ATTRIBUTES = (Public, ); }; }; E5868779EDF1A0EECF30F714AABD1A97 /* IGListDiffKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 53FB42C0F73BE3B531945BE429513243 /* IGListDiffKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; E75E6AC70F1D2148A70312AF9E370154 /* IGListIndexSetResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 797790920421ACCB6E4AD187A8C02567 /* IGListIndexSetResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; EC223EBF07D5D0EC88CECB2A47CF74DE /* IGListMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FCD7ECDFEC351BA09EAEF9216D21A30 /* IGListMacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; F5BDE92E37209743EB917C28668FC993 /* IGListExperiments.h in Headers */ = {isa = PBXBuildFile; fileRef = 07C58B2CD3CE9171839925A43D7509F9 /* IGListExperiments.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 07C58B2CD3CE9171839925A43D7509F9 /* IGListExperiments.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListExperiments.h; path = Source/IGListDiffKit/IGListExperiments.h; sourceTree = ""; }; 09906A73B522E0B399586722C2E4010E /* IGListDiffKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IGListDiffKit.debug.xcconfig; sourceTree = ""; }; 1A221EDAD4E2184FCCA736DAE49BED72 /* IGListDiff.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListDiff.h; path = Source/IGListDiffKit/IGListDiff.h; sourceTree = ""; }; 1B1CD1A034AD3F64EF1A6B1E12871787 /* IGListIndexPathResultInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListIndexPathResultInternal.h; path = Source/IGListDiffKit/Internal/IGListIndexPathResultInternal.h; sourceTree = ""; }; 2D040CE96D774B44BD2D25941B823775 /* IGListIndexPathResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListIndexPathResult.m; path = Source/IGListDiffKit/IGListIndexPathResult.m; sourceTree = ""; }; 3A8AAAAF750EB65101D319B91D06A6B8 /* IGListIndexSetResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListIndexSetResult.m; path = Source/IGListDiffKit/IGListIndexSetResult.m; sourceTree = ""; }; 3D09B8D220B715033D7540B4338537C6 /* IGListDiffKit */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = IGListDiffKit; path = IGListDiffKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3FCD7ECDFEC351BA09EAEF9216D21A30 /* IGListMacros.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListMacros.h; path = Source/IGListDiffKit/IGListMacros.h; sourceTree = ""; }; 4500275AFBA8B77116EC51FBF9CDB67A /* NSNumber+IGListDiffable.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSNumber+IGListDiffable.h"; path = "Source/IGListDiffKit/NSNumber+IGListDiffable.h"; sourceTree = ""; }; 4AEF45B68E019E8EFB6FCE002BB87957 /* IGListDiffKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IGListDiffKit.release.xcconfig; sourceTree = ""; }; 4B2DA503A39987D3759F731F635458D3 /* IGListDiffKit.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = IGListDiffKit.modulemap; sourceTree = ""; }; 4DB29E6E4830E5DB57D6F1ED8C147E99 /* IGListIndexSetResultInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListIndexSetResultInternal.h; path = Source/IGListDiffKit/Internal/IGListIndexSetResultInternal.h; sourceTree = ""; }; 50486C82FB8A45758EA656C470AAF32D /* IGListDiffKit.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListDiffKit.h; path = Source/IGListDiffKit/IGListDiffKit.h; sourceTree = ""; }; 529D8B78455D278363BA20251A9036A1 /* IGListMoveIndex.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListMoveIndex.h; path = Source/IGListDiffKit/IGListMoveIndex.h; sourceTree = ""; }; 52DC5E1716D36FEAF32C7C38B5EB7FC6 /* IGListMoveIndexPathInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListMoveIndexPathInternal.h; path = Source/IGListDiffKit/Internal/IGListMoveIndexPathInternal.h; sourceTree = ""; }; 53FB42C0F73BE3B531945BE429513243 /* IGListDiffKit-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IGListDiffKit-umbrella.h"; sourceTree = ""; }; 563A74A4AAE0AE3F80D15DD13EAA1FD9 /* NSNumber+IGListDiffable.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSNumber+IGListDiffable.m"; path = "Source/IGListDiffKit/NSNumber+IGListDiffable.m"; sourceTree = ""; }; 58A29333CCD6F15FBB167E1B9F9F0686 /* IGListMoveIndex.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListMoveIndex.m; path = Source/IGListDiffKit/IGListMoveIndex.m; sourceTree = ""; }; 66138C4701012355A8B43FDC990F9E79 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 688D88734730651A988AA978538DA29B /* IGListDiffable.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListDiffable.h; path = Source/IGListDiffKit/IGListDiffable.h; sourceTree = ""; }; 68DC990799B9F016F837531000EADBFB /* IGListDiffKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IGListDiffKit-prefix.pch"; sourceTree = ""; }; 797790920421ACCB6E4AD187A8C02567 /* IGListIndexSetResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListIndexSetResult.h; path = Source/IGListDiffKit/IGListIndexSetResult.h; sourceTree = ""; }; 85B69351814D2368E6A962E35AB63843 /* IGListIndexPathResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListIndexPathResult.h; path = Source/IGListDiffKit/IGListIndexPathResult.h; sourceTree = ""; }; 891CC7C693988BFC9A8D26C0301B8648 /* NSString+IGListDiffable.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+IGListDiffable.m"; path = "Source/IGListDiffKit/NSString+IGListDiffable.m"; sourceTree = ""; }; 898B5668B5F49B68967804ED76E9DA5C /* IGListMoveIndexPath.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListMoveIndexPath.m; path = Source/IGListDiffKit/IGListMoveIndexPath.m; sourceTree = ""; }; 8D57CBA4A6A588D33F27392773D9F6DD /* IGListAssert.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAssert.h; path = Source/IGListDiffKit/IGListAssert.h; sourceTree = ""; }; 8F07DA24694AA6DB43A506C9DEA41617 /* IGListMoveIndexPath.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListMoveIndexPath.h; path = Source/IGListDiffKit/IGListMoveIndexPath.h; sourceTree = ""; }; 9579ACF571D506683799F4307415BCC0 /* IGListDiffKit-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "IGListDiffKit-Info.plist"; sourceTree = ""; }; CAE24744139B94E829E268A9D55522EF /* IGListMoveIndexInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListMoveIndexInternal.h; path = Source/IGListDiffKit/Internal/IGListMoveIndexInternal.h; sourceTree = ""; }; CAF7C8ABD4F22C75D54B309648589D36 /* IGListDiffKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "IGListDiffKit-dummy.m"; sourceTree = ""; }; D25C9CCFEB555036C7B8D4289B775FD3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; D305A8FA60FA27EF707A75D040E0DF36 /* IGListCompatibility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListCompatibility.h; path = Source/IGListDiffKit/IGListCompatibility.h; sourceTree = ""; }; D34C93E79073FEB04582F4513327E0FC /* IGListDiff.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = IGListDiff.mm; path = Source/IGListDiffKit/IGListDiff.mm; sourceTree = ""; }; D5879AB360AAD21A13724A3E132B2033 /* NSString+IGListDiffable.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+IGListDiffable.h"; path = "Source/IGListDiffKit/NSString+IGListDiffable.h"; sourceTree = ""; }; E9744EADF79114CCF14C8CC73294E313 /* IGListBatchUpdateData.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = IGListBatchUpdateData.mm; path = Source/IGListDiffKit/IGListBatchUpdateData.mm; sourceTree = ""; }; FEDBAE08ED0521773AEF7C6EE588C21F /* IGListBatchUpdateData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListBatchUpdateData.h; path = Source/IGListDiffKit/IGListBatchUpdateData.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 3313EB213E440351B6DDEB1787E2FACD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 71D7D70B6E8302C82CBCD8EACAC20D7C /* Foundation.framework in Frameworks */, CED21D9D67576CD13A71FB3B5AF34B69 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0213B4968B149ADE9A6FB192D8431B4E /* Products */ = { isa = PBXGroup; children = ( 3D09B8D220B715033D7540B4338537C6 /* IGListDiffKit */, ); name = Products; sourceTree = ""; }; 32A04C87608171B5B1C10FDA65C61137 /* Frameworks */ = { isa = PBXGroup; children = ( 87ED95DC7ED385F8EB7A0B20281E5F14 /* iOS */, ); name = Frameworks; sourceTree = ""; }; 51E6DDA9E73D1A9EE26513524CB577A5 = { isa = PBXGroup; children = ( 32A04C87608171B5B1C10FDA65C61137 /* Frameworks */, A8F56ED980735121AED4CFC967C59371 /* IGListDiffKit */, 0213B4968B149ADE9A6FB192D8431B4E /* Products */, ); sourceTree = ""; }; 686053AFCF0016C9C90270948248D52A /* Support Files */ = { isa = PBXGroup; children = ( 4B2DA503A39987D3759F731F635458D3 /* IGListDiffKit.modulemap */, CAF7C8ABD4F22C75D54B309648589D36 /* IGListDiffKit-dummy.m */, 9579ACF571D506683799F4307415BCC0 /* IGListDiffKit-Info.plist */, 68DC990799B9F016F837531000EADBFB /* IGListDiffKit-prefix.pch */, 53FB42C0F73BE3B531945BE429513243 /* IGListDiffKit-umbrella.h */, 09906A73B522E0B399586722C2E4010E /* IGListDiffKit.debug.xcconfig */, 4AEF45B68E019E8EFB6FCE002BB87957 /* IGListDiffKit.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/IGListDiffKit"; sourceTree = ""; }; 87ED95DC7ED385F8EB7A0B20281E5F14 /* iOS */ = { isa = PBXGroup; children = ( D25C9CCFEB555036C7B8D4289B775FD3 /* Foundation.framework */, 66138C4701012355A8B43FDC990F9E79 /* UIKit.framework */, ); name = iOS; sourceTree = ""; }; A8F56ED980735121AED4CFC967C59371 /* IGListDiffKit */ = { isa = PBXGroup; children = ( 8D57CBA4A6A588D33F27392773D9F6DD /* IGListAssert.h */, FEDBAE08ED0521773AEF7C6EE588C21F /* IGListBatchUpdateData.h */, E9744EADF79114CCF14C8CC73294E313 /* IGListBatchUpdateData.mm */, D305A8FA60FA27EF707A75D040E0DF36 /* IGListCompatibility.h */, 1A221EDAD4E2184FCCA736DAE49BED72 /* IGListDiff.h */, D34C93E79073FEB04582F4513327E0FC /* IGListDiff.mm */, 688D88734730651A988AA978538DA29B /* IGListDiffable.h */, 50486C82FB8A45758EA656C470AAF32D /* IGListDiffKit.h */, 07C58B2CD3CE9171839925A43D7509F9 /* IGListExperiments.h */, 85B69351814D2368E6A962E35AB63843 /* IGListIndexPathResult.h */, 2D040CE96D774B44BD2D25941B823775 /* IGListIndexPathResult.m */, 1B1CD1A034AD3F64EF1A6B1E12871787 /* IGListIndexPathResultInternal.h */, 797790920421ACCB6E4AD187A8C02567 /* IGListIndexSetResult.h */, 3A8AAAAF750EB65101D319B91D06A6B8 /* IGListIndexSetResult.m */, 4DB29E6E4830E5DB57D6F1ED8C147E99 /* IGListIndexSetResultInternal.h */, 3FCD7ECDFEC351BA09EAEF9216D21A30 /* IGListMacros.h */, 529D8B78455D278363BA20251A9036A1 /* IGListMoveIndex.h */, 58A29333CCD6F15FBB167E1B9F9F0686 /* IGListMoveIndex.m */, CAE24744139B94E829E268A9D55522EF /* IGListMoveIndexInternal.h */, 8F07DA24694AA6DB43A506C9DEA41617 /* IGListMoveIndexPath.h */, 898B5668B5F49B68967804ED76E9DA5C /* IGListMoveIndexPath.m */, 52DC5E1716D36FEAF32C7C38B5EB7FC6 /* IGListMoveIndexPathInternal.h */, 4500275AFBA8B77116EC51FBF9CDB67A /* NSNumber+IGListDiffable.h */, 563A74A4AAE0AE3F80D15DD13EAA1FD9 /* NSNumber+IGListDiffable.m */, D5879AB360AAD21A13724A3E132B2033 /* NSString+IGListDiffable.h */, 891CC7C693988BFC9A8D26C0301B8648 /* NSString+IGListDiffable.m */, 686053AFCF0016C9C90270948248D52A /* Support Files */, ); name = IGListDiffKit; path = IGListDiffKit; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ DFEDAEE18D870C0BE780FF3982BD969E /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( E1D9BD1F669E7D4A6A466442B4A5F441 /* IGListAssert.h in Headers */, 41559189D435B4EEDD5CFD198FDCF73F /* IGListBatchUpdateData.h in Headers */, 2E428040542468E65509083E3B6CF0F6 /* IGListCompatibility.h in Headers */, B2545C4C4EA775048BF0ABF7994B9D1D /* IGListDiff.h in Headers */, 843D71F9C98B0BCF89B5927C99C66A71 /* IGListDiffable.h in Headers */, 8800BD3FBF12AEC2C82020FB97363427 /* IGListDiffKit.h in Headers */, E5868779EDF1A0EECF30F714AABD1A97 /* IGListDiffKit-umbrella.h in Headers */, F5BDE92E37209743EB917C28668FC993 /* IGListExperiments.h in Headers */, 21DE7B226CB11026BEB28EE188785E5D /* IGListIndexPathResult.h in Headers */, 6FC4B2264ED4D460053E300C3E5BE109 /* IGListIndexPathResultInternal.h in Headers */, E75E6AC70F1D2148A70312AF9E370154 /* IGListIndexSetResult.h in Headers */, D1C38BED14869358A5B6E53FB461F6F7 /* IGListIndexSetResultInternal.h in Headers */, EC223EBF07D5D0EC88CECB2A47CF74DE /* IGListMacros.h in Headers */, 6BFE1372141D1D1C93E44E508CDB9E67 /* IGListMoveIndex.h in Headers */, 805C1C2E0B9E4E74E69C9209453D901F /* IGListMoveIndexInternal.h in Headers */, 8774A64D1962DFA2002048AC7B10344E /* IGListMoveIndexPath.h in Headers */, 6664644E3E0B730609FB3B13926D871F /* IGListMoveIndexPathInternal.h in Headers */, 1C0FEC4DBC92CA502EB97ABECD940922 /* NSNumber+IGListDiffable.h in Headers */, 8BF32B5178F06AB0468C2FA7D10D1134 /* NSString+IGListDiffable.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 9074A5DFB260E6240F743D74D3F432DD /* IGListDiffKit */ = { isa = PBXNativeTarget; buildConfigurationList = 90343FA0F0F3FD4E515F8011D89B7655 /* Build configuration list for PBXNativeTarget "IGListDiffKit" */; buildPhases = ( DFEDAEE18D870C0BE780FF3982BD969E /* Headers */, 434AF7F7EF11B97F7B0A49C9D84CE274 /* Sources */, 3313EB213E440351B6DDEB1787E2FACD /* Frameworks */, 7E74210CCEC52B33E183CF0F9BFEE885 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = IGListDiffKit; productName = IGListDiffKit; productReference = 3D09B8D220B715033D7540B4338537C6 /* IGListDiffKit */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 8B76D7491E1F67A0F4D5DF287FE0F5DD /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 5D29734D6ABF87BF0167C68BFC6AAC1B /* Build configuration list for PBXProject "IGListDiffKit" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 51E6DDA9E73D1A9EE26513524CB577A5; productRefGroup = 0213B4968B149ADE9A6FB192D8431B4E /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 9074A5DFB260E6240F743D74D3F432DD /* IGListDiffKit */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 7E74210CCEC52B33E183CF0F9BFEE885 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 434AF7F7EF11B97F7B0A49C9D84CE274 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3C991924113297BCE709D5A164F250E4 /* IGListBatchUpdateData.mm in Sources */, 9431688AF26EBCB3A62B39BBE3AFDDC8 /* IGListDiff.mm in Sources */, 1CA3C8653E3F742C0FA38B7589F39B40 /* IGListDiffKit-dummy.m in Sources */, 3BC80255D812EB19D1F0A5F4F9047F86 /* IGListIndexPathResult.m in Sources */, B59E967EAC4DC977EA6C8A7D7E4F3371 /* IGListIndexSetResult.m in Sources */, 6F44268A22C89E67711B2D9686BEC00C /* IGListMoveIndex.m in Sources */, B484BDF8EDCA5419E87AFC9C84C51CED /* IGListMoveIndexPath.m in Sources */, 1C41AA2C97C7743D2B5FACD8981DBD1D /* NSNumber+IGListDiffable.m in Sources */, 0E7B5A4FA3947991B079FC8036B1D57E /* NSString+IGListDiffable.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 831D2B424C71C452F2DECB3B1CC388D2 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; 98DB69DBC18935DE6609ABCD9987EA2C /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 09906A73B522E0B399586722C2E4010E /* IGListDiffKit.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/IGListDiffKit/IGListDiffKit-prefix.pch"; INFOPLIST_FILE = "Target Support Files/IGListDiffKit/IGListDiffKit-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/IGListDiffKit/IGListDiffKit.modulemap"; PRODUCT_MODULE_NAME = IGListDiffKit; PRODUCT_NAME = IGListDiffKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; AA1381E1295BC961C43CB26FA88E977A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; AFA842305C0BC253DE7DDA56FDA40357 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4AEF45B68E019E8EFB6FCE002BB87957 /* IGListDiffKit.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/IGListDiffKit/IGListDiffKit-prefix.pch"; INFOPLIST_FILE = "Target Support Files/IGListDiffKit/IGListDiffKit-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/IGListDiffKit/IGListDiffKit.modulemap"; PRODUCT_MODULE_NAME = IGListDiffKit; PRODUCT_NAME = IGListDiffKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 5D29734D6ABF87BF0167C68BFC6AAC1B /* Build configuration list for PBXProject "IGListDiffKit" */ = { isa = XCConfigurationList; buildConfigurations = ( AA1381E1295BC961C43CB26FA88E977A /* Debug */, 831D2B424C71C452F2DECB3B1CC388D2 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 90343FA0F0F3FD4E515F8011D89B7655 /* Build configuration list for PBXNativeTarget "IGListDiffKit" */ = { isa = XCConfigurationList; buildConfigurations = ( 98DB69DBC18935DE6609ABCD9987EA2C /* Debug */, AFA842305C0BC253DE7DDA56FDA40357 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 8B76D7491E1F67A0F4D5DF287FE0F5DD /* Project object */; } ================================================ FILE: JetChat/Pods/IGListKit/LICENSE.md ================================================ MIT License Copyright (c) Facebook, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/IGListKit/README.md ================================================

Build Status Coverage Status Pods Version Platforms Carthage Compatible

---------------- A data-driven `UICollectionView` framework for building fast and flexible lists. | | Main Features | ----------|----------------- 🙅 | Never call `performBatchUpdates(_:, completion:)` or `reloadData()` again 🏠 | Better architecture with reusable cells and components 🔠 | Create collections with multiple data types 🔑 | Decoupled diffing algorithm ✅ | Fully unit tested 🔍 | Customize your diffing behavior for your models 📱 | Simply `UICollectionView` at its core 🚀 | Extendable API 🐦 | Written in Objective-C with full Swift interop support `IGListKit` is built and maintained with ❤️ by [Instagram engineering](https://engineering.instagram.com/). We use the open source version `master` branch in the Instagram app. ## Requirements - Xcode 9.0+ - iOS 9.0+ - tvOS 9.0+ - macOS 10.11+ *(diffing algorithm components only)* - Interoperability with Swift 3.0+ ## Installation ### CocoaPods The preferred installation method is with [CocoaPods](https://cocoapods.org). Add the following to your `Podfile`: ```ruby pod 'IGListKit', '~> 4.0.0' ``` ### Carthage For [Carthage](https://github.com/Carthage/Carthage), add the following to your `Cartfile`: ```ogdl github "Instagram/IGListKit" ~> 4.0.0 ``` > For advanced usage, see our [Installation Guide](https://instagram.github.io/IGListKit/installation.html). ## Getting Started ```bash $ git clone https://github.com/Instagram/IGListKit.git $ cd IGListKit/ $ ./scripts/setup.sh ``` - Our [Getting Started guide](https://instagram.github.io/IGListKit/getting-started.html) - Ray Wenderlich's [IGListKit Tutorial: Better UICollectionViews](https://www.raywenderlich.com/147162/iglistkit-tutorial-better-uicollectionviews) - Our [example projects](https://github.com/Instagram/IGListKit/tree/master/Examples) - Ryan Nystrom's [talk at try! Swift NYC](https://realm.io/news/tryswift-ryan-nystrom-refactoring-at-scale-lessons-learned-rewriting-instagram-feed/) (Note: this talk was for an earlier version. Some APIs have changed.) - [Migrating an UITableView to IGListCollectionView](https://medium.com/cocoaacademymag/iglistkit-migrating-an-uitableview-to-iglistkitcollectionview-65a30cf9bac9), by Rodrigo Cavalcante - [Keeping data fresh in Buffer for iOS with AsyncDisplayKit, IGListKit & Pusher](https://overflow.buffer.com/2017/04/10/keeping-data-fresh-buffer-ios-asyncdisplaykit-iglistkit-pusher/), Andy Yates, Buffer ## Documentation You can find [the docs here](https://instagram.github.io/IGListKit). Documentation is generated with [jazzy](https://github.com/realm/jazzy) and hosted on [GitHub-Pages](https://pages.github.com). To regenerate docs, run `./scripts/build_docs.sh` from the root directory in the repo. ## Vision For the long-term goals and "vision" of `IGListKit`, please read our [Vision](https://github.com/Instagram/IGListKit/blob/master/Guides/VISION.md) doc. ## Contributing Please see the [CONTRIBUTING](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md) file for how to help. At Instagram, we sync the open source version of `IGListKit` daily, so we're always testing the latest changes. But that requires all changes be thoroughly tested and follow our style guide. We have a set of [starter tasks](https://github.com/Instagram/IGListKit/issues?q=is%3Aissue+is%3Aopen+label%3Astarter-task) that are great for beginners to jump in on and start contributing. ## License `IGListKit` is [MIT-licensed](./LICENSE). The files in the `/Examples/` directory are licensed under a separate license as specified in each file. Documentation is licensed [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/). ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListDiffKit/Internal/IGListIndexPathResultInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import NS_ASSUME_NONNULL_BEGIN @interface IGListIndexPathResult() - (instancetype)initWithInserts:(NSArray *)inserts deletes:(NSArray *)deletes updates:(NSArray *)updates moves:(NSArray *)moves oldIndexPathMap:(NSMapTable, NSIndexPath *> *)oldIndexPathMap newIndexPathMap:(NSMapTable, NSIndexPath *> *)newIndexPathMap; @property (nonatomic, assign, readonly) NSInteger changeCount; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListDiffKit/Internal/IGListIndexSetResultInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import NS_ASSUME_NONNULL_BEGIN @interface IGListIndexSetResult() - (instancetype)initWithInserts:(NSIndexSet *)inserts deletes:(NSIndexSet *)deletes updates:(NSIndexSet *)updates moves:(NSArray *)moves oldIndexMap:(NSMapTable, NSNumber *> *)oldIndexMap newIndexMap:(NSMapTable, NSNumber *> *)newIndexMap; @property (nonatomic, assign, readonly) NSInteger changeCount; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListDiffKit/Internal/IGListMoveIndexInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import NS_ASSUME_NONNULL_BEGIN @interface IGListMoveIndex () - (instancetype)initWithFrom:(NSInteger)from to:(NSInteger)to NS_DESIGNATED_INITIALIZER; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListDiffKit/Internal/IGListMoveIndexPathInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import NS_ASSUME_NONNULL_BEGIN @interface IGListMoveIndexPath () - (instancetype)initWithFrom:(NSIndexPath *)from to:(NSIndexPath *)to NS_DESIGNATED_INITIALIZER; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListAdapter.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import #import #import #import #import #import @protocol IGListUpdatingDelegate; @class IGListSectionController; NS_ASSUME_NONNULL_BEGIN /** A block to execute when the list updates are completed. @param finished Specifies whether or not the update animations completed successfully. */ NS_SWIFT_NAME(ListUpdaterCompletion) typedef void (^IGListUpdaterCompletion)(BOOL finished); /** `IGListAdapter` objects provide an abstraction for feeds of objects in a `UICollectionView` by breaking each object into individual sections, called "section controllers". These controllers (objects subclassing to `IGListSectionController`) act as a data source and delegate for each section. Feed implementations must act as the data source for an `IGListAdapter` in order to drive the objects and section controllers in a collection view. */ IGLK_SUBCLASSING_RESTRICTED NS_SWIFT_NAME(ListAdapter) @interface IGListAdapter : NSObject /** The view controller that houses the adapter. */ @property (nonatomic, nullable, weak) UIViewController *viewController; /** The collection view used with the adapter. @note Setting this property will automatically set isPrefetchingEnabled to `NO` for performance reasons. */ @property (nonatomic, nullable, weak) UICollectionView *collectionView; /** The object that acts as the data source for the adapter. */ @property (nonatomic, nullable, weak) id dataSource; /** The object that receives top-level events for section controllers. */ @property (nonatomic, nullable, weak) id delegate; /** The object that receives `UICollectionViewDelegate` events. @note This object *will not* receive `UIScrollViewDelegate` events. Instead use scrollViewDelegate. */ @property (nonatomic, nullable, weak) id collectionViewDelegate; /** The object that receives `UIScrollViewDelegate` events. */ @property (nonatomic, nullable, weak) id scrollViewDelegate; /** The object that receives `IGListAdapterMoveDelegate` events resulting from interactive reordering of sections. @note This works with UICollectionView interactive reordering available on iOS 9.0+ */ @property (nonatomic, nullable, weak) id moveDelegate NS_AVAILABLE_IOS(9_0); /** The object that receives `IGListAdapterPerformanceDelegate` events to measure performance. */ @property (nonatomic, nullable, weak) id performanceDelegate; /** The updater for the adapter. */ @property (nonatomic, strong, readonly) id updater; /** A bitmask of experiments to conduct on the adapter. */ @property (nonatomic, assign) IGListExperiment experiments; /** Initializes a new `IGListAdapter` object. @param updater An object that manages updates to the collection view. @param viewController The view controller that will house the adapter. @param workingRangeSize The number of objects before and after the viewport to consider within the working range. @return A new list adapter object. @note The working range is the number of objects beyond the visible objects (plus and minus) that should be notified when they are close to being visible. For instance, if you have 3 objects on screen and a working range of 2, the previous and succeeding 2 objects will be notified that they are within the working range. As you scroll the list the range is updated as objects enter and exit the working range. To opt out of using the working range, use `initWithUpdater:viewController:` or provide a working range of `0`. */ - (instancetype)initWithUpdater:(id )updater viewController:(nullable UIViewController *)viewController workingRangeSize:(NSInteger)workingRangeSize NS_DESIGNATED_INITIALIZER; /** Initializes a new `IGListAdapter` object with a working range of `0`. @param updater An object that manages updates to the collection view. @param viewController The view controller that will house the adapter. @return A new list adapter object. */ - (instancetype)initWithUpdater:(id )updater viewController:(nullable UIViewController *)viewController; /** Perform an update from the previous state of the data source. This is analogous to calling `-[UICollectionView performBatchUpdates:completion:]`. @param animated A flag indicating if the transition should be animated. @param completion The block to execute when the updates complete. */ - (void)performUpdatesAnimated:(BOOL)animated completion:(nullable IGListUpdaterCompletion)completion; /** Perform an immediate reload of the data in the data source, discarding the old objects. @param completion The block to execute when the reload completes. @warning Do not use this method to update without animations as it can be very expensive to teardown and rebuild all section controllers. Use `-[IGListAdapter performUpdatesAnimated:completion]` instead. */ - (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion; /** Reload the list for only the specified objects. @param objects The objects to reload. */ - (void)reloadObjects:(NSArray *)objects; /** Query the section controller at a given section index. Constant time lookup. @param section A section in the list. @return A section controller or `nil` if the section does not exist. */ - (nullable IGListSectionController *)sectionControllerForSection:(NSInteger)section; /** Query the section index of a list. Constant time lookup. @param sectionController A list object. @return The section index of the list if it exists, otherwise `NSNotFound`. */ - (NSInteger)sectionForSectionController:(IGListSectionController *)sectionController; /** Returns the section controller for the specified object. Constant time lookup. @param object An object from the data source. @return A section controller or `nil` if `object` is not in the list. @see `-[IGListAdapterDataSource listAdapter:sectionControllerForObject:]` */ - (__kindof IGListSectionController * _Nullable)sectionControllerForObject:(id)object; /** Returns the object corresponding to the specified section controller in the list. Constant time lookup. @param sectionController A section controller in the list. @return The object for the specified section controller, or `nil` if not found. */ - (nullable id)objectForSectionController:(IGListSectionController *)sectionController; /** Returns the object corresponding to a section in the list. Constant time lookup. @param section A section in the list. @return The object for the specified section, or `nil` if the section does not exist. */ - (nullable id)objectAtSection:(NSInteger)section; /** Returns the section corresponding to the specified object in the list. Constant time lookup. @param object An object in the list. @return The section index of `object` if found, otherwise `NSNotFound`. */ - (NSInteger)sectionForObject:(id)object; /** Returns a copy of all the objects currently driving the adapter. @return An array of objects. */ - (NSArray *)objects; /** An unordered array of the currently visible section controllers. @return An array of section controllers. */ - (NSArray *)visibleSectionControllers; /** An unordered array of the currently visible objects. @return An array of objects */ - (NSArray *)visibleObjects; /** An unordered array of the currently visible cells for a given object. @param object An object in the list @return An array of collection view cells. */ - (NSArray *)visibleCellsForObject:(id)object; /** Scrolls to the specified object in the list adapter. @param object The object to which to scroll. @param supplementaryKinds The types of supplementary views in the section. @param scrollDirection An option indicating the direction to scroll. @param scrollPosition An option that specifies where the item should be positioned when scrolling finishes. @param animated A flag indicating if the scrolling should be animated. */ - (void)scrollToObject:(id)object supplementaryKinds:(nullable NSArray *)supplementaryKinds scrollDirection:(UICollectionViewScrollDirection)scrollDirection scrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated; /** Returns the size of a cell at the specified index path. @param indexPath The index path of the cell. @return The size of the cell. */ - (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath; /** Returns the size of a supplementary view in the list at the specified index path. @param elementKind The kind of supplementary view. @param indexPath The index path of the supplementary view. @return The size of the supplementary view. */ - (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath; /** Adds a listener to the list adapter. @param updateListener The object conforming to the `IGListAdapterUpdateListener` protocol. @note Listeners are held weakly so there is no need to call `-[IGListAdapter removeUpdateListener:]` on `dealloc`. */ - (void)addUpdateListener:(id)updateListener; /** Removes a listener from the list adapter. @param updateListener The object conforming to the `IGListAdapterUpdateListener` protocol. */ - (void)removeUpdateListener:(id)updateListener; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListAdapter.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListAdapterInternal.h" #import #import #import #import "IGListArrayUtilsInternal.h" #import "IGListDebugger.h" #import "IGListSectionControllerInternal.h" #import "UICollectionViewLayout+InteractiveReordering.h" #import "UIScrollView+IGListKit.h" @implementation IGListAdapter { NSMapTable *_viewSectionControllerMap; // An array of blocks to execute once batch updates are finished NSMutableArray *_queuedCompletionBlocks; NSHashTable> *_updateListeners; } - (void)dealloc { [self.sectionMap reset]; } #pragma mark - Init - (instancetype)initWithUpdater:(id )updater viewController:(UIViewController *)viewController workingRangeSize:(NSInteger)workingRangeSize { IGAssertMainThread(); IGParameterAssert(updater); if (self = [super init]) { NSPointerFunctions *keyFunctions = [updater objectLookupPointerFunctions]; NSPointerFunctions *valueFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory]; NSMapTable *table = [[NSMapTable alloc] initWithKeyPointerFunctions:keyFunctions valuePointerFunctions:valueFunctions capacity:0]; _sectionMap = [[IGListSectionMap alloc] initWithMapTable:table]; _displayHandler = [IGListDisplayHandler new]; _workingRangeHandler = [[IGListWorkingRangeHandler alloc] initWithWorkingRangeSize:workingRangeSize]; _updateListeners = [NSHashTable weakObjectsHashTable]; _viewSectionControllerMap = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality | NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory]; _updater = updater; _viewController = viewController; [IGListDebugger trackAdapter:self]; } return self; } - (instancetype)initWithUpdater:(id)updater viewController:(UIViewController *)viewController { return [self initWithUpdater:updater viewController:viewController workingRangeSize:0]; } - (UICollectionView *)collectionView { return _collectionView; } - (void)setCollectionView:(UICollectionView *)collectionView { IGAssertMainThread(); // if collection view has been used by a different list adapter, treat it as if we were using a new collection view // this happens when embedding a UICollectionView inside a UICollectionViewCell that is reused if (_collectionView != collectionView || _collectionView.dataSource != self) { // if the collection view was being used with another IGListAdapter (e.g. cell reuse) // destroy the previous association so the old adapter doesn't update the wrong collection view static NSMapTable *globalCollectionViewAdapterMap = nil; if (globalCollectionViewAdapterMap == nil) { globalCollectionViewAdapterMap = [NSMapTable weakToWeakObjectsMapTable]; } [globalCollectionViewAdapterMap removeObjectForKey:_collectionView]; [[globalCollectionViewAdapterMap objectForKey:collectionView] setCollectionView:nil]; [globalCollectionViewAdapterMap setObject:self forKey:collectionView]; // dump old registered section controllers in the case that we are changing collection views or setting for // the first time _registeredCellIdentifiers = [NSMutableSet new]; _registeredNibNames = [NSMutableSet new]; _registeredSupplementaryViewIdentifiers = [NSMutableSet new]; _registeredSupplementaryViewNibNames = [NSMutableSet new]; const BOOL settingFirstCollectionView = _collectionView == nil; _collectionView = collectionView; _collectionView.dataSource = self; if (@available(iOS 10.0, tvOS 10, *)) { _collectionView.prefetchingEnabled = NO; } [_collectionView.collectionViewLayout ig_hijackLayoutInteractiveReorderingMethodForAdapter:self]; [_collectionView.collectionViewLayout invalidateLayout]; [self _updateCollectionViewDelegate]; // only construct if (!IGListExperimentEnabled(self.experiments, IGListExperimentGetCollectionViewAtUpdate) || settingFirstCollectionView) { [self _updateAfterPublicSettingsChange]; } } } - (void)setDataSource:(id)dataSource { if (_dataSource != dataSource) { _dataSource = dataSource; [self _updateAfterPublicSettingsChange]; } } // reset and configure the delegate proxy whenever this property is set - (void)setCollectionViewDelegate:(id)collectionViewDelegate { IGAssertMainThread(); IGAssert(![collectionViewDelegate conformsToProtocol:@protocol(UICollectionViewDelegateFlowLayout)], @"UICollectionViewDelegateFlowLayout conformance is automatically handled by IGListAdapter."); if (_collectionViewDelegate != collectionViewDelegate) { _collectionViewDelegate = collectionViewDelegate; [self _createProxyAndUpdateCollectionViewDelegate]; } } - (void)setScrollViewDelegate:(id)scrollViewDelegate { IGAssertMainThread(); if (_scrollViewDelegate != scrollViewDelegate) { _scrollViewDelegate = scrollViewDelegate; [self _createProxyAndUpdateCollectionViewDelegate]; } } - (void)_updateAfterPublicSettingsChange { id dataSource = _dataSource; if (_collectionView != nil && dataSource != nil) { NSArray *uniqueObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:self]); [self _updateObjects:uniqueObjects dataSource:dataSource]; } } - (void)_createProxyAndUpdateCollectionViewDelegate { // there is a known bug with accessibility and using an NSProxy as the delegate that will cause EXC_BAD_ACCESS // when voiceover is enabled. it will hold an unsafe ref to the delegate _collectionView.delegate = nil; self.delegateProxy = [[IGListAdapterProxy alloc] initWithCollectionViewTarget:_collectionViewDelegate scrollViewTarget:_scrollViewDelegate interceptor:self]; [self _updateCollectionViewDelegate]; } - (void)_updateCollectionViewDelegate { // set up the delegate to the proxy so the adapter can intercept events // default to the adapter simply being the delegate _collectionView.delegate = (id)self.delegateProxy ?: self; } #pragma mark - Scrolling - (void)scrollToObject:(id)object supplementaryKinds:(NSArray *)supplementaryKinds scrollDirection:(UICollectionViewScrollDirection)scrollDirection scrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated { IGAssertMainThread(); IGParameterAssert(object != nil); const NSInteger section = [self sectionForObject:object]; if (section == NSNotFound) { return; } UICollectionView *collectionView = self.collectionView; const BOOL avoidLayout = IGListExperimentEnabled(self.experiments, IGListExperimentAvoidLayoutOnScrollToObject); // Experiment with skipping the forced layout to avoid creating off-screen cells. // Calling [collectionView layoutIfNeeded] creates the current visible cells that will no longer be visible after the scroll. // We can avoid that by asking the UICollectionView (not the layout object) for the attributes. So if the attributes are not // ready, the UICollectionView will call -prepareLayout, return the attributes, but doesn't generate the cells just yet. if (!avoidLayout) { [collectionView setNeedsLayout]; [collectionView layoutIfNeeded]; } NSIndexPath *indexPathFirstElement = [NSIndexPath indexPathForItem:0 inSection:section]; // collect the layout attributes for the cell and supplementary views for the first index // this will break if there are supplementary views beyond item 0 NSMutableArray *attributes = nil; const NSInteger numberOfItems = [collectionView numberOfItemsInSection:section]; if (numberOfItems > 0) { attributes = [self _layoutAttributesForItemAndSupplementaryViewAtIndexPath:indexPathFirstElement supplementaryKinds:supplementaryKinds].mutableCopy; if (numberOfItems > 1) { NSIndexPath *indexPathLastElement = [NSIndexPath indexPathForItem:(numberOfItems - 1) inSection:section]; UICollectionViewLayoutAttributes *lastElementattributes = [self _layoutAttributesForItemAndSupplementaryViewAtIndexPath:indexPathLastElement supplementaryKinds:supplementaryKinds].firstObject; if (lastElementattributes != nil) { [attributes addObject:lastElementattributes]; } } } else { NSMutableArray *supplementaryAttributes = [NSMutableArray new]; for (NSString* supplementaryKind in supplementaryKinds) { UICollectionViewLayoutAttributes *supplementaryAttribute = [self _layoutAttributesForSupplementaryViewOfKind:supplementaryKind atIndexPath:indexPathFirstElement]; if (supplementaryAttribute != nil) { [supplementaryAttributes addObject: supplementaryAttribute]; } } attributes = supplementaryAttributes; } CGFloat offsetMin = 0.0; CGFloat offsetMax = 0.0; for (UICollectionViewLayoutAttributes *attribute in attributes) { const CGRect frame = attribute.frame; CGFloat originMin; CGFloat endMax; switch (scrollDirection) { case UICollectionViewScrollDirectionHorizontal: originMin = CGRectGetMinX(frame); endMax = CGRectGetMaxX(frame); break; case UICollectionViewScrollDirectionVertical: originMin = CGRectGetMinY(frame); endMax = CGRectGetMaxY(frame); break; } // find the minimum origin value of all the layout attributes if (attribute == attributes.firstObject || originMin < offsetMin) { offsetMin = originMin; } // find the maximum end value of all the layout attributes if (attribute == attributes.firstObject || endMax > offsetMax) { offsetMax = endMax; } } const CGFloat offsetMid = (offsetMin + offsetMax) / 2.0; const CGFloat collectionViewWidth = collectionView.bounds.size.width; const CGFloat collectionViewHeight = collectionView.bounds.size.height; const UIEdgeInsets contentInset = collectionView.ig_contentInset; CGPoint contentOffset = collectionView.contentOffset; switch (scrollDirection) { case UICollectionViewScrollDirectionHorizontal: { switch (scrollPosition) { case UICollectionViewScrollPositionRight: contentOffset.x = offsetMax - collectionViewWidth + contentInset.right; break; case UICollectionViewScrollPositionCenteredHorizontally: { const CGFloat insets = (contentInset.left - contentInset.right) / 2.0; contentOffset.x = offsetMid - collectionViewWidth / 2.0 - insets; break; } case UICollectionViewScrollPositionLeft: case UICollectionViewScrollPositionNone: case UICollectionViewScrollPositionTop: case UICollectionViewScrollPositionBottom: case UICollectionViewScrollPositionCenteredVertically: contentOffset.x = offsetMin - contentInset.left; break; } const CGFloat maxOffsetX = collectionView.contentSize.width - collectionView.frame.size.width + contentInset.right; const CGFloat minOffsetX = -contentInset.left; contentOffset.x = MIN(contentOffset.x, maxOffsetX); contentOffset.x = MAX(contentOffset.x, minOffsetX); break; } case UICollectionViewScrollDirectionVertical: { switch (scrollPosition) { case UICollectionViewScrollPositionBottom: contentOffset.y = offsetMax - collectionViewHeight + contentInset.bottom; break; case UICollectionViewScrollPositionCenteredVertically: { const CGFloat insets = (contentInset.top - contentInset.bottom) / 2.0; contentOffset.y = offsetMid - collectionViewHeight / 2.0 - insets; break; } case UICollectionViewScrollPositionTop: case UICollectionViewScrollPositionNone: case UICollectionViewScrollPositionLeft: case UICollectionViewScrollPositionRight: case UICollectionViewScrollPositionCenteredHorizontally: contentOffset.y = offsetMin - contentInset.top; break; } CGFloat maxHeight; if (avoidLayout) { // If we don't call [collectionView layoutIfNeeded], the collectionView.contentSize does not get updated. // So lets use the layout object, since it should have been updated by now. maxHeight = collectionView.collectionViewLayout.collectionViewContentSize.height; } else { maxHeight = collectionView.contentSize.height; } const CGFloat maxOffsetY = maxHeight - collectionView.frame.size.height + contentInset.bottom; const CGFloat minOffsetY = -contentInset.top; contentOffset.y = MIN(contentOffset.y, maxOffsetY); contentOffset.y = MAX(contentOffset.y, minOffsetY); break; } } [collectionView setContentOffset:contentOffset animated:animated]; } #pragma mark - Editing - (void)performUpdatesAnimated:(BOOL)animated completion:(IGListUpdaterCompletion)completion { IGAssertMainThread(); id dataSource = self.dataSource; UICollectionView *collectionView = self.collectionView; if (dataSource == nil || collectionView == nil) { IGLKLog(@"Warning: Your call to %s is ignored as dataSource or collectionView haven't been set.", __PRETTY_FUNCTION__); if (completion) { completion(NO); } return; } NSArray *fromObjects = self.sectionMap.objects; IGListToObjectBlock toObjectsBlock; __weak __typeof__(self) weakSelf = self; if (IGListExperimentEnabled(self.experiments, IGListExperimentDeferredToObjectCreation)) { toObjectsBlock = ^NSArray *{ __typeof__(self) strongSelf = weakSelf; if (strongSelf == nil) { return nil; } return [dataSource objectsForListAdapter:strongSelf]; }; } else { NSArray *newObjects = [dataSource objectsForListAdapter:self]; toObjectsBlock = ^NSArray *{ return newObjects; }; } [self _enterBatchUpdates]; [self.updater performUpdateWithCollectionViewBlock:[self _collectionViewBlock] fromObjects:fromObjects toObjectsBlock:toObjectsBlock animated:animated objectTransitionBlock:^(NSArray *toObjects) { // temporarily capture the item map that we are transitioning from in case // there are any item deletes at the same weakSelf.previousSectionMap = [weakSelf.sectionMap copy]; [weakSelf _updateObjects:toObjects dataSource:dataSource]; } completion:^(BOOL finished) { // release the previous items weakSelf.previousSectionMap = nil; [weakSelf _notifyDidUpdate:IGListAdapterUpdateTypePerformUpdates animated:animated]; if (completion) { completion(finished); } [weakSelf _exitBatchUpdates]; }]; } - (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion { IGAssertMainThread(); id dataSource = self.dataSource; UICollectionView *collectionView = self.collectionView; if (dataSource == nil || collectionView == nil) { IGLKLog(@"Warning: Your call to %s is ignored as dataSource or collectionView haven't been set.", __PRETTY_FUNCTION__); if (completion) { completion(NO); } return; } NSArray *uniqueObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:self]); __weak __typeof__(self) weakSelf = self; [self.updater reloadDataWithCollectionViewBlock:[self _collectionViewBlock] reloadUpdateBlock:^{ // purge all section controllers from the item map so that they are regenerated [weakSelf.sectionMap reset]; [weakSelf _updateObjects:uniqueObjects dataSource:dataSource]; } completion:^(BOOL finished) { [weakSelf _notifyDidUpdate:IGListAdapterUpdateTypeReloadData animated:NO]; if (completion) { completion(finished); } }]; } - (void)reloadObjects:(NSArray *)objects { IGAssertMainThread(); IGParameterAssert(objects); NSMutableIndexSet *sections = [NSMutableIndexSet new]; // use the item map based on whether or not we're in an update block IGListSectionMap *map = [self _sectionMapUsingPreviousIfInUpdateBlock:YES]; for (id object in objects) { // look up the item using the map's lookup function. might not be the same item const NSInteger section = [map sectionForObject:object]; const BOOL notFound = section == NSNotFound; if (notFound) { continue; } [sections addIndex:section]; // reverse lookup the item using the section. if the pointer has changed the trigger update events and swap items if (object != [map objectForSection:section]) { [map updateObject:object]; [[map sectionControllerForSection:section] didUpdateToObject:object]; } } UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Tried to reload the adapter without a collection view"); [self.updater reloadCollectionView:collectionView sections:sections]; } - (void)addUpdateListener:(id)updateListener { IGAssertMainThread(); IGParameterAssert(updateListener != nil); [_updateListeners addObject:updateListener]; } - (void)removeUpdateListener:(id)updateListener { IGAssertMainThread(); IGParameterAssert(updateListener != nil); [_updateListeners removeObject:updateListener]; } - (void)_notifyDidUpdate:(IGListAdapterUpdateType)update animated:(BOOL)animated { for (id listener in _updateListeners) { [listener listAdapter:self didFinishUpdate:update animated:animated]; } } #pragma mark - List Items & Sections - (nullable IGListSectionController *)sectionControllerForSection:(NSInteger)section { IGAssertMainThread(); return [self.sectionMap sectionControllerForSection:section]; } - (NSInteger)sectionForSectionController:(IGListSectionController *)sectionController { IGAssertMainThread(); IGParameterAssert(sectionController != nil); return [self.sectionMap sectionForSectionController:sectionController]; } - (IGListSectionController *)sectionControllerForObject:(id)object { IGAssertMainThread(); IGParameterAssert(object != nil); return [self.sectionMap sectionControllerForObject:object]; } - (id)objectForSectionController:(IGListSectionController *)sectionController { IGAssertMainThread(); IGParameterAssert(sectionController != nil); const NSInteger section = [self.sectionMap sectionForSectionController:sectionController]; return [self.sectionMap objectForSection:section]; } - (id)objectAtSection:(NSInteger)section { IGAssertMainThread(); return [self.sectionMap objectForSection:section]; } - (NSInteger)sectionForObject:(id)item { IGAssertMainThread(); IGParameterAssert(item != nil); return [self.sectionMap sectionForObject:item]; } - (NSArray *)objects { IGAssertMainThread(); return self.sectionMap.objects; } - (id)_supplementaryViewSourceAtIndexPath:(NSIndexPath *)indexPath { IGListSectionController *sectionController = [self sectionControllerForSection:indexPath.section]; return [sectionController supplementaryViewSource]; } - (NSArray *)visibleSectionControllers { IGAssertMainThread(); return [[self.displayHandler visibleListSections] allObjects]; } - (NSArray *)visibleObjects { IGAssertMainThread(); NSArray *visibleCells = [self.collectionView visibleCells]; NSMutableSet *visibleObjects = [NSMutableSet new]; for (UICollectionViewCell *cell in visibleCells) { IGListSectionController *sectionController = [self sectionControllerForView:cell]; IGAssert(sectionController != nil, @"Section controller nil for cell %@", cell); if (sectionController != nil) { const NSInteger section = [self sectionForSectionController:sectionController]; id object = [self objectAtSection:section]; IGAssert(object != nil, @"Object not found for section controller %@ at section %li", sectionController, (long)section); if (object != nil) { [visibleObjects addObject:object]; } } } return [visibleObjects allObjects]; } - (NSArray *)visibleCellsForObject:(id)object { IGAssertMainThread(); IGParameterAssert(object != nil); const NSInteger section = [self.sectionMap sectionForObject:object]; if (section == NSNotFound) { return [NSArray new]; } NSArray *visibleCells = [self.collectionView visibleCells]; UICollectionView *collectionView = self.collectionView; NSPredicate *controllerPredicate = [NSPredicate predicateWithBlock:^BOOL(UICollectionViewCell* cell, NSDictionary* bindings) { NSIndexPath *indexPath = [collectionView indexPathForCell:cell]; return indexPath.section == section; }]; return [visibleCells filteredArrayUsingPredicate:controllerPredicate]; } #pragma mark - Layout - (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath { IGAssertMainThread(); id performanceDelegate = self.performanceDelegate; [performanceDelegate listAdapterWillCallSize:self]; IGListSectionController *sectionController = [self sectionControllerForSection:indexPath.section]; const CGSize size = [sectionController sizeForItemAtIndex:indexPath.item]; const CGSize positiveSize = CGSizeMake(MAX(size.width, 0.0), MAX(size.height, 0.0)); [performanceDelegate listAdapter:self didCallSizeOnSectionController:sectionController atIndex:indexPath.item]; return positiveSize; } - (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { IGAssertMainThread(); id supplementaryViewSource = [self _supplementaryViewSourceAtIndexPath:indexPath]; if ([[supplementaryViewSource supportedElementKinds] containsObject:elementKind]) { const CGSize size = [supplementaryViewSource sizeForSupplementaryViewOfKind:elementKind atIndex:indexPath.item]; return CGSizeMake(MAX(size.width, 0.0), MAX(size.height, 0.0)); } return CGSizeZero; } #pragma mark - Private API - (IGListCollectionViewBlock)_collectionViewBlock { if (IGListExperimentEnabled(self.experiments, IGListExperimentGetCollectionViewAtUpdate)) { __weak __typeof__(self) weakSelf = self; return ^UICollectionView *{ return weakSelf.collectionView; }; } else { __weak UICollectionView *collectionView = _collectionView; return ^UICollectionView *{ return collectionView; }; } } // this method is what updates the "source of truth" // this should only be called just before the collection view is updated - (void)_updateObjects:(NSArray *)objects dataSource:(id)dataSource { IGParameterAssert(dataSource != nil); #if DEBUG for (id object in objects) { IGAssert([object isEqualToDiffableObject:object], @"Object instance %@ not equal to itself. This will break infra map tables.", object); } #endif NSMutableArray *sectionControllers = [NSMutableArray new]; NSMutableArray *validObjects = [NSMutableArray new]; IGListSectionMap *map = self.sectionMap; // collect items that have changed since the last update NSMutableSet *updatedObjects = [NSMutableSet new]; // push the view controller and collection context into a local thread container so they are available on init // for IGListSectionController subclasses after calling [super init] IGListSectionControllerPushThread(self.viewController, self); for (id object in objects) { // infra checks to see if a controller exists IGListSectionController *sectionController = [map sectionControllerForObject:object]; // if not, query the data source for a new one if (sectionController == nil) { sectionController = [dataSource listAdapter:self sectionControllerForObject:object]; } if (sectionController == nil) { IGLKLog(@"WARNING: Ignoring nil section controller returned by data source %@ for object %@.", dataSource, object); continue; } // in case the section controller was created outside of -listAdapter:sectionControllerForObject: sectionController.collectionContext = self; sectionController.viewController = self.viewController; // check if the item has changed instances or is new const NSInteger oldSection = [map sectionForObject:object]; if (oldSection == NSNotFound || [map objectForSection:oldSection] != object) { [updatedObjects addObject:object]; } [sectionControllers addObject:sectionController]; [validObjects addObject:object]; } #if DEBUG IGAssert([NSSet setWithArray:sectionControllers].count == sectionControllers.count, @"Section controllers array is not filled with unique objects; section controllers are being reused"); #endif // clear the view controller and collection context IGListSectionControllerPopThread(); [map updateWithObjects:validObjects sectionControllers:sectionControllers]; // now that the maps have been created and contexts are assigned, we consider the section controller "fully loaded" for (id object in updatedObjects) { [[map sectionControllerForObject:object] didUpdateToObject:object]; } NSInteger itemCount = 0; for (IGListSectionController *sectionController in sectionControllers) { itemCount += [sectionController numberOfItems]; } [self _updateBackgroundViewShouldHide:itemCount > 0]; } - (void)_updateBackgroundViewShouldHide:(BOOL)shouldHide { if (self.isInUpdateBlock) { return; // will be called again when update block completes } UIView *backgroundView = [self.dataSource emptyViewForListAdapter:self]; // don't do anything if the client is using the same view if (backgroundView != _collectionView.backgroundView) { // collection view will just stack the background views underneath each other if we do not remove the previous // one first. also fine if it is nil [_collectionView.backgroundView removeFromSuperview]; _collectionView.backgroundView = backgroundView; } _collectionView.backgroundView.hidden = shouldHide; } - (BOOL)_itemCountIsZero { __block BOOL isZero = YES; [self.sectionMap enumerateUsingBlock:^(id _Nonnull object, IGListSectionController * _Nonnull sectionController, NSInteger section, BOOL * _Nonnull stop) { if (sectionController.numberOfItems > 0) { isZero = NO; *stop = YES; } }]; return isZero; } - (IGListSectionMap *)_sectionMapUsingPreviousIfInUpdateBlock:(BOOL)usePreviousMapIfInUpdateBlock { // if we are inside an update block, we may have to use the /previous/ item map for some operations IGListSectionMap *previousSectionMap = self.previousSectionMap; if (usePreviousMapIfInUpdateBlock && self.isInUpdateBlock && previousSectionMap != nil) { return previousSectionMap; } else { return self.sectionMap; } } - (NSArray *)indexPathsFromSectionController:(IGListSectionController *)sectionController indexes:(NSIndexSet *)indexes usePreviousIfInUpdateBlock:(BOOL)usePreviousIfInUpdateBlock { NSMutableArray *indexPaths = [NSMutableArray new]; IGListSectionMap *map = [self _sectionMapUsingPreviousIfInUpdateBlock:usePreviousIfInUpdateBlock]; const NSInteger section = [map sectionForSectionController:sectionController]; if (section != NSNotFound) { [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { [indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]]; }]; } return indexPaths; } - (NSIndexPath *)indexPathForSectionController:(IGListSectionController *)controller index:(NSInteger)index usePreviousIfInUpdateBlock:(BOOL)usePreviousIfInUpdateBlock { IGListSectionMap *map = [self _sectionMapUsingPreviousIfInUpdateBlock:usePreviousIfInUpdateBlock]; const NSInteger section = [map sectionForSectionController:controller]; if (section == NSNotFound) { return nil; } else { return [NSIndexPath indexPathForItem:index inSection:section]; } } - (NSArray *)_layoutAttributesForItemAndSupplementaryViewAtIndexPath:(NSIndexPath *)indexPath supplementaryKinds:(NSArray *)supplementaryKinds { NSMutableArray *attributes = [NSMutableArray new]; UICollectionViewLayoutAttributes *cellAttributes = [self _layoutAttributesForItemAtIndexPath:indexPath]; if (cellAttributes) { [attributes addObject:cellAttributes]; } for (NSString *kind in supplementaryKinds) { UICollectionViewLayoutAttributes *supplementaryAttributes = [self _layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; if (supplementaryAttributes) { [attributes addObject:supplementaryAttributes]; } } return attributes; } - (nullable UICollectionViewLayoutAttributes *)_layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { if (IGListExperimentEnabled(self.experiments, IGListExperimentAvoidLayoutOnScrollToObject)) { return [self.collectionView layoutAttributesForItemAtIndexPath:indexPath]; } else { return [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath]; } } - (nullable UICollectionViewLayoutAttributes *)_layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { if (IGListExperimentEnabled(self.experiments, IGListExperimentAvoidLayoutOnScrollToObject)) { return [self.collectionView layoutAttributesForSupplementaryElementOfKind:elementKind atIndexPath:indexPath]; } else { return [self.collectionView.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath]; } } - (void)mapView:(UICollectionReusableView *)view toSectionController:(IGListSectionController *)sectionController { IGAssertMainThread(); IGParameterAssert(view != nil); IGParameterAssert(sectionController != nil); [_viewSectionControllerMap setObject:sectionController forKey:view]; } - (nullable IGListSectionController *)sectionControllerForView:(UICollectionReusableView *)view { IGAssertMainThread(); return [_viewSectionControllerMap objectForKey:view]; } - (void)removeMapForView:(UICollectionReusableView *)view { IGAssertMainThread(); [_viewSectionControllerMap removeObjectForKey:view]; } - (void)_deferBlockBetweenBatchUpdates:(void (^)(void))block { IGAssertMainThread(); if (_queuedCompletionBlocks == nil) { block(); } else { [_queuedCompletionBlocks addObject:block]; } } - (void)_enterBatchUpdates { _queuedCompletionBlocks = [NSMutableArray new]; } - (void)_exitBatchUpdates { NSArray *blocks = [_queuedCompletionBlocks copy]; _queuedCompletionBlocks = nil; for (void (^block)(void) in blocks) { block(); } } #pragma mark - UIScrollViewDelegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView { id performanceDelegate = self.performanceDelegate; [performanceDelegate listAdapterWillCallScroll:self]; // forward this method to the delegate b/c this implementation will steal the message from the proxy id scrollViewDelegate = self.scrollViewDelegate; if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) { [scrollViewDelegate scrollViewDidScroll:scrollView]; } NSArray *visibleSectionControllers = [self visibleSectionControllers]; for (IGListSectionController *sectionController in visibleSectionControllers) { [[sectionController scrollDelegate] listAdapter:self didScrollSectionController:sectionController]; } [performanceDelegate listAdapter:self didCallScroll:scrollView]; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { // forward this method to the delegate b/c this implementation will steal the message from the proxy id scrollViewDelegate = self.scrollViewDelegate; if ([scrollViewDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) { [scrollViewDelegate scrollViewWillBeginDragging:scrollView]; } NSArray *visibleSectionControllers = [self visibleSectionControllers]; for (IGListSectionController *sectionController in visibleSectionControllers) { [[sectionController scrollDelegate] listAdapter:self willBeginDraggingSectionController:sectionController]; } } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { // forward this method to the delegate b/c this implementation will steal the message from the proxy id scrollViewDelegate = self.scrollViewDelegate; if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) { [scrollViewDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; } NSArray *visibleSectionControllers = [self visibleSectionControllers]; for (IGListSectionController *sectionController in visibleSectionControllers) { [[sectionController scrollDelegate] listAdapter:self didEndDraggingSectionController:sectionController willDecelerate:decelerate]; } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { // forward this method to the delegate b/c this implementation will steal the message from the proxy id scrollViewDelegate = self.scrollViewDelegate; if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) { [scrollViewDelegate scrollViewDidEndDecelerating:scrollView]; } NSArray *visibleSectionControllers = [self visibleSectionControllers]; for (IGListSectionController *sectionController in visibleSectionControllers) { id scrollDelegate = [sectionController scrollDelegate]; if ([scrollDelegate respondsToSelector:@selector(listAdapter:didEndDeceleratingSectionController:)]) { [scrollDelegate listAdapter:self didEndDeceleratingSectionController:sectionController]; } } } #pragma mark - IGListCollectionContext - (CGSize)containerSize { return self.collectionView.bounds.size; } - (UIEdgeInsets)containerInset { return self.collectionView.contentInset; } - (UIEdgeInsets)adjustedContainerInset { return self.collectionView.ig_contentInset; } - (CGSize)insetContainerSize { UICollectionView *collectionView = self.collectionView; return UIEdgeInsetsInsetRect(collectionView.bounds, collectionView.ig_contentInset).size; } - (IGListCollectionScrollingTraits)scrollingTraits { UICollectionView *collectionView = self.collectionView; return (IGListCollectionScrollingTraits) { .isTracking = collectionView.isTracking, .isDragging = collectionView.isDragging, .isDecelerating = collectionView.isDecelerating, }; } - (CGSize)containerSizeForSectionController:(IGListSectionController *)sectionController { const UIEdgeInsets inset = sectionController.inset; return CGSizeMake(self.containerSize.width - inset.left - inset.right, self.containerSize.height - inset.top - inset.bottom); } - (NSInteger)indexForCell:(UICollectionViewCell *)cell sectionController:(nonnull IGListSectionController *)sectionController { IGAssertMainThread(); IGParameterAssert(cell != nil); IGParameterAssert(sectionController != nil); NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; IGAssert(indexPath == nil || indexPath.section == [self sectionForSectionController:sectionController], @"Requesting a cell from another section controller is not allowed."); return indexPath != nil ? indexPath.item : NSNotFound; } - (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController { IGAssertMainThread(); IGParameterAssert(sectionController != nil); // if this is accessed while a cell is being dequeued or displaying working range elements, just return nil if (_isDequeuingCell || _isSendingWorkingRangeDisplayUpdates) { return nil; } NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:YES]; // prevent querying the collection view if it isn't fully reloaded yet for the current data set if (indexPath != nil && indexPath.section < [self.collectionView numberOfSections]) { // only return a cell if it belongs to the section controller // this association is created in -collectionView:cellForItemAtIndexPath: UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; if ([self sectionControllerForView:cell] == sectionController) { return cell; } } return nil; } - (NSArray *)visibleCellsForSectionController:(IGListSectionController *)sectionController { NSMutableArray *cells = [NSMutableArray new]; UICollectionView *collectionView = self.collectionView; NSArray *visibleCells = [collectionView visibleCells]; const NSInteger section = [self sectionForSectionController:sectionController]; for (UICollectionViewCell *cell in visibleCells) { if ([collectionView indexPathForCell:cell].section == section) { [cells addObject:cell]; } } return cells; } - (NSArray *)visibleIndexPathsForSectionController:(IGListSectionController *) sectionController { NSMutableArray *paths = [NSMutableArray new]; UICollectionView *collectionView = self.collectionView; NSArray *visiblePaths = [collectionView indexPathsForVisibleItems]; const NSInteger section = [self sectionForSectionController:sectionController]; for (NSIndexPath *path in visiblePaths) { if (path.section == section) { [paths addObject:path]; } } return paths; } - (void)deselectItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController animated:(BOOL)animated { IGAssertMainThread(); IGParameterAssert(sectionController != nil); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO]; [self.collectionView deselectItemAtIndexPath:indexPath animated:animated]; } - (void)selectItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition { IGAssertMainThread(); IGParameterAssert(sectionController != nil); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO]; [self.collectionView selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; } - (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass withReuseIdentifier:(NSString *)reuseIdentifier forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index { IGAssertMainThread(); IGParameterAssert(sectionController != nil); IGParameterAssert(cellClass != nil); IGParameterAssert(index >= 0); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Dequeueing cell of class %@ with reuseIdentifier %@ from section controller %@ without a collection view at index %li", NSStringFromClass(cellClass), reuseIdentifier, sectionController, (long)index); NSString *identifier = IGListReusableViewIdentifier(cellClass, nil, reuseIdentifier); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO]; if (![self.registeredCellIdentifiers containsObject:identifier]) { [self.registeredCellIdentifiers addObject:identifier]; [collectionView registerClass:cellClass forCellWithReuseIdentifier:identifier]; } return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath]; } - (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index { return [self dequeueReusableCellOfClass:cellClass withReuseIdentifier:nil forSectionController:sectionController atIndex:index]; } - (__kindof UICollectionViewCell *)dequeueReusableCellFromStoryboardWithIdentifier:(NSString *)identifier forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index { IGAssertMainThread(); IGParameterAssert(sectionController != nil); IGParameterAssert(identifier.length > 0); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Reloading adapter without a collection view."); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO]; return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath]; } - (UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index { IGAssertMainThread(); IGParameterAssert([nibName length] > 0); IGParameterAssert(sectionController != nil); IGParameterAssert(index >= 0); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Dequeueing cell with nib name %@ and bundle %@ from section controller %@ without a collection view at index %li.", nibName, bundle, sectionController, (long)index); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO]; if (![self.registeredNibNames containsObject:nibName]) { [self.registeredNibNames addObject:nibName]; UINib *nib = [UINib nibWithNibName:nibName bundle:bundle]; [collectionView registerNib:nib forCellWithReuseIdentifier:nibName]; } return [collectionView dequeueReusableCellWithReuseIdentifier:nibName forIndexPath:indexPath]; } - (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind forSectionController:(IGListSectionController *)sectionController class:(Class)viewClass atIndex:(NSInteger)index { IGAssertMainThread(); IGParameterAssert(elementKind.length > 0); IGParameterAssert(sectionController != nil); IGParameterAssert(viewClass != nil); IGParameterAssert(index >= 0); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Dequeueing cell of class %@ from section controller %@ without a collection view at index %li with supplementary view %@", NSStringFromClass(viewClass), sectionController, (long)index, elementKind); NSString *identifier = IGListReusableViewIdentifier(viewClass, elementKind, nil); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO]; if (![self.registeredSupplementaryViewIdentifiers containsObject:identifier]) { [self.registeredSupplementaryViewIdentifiers addObject:identifier]; [collectionView registerClass:viewClass forSupplementaryViewOfKind:elementKind withReuseIdentifier:identifier]; } return [collectionView dequeueReusableSupplementaryViewOfKind:elementKind withReuseIdentifier:identifier forIndexPath:indexPath]; } - (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewFromStoryboardOfKind:(NSString *)elementKind withIdentifier:(NSString *)identifier forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index { IGAssertMainThread(); IGParameterAssert(elementKind.length > 0); IGParameterAssert(identifier.length > 0); IGParameterAssert(sectionController != nil); IGParameterAssert(index >= 0); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Dequeueing Supplementary View from storyboard of kind %@ with identifier %@ for section controller %@ without a collection view at index %li", elementKind, identifier, sectionController, (long)index); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO]; return [collectionView dequeueReusableSupplementaryViewOfKind:elementKind withReuseIdentifier:identifier forIndexPath:indexPath]; } - (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind forSectionController:(IGListSectionController *)sectionController nibName:(NSString *)nibName bundle:(NSBundle *)bundle atIndex:(NSInteger)index { IGAssertMainThread(); IGParameterAssert([nibName length] > 0); IGParameterAssert([elementKind length] > 0); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Reloading adapter without a collection view."); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO]; if (![self.registeredSupplementaryViewNibNames containsObject:nibName]) { [self.registeredSupplementaryViewNibNames addObject:nibName]; UINib *nib = [UINib nibWithNibName:nibName bundle:bundle]; [collectionView registerNib:nib forSupplementaryViewOfKind:elementKind withReuseIdentifier:nibName]; } return [collectionView dequeueReusableSupplementaryViewOfKind:elementKind withReuseIdentifier:nibName forIndexPath:indexPath]; } - (void)performBatchAnimated:(BOOL)animated updates:(void (^)(id))updates completion:(void (^)(BOOL))completion { IGAssertMainThread(); IGParameterAssert(updates != nil); IGAssert(self.collectionView != nil, @"Performing batch updates without a collection view."); [self _enterBatchUpdates]; __weak __typeof__(self) weakSelf = self; [self.updater performUpdateWithCollectionViewBlock:[self _collectionViewBlock] animated:animated itemUpdates:^{ weakSelf.isInUpdateBlock = YES; // the adapter acts as the batch context with its API stripped to just the IGListBatchContext protocol updates(weakSelf); weakSelf.isInUpdateBlock = NO; } completion: ^(BOOL finished) { [weakSelf _updateBackgroundViewShouldHide:![weakSelf _itemCountIsZero]]; [weakSelf _notifyDidUpdate:IGListAdapterUpdateTypeItemUpdates animated:animated]; if (completion) { completion(finished); } [weakSelf _exitBatchUpdates]; }]; } - (void)scrollToSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index scrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated { IGAssertMainThread(); IGParameterAssert(sectionController != nil); NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO]; [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; } - (void)invalidateLayoutForSectionController:(IGListSectionController *)sectionController completion:(void (^)(BOOL finished))completion{ const NSInteger section = [self sectionForSectionController:sectionController]; const NSInteger items = [_collectionView numberOfItemsInSection:section]; NSMutableArray *indexPaths = [NSMutableArray new]; for (NSInteger item = 0; item < items; item++) { [indexPaths addObject:[NSIndexPath indexPathForItem:item inSection:section]]; } UICollectionViewLayout *layout = _collectionView.collectionViewLayout; UICollectionViewLayoutInvalidationContext *context = [[[layout.class invalidationContextClass] alloc] init]; [context invalidateItemsAtIndexPaths:indexPaths]; __weak __typeof__(_collectionView) weakCollectionView = _collectionView; // do not call -[UICollectionView performBatchUpdates:completion:] while already updating. defer it until completed. [self _deferBlockBetweenBatchUpdates:^{ [weakCollectionView performBatchUpdates:^{ [layout invalidateLayoutWithContext:context]; } completion:completion]; }]; } #pragma mark - IGListBatchContext - (void)reloadInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { IGAssertMainThread(); IGParameterAssert(indexes != nil); IGParameterAssert(sectionController != nil); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Tried to reload the adapter from %@ without a collection view at indexes %@.", sectionController, indexes); if (indexes.count == 0) { return; } /** UICollectionView is not designed to support -reloadSections: or -reloadItemsAtIndexPaths: during batch updates. Internally it appears to convert these operations to a delete+insert. However the transformation is too simple in that it doesn't account for the item's section being moved (naturally or explicitly) and can queue animation collisions. If you have an object at section 2 with 4 items and attempt to reload item at index 1, you would create an NSIndexPath at section: 2, item: 1. Within -performBatchUpdates:, UICollectionView converts this to a delete and insert at the same NSIndexPath. If a section were inserted at position 2, the original section 2 has naturally shifted to section 3. However, the insert NSIndexPath is section: 2, item: 1. Now the UICollectionView has a section animation at section 2, as well as an item insert animation at section: 2, item: 1, and it will throw an exception. IGListAdapter tracks the before/after mapping of section controllers to make precise NSIndexPath conversions. */ [indexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { NSIndexPath *fromIndexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:YES]; NSIndexPath *toIndexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO]; // index paths could be nil if a section controller is prematurely reloading or a reload was batched with // the section controller being deleted if (fromIndexPath != nil && toIndexPath != nil) { [self.updater reloadItemInCollectionView:collectionView fromIndexPath:fromIndexPath toIndexPath:toIndexPath]; } }]; } - (void)insertInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { IGAssertMainThread(); IGParameterAssert(indexes != nil); IGParameterAssert(sectionController != nil); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Inserting items from %@ without a collection view at indexes %@.", sectionController, indexes); if (indexes.count == 0) { return; } NSArray *indexPaths = [self indexPathsFromSectionController:sectionController indexes:indexes usePreviousIfInUpdateBlock:NO]; [self.updater insertItemsIntoCollectionView:collectionView indexPaths:indexPaths]; [self _updateBackgroundViewShouldHide:![self _itemCountIsZero]]; } - (void)deleteInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { IGAssertMainThread(); IGParameterAssert(indexes != nil); IGParameterAssert(sectionController != nil); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Deleting items from %@ without a collection view at indexes %@.", sectionController, indexes); if (indexes.count == 0) { return; } NSArray *indexPaths = [self indexPathsFromSectionController:sectionController indexes:indexes usePreviousIfInUpdateBlock:YES]; [self.updater deleteItemsFromCollectionView:collectionView indexPaths:indexPaths]; [self _updateBackgroundViewShouldHide:![self _itemCountIsZero]]; } - (void)invalidateLayoutInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { IGAssertMainThread(); IGParameterAssert(indexes != nil); IGParameterAssert(sectionController != nil); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Invalidating items from %@ without a collection view at indexes %@.", sectionController, indexes); if (indexes.count == 0) { return; } NSArray *indexPaths = [self indexPathsFromSectionController:sectionController indexes:indexes usePreviousIfInUpdateBlock:NO]; UICollectionViewLayout *layout = collectionView.collectionViewLayout; UICollectionViewLayoutInvalidationContext *context = [[[layout.class invalidationContextClass] alloc] init]; [context invalidateItemsAtIndexPaths:indexPaths]; [layout invalidateLayoutWithContext:context]; } - (void)moveInSectionController:(IGListSectionController *)sectionController fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex { IGAssertMainThread(); IGParameterAssert(sectionController != nil); IGParameterAssert(fromIndex >= 0); IGParameterAssert(toIndex >= 0); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Moving items from %@ without a collection view from index %li to index %li.", sectionController, (long)fromIndex, (long)toIndex); NSIndexPath *fromIndexPath = [self indexPathForSectionController:sectionController index:fromIndex usePreviousIfInUpdateBlock:YES]; NSIndexPath *toIndexPath = [self indexPathForSectionController:sectionController index:toIndex usePreviousIfInUpdateBlock:NO]; if (fromIndexPath == nil || toIndexPath == nil) { return; } [self.updater moveItemInCollectionView:collectionView fromIndexPath:fromIndexPath toIndexPath:toIndexPath]; } - (void)reloadSectionController:(IGListSectionController *)sectionController { IGAssertMainThread(); IGParameterAssert(sectionController != nil); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Reloading items from %@ without a collection view.", sectionController); IGListSectionMap *map = [self _sectionMapUsingPreviousIfInUpdateBlock:YES]; const NSInteger section = [map sectionForSectionController:sectionController]; if (section == NSNotFound) { return; } NSIndexSet *sections = [NSIndexSet indexSetWithIndex:section]; [self.updater reloadCollectionView:collectionView sections:sections]; [self _updateBackgroundViewShouldHide:![self _itemCountIsZero]]; } - (void)moveSectionControllerInteractive:(IGListSectionController *)sectionController fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex NS_AVAILABLE_IOS(9_0) { IGAssertMainThread(); IGParameterAssert(sectionController != nil); IGParameterAssert(fromIndex >= 0); IGParameterAssert(toIndex >= 0); UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Moving section %@ without a collection view from index %li to index %li.", sectionController, (long)fromIndex, (long)toIndex); IGAssert(self.moveDelegate != nil, @"Moving section %@ without a moveDelegate set", sectionController); if (fromIndex != toIndex) { id dataSource = self.dataSource; NSArray *previousObjects = [self.sectionMap objects]; if (self.isLastInteractiveMoveToLastSectionIndex) { self.isLastInteractiveMoveToLastSectionIndex = NO; } else if (fromIndex < toIndex) { toIndex -= 1; } NSMutableArray *mutObjects = [previousObjects mutableCopy]; id object = [previousObjects objectAtIndex:fromIndex]; [mutObjects removeObjectAtIndex:fromIndex]; [mutObjects insertObject:object atIndex:toIndex]; NSArray *objects = [mutObjects copy]; // inform the data source to update its model [self.moveDelegate listAdapter:self moveObject:object from:previousObjects to:objects]; // update our model based on that provided by the data source NSArray> *updatedObjects = [dataSource objectsForListAdapter:self]; [self _updateObjects:updatedObjects dataSource:dataSource]; } // even if from and to index are equal, we need to perform the "move" // iOS interactively moves items, not sections, so we might have actually moved the item // to the end of the preceeding section or beginning of the following section [self.updater moveSectionInCollectionView:collectionView fromIndex:fromIndex toIndex:toIndex]; } - (void)moveInSectionControllerInteractive:(IGListSectionController *)sectionController fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex NS_AVAILABLE_IOS(9_0) { IGAssertMainThread(); IGParameterAssert(sectionController != nil); IGParameterAssert(fromIndex >= 0); IGParameterAssert(toIndex >= 0); [sectionController moveObjectFromIndex:fromIndex toIndex:toIndex]; } - (void)revertInvalidInteractiveMoveFromIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath NS_AVAILABLE_IOS(9_0) { UICollectionView *collectionView = self.collectionView; IGAssert(collectionView != nil, @"Reverting move without a collection view from %@ to %@.", sourceIndexPath, destinationIndexPath); // revert by moving back in the opposite direction [collectionView moveItemAtIndexPath:destinationIndexPath toIndexPath:sourceIndexPath]; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListAdapterDataSource.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import @class IGListAdapter; @class IGListSectionController; NS_ASSUME_NONNULL_BEGIN /** Implement this protocol to provide data to an `IGListAdapter`. */ NS_SWIFT_NAME(ListAdapterDataSource) @protocol IGListAdapterDataSource /** Asks the data source for the objects to display in the list. @param listAdapter The list adapter requesting this information. @return An array of objects for the list. */ - (NSArray> *)objectsForListAdapter:(IGListAdapter *)listAdapter; /** Asks the data source for a section controller for the specified object in the list. @param listAdapter The list adapter requesting this information. @param object An object in the list. @return A new section controller instance that can be displayed in the list. @note New section controllers should be initialized here for objects when asked. You may pass any other data to the section controller at this time. Section controllers are initialized for all objects whenever the `IGListAdapter` is created, updated, or reloaded. Section controllers are reused when objects are moved or updated. Maintaining the `-[IGListDiffable diffIdentifier]` guarantees this. */ - (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object; /** Asks the data source for a view to use as the collection view background when the list is empty. @param listAdapter The list adapter requesting this information. @return A view to use as the collection view background, or `nil` if you don't want a background view. @note This method is called every time the list adapter is updated. You are free to return new views every time, but for performance reasons you may want to retain the view and return it here. The infra is only responsible for adding the background view and maintaining its visibility. */ - (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListAdapterDelegate.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListAdapter; NS_ASSUME_NONNULL_BEGIN /** Conform to `IGListAdapterDelegate` to receive display events for objects in a list. */ NS_SWIFT_NAME(ListAdapterDelegate) @protocol IGListAdapterDelegate /** Notifies the delegate that a list object is about to be displayed. @param listAdapter The list adapter sending this information. @param object The object that will display. @param index The index of the object in the list. */ - (void)listAdapter:(IGListAdapter *)listAdapter willDisplayObject:(id)object atIndex:(NSInteger)index; /** Notifies the delegate that a list object is no longer being displayed. @param listAdapter The list adapter sending this information. @param object The object that ended display. @param index The index of the object in the list. */ - (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingObject:(id)object atIndex:(NSInteger)index; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListAdapterMoveDelegate.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListAdapter; NS_ASSUME_NONNULL_BEGIN /** Conform to `IGListAdapterMoveDelegate` to receive interactive reordering requests. */ NS_SWIFT_NAME(ListAdapterMoveDelegate) @protocol IGListAdapterMoveDelegate /** Asks the delegate to move a section object as the result of interactive reordering. @param listAdapter The list adapter sending this information. @param object the object that was moved @param previousObjects The array of objects prior to the move. @param objects The array of objects after the move. */ - (void)listAdapter:(IGListAdapter *)listAdapter moveObject:(id)object from:(NSArray *)previousObjects to:(NSArray *)objects; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListAdapterPerformanceDelegate.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListAdapter; @class IGListSectionController; NS_ASSUME_NONNULL_BEGIN /** `IGListAdapterPerformanceDelegate` can be used to measure cell dequeue, display, size, and scroll callbacks. */ NS_SWIFT_NAME(ListAdapterPerformanceDelegate) @protocol IGListAdapterPerformanceDelegate /** Will call `-[IGListAdapter collectionView:cellForItemAtIndexPath:]`. @param listAdapter The list adapter sending this information. */ - (void)listAdapterWillCallDequeueCell:(IGListAdapter *)listAdapter; /** Did finish calling `-[IGListAdapter collectionView:cellForItemAtIndexPath:]`. @param listAdapter The list adapter sending this information. @param cell A cell that was dequeued. @param sectionController The section controller providing the cell. @param index Item index of the cell. */ - (void)listAdapter:(IGListAdapter *)listAdapter didCallDequeueCell:(UICollectionViewCell *)cell onSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; /** Will call `-[IGListAdapter collectionView:willDisplayCell:forItemAtIndexPath:]`. @param listAdapter The list adapter sending this information. */ - (void)listAdapterWillCallDisplayCell:(IGListAdapter *)listAdapter; /** Did finish calling `-[IGListAdapter collectionView:willDisplayCell:forItemAtIndexPath:]`. @param listAdapter The list adapter sending this information. @param cell A cell that will be displayed. @param sectionController The section controller for that cell. @param index Item index of the cell. @note Keep in mind this also includes calling the `IGListAdapter`'s collectionViewDelegate and workingRangeHandler. */ - (void)listAdapter:(IGListAdapter *)listAdapter didCallDisplayCell:(UICollectionViewCell *)cell onSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; /** Will call `-[IGListAdapter collectionView:didEndDisplayingCell:forItemAtIndexPath:]`. @param listAdapter The list adapter sending this information. */ - (void)listAdapterWillCallEndDisplayCell:(IGListAdapter *)listAdapter; /** Did finish calling `-[IGListAdapter collectionView:didEndDisplayingCell:forItemAtIndexPath:]`. @param listAdapter The list adapter sending this information. @param cell A cell that was displayed. @param sectionController The section controller for that cell. @param index Item index of the cell. @note Keep in mind this also includes calling the `IGListAdapter`'s collectionViewDelegate and workingRangeHandler. */ - (void)listAdapter:(IGListAdapter *)listAdapter didCallEndDisplayCell:(UICollectionViewCell *)cell onSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; /** Will call `-[IGListAdapter collectionView:collectionViewLayout:sizeForItemAtIndexPath:]`. @param listAdapter The list adapter sending this information. */ - (void)listAdapterWillCallSize:(IGListAdapter *)listAdapter; /** Did finish calling `-[IGListAdapter collectionView:collectionViewLayout:sizeForItemAtIndexPath:]`. @param listAdapter The list adapter sending this information. @param sectionController The section controller providing the size. @param index Item index used to calculate the size. */ - (void)listAdapter:(IGListAdapter *)listAdapter didCallSizeOnSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; /** Will call `-[IGListAdapter scrollViewDidScroll:]`. @param listAdapter The list adapter sending this information. */ - (void)listAdapterWillCallScroll:(IGListAdapter *)listAdapter; /** Did finish calling `-[IGListAdapter scrollViewDidScroll:]`. @param listAdapter The list adapter sending this information. @param scrollView The scroll view backing the UICollectionView. @note Keep in mind this also includes calling the `IGListAdapter`'s scrollViewDelegate and all visible `IGListSectioControllers`. */ - (void)listAdapter:(IGListAdapter *)listAdapter didCallScroll:(UIScrollView *)scrollView; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListAdapterUpdateListener.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListAdapter; NS_ASSUME_NONNULL_BEGIN /** The type of update that was performed by an `IGListAdapter`. */ NS_SWIFT_NAME(ListAdapterUpdateType) typedef NS_ENUM(NSInteger, IGListAdapterUpdateType) { /** `-[IGListAdapter performUpdatesAnimated:completion:]` was executed. */ IGListAdapterUpdateTypePerformUpdates, /** `-[IGListAdapter reloadDataWithCompletion:]` was executed. */ IGListAdapterUpdateTypeReloadData, /** `-[IGListCollectionContext performBatchAnimated:updates:completion:]` was executed by an `IGListSectionController`. */ IGListAdapterUpdateTypeItemUpdates, }; /** Conform to this protocol to receive events about `IGListAdapter` updates. */ NS_SWIFT_NAME(ListAdapterUpdateListener) @protocol IGListAdapterUpdateListener /** Notifies a listener that the listAdapter was updated. @param listAdapter The `IGListAdapter` that updated. @param update The type of update executed. @param animated A flag indicating if the update was animated. Always `NO` for `IGListAdapterUpdateTypeReloadData`. @note This event is sent before the completion block in `-[IGListAdapter performUpdatesAnimated:completion:]` and `-[IGListAdapter reloadDataWithCompletion:]` is executed. This event is also delivered when an `IGListSectionController` updates via `-[IGListCollectionContext performBatchAnimated:updates:completion:]`. */ - (void)listAdapter:(IGListAdapter *)listAdapter didFinishUpdate:(IGListAdapterUpdateType)update animated:(BOOL)animated; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListAdapterUpdater.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import #import NS_ASSUME_NONNULL_BEGIN /** An `IGListAdapterUpdater` is a concrete type that conforms to `IGListUpdatingDelegate`. It is an out-of-box updater for `IGListAdapter` objects to use. @note This updater performs re-entrant, coalesced updating for a list. It also uses a least-minimal diff for calculating UI updates when `IGListAdapter` calls `-performUpdateWithCollectionView:fromObjects:toObjects:completion:`. */ IGLK_SUBCLASSING_RESTRICTED NS_SWIFT_NAME(ListAdapterUpdater) @interface IGListAdapterUpdater : NSObject /** The delegate that receives events with data on the performance of a transition. */ @property (nonatomic, weak) id delegate; /** A flag indicating if a move should be treated as a "delete, then insert" operation. */ @property (nonatomic, assign) BOOL movesAsDeletesInserts; /** A flag indicating that section reloads should be treated as item reloads, instead of converting them to "delete, then insert" operations. This only applies if the number of items for the section is unchanged. @note If the number of items for the section is changed, we would fallback to the default behavior and convert it to "delete + insert", because the collectionView can crash otherwise. Default is NO. */ @property (nonatomic, assign) BOOL preferItemReloadsForSectionReloads; /** A flag indicating whether this updater should skip diffing and simply call `reloadData` for updates when the collection view is not in a window. The default value is `YES`. @note This will result in better performance, but will not generate the same delegate callbacks. If using a custom layout, it will not receive `prepareForCollectionViewUpdates:`. @warning On iOS < 8.3, this behavior is unsupported and will always be treated as `NO`. */ @property (nonatomic, assign) BOOL allowsBackgroundReloading; /** A bitmask of experiments to conduct on the updater. */ @property (nonatomic, assign) IGListExperiment experiments; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListAdapterUpdater.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListAdapterUpdater.h" #import "IGListAdapterUpdaterInternal.h" #import #import "IGListArrayUtilsInternal.h" #import "IGListIndexSetResultInternal.h" #import "IGListMoveIndexPathInternal.h" #import "IGListReloadIndexPath.h" #import "UICollectionView+IGListBatchUpdateData.h" @implementation IGListAdapterUpdater - (instancetype)init { IGAssertMainThread(); if (self = [super init]) { // the default is to use animations unless NO is passed _queuedUpdateIsAnimated = YES; _completionBlocks = [NSMutableArray new]; _batchUpdates = [IGListBatchUpdates new]; _allowsBackgroundReloading = YES; } return self; } #pragma mark - Private API - (BOOL)hasChanges { return self.hasQueuedReloadData || [self.batchUpdates hasChanges] || self.fromObjects != nil || self.toObjectsBlock != nil; } - (void)performReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock { IGAssertMainThread(); id delegate = self.delegate; void (^reloadUpdates)(void) = self.reloadUpdates; IGListBatchUpdates *batchUpdates = self.batchUpdates; NSMutableArray *completionBlocks = [self.completionBlocks mutableCopy]; [self cleanStateBeforeUpdates]; void (^executeCompletionBlocks)(BOOL) = ^(BOOL finished) { for (IGListUpdatingCompletion block in completionBlocks) { block(finished); } self.state = IGListBatchUpdateStateIdle; }; // bail early if the collection view has been deallocated in the time since the update was queued UICollectionView *collectionView = collectionViewBlock(); if (collectionView == nil) { [self _cleanStateAfterUpdates]; executeCompletionBlocks(NO); return; } // item updates must not send mutations to the collection view while we are reloading self.state = IGListBatchUpdateStateExecutingBatchUpdateBlock; if (reloadUpdates) { reloadUpdates(); } // execute all stored item update blocks even if we are just calling reloadData. the actual collection view // mutations will be discarded, but clients are encouraged to put their actual /data/ mutations inside the // update block as well, so if we don't execute the block the changes will never happen for (IGListItemUpdateBlock itemUpdateBlock in batchUpdates.itemUpdateBlocks) { itemUpdateBlock(); } // add any completion blocks from item updates. added after item blocks are executed in order to capture any // re-entrant updates [completionBlocks addObjectsFromArray:batchUpdates.itemCompletionBlocks]; self.state = IGListBatchUpdateStateExecutedBatchUpdateBlock; [self _cleanStateAfterUpdates]; [delegate listAdapterUpdater:self willReloadDataWithCollectionView:collectionView]; [collectionView reloadData]; [collectionView.collectionViewLayout invalidateLayout]; [collectionView layoutIfNeeded]; [delegate listAdapterUpdater:self didReloadDataWithCollectionView:collectionView]; executeCompletionBlocks(YES); } - (void)performBatchUpdatesWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock { IGAssertMainThread(); IGAssert(self.state == IGListBatchUpdateStateIdle, @"Should not call batch updates when state isn't idle"); // create local variables so we can immediately clean our state but pass these items into the batch update block id delegate = self.delegate; NSArray *fromObjects = [self.fromObjects copy]; IGListToObjectBlock toObjectsBlock = [self.toObjectsBlock copy]; NSMutableArray *completionBlocks = [self.completionBlocks mutableCopy]; void (^objectTransitionBlock)(NSArray *) = [self.objectTransitionBlock copy]; const BOOL animated = self.queuedUpdateIsAnimated; IGListBatchUpdates *batchUpdates = self.batchUpdates; // clean up all state so that new updates can be coalesced while the current update is in flight [self cleanStateBeforeUpdates]; void (^executeCompletionBlocks)(BOOL) = ^(BOOL finished) { self.applyingUpdateData = nil; self.state = IGListBatchUpdateStateIdle; for (IGListUpdatingCompletion block in completionBlocks) { block(finished); } }; // bail early if the collection view has been deallocated in the time since the update was queued UICollectionView *collectionView = collectionViewBlock(); if (collectionView == nil) { [self _cleanStateAfterUpdates]; executeCompletionBlocks(NO); return; } NSArray *toObjects = nil; if (toObjectsBlock != nil) { toObjects = objectsWithDuplicateIdentifiersRemoved(toObjectsBlock()); } #ifdef DEBUG for (id obj in toObjects) { IGAssert([obj conformsToProtocol:@protocol(IGListDiffable)], @"In order to use IGListAdapterUpdater, object %@ must conform to IGListDiffable", obj); IGAssert([obj diffIdentifier] != nil, @"Cannot have a nil diffIdentifier for object %@", obj); } #endif void (^executeUpdateBlocks)(void) = ^{ self.state = IGListBatchUpdateStateExecutingBatchUpdateBlock; // run the update block so that the adapter can set its items. this makes sure that just before the update is // committed that the data source is updated to the /latest/ "toObjects". this makes the data source in sync // with the items that the updater is transitioning to if (objectTransitionBlock != nil) { objectTransitionBlock(toObjects); } // execute each item update block which should make calls like insert, delete, and reload for index paths // we collect all mutations in corresponding sets on self, then filter based on UICollectionView shortcomings // call after the objectTransitionBlock so section level mutations happen before any items for (IGListItemUpdateBlock itemUpdateBlock in batchUpdates.itemUpdateBlocks) { itemUpdateBlock(); } // add any completion blocks from item updates. added after item blocks are executed in order to capture any // re-entrant updates [completionBlocks addObjectsFromArray:batchUpdates.itemCompletionBlocks]; self.state = IGListBatchUpdateStateExecutedBatchUpdateBlock; }; void (^reloadDataFallback)(void) = ^{ executeUpdateBlocks(); [self _cleanStateAfterUpdates]; [self _performBatchUpdatesItemBlockApplied]; [collectionView reloadData]; [collectionView layoutIfNeeded]; executeCompletionBlocks(YES); }; // if the collection view isn't in a visible window, skip diffing and batch updating. execute all transition blocks, // reload data, execute completion blocks, and get outta here if (self.allowsBackgroundReloading && collectionView.window == nil) { [self _beginPerformBatchUpdatesToObjects:toObjects]; reloadDataFallback(); return; } // disables multiple performBatchUpdates: from happening at the same time [self _beginPerformBatchUpdatesToObjects:toObjects]; const IGListExperiment experiments = self.experiments; IGListIndexSetResult *(^performDiff)(void) = ^{ return IGListDiffExperiment(fromObjects, toObjects, IGListDiffEquality, experiments); }; // block executed in the first param block of -[UICollectionView performBatchUpdates:completion:] void (^batchUpdatesBlock)(IGListIndexSetResult *result) = ^(IGListIndexSetResult *result){ executeUpdateBlocks(); self.applyingUpdateData = [self _flushCollectionView:collectionView withDiffResult:result batchUpdates:self.batchUpdates fromObjects:fromObjects]; [self _cleanStateAfterUpdates]; [self _performBatchUpdatesItemBlockApplied]; }; // block used as the second param of -[UICollectionView performBatchUpdates:completion:] void (^batchUpdatesCompletionBlock)(BOOL) = ^(BOOL finished) { IGListBatchUpdateData *oldApplyingUpdateData = self.applyingUpdateData; executeCompletionBlocks(finished); [delegate listAdapterUpdater:self didPerformBatchUpdates:oldApplyingUpdateData collectionView:collectionView]; // queue another update in case something changed during batch updates. this method will bail next runloop if // there are no changes [self _queueUpdateWithCollectionViewBlock:collectionViewBlock]; }; // block that executes the batch update and exception handling void (^performUpdate)(IGListIndexSetResult *) = ^(IGListIndexSetResult *result){ [collectionView layoutIfNeeded]; @try { [delegate listAdapterUpdater:self willPerformBatchUpdatesWithCollectionView:collectionView fromObjects:fromObjects toObjects:toObjects listIndexSetResult:result]; if (collectionView.dataSource == nil) { // If the data source is nil, we should not call any collection view update. batchUpdatesCompletionBlock(NO); } else if (result.changeCount > 100 && IGListExperimentEnabled(experiments, IGListExperimentReloadDataFallback)) { reloadDataFallback(); } else if (animated) { [collectionView performBatchUpdates:^{ batchUpdatesBlock(result); } completion:batchUpdatesCompletionBlock]; } else { [CATransaction begin]; [CATransaction setDisableActions:YES]; [collectionView performBatchUpdates:^{ batchUpdatesBlock(result); } completion:^(BOOL finished) { [CATransaction commit]; batchUpdatesCompletionBlock(finished); }]; } } @catch (NSException *exception) { [delegate listAdapterUpdater:self collectionView:collectionView willCrashWithException:exception fromObjects:fromObjects toObjects:toObjects diffResult:result updates:(id)self.applyingUpdateData]; @throw exception; } }; if (IGListExperimentEnabled(experiments, IGListExperimentBackgroundDiffing)) { dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ IGListIndexSetResult *result = performDiff(); dispatch_async(dispatch_get_main_queue(), ^{ performUpdate(result); }); }); } else { IGListIndexSetResult *result = performDiff(); performUpdate(result); } } void convertReloadToDeleteInsert(NSMutableIndexSet *reloads, NSMutableIndexSet *deletes, NSMutableIndexSet *inserts, IGListIndexSetResult *result, NSArray> *fromObjects) { // reloadSections: is unsafe to use within performBatchUpdates:, so instead convert all reloads into deletes+inserts const BOOL hasObjects = [fromObjects count] > 0; [[reloads copy] enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { // if a diff was not performed, there are no changes. instead use the same index that was originally queued id diffIdentifier = hasObjects ? [fromObjects[idx] diffIdentifier] : nil; const NSInteger from = hasObjects ? [result oldIndexForIdentifier:diffIdentifier] : idx; const NSInteger to = hasObjects ? [result newIndexForIdentifier:diffIdentifier] : idx; [reloads removeIndex:from]; // if a reload is queued outside the diff and the object was inserted or deleted it cannot be if (from != NSNotFound && to != NSNotFound) { [deletes addIndex:from]; [inserts addIndex:to]; } else { IGAssert([result.deletes containsIndex:idx], @"Reloaded section %lu was not found in deletes with from: %li, to: %li, deletes: %@, fromClass: %@", (unsigned long)idx, (long)from, (long)to, deletes, [(id)fromObjects[idx] class]); } }]; } static NSArray *convertSectionReloadToItemUpdates(NSIndexSet *sectionReloads, UICollectionView *collectionView) { NSMutableArray *updates = [NSMutableArray new]; [sectionReloads enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL * _Nonnull stop) { NSUInteger numberOfItems = [collectionView numberOfItemsInSection:sectionIndex]; for (NSUInteger itemIndex = 0; itemIndex < numberOfItems; itemIndex++) { [updates addObject:[NSIndexPath indexPathForItem:itemIndex inSection:sectionIndex]]; } }]; return [updates copy]; } - (IGListBatchUpdateData *)_flushCollectionView:(UICollectionView *)collectionView withDiffResult:(IGListIndexSetResult *)diffResult batchUpdates:(IGListBatchUpdates *)batchUpdates fromObjects:(NSArray > *)fromObjects { NSSet *moves = [[NSSet alloc] initWithArray:diffResult.moves]; // combine section reloads from the diff and manual reloads via reloadItems: NSMutableIndexSet *reloads = [diffResult.updates mutableCopy]; [reloads addIndexes:batchUpdates.sectionReloads]; NSMutableIndexSet *inserts = [diffResult.inserts mutableCopy]; NSMutableIndexSet *deletes = [diffResult.deletes mutableCopy]; NSMutableArray *itemUpdates = [NSMutableArray new]; if (self.movesAsDeletesInserts) { for (IGListMoveIndex *move in moves) { [deletes addIndex:move.from]; [inserts addIndex:move.to]; } // clear out all moves moves = [NSSet new]; } // Item reloads are not safe, if any section moves happened or there are inserts/deletes. if (self.preferItemReloadsForSectionReloads && moves.count == 0 && inserts.count == 0 && deletes.count == 0 && reloads.count > 0) { [reloads enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL * _Nonnull stop) { NSMutableIndexSet *localIndexSet = [NSMutableIndexSet indexSetWithIndex:sectionIndex]; if (sectionIndex < [collectionView numberOfSections] && sectionIndex < [collectionView.dataSource numberOfSectionsInCollectionView:collectionView] && [collectionView numberOfItemsInSection:sectionIndex] == [collectionView.dataSource collectionView:collectionView numberOfItemsInSection:sectionIndex]) { // Perfer to do item reloads instead, if the number of items in section is unchanged. [itemUpdates addObjectsFromArray:convertSectionReloadToItemUpdates(localIndexSet, collectionView)]; } else { // Otherwise, fallback to convert into delete+insert section operation. convertReloadToDeleteInsert(localIndexSet, deletes, inserts, diffResult, fromObjects); } }]; } else { // reloadSections: is unsafe to use within performBatchUpdates:, so instead convert all reloads into deletes+inserts convertReloadToDeleteInsert(reloads, deletes, inserts, diffResult, fromObjects); } NSMutableArray *itemInserts = batchUpdates.itemInserts; NSMutableArray *itemDeletes = batchUpdates.itemDeletes; NSMutableArray *itemMoves = batchUpdates.itemMoves; NSSet *uniqueDeletes = [NSSet setWithArray:itemDeletes]; NSMutableSet *reloadDeletePaths = [NSMutableSet new]; NSMutableSet *reloadInsertPaths = [NSMutableSet new]; for (IGListReloadIndexPath *reload in batchUpdates.itemReloads) { if (![uniqueDeletes containsObject:reload.fromIndexPath]) { [reloadDeletePaths addObject:reload.fromIndexPath]; [reloadInsertPaths addObject:reload.toIndexPath]; } } [itemDeletes addObjectsFromArray:[reloadDeletePaths allObjects]]; [itemInserts addObjectsFromArray:[reloadInsertPaths allObjects]]; const BOOL fixIndexPathImbalance = IGListExperimentEnabled(self.experiments, IGListExperimentFixIndexPathImbalance); IGListBatchUpdateData *updateData = [[IGListBatchUpdateData alloc] initWithInsertSections:inserts deleteSections:deletes moveSections:moves insertIndexPaths:itemInserts deleteIndexPaths:itemDeletes updateIndexPaths:itemUpdates moveIndexPaths:itemMoves fixIndexPathImbalance:fixIndexPathImbalance]; [collectionView ig_applyBatchUpdateData:updateData]; return updateData; } - (void)_beginPerformBatchUpdatesToObjects:(NSArray *)toObjects { self.pendingTransitionToObjects = toObjects; self.state = IGListBatchUpdateStateQueuedBatchUpdate; } - (void)_performBatchUpdatesItemBlockApplied { self.pendingTransitionToObjects = nil; } - (void)cleanStateBeforeUpdates { self.queuedUpdateIsAnimated = YES; // destroy to/from transition items self.fromObjects = nil; self.toObjectsBlock = nil; // destroy reloadData state self.reloadUpdates = nil; self.queuedReloadData = NO; // remove indexpath/item changes self.objectTransitionBlock = nil; // removes all object completion blocks. done before updates to start collecting completion blocks for coalesced // or re-entrant object updates [self.completionBlocks removeAllObjects]; } - (void)_cleanStateAfterUpdates { self.batchUpdates = [IGListBatchUpdates new]; } - (void)_queueUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock { IGAssertMainThread(); __weak __typeof__(self) weakSelf = self; // dispatch_async to give the main queue time to collect more batch updates so that a minimum amount of work // (diffing, etc) is done on main. dispatch_async does not garauntee a full runloop turn will pass though. // see -performUpdateWithCollectionView:fromObjects:toObjects:animated:objectTransitionBlock:completion: for more // details on how coalescence is done. dispatch_async(dispatch_get_main_queue(), ^{ if (weakSelf.state != IGListBatchUpdateStateIdle || ![weakSelf hasChanges]) { return; } if (weakSelf.hasQueuedReloadData) { [weakSelf performReloadDataWithCollectionViewBlock:collectionViewBlock]; } else { [weakSelf performBatchUpdatesWithCollectionViewBlock:collectionViewBlock]; } }); } #pragma mark - IGListUpdatingDelegate static BOOL IGListIsEqual(const void *a, const void *b, NSUInteger (*size)(const void *item)) { const id left = (__bridge id)a; const id right = (__bridge id)b; return [left class] == [right class] && [[left diffIdentifier] isEqual:[right diffIdentifier]]; } // since the diffing algo used in this updater keys items based on their -diffIdentifier, we must use a map table that // precisely mimics this behavior static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(const void *item)) { return [[(__bridge id)item diffIdentifier] hash]; } - (NSPointerFunctions *)objectLookupPointerFunctions { NSPointerFunctions *functions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory]; functions.hashFunction = IGListIdentifierHash; functions.isEqualFunction = IGListIsEqual; return functions; } - (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock fromObjects:(NSArray *)fromObjects toObjectsBlock:(IGListToObjectBlock)toObjectsBlock animated:(BOOL)animated objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock completion:(IGListUpdatingCompletion)completion { IGAssertMainThread(); IGParameterAssert(collectionViewBlock != nil); IGParameterAssert(objectTransitionBlock != nil); // only update the items that we are coming from if it has not been set // this allows multiple updates to be called while an update is already in progress, and the transition from > to // will be done on the first "fromObjects" received and the last "toObjects" // if performBatchUpdates: hasn't applied the update block, then data source hasn't transitioned its state. if an // update is queued in between then we must use the pending toObjects self.fromObjects = self.fromObjects ?: self.pendingTransitionToObjects ?: fromObjects; self.toObjectsBlock = toObjectsBlock; // disabled animations will always take priority // reset to YES in -cleanupState self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated; // always use the last update block, even though this should always do the exact same thing self.objectTransitionBlock = objectTransitionBlock; IGListUpdatingCompletion localCompletion = completion; if (localCompletion) { [self.completionBlocks addObject:localCompletion]; } [self _queueUpdateWithCollectionViewBlock:collectionViewBlock]; } - (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock animated:(BOOL)animated itemUpdates:(void (^)(void))itemUpdates completion:(void (^)(BOOL))completion { IGAssertMainThread(); IGParameterAssert(collectionViewBlock != nil); IGParameterAssert(itemUpdates != nil); IGListBatchUpdates *batchUpdates = self.batchUpdates; if (completion != nil) { [batchUpdates.itemCompletionBlocks addObject:completion]; } // if already inside the execution of the update block, immediately unload the itemUpdates block. // the completion blocks are executed later in the lifecycle, so that still needs to be added to the batch if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) { itemUpdates(); } else { [batchUpdates.itemUpdateBlocks addObject:itemUpdates]; // disabled animations will always take priority // reset to YES in -cleanupState self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated; [self _queueUpdateWithCollectionViewBlock:collectionViewBlock]; } } - (void)insertItemsIntoCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths { IGAssertMainThread(); IGParameterAssert(collectionView != nil); IGParameterAssert(indexPaths != nil); if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) { [self.batchUpdates.itemInserts addObjectsFromArray:indexPaths]; } else { [self.delegate listAdapterUpdater:self willInsertIndexPaths:indexPaths collectionView:collectionView]; [collectionView insertItemsAtIndexPaths:indexPaths]; } } - (void)deleteItemsFromCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths { IGAssertMainThread(); IGParameterAssert(collectionView != nil); IGParameterAssert(indexPaths != nil); if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) { [self.batchUpdates.itemDeletes addObjectsFromArray:indexPaths]; } else { [self.delegate listAdapterUpdater:self willDeleteIndexPaths:indexPaths collectionView:collectionView]; [collectionView deleteItemsAtIndexPaths:indexPaths]; } } - (void)moveItemInCollectionView:(UICollectionView *)collectionView fromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) { IGListMoveIndexPath *move = [[IGListMoveIndexPath alloc] initWithFrom:fromIndexPath to:toIndexPath]; [self.batchUpdates.itemMoves addObject:move]; } else { [self.delegate listAdapterUpdater:self willMoveFromIndexPath:fromIndexPath toIndexPath:toIndexPath collectionView:collectionView]; [collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; } } - (void)reloadItemInCollectionView:(UICollectionView *)collectionView fromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) { IGListReloadIndexPath *reload = [[IGListReloadIndexPath alloc] initWithFromIndexPath:fromIndexPath toIndexPath:toIndexPath]; [self.batchUpdates.itemReloads addObject:reload]; } else { [self.delegate listAdapterUpdater:self willReloadIndexPaths:@[fromIndexPath] collectionView:collectionView]; [collectionView reloadItemsAtIndexPaths:@[fromIndexPath]]; } } - (void)moveSectionInCollectionView:(UICollectionView *)collectionView fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex { IGAssertMainThread(); IGParameterAssert(collectionView != nil); // iOS expects interactive reordering to be movement of items not sections // after moving a single-item section controller, // you end up with two items in the section for the drop location, // and zero items in the section originating at the drag location // so, we have to reload data rather than doing a section move [collectionView reloadData]; // It seems that reloadData called during UICollectionView's moveItemAtIndexPath // delegate call does not reload all cells as intended // So, we further reload all visible sections to make sure none of our cells // are left with data that's out of sync with our dataSource id delegate = self.delegate; NSMutableIndexSet *visibleSections = [NSMutableIndexSet new]; NSArray *visibleIndexPaths = [collectionView indexPathsForVisibleItems]; for (NSIndexPath *visibleIndexPath in visibleIndexPaths) { [visibleSections addIndex:visibleIndexPath.section]; } [delegate listAdapterUpdater:self willReloadSections:visibleSections collectionView:collectionView]; // prevent double-animation from reloadData + reloadSections [CATransaction begin]; [CATransaction setDisableActions:YES]; [collectionView performBatchUpdates:^{ [collectionView reloadSections:visibleSections]; } completion:^(BOOL finished) { [CATransaction commit]; }]; } - (void)reloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock completion:(nullable IGListUpdatingCompletion)completion { IGAssertMainThread(); IGParameterAssert(collectionViewBlock != nil); IGParameterAssert(reloadUpdateBlock != nil); IGListUpdatingCompletion localCompletion = completion; if (localCompletion) { [self.completionBlocks addObject:localCompletion]; } self.reloadUpdates = reloadUpdateBlock; self.queuedReloadData = YES; [self _queueUpdateWithCollectionViewBlock:collectionViewBlock]; } - (void)reloadCollectionView:(UICollectionView *)collectionView sections:(NSIndexSet *)sections { IGAssertMainThread(); IGParameterAssert(collectionView != nil); IGParameterAssert(sections != nil); if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) { [self.batchUpdates.sectionReloads addIndexes:sections]; } else { [self.delegate listAdapterUpdater:self willReloadSections:sections collectionView:collectionView]; [collectionView reloadSections:sections]; } } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListAdapterUpdaterDelegate.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import @class IGListAdapterUpdater; @class IGListBatchUpdates; @class IGListIndexSetResult; @protocol IGListDiffable; NS_ASSUME_NONNULL_BEGIN /** A protocol that receives events about `IGListAdapterUpdater` operations. */ NS_SWIFT_NAME(ListAdapterUpdaterDelegate) @protocol IGListAdapterUpdaterDelegate /** Notifies the delegate that the updater will call `-[UICollectionView performBatchUpdates:completion:]`. @param listAdapterUpdater The adapter updater owning the transition. @param collectionView The collection view that will perform the batch updates. @param fromObjects The items transitioned from in the batch updates, if any. @param toObjects The items transitioned to in the batch updates, if any. @param listIndexSetResults The diffing result of indices to be inserted/removed/updated/moved/etc. */ - (void) listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willPerformBatchUpdatesWithCollectionView:(UICollectionView *)collectionView fromObjects:(nullable NSArray > *)fromObjects toObjects:(nullable NSArray > *)toObjects listIndexSetResult:(nullable IGListIndexSetResult *)listIndexSetResults; /** Notifies the delegate that the updater successfully finished `-[UICollectionView performBatchUpdates:completion:]`. @param listAdapterUpdater The adapter updater owning the transition. @param updates The batch updates that were applied to the collection view. @param collectionView The collection view that performed the batch updates. @note This event is called in the completion block of the batch update. */ - (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater didPerformBatchUpdates:(IGListBatchUpdateData *)updates collectionView:(UICollectionView *)collectionView; /** Notifies the delegate that the updater will call `-[UICollectionView insertItemsAtIndexPaths:]`. @param listAdapterUpdater The adapter updater owning the transition. @param indexPaths An array of index paths that will be inserted. @param collectionView The collection view that will perform the insert. @note This event is only sent when outside of `-[UICollectionView performBatchUpdates:completion:]`. */ - (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willInsertIndexPaths:(NSArray *)indexPaths collectionView:(UICollectionView *)collectionView; /** Notifies the delegate that the updater will call `-[UICollectionView deleteItemsAtIndexPaths:]`. @param listAdapterUpdater The adapter updater owning the transition. @param indexPaths An array of index paths that will be deleted. @param collectionView The collection view that will perform the delete. @note This event is only sent when outside of `-[UICollectionView performBatchUpdates:completion:]`. */ - (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willDeleteIndexPaths:(NSArray *)indexPaths collectionView:(UICollectionView *)collectionView; /** Notifies the delegate that the updater will call `-[UICollectionView moveItemAtIndexPath:toIndexPath:]` @param listAdapterUpdater The adapter updater owning the transition. @param fromIndexPath The index path of the item that will be moved. @param toIndexPath The index path to move the item to. @param collectionView The collection view that will perform the move. @note This event is only sent when outside of `-[UICollectionView performBatchUpdates:completion:]`. */ - (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willMoveFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath collectionView:(UICollectionView *)collectionView; /** Notifies the delegate that the updater will call `-[UICollectionView reloadItemsAtIndexPaths:]`. @param listAdapterUpdater The adapter updater owning the transition. @param indexPaths An array of index paths that will be reloaded. @param collectionView The collection view that will perform the reload. @note This event is only sent when outside of `-[UICollectionView performBatchUpdates:completion:]`. */ - (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willReloadIndexPaths:(NSArray *)indexPaths collectionView:(UICollectionView *)collectionView; /** Notifies the delegate that the updater will call `-[UICollectionView reloadSections:]`. @param listAdapterUpdater The adapter updater owning the transition. @param sections The sections that will be reloaded @param collectionView The collection view that will perform the reload. @note This event is only sent when outside of `-[UICollectionView performBatchUpdates:completion:]`. */ - (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willReloadSections:(NSIndexSet *)sections collectionView:(UICollectionView *)collectionView; /** Notifies the delegate that the updater will call `-[UICollectionView reloadData]`. @param listAdapterUpdater The adapter updater owning the transition. @param collectionView The collection view that will be reloaded. */ - (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willReloadDataWithCollectionView:(UICollectionView *)collectionView; /** Notifies the delegate that the updater successfully called `-[UICollectionView reloadData]`. @param listAdapterUpdater The adapter updater owning the transition. @param collectionView The collection view that reloaded. */ - (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater didReloadDataWithCollectionView:(UICollectionView *)collectionView; /** Notifies the delegate that the collection view threw an exception in `-[UICollectionView performBatchUpdates:completion:]`. @param listAdapterUpdater The adapter updater owning the transition. @param collectionView The collection view being updated. @param exception The exception thrown by the collection view. @param fromObjects The items transitioned from in the diff, if any. @param toObjects The items transitioned to in the diff, if any. @param diffResult The diff result that were computed from `fromObjects` and `toObjects`. @param updates The batch updates that were applied to the collection view. */ - (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater collectionView:(UICollectionView *)collectionView willCrashWithException:(NSException *)exception fromObjects:(nullable NSArray *)fromObjects toObjects:(nullable NSArray *)toObjects diffResult:(IGListIndexSetResult *)diffResult updates:(IGListBatchUpdateData *)updates; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListBatchContext.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListSectionController; NS_ASSUME_NONNULL_BEGIN /** Objects conforming to the IGListBatchContext protocol provide a way for section controllers to mutate their cells or reload everything within the section. */ NS_SWIFT_NAME(ListBatchContext) @protocol IGListBatchContext /** Reloads cells in the section controller. @param sectionController The section controller who's cells need reloading. @param indexes The indexes of items that need reloading. */ - (void)reloadInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes; /** Inserts cells in the list. @param sectionController The section controller who's cells need inserting. @param indexes The indexes of items that need inserting. */ - (void)insertInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes; /** Deletes cells in the list. @param sectionController The section controller who's cells need deleted. @param indexes The indexes of items that need deleting. */ - (void)deleteInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes; /** Invalidates layouts of cells at specific in the section controller. @param sectionController The section controller who's cells need invalidating. @param indexes The indexes of items that need invalidating. */ - (void)invalidateLayoutInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes; /** Moves a cell from one index to another within the section controller. @param sectionController The section controller who's cell needs moved. @param fromIndex The index the cell is currently in. @param toIndex The index the cell should move to. */ - (void)moveInSectionController:(IGListSectionController *)sectionController fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex; /** Reloads the entire section controller. @param sectionController The section controller who's cells need reloading. */ - (void)reloadSectionController:(IGListSectionController *)sectionController; /** Moves a section controller from one index to another during interactive reordering. @param sectionController The section controller to move. @param fromIndex The index where the section currently resides. @param toIndex The index the section should move to. */ - (void)moveSectionControllerInteractive:(IGListSectionController *)sectionController fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex NS_AVAILABLE_IOS(9_0); /** Moves an object within a section controller from one index to another during interactive reordering. @param sectionController The section controller containing the object to move. @param fromIndex The index where the object currently resides. @param toIndex The index the object should move to. */ - (void)moveInSectionControllerInteractive:(IGListSectionController *)sectionController fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex NS_AVAILABLE_IOS(9_0); /** Reverts an move from one indexPath to another during interactive reordering. @param sourceIndexPath The indexPath the item was originally in. @param destinationIndexPath The indexPath the item was moving to. */ - (void)revertInvalidInteractiveMoveFromIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath NS_AVAILABLE_IOS(9_0); @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListBindable.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import NS_ASSUME_NONNULL_BEGIN /** A protocol for cells that configure themselves given a view model. */ NS_SWIFT_NAME(ListBindable) @protocol IGListBindable /** Tells the cell to configure itself with the given view model. @param viewModel The view model for the cell. @note The view model can change many times throughout the lifetime of a cell as the model values change and the cell is reused. Implementations should use only this method to do their configuration. */ - (void)bindViewModel:(id)viewModel; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListBindingSectionController.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import #import @protocol IGListDiffable; @protocol IGListBindable; @class IGListBindingSectionController; NS_ASSUME_NONNULL_BEGIN /** This section controller uses a data source to transform its "top level" object into an array of diffable view models. It then automatically binds each view model to cells via the `IGListBindable` protocol. Models used with `IGListBindingSectionController` should take special care to always return `YES` for identical objects. That is, any objects with matching `-diffIdentifier`s should always be equal, that way the section controller can create new view models via the data source, create a diff, and update the specific cells that have changed. In Objective-C, your `-isEqualToDiffableObject:` can simply be: ``` - (BOOL)isEqualToDiffableObject:(id)object { return YES; } ``` In Swift: ``` func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { return true } ``` Only when `-diffIdentifier`s match is object equality compared, so you can assume the class is the same, and the instance has already been checked. */ NS_SWIFT_NAME(ListBindingSectionController) @interface IGListBindingSectionController<__covariant ObjectType : id> : IGListSectionController /** A data source that transforms a top-level object into view models, and returns cells and sizes for given view models. */ @property (nonatomic, weak, nullable) id dataSource; /** A delegate that receives selection events from cells in an `IGListBindingSectionController` instance. */ @property (nonatomic, weak, nullable) id selectionDelegate; /** The object currently assigned to the section controller, if any. */ @property (nonatomic, strong, readonly, nullable) ObjectType object; /** The array of view models created from the data source. Values are changed when the top-level object changes or by calling `-updateAnimated:completion:` manually. */ @property (nonatomic, strong, readonly) NSArray> *viewModels; /** Tells the section controller to query for new view models, diff the changes, and update its cells. @param animated A flag indicating if the transition should be animated or not. @param completion An optional completion block executed after updates finish. Parameter is YES if updates were applied. */ - (void)updateAnimated:(BOOL)animated completion:(nullable void (^)(BOOL updated))completion; /** Notifies the section that a list object should move within a section as the result of interactive reordering. @param sourceIndex The starting index of the object. @param destinationIndex The ending index of the object. @note this method must be implemented if interactive reordering is enabled. To ensure updating the internal viewModels array, **calling super is required**, preferably before your own implementation. */ - (void)moveObjectFromIndex:(NSInteger)sourceIndex toIndex:(NSInteger)destinationIndex NS_REQUIRES_SUPER; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListBindingSectionController.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListBindingSectionController.h" #import #import #import "IGListArrayUtilsInternal.h" typedef NS_ENUM(NSInteger, IGListDiffingSectionState) { IGListDiffingSectionStateIdle = 0, IGListDiffingSectionStateUpdateQueued, IGListDiffingSectionStateUpdateApplied }; @interface IGListBindingSectionController() @property (nonatomic, strong, readwrite) NSArray> *viewModels; @property (nonatomic, strong) id object; @property (nonatomic, assign) IGListDiffingSectionState state; @end @implementation IGListBindingSectionController #pragma mark - Public API - (void)updateAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { IGAssertMainThread(); if (self.state != IGListDiffingSectionStateIdle) { if (completion != nil) { completion(NO); } return; } self.state = IGListDiffingSectionStateUpdateQueued; __block IGListIndexSetResult *result = nil; __block NSArray> *oldViewModels = nil; id collectionContext = self.collectionContext; [self.collectionContext performBatchAnimated:animated updates:^(id batchContext) { if (self.state != IGListDiffingSectionStateUpdateQueued) { return; } oldViewModels = self.viewModels; id object = self.object; IGAssert(object != nil, @"Expected IGListBindingSectionController object to be non-nil before updating."); NSArray *newViewModels = [self.dataSource sectionController:self viewModelsForObject:object]; self.viewModels = objectsWithDuplicateIdentifiersRemoved(newViewModels); result = IGListDiff(oldViewModels, self.viewModels, IGListDiffEquality); [result.updates enumerateIndexesUsingBlock:^(NSUInteger oldUpdatedIndex, BOOL *stop) { id identifier = [oldViewModels[oldUpdatedIndex] diffIdentifier]; const NSInteger indexAfterUpdate = [result newIndexForIdentifier:identifier]; if (indexAfterUpdate != NSNotFound) { UICollectionViewCell *cell = [collectionContext cellForItemAtIndex:oldUpdatedIndex sectionController:self]; [cell bindViewModel:self.viewModels[indexAfterUpdate]]; } }]; if (IGListExperimentEnabled(self.collectionContext.experiments, IGListExperimentInvalidateLayoutForUpdates)) { [batchContext invalidateLayoutInSectionController:self atIndexes:result.updates]; } [batchContext deleteInSectionController:self atIndexes:result.deletes]; [batchContext insertInSectionController:self atIndexes:result.inserts]; for (IGListMoveIndex *move in result.moves) { [batchContext moveInSectionController:self fromIndex:move.from toIndex:move.to]; } self.state = IGListDiffingSectionStateUpdateApplied; } completion:^(BOOL finished) { self.state = IGListDiffingSectionStateIdle; if (completion != nil) { completion(YES); } }]; } #pragma mark - IGListSectionController Overrides - (NSInteger)numberOfItems { return self.viewModels.count; } - (CGSize)sizeForItemAtIndex:(NSInteger)index { return [self.dataSource sectionController:self sizeForViewModel:self.viewModels[index] atIndex:index]; } - (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index { id viewModel = self.viewModels[index]; UICollectionViewCell *cell = [self.dataSource sectionController:self cellForViewModel:viewModel atIndex:index]; [cell bindViewModel:viewModel]; return cell; } - (void)didUpdateToObject:(id)object { id oldObject = self.object; self.object = object; if (oldObject == nil) { NSArray *viewModels = [self.dataSource sectionController:self viewModelsForObject:object]; self.viewModels = objectsWithDuplicateIdentifiersRemoved(viewModels); } else { #if IGLK_LOGGING_ENABLED if (![oldObject isEqualToDiffableObject:object]) { IGLKLog(@"Warning: Unequal objects %@ and %@ will cause IGListBindingSectionController to reload the entire section", oldObject, object); } #endif [self updateAnimated:YES completion:nil]; } } - (void)moveObjectFromIndex:(NSInteger)sourceIndex toIndex:(NSInteger)destinationIndex { NSMutableArray *viewModels = [self.viewModels mutableCopy]; id modelAtSource = [viewModels objectAtIndex:sourceIndex]; [viewModels removeObjectAtIndex:sourceIndex]; [viewModels insertObject:modelAtSource atIndex:destinationIndex]; self.viewModels = viewModels; } - (void)didSelectItemAtIndex:(NSInteger)index { [self.selectionDelegate sectionController:self didSelectItemAtIndex:index viewModel:self.viewModels[index]]; } - (void)didDeselectItemAtIndex:(NSInteger)index { [self.selectionDelegate sectionController:self didDeselectItemAtIndex:index viewModel:self.viewModels[index]]; } - (void)didHighlightItemAtIndex:(NSInteger)index { [self.selectionDelegate sectionController:self didHighlightItemAtIndex:index viewModel:self.viewModels[index]]; } - (void)didUnhighlightItemAtIndex:(NSInteger)index { [self.selectionDelegate sectionController:self didUnhighlightItemAtIndex:index viewModel:self.viewModels[index]]; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListBindingSectionControllerDataSource.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListBindingSectionController; @protocol IGListBindable; @protocol IGListDiffable; NS_ASSUME_NONNULL_BEGIN /** A protocol that returns data to power cells in an `IGListBindingSectionController`. */ NS_SWIFT_NAME(ListBindingSectionControllerDataSource) @protocol IGListBindingSectionControllerDataSource /** Create an array of view models given a top-level object. @param sectionController The section controller requesting view models. @param object The top-level object that powers the section controller. @return A new array of view models. */ - (NSArray> *)sectionController:(IGListBindingSectionController *)sectionController viewModelsForObject:(id)object; /** Return a dequeued cell for a given view model. @param sectionController The section controller requesting a cell. @param viewModel The view model for the cell. @param index The index of the view model. @return A dequeued cell. @note The section controller will call `-bindViewModel:` with the provided view model after the cell is dequeued. You should handle cell configuration using this method. However, you can do additional configuration at this stage as well. */ - (UICollectionViewCell *)sectionController:(IGListBindingSectionController *)sectionController cellForViewModel:(id)viewModel atIndex:(NSInteger)index; /** Return a cell size for a given view model. @param sectionController The section controller requesting a size. @param viewModel The view model for the cell. @param index The index of the view model. @return A size for the view model. */ - (CGSize)sectionController:(IGListBindingSectionController *)sectionController sizeForViewModel:(id)viewModel atIndex:(NSInteger)index; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListBindingSectionControllerSelectionDelegate.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListBindingSectionController; NS_ASSUME_NONNULL_BEGIN /** A protocol that handles cell selection events in an `IGListBindingSectionController`. */ NS_SWIFT_NAME(ListBindingSectionControllerSelectionDelegate) @protocol IGListBindingSectionControllerSelectionDelegate /** Tells the delegate that a cell at a given index was selected. @param sectionController The section controller the selection occurred in. @param index The index of the selected cell. @param viewModel The view model that was bound to the cell. */ - (void)sectionController:(IGListBindingSectionController *)sectionController didSelectItemAtIndex:(NSInteger)index viewModel:(id)viewModel; /** Tells the delegate that a cell at a given index was deselected. @param sectionController The section controller the deselection occurred in. @param index The index of the deselected cell. @param viewModel The view model that was bound to the cell. */ - (void)sectionController:(IGListBindingSectionController *)sectionController didDeselectItemAtIndex:(NSInteger)index viewModel:(id)viewModel; /** Tells the delegate that a cell at a given index was highlighted. @param sectionController The section controller the highlight occurred in. @param index The index of the highlighted cell. @param viewModel The view model that was bound to the cell. */ - (void)sectionController:(IGListBindingSectionController *)sectionController didHighlightItemAtIndex:(NSInteger)index viewModel:(id)viewModel; /** Tells the delegate that a cell at a given index was unhighlighted. @param sectionController The section controller the unhighlight occurred in. @param index The index of the unhighlighted cell. @param viewModel The view model that was bound to the cell. */ - (void)sectionController:(IGListBindingSectionController *)sectionController didUnhighlightItemAtIndex:(NSInteger)index viewModel:(id)viewModel; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListCollectionContext.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import NS_ASSUME_NONNULL_BEGIN @class IGListSectionController; /** The collection context provides limited access to the collection-related information that section controllers need for operations like sizing, dequeuing cells, inserting, deleting, reloading, etc. */ NS_SWIFT_NAME(ListCollectionContext) @protocol IGListCollectionContext /** The size of the collection view. You can use this for sizing cells. */ @property (nonatomic, readonly) CGSize containerSize; /** The content insets of the collection view. You can use this for sizing cells. */ @property (nonatomic, readonly) UIEdgeInsets containerInset; /** The adjusted content insets of the collection view. Equivalent to containerInset under iOS 11. */ @property (nonatomic, readonly) UIEdgeInsets adjustedContainerInset; /** The size of the collection view with content insets applied. */ @property (nonatomic, readonly) CGSize insetContainerSize; /** The current scrolling traits of the underlying collection view. */ @property (nonatomic, readonly) IGListCollectionScrollingTraits scrollingTraits; /** A bitmask of experiments to conduct on the section controller. */ @property (nonatomic, assign) IGListExperiment experiments; /** Returns size of the collection view relative to the section controller. @param sectionController The section controller requesting this information. @return The size of the collection view minus the given section controller's insets. */ - (CGSize)containerSizeForSectionController:(IGListSectionController *)sectionController; /** Returns the index of the specified cell in the collection relative to the section controller. @param cell An existing cell in the collection. @param sectionController The section controller requesting this information. @return The index of the cell or `NSNotFound` if it does not exist in the collection. */ - (NSInteger)indexForCell:(UICollectionViewCell *)cell sectionController:(IGListSectionController *)sectionController; /** Returns the cell in the collection at the specified index for the section controller. @param index The index of the desired cell. @param sectionController The section controller requesting this information. @return The collection view cell, or `nil` if not found. @warning This method may return `nil` if the cell is offscreen. */ - (nullable __kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController; /** Returns the visible cells for the given section controller. @param sectionController The section controller requesting this information. @return An array of visible cells, or an empty array if none are found. */ - (NSArray *)visibleCellsForSectionController:(IGListSectionController *)sectionController; /** Returns the visible paths for the given section controller. @param sectionController The section controller requesting this information. @return An array of visible index paths, or an empty array if none are found. */ - (NSArray *)visibleIndexPathsForSectionController:(IGListSectionController *) sectionController; /** Deselects a cell in the collection. @param index The index of the item to deselect. @param sectionController The section controller requesting this information. @param animated Pass `YES` to animate the change, `NO` otherwise. */ - (void)deselectItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController animated:(BOOL)animated; /** Selects a cell in the collection. @param index The index of the item to select. @param sectionController The section controller requesting this information. @param animated Pass `YES` to animate the change, `NO` otherwise. @param scrollPosition An option that specifies where the item should be positioned when scrolling finishes. */ - (void)selectItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition; /** Dequeues a cell from the collection view reuse pool. @param cellClass The class of the cell you want to dequeue. @param reuseIdentifier A reuse identifier for the specified cell. This parameter may be `nil`. @param sectionController The section controller requesting this information. @param index The index of the cell. @return A cell dequeued from the reuse pool or a newly created one. @note This method uses a string representation of the cell class as the identifier. */ - (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass withReuseIdentifier:(nullable NSString *)reuseIdentifier forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; /** Dequeues a cell from the collection view reuse pool. @param cellClass The class of the cell you want to dequeue. @param sectionController The section controller requesting this information. @param index The index of the cell. @return A cell dequeued from the reuse pool or a newly created one. @note This method uses a string representation of the cell class as the identifier. */ - (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; /** Dequeues a cell from the collection view reuse pool. @param nibName The name of the nib file. @param bundle The bundle in which to search for the nib file. If `nil`, this method searches the main bundle. @param sectionController The section controller requesting this information. @param index The index of the cell. @return A cell dequeued from the reuse pool or a newly created one. @note This method uses a string representation of the cell class as the identifier. */ - (__kindof UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName bundle:(nullable NSBundle *)bundle forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; /** Dequeues a storyboard prototype cell from the collection view reuse pool. @param identifier The identifier of the cell prototype in storyboard. @param sectionController The section controller requesting this information. @param index The index of the cell. @return A cell dequeued from the reuse pool or a newly created one. */ - (__kindof UICollectionViewCell *)dequeueReusableCellFromStoryboardWithIdentifier:(NSString *)identifier forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; /** Dequeues a supplementary view from the collection view reuse pool. @param elementKind The kind of supplementary view. @param sectionController The section controller requesting this information. @param viewClass The class of the supplementary view. @param index The index of the supplementary view. @return A supplementary view dequeued from the reuse pool or a newly created one. @note This method uses a string representation of the view class as the identifier. */ - (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind forSectionController:(IGListSectionController *)sectionController class:(Class)viewClass atIndex:(NSInteger)index; /** Dequeues a supplementary view from the collection view reuse pool. @param elementKind The kind of supplementary view. @param identifier The identifier of the supplementary view in storyboard. @param sectionController The section controller requesting this information. @param index The index of the supplementary view. @return A supplementary view dequeued from the reuse pool or a newly created one. */ - (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewFromStoryboardOfKind:(NSString *)elementKind withIdentifier:(NSString *)identifier forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; /** Dequeues a supplementary view from the collection view reuse pool. @param elementKind The kind of supplementary view. @param sectionController The section controller requesting this information. @param nibName The name of the nib file. @param bundle The bundle in which to search for the nib file. If `nil`, this method searches the main bundle. @param index The index of the supplementary view. @return A supplementary view dequeued from the reuse pool or a newly created one. @note This method uses a string representation of the view class as the identifier. */ - (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind forSectionController:(IGListSectionController *)sectionController nibName:(NSString *)nibName bundle:(nullable NSBundle *)bundle atIndex:(NSInteger)index; /** Invalidate the backing `UICollectionViewLayout` for all items in the section controller. @param sectionController The section controller that needs invalidating. @param completion An optional completion block to execute when the updates are finished. @note This method can be wrapped in `UIView` animation APIs to control the duration or perform without animations. This will end up calling `-[UICollectionView performBatchUpdates:completion:]` internally, so invalidated changes may not be reflected in the cells immediately. */ - (void)invalidateLayoutForSectionController:(IGListSectionController *)sectionController completion:(nullable void (^)(BOOL finished))completion; /** Batches and performs many cell-level updates in a single transaction. @param animated A flag indicating if the transition should be animated. @param updates A block with a context parameter to make mutations. @param completion An optional completion block to execute when the updates are finished. @note You should make state changes that impact the number of items in your section controller within the updates block alongside changes on the context object. For example, inside your section controllers, you may want to delete *and* insert into the data source that backs your section controller. For example: ``` [self.collectionContext performBatchItemUpdates:^ (id batchContext>){ // perform data source changes inside the update block [self.items addObject:newItem]; [self.items removeObjectAtIndex:0]; NSIndexSet *inserts = [NSIndexSet indexSetWithIndex:[self.items count] - 1]; [batchContext insertInSectionController:self atIndexes:inserts]; NSIndexSet *deletes = [NSIndexSet indexSetWithIndex:0]; [batchContext deleteInSectionController:self atIndexes:deletes]; } completion:nil]; ``` @warning You **must** perform data modifications **inside** the update block. Updates will not be performed synchronously, so you should make sure that your data source changes only when necessary. */ - (void)performBatchAnimated:(BOOL)animated updates:(void (^)(id batchContext))updates completion:(nullable void (^)(BOOL finished))completion; /** Scrolls to the specified section controller in the list. @param sectionController The section controller. @param index The index of the item in the section controller to which to scroll. @param scrollPosition An option that specifies where the item should be positioned when scrolling finishes. @param animated A flag indicating if the scrolling should be animated. */ - (void)scrollToSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index scrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListCollectionScrollingTraits.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import /** The current scrolling traits of the underlying collection view. The attributes are always equal to their corresponding properties on the underlying collection view. */ NS_SWIFT_NAME(ListCollectionScrollingTraits) typedef struct IGListCollectionScrollingTraits { /// returns YES if user has touched. may not yet have started dragging. bool isTracking; /// returns YES if user has started scrolling. this may require some time and or distance to move to initiate dragging bool isDragging; /// returns YES if user isn't dragging (touch up) but scroll view is still moving. bool isDecelerating; } IGListCollectionScrollingTraits; ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListCollectionView.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @protocol IGListCollectionViewLayoutCompatible; NS_ASSUME_NONNULL_BEGIN /** This `UICollectionView` subclass allows for partial layout invalidation using `IGListCollectionViewLayout`, or custom layout classes that conform to IGListCollectionViewLayoutCompatible. @note When updating a collection view (ex: calling `-insertSections`), `-invalidateLayoutWithContext` gets called on the layout object. However, the invalidation context doesn't provide details on which index paths are being modified, which typically forces a full layout re-calculation. `IGListCollectionView` gives `IGListCollectionViewLayout` the missing information to re-calculate only the modified layout attributes. */ NS_SWIFT_NAME(ListCollectionView) @interface IGListCollectionView : UICollectionView /** Create a new view with an `IGListcollectionViewLayout` class or subclass. @param frame The frame to initialize with. @param collectionViewLayout The layout to use with the collection view. You can use IGListCollectionViewLayout here, or a custom layout class that conforms to IGListCollectionViewLayoutCompatible. @note You can initialize a new view with a base layout by simply calling `-[IGListCollectionView initWithFrame:]`. */ - (instancetype)initWithFrame:(CGRect)frame listCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout NS_DESIGNATED_INITIALIZER; /** :nodoc: */ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)collectionViewLayout NS_UNAVAILABLE; /** :nodoc: */ - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListCollectionView.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListCollectionView.h" #import "IGListCollectionViewLayout.h" #import "IGListCollectionViewLayoutCompatible.h" @implementation IGListCollectionView #pragma mark - Init - (instancetype)initWithFrame:(CGRect)frame { IGListCollectionViewLayout *layout = [[IGListCollectionViewLayout alloc] initWithStickyHeaders:NO topContentInset:0 stretchToEdge:YES]; return [self initWithFrame:frame listCollectionViewLayout:layout]; } - (instancetype)initWithFrame:(CGRect)frame listCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout { return [super initWithFrame:frame collectionViewLayout:collectionViewLayout]; } #pragma mark - IGListCollectionViewLayout - (UICollectionViewLayout *)_listLayout { if ([self.collectionViewLayout conformsToProtocol:@protocol(IGListCollectionViewLayoutCompatible)]) { return (UICollectionViewLayout *)self.collectionViewLayout; } return nil; } #pragma mark - Overides reloads - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths { [self _didModifyIndexPaths:indexPaths]; [super reloadItemsAtIndexPaths:indexPaths]; } - (void)reloadSections:(NSIndexSet *)sections { [self _didModifySections:sections]; [super reloadSections:sections]; } #pragma mark - Override deletes - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths { [self _didModifyIndexPaths:indexPaths]; [super deleteItemsAtIndexPaths:indexPaths]; } - (void)deleteSections:(NSIndexSet *)sections { [self _didModifySections:sections]; [super deleteSections:sections]; } #pragma mark - Override inserts - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths { [self _didModifyIndexPaths:indexPaths]; [super insertItemsAtIndexPaths:indexPaths]; } - (void)insertSections:(NSIndexSet *)sections { [self _didModifySections:sections]; [super insertSections:sections]; } #pragma mark - Override moves - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { [self _didModifyIndexPaths:@[indexPath, newIndexPath]]; [super moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { [self _didModifySection:MIN(section, newSection)]; [super moveSection:section toSection:newSection]; } #pragma mark - Modify section - (void)_didModifySections:(NSIndexSet *)sections { if (sections.count == 0) { return; } [self _didModifySection:sections.firstIndex]; } - (void)_didModifySection:(NSUInteger)section { [self._listLayout didModifySection:section]; } #pragma mark - Modified index path - (void)_didModifyIndexPaths:(NSArray *)indexPaths { for (NSIndexPath *indexPath in indexPaths) { [self _didModifySection:indexPath.section]; } } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListCollectionViewDelegateLayout.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import /** Conform to `IGListCollectionViewDelegateLayout` to provide customized layout information for a collection view. */ @protocol IGListCollectionViewDelegateLayout /** Asks the delegate to customize and return the starting layout information for an item being inserted into the collection view. @param collectionView The collection view to perform the transition on. @param collectionViewLayout The layout to use with the collection view. @param attributes The starting layout information for an item being inserted into the collection view. @param indexPath The index path of the item being inserted. */ - (UICollectionViewLayoutAttributes *)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout customizedInitialLayoutAttributes:(UICollectionViewLayoutAttributes *)attributes atIndexPath:(NSIndexPath *)indexPath; /** Asks the delegate to customize and return the final layout information for an item that is about to be removed from the collection view. @param collectionView The collection view to perform the transition on. @param collectionViewLayout The layout to use with the collection view. @param attributes The final layout information for an item that is about to be removed from the collection view. @param indexPath The index path of the item being deleted. */ - (UICollectionViewLayoutAttributes *)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout customizedFinalLayoutAttributes:(UICollectionViewLayoutAttributes *)attributes atIndexPath:(NSIndexPath *)indexPath; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListCollectionViewLayout.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import "IGListCollectionViewLayoutCompatible.h" NS_ASSUME_NONNULL_BEGIN /** This UICollectionViewLayout subclass is for vertically or horizontally scrolling lists of data with variable widths and heights. It supports an infinite number of sections and items. All work is done on the main thread, and while extremely efficient, care must be taken not to stall the main thread in sizing delegate methods. This layout piggybacks on the mechanics of UICollectionViewFlowLayout in that: - Your UICollectionView data source must also conform to UICollectionViewDelegateFlowLayout - Header support given via UICollectionElementKindSectionHeader All UICollectionViewDelegateFlowLayout methods are required and used by this layout: ``` - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath; - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section; - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section; - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section; - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section; ``` In a vertically scrolling layout, sections and items are put into the same horizontal row until the max-x position of an item extends beyond the width of the collection view. When that happens, the item is "newlined" to the next row. The y position of that row is determined by the maximum height (including section insets) of the section/item of the previous row. Ex. of a section (2,0) with a large width causing a newline. ``` |[ 0,0 ][ 1,0 ] | |[ 2,0 ]| ``` A section with a non-zero height header will always cause that section to newline. Headers are always stretched to the width of the collection view, pinched with the section insets. Ex. of a section (2,0) with a header inset on the left/right. ``` |[ 0,0 ][ 1,0 ] | | >======header=======< | | [ 2,0 ] | ``` Section insets apply to items in the section no matter if they begin on a new row or are on the same row as a previous section. Ex. of a section (2) with multiple items and a left inset. ``` |[ 0,0 ][ 1,0 ] >[ 2,0 ]| | >[ 2,1 ][ 2,2 ][ 2,3 ]| ``` Interitem spacing applies to items and sections within the same row. Line spacing only applies to items within the same section. In a horizontally scrolling layout, sections and items are flowed vertically until they need to be "newlined" to the next column. Headers, if used, are stretched to the height of the collection view, minus the section insets. Please see the unit tests for more configuration examples and expected output. */ NS_SWIFT_NAME(ListCollectionViewLayout) @interface IGListCollectionViewLayout : UICollectionViewLayout /** Direction in which layout will be scrollable; items will be flowed in the perpendicular direction, "newlining" when they run out of space along that axis or when a non-zero header is found. */ @property (nonatomic, readonly) UICollectionViewScrollDirection scrollDirection; /** Set this to adjust the offset of the sticky headers in the scrolling direction. Can be used to change the sticky header position as UI like the navigation bar is scrolled offscreen. In a vertically scrolling layout, changing this to the height of the navigation bar will give the effect of the headers sticking to the nav as it is collapsed. @note Changing the value on this method will invalidate the layout every time. */ @property (nonatomic, assign) CGFloat stickyHeaderYOffset; /** Set this to `YES` to show sticky header when a section had no item. Default is `NO`. */ @property (nonatomic, assign) BOOL showHeaderWhenEmpty; /** A bitmask of experiments to conduct on the adapter. */ @property (nonatomic, assign) IGListExperiment experiments; /** Create and return a new collection view layout. @param stickyHeaders Set to `YES` to stick section headers to the top of the bounds while scrolling. @param scrollDirection Direction along which the collection view will be scrollable (if content size exceeds the frame size) @param topContentInset The content inset (top or left, depending on scrolling direction) used to offset the sticky headers. Ignored if stickyHeaders is `NO`. @param stretchToEdge Specifies whether to stretch width (in vertically scrolling layout) or height (horizontally scrolling) of last item to right/bottom edge when distance from last item to right/bottom edge < epsilon(1) @return A new collection view layout. */ - (instancetype)initWithStickyHeaders:(BOOL)stickyHeaders scrollDirection:(UICollectionViewScrollDirection)scrollDirection topContentInset:(CGFloat)topContentInset stretchToEdge:(BOOL)stretchToEdge NS_DESIGNATED_INITIALIZER; /** Create and return a new vertically scrolling collection view layout. @param stickyHeaders Set to `YES` to stick section headers to the top of the bounds while scrolling. @param topContentInset The top content inset used to offset the sticky headers. Ignored if stickyHeaders is `NO`. @param stretchToEdge Specifies whether to stretch width of last item to right edge when distance from last item to right edge < epsilon(1) @return A new collection view layout. */ - (instancetype)initWithStickyHeaders:(BOOL)stickyHeaders topContentInset:(CGFloat)topContentInset stretchToEdge:(BOOL)stretchToEdge; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListCollectionViewLayout.mm ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListCollectionViewLayout.h" #import "IGListCollectionViewLayoutInternal.h" #import #import #import #import "UIScrollView+IGListKit.h" static CGFloat UIEdgeInsetsLeadingInsetInDirection(UIEdgeInsets insets, UICollectionViewScrollDirection direction) { switch (direction) { case UICollectionViewScrollDirectionVertical: return insets.top; case UICollectionViewScrollDirectionHorizontal: return insets.left; } } static CGFloat UIEdgeInsetsTrailingInsetInDirection(UIEdgeInsets insets, UICollectionViewScrollDirection direction) { switch (direction) { case UICollectionViewScrollDirectionVertical: return insets.bottom; case UICollectionViewScrollDirectionHorizontal: return insets.right; } } static CGFloat CGPointGetCoordinateInDirection(CGPoint point, UICollectionViewScrollDirection direction) { switch (direction) { case UICollectionViewScrollDirectionVertical: return point.y; case UICollectionViewScrollDirectionHorizontal: return point.x; } } static CGFloat CGRectGetLengthInDirection(CGRect rect, UICollectionViewScrollDirection direction) { switch (direction) { case UICollectionViewScrollDirectionVertical: return rect.size.height; case UICollectionViewScrollDirectionHorizontal: return rect.size.width; } } static CGFloat CGRectGetMaxInDirection(CGRect rect, UICollectionViewScrollDirection direction) { switch (direction) { case UICollectionViewScrollDirectionVertical: return CGRectGetMaxY(rect); case UICollectionViewScrollDirectionHorizontal: return CGRectGetMaxX(rect); } } static CGFloat CGRectGetMinInDirection(CGRect rect, UICollectionViewScrollDirection direction) { switch (direction) { case UICollectionViewScrollDirectionVertical: return CGRectGetMinY(rect); case UICollectionViewScrollDirectionHorizontal: return CGRectGetMinX(rect); } } static CGFloat CGSizeGetLengthInDirection(CGSize size, UICollectionViewScrollDirection direction) { switch (direction) { case UICollectionViewScrollDirectionVertical: return size.height; case UICollectionViewScrollDirectionHorizontal: return size.width; } } static NSIndexPath *indexPathForSection(NSInteger section) { return [NSIndexPath indexPathForItem:0 inSection:section]; } static NSInteger IGListMergeMinimumInvalidatedSection(NSInteger section, NSInteger otherSection) { if (section == NSNotFound && otherSection == NSNotFound) { return NSNotFound; } else if (section == NSNotFound) { return otherSection; } else if (otherSection == NSNotFound) { return section; } return MIN(section, otherSection); } struct IGListSectionEntry { /** Represents the minimum-bounding box of every element in the section. This includes all item frames as well as the header bounds. It is made simply by unioning all item and header frames. Use this to find section intersections to build layout attributes given a rect. */ CGRect bounds; // The insets for the section. Used to find total content size of the section. UIEdgeInsets insets; // The RESTING frame of the header view (e.g. when the header is not sticking to the top of the scroll view). CGRect headerBounds; // The RESTING frame of the footer view CGRect footerBounds; // An array of frames for each cell in the section. std::vector itemBounds; // last item distance in scroll direction, used for partial invalidation CGFloat lastItemCoordInScrollDirection; // last item distance in fixed direction, used for partial invalidation CGFloat lastItemCoordInFixedDirection; // last next row distance in scroll direction, used for partial invalidation CGFloat lastNextRowCoordInScrollDirection; // Returns YES when the section has visible content (header and/or items). BOOL isValid() { return !CGSizeEqualToSize(bounds.size, CGSizeZero); } }; // Each section has a base zIndex of section * maxZIndexPerSection; // section header adds (maxZIndexPerSection - 1) to the base zIndex; // other cells adds (item) to the base zIndex. // This allows us to present tooltips that can grow from the cell to its top. static void adjustZIndexForAttributes(UICollectionViewLayoutAttributes *attributes) { const NSInteger maxZIndexPerSection = 1000; const NSInteger baseZIndex = attributes.indexPath.section * maxZIndexPerSection; switch (attributes.representedElementCategory) { case UICollectionElementCategoryCell: attributes.zIndex = baseZIndex + attributes.indexPath.item; break; case UICollectionElementCategorySupplementaryView: attributes.zIndex = baseZIndex + maxZIndexPerSection - 1; break; case UICollectionElementCategoryDecorationView: attributes.zIndex = baseZIndex - 1; break; } } @interface IGListCollectionViewLayoutInvalidationContext : UICollectionViewLayoutInvalidationContext @property (nonatomic, assign) BOOL ig_invalidateSupplementaryAttributes; @property (nonatomic, assign) BOOL ig_invalidateAllAttributes; @end @implementation IGListCollectionViewLayoutInvalidationContext @end @interface IGListCollectionViewLayout () @property (nonatomic, assign, readonly) BOOL stickyHeaders; @property (nonatomic, assign, readonly) CGFloat topContentInset; @property (nonatomic, assign, readonly) BOOL stretchToEdge; @end @implementation IGListCollectionViewLayout { std::vector _sectionData; NSMutableDictionary *_attributesCache; // invalidate starting at this section NSInteger _minimumInvalidatedSection; /** The workflow for getting sticky headers working: 1. Use a custom invalidation context to mark supplementary attributes invalid. 2. Return YES from -shouldInvalidateLayoutForBoundsChange: 3. In -invalidationContextForBoundsChange: mark supplementary attributes invalid on the custom context. 4. Purge supplementary caches in -invalidateLayoutWithContext: if context says they are invalid 5. Use cached attributes in -layoutAttributesForSupplementaryViewOfKind:atIndexPath: if they exist, else rebuild 6. Make sure -layoutAttributesForElementsInRect: always uses the attributes returned from -layoutAttributesForSupplementaryViewOfKind:atIndexPath:. */ NSMutableDictionary *> *_supplementaryAttributesCache; } - (instancetype)initWithStickyHeaders:(BOOL)stickyHeaders topContentInset:(CGFloat)topContentInset stretchToEdge:(BOOL)stretchToEdge { return [self initWithStickyHeaders:stickyHeaders scrollDirection:UICollectionViewScrollDirectionVertical topContentInset:topContentInset stretchToEdge:stretchToEdge]; } - (instancetype)initWithStickyHeaders:(BOOL)stickyHeaders scrollDirection:(UICollectionViewScrollDirection)scrollDirection topContentInset:(CGFloat)topContentInset stretchToEdge:(BOOL)stretchToEdge { if (self = [super init]) { _scrollDirection = scrollDirection; _stickyHeaders = stickyHeaders; _topContentInset = topContentInset; _stretchToEdge = stretchToEdge; _attributesCache = [NSMutableDictionary new]; _supplementaryAttributesCache = [NSMutableDictionary dictionaryWithDictionary:@{ UICollectionElementKindSectionHeader: [NSMutableDictionary new], UICollectionElementKindSectionFooter: [NSMutableDictionary new], }]; _minimumInvalidatedSection = NSNotFound; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { return [self initWithStickyHeaders:NO topContentInset:0 stretchToEdge:NO]; } #pragma mark - UICollectionViewLayout - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath { UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; id delegate = (id)self.collectionView.delegate; if ([delegate respondsToSelector:@selector(collectionView:layout:customizedInitialLayoutAttributes:atIndexPath:)]) { return [delegate collectionView:self.collectionView layout:self customizedInitialLayoutAttributes:attributes atIndexPath:itemIndexPath]; } return attributes; } - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{ UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath]; id delegate = (id)self.collectionView.delegate; if ([delegate respondsToSelector:@selector(collectionView:layout:customizedFinalLayoutAttributes:atIndexPath:)]) { return [delegate collectionView:self.collectionView layout:self customizedFinalLayoutAttributes:attributes atIndexPath:itemIndexPath]; } return attributes; } - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { IGAssertMainThread(); NSMutableArray *result = [NSMutableArray new]; const NSRange range = [self _rangeOfSectionsInRect:rect]; if (range.location == NSNotFound) { return nil; } for (NSInteger section = range.location; section < NSMaxRange(range); section++) { const NSInteger itemCount = _sectionData[section].itemBounds.size(); // do not add headers if there are no items if (itemCount > 0 || self.showHeaderWhenEmpty) { for (NSString *elementKind in _supplementaryAttributesCache.allKeys) { NSIndexPath *indexPath = indexPathForSection(section); UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath]; // do not add zero height headers/footers or headers/footers that are outside the rect const CGRect frame = attributes.frame; const CGRect intersection = CGRectIntersection(frame, rect); if (attributes && !CGRectIsEmpty(intersection) && CGRectGetLengthInDirection(frame, self.scrollDirection) > 0.0) { [result addObject:attributes]; } } } // add all cells within the rect, return early if it starts iterating outside for (NSInteger item = 0; item < itemCount; item++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section]; UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; if (CGRectIntersectsRect(attributes.frame, rect)) { [result addObject:attributes]; } } } return result; } - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { IGAssertMainThread(); IGParameterAssert(indexPath != nil); UICollectionViewLayoutAttributes *attributes = _attributesCache[indexPath]; if (attributes != nil) { return attributes; } // avoid OOB errors const NSInteger section = indexPath.section; const NSInteger item = indexPath.item; if (section >= _sectionData.size() || item >= _sectionData[section].itemBounds.size()) { return nil; } attributes = [[[self class] layoutAttributesClass] layoutAttributesForCellWithIndexPath:indexPath]; attributes.frame = _sectionData[indexPath.section].itemBounds[indexPath.item]; adjustZIndexForAttributes(attributes); _attributesCache[indexPath] = attributes; return attributes; } - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { IGAssertMainThread(); IGParameterAssert(indexPath != nil); UICollectionViewLayoutAttributes *attributes = _supplementaryAttributesCache[elementKind][indexPath]; if (attributes != nil) { return attributes; } // avoid OOB errors const NSInteger section = indexPath.section; if (section >= _sectionData.size()) { return nil; } UICollectionView *collectionView = self.collectionView; const IGListSectionEntry entry = _sectionData[section]; const CGFloat minOffset = CGRectGetMinInDirection(entry.bounds, self.scrollDirection); CGRect frame = CGRectZero; if ([elementKind isEqualToString:UICollectionElementKindSectionHeader]) { frame = entry.headerBounds; if (self.stickyHeaders) { CGFloat offset = CGPointGetCoordinateInDirection(collectionView.contentOffset, self.scrollDirection) + self.topContentInset + self.stickyHeaderYOffset; if (section + 1 == _sectionData.size()) { offset = MAX(minOffset, offset); } else { const CGFloat maxOffset = CGRectGetMinInDirection(_sectionData[section + 1].bounds, self.scrollDirection) - CGRectGetLengthInDirection(frame, self.scrollDirection); offset = MIN(MAX(minOffset, offset), maxOffset); } switch (self.scrollDirection) { case UICollectionViewScrollDirectionVertical: frame.origin.y = offset; break; case UICollectionViewScrollDirectionHorizontal: frame.origin.x = offset; break; } } } else if ([elementKind isEqualToString:UICollectionElementKindSectionFooter]) { frame = entry.footerBounds; } if (CGRectIsEmpty(frame)) { // Just like UICollectionViewFlowLayout, if the header/footer size is empty, do not not return an attribute. // If we did return something, calling [UICollectionView layoutAttributesForSupplementaryElementOfKind...] would too, // which could then crash if the UICollectionViewDelegate is not expecting to actually return a supplimentary view. return nil; } else { attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath]; attributes.frame = frame; adjustZIndexForAttributes(attributes); _supplementaryAttributesCache[elementKind][indexPath] = attributes; return attributes; } } - (CGSize)collectionViewContentSize { IGAssertMainThread(); const NSInteger sectionCount = _sectionData.size(); if (sectionCount == 0) { return CGSizeZero; } const IGListSectionEntry section = _sectionData[sectionCount - 1]; UICollectionView *collectionView = self.collectionView; const UIEdgeInsets contentInset = collectionView.ig_contentInset; switch (self.scrollDirection) { case UICollectionViewScrollDirectionVertical: { const CGFloat height = CGRectGetMaxY(section.bounds) + section.insets.bottom; return CGSizeMake(CGRectGetWidth(collectionView.bounds) - contentInset.left - contentInset.right, height); } case UICollectionViewScrollDirectionHorizontal: { const CGFloat width = CGRectGetMaxX(section.bounds) + section.insets.right; return CGSizeMake(width, CGRectGetHeight(collectionView.bounds) - contentInset.top - contentInset.bottom); } } } - (void)invalidateLayoutWithContext:(IGListCollectionViewLayoutInvalidationContext *)context { BOOL hasInvalidatedItemIndexPaths = NO; if ([context respondsToSelector:@selector(invalidatedItemIndexPaths)]) { hasInvalidatedItemIndexPaths = [context invalidatedItemIndexPaths].count > 0; } if (hasInvalidatedItemIndexPaths || [context invalidateEverything] || context.ig_invalidateAllAttributes) { // invalidates all _minimumInvalidatedSection = 0; } else if ([context invalidateDataSourceCounts] && _minimumInvalidatedSection == NSNotFound) { // invalidate all if count changed and we don't have information on the minimum invalidated section _minimumInvalidatedSection = 0; } if (context.ig_invalidateSupplementaryAttributes) { [self _resetSupplementaryAttributesCache]; } [super invalidateLayoutWithContext:context]; } + (Class)invalidationContextClass { return [IGListCollectionViewLayoutInvalidationContext class]; } - (UICollectionViewLayoutInvalidationContext *)invalidationContextForBoundsChange:(CGRect)newBounds { const CGRect oldBounds = self.collectionView.bounds; IGListCollectionViewLayoutInvalidationContext *context = (IGListCollectionViewLayoutInvalidationContext *)[super invalidationContextForBoundsChange:newBounds]; context.ig_invalidateSupplementaryAttributes = YES; if (!CGSizeEqualToSize(oldBounds.size, newBounds.size)) { context.ig_invalidateAllAttributes = YES; } return context; } - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { const CGRect oldBounds = self.collectionView.bounds; // always invalidate for size changes if (!CGSizeEqualToSize(oldBounds.size, newBounds.size)) { return YES; } // if the y origin has changed, only invalidate when using sticky headers if (CGRectGetMinInDirection(newBounds, self.scrollDirection) != CGRectGetMinInDirection(oldBounds, self.scrollDirection)) { return self.stickyHeaders; } return NO; } - (void)prepareLayout { [self _calculateLayoutIfNeeded]; } #pragma mark - Public API - (void)setStickyHeaderYOffset:(CGFloat)stickyHeaderYOffset { IGAssertMainThread(); if (_stickyHeaderYOffset != stickyHeaderYOffset) { _stickyHeaderYOffset = stickyHeaderYOffset; IGListCollectionViewLayoutInvalidationContext *invalidationContext = [IGListCollectionViewLayoutInvalidationContext new]; invalidationContext.ig_invalidateSupplementaryAttributes = YES; [self invalidateLayoutWithContext:invalidationContext]; } } #pragma mark - Private API - (void)_calculateLayoutIfNeeded { if (_minimumInvalidatedSection == NSNotFound) { return; } // purge attribute caches so they are rebuilt [_attributesCache removeAllObjects]; [self _resetSupplementaryAttributesCache]; UICollectionView *collectionView = self.collectionView; id dataSource = collectionView.dataSource; id delegate = (id)collectionView.delegate; const NSInteger sectionCount = (IGListExperimentEnabled(_experiments, IGListExperimentUseCollectionViewInsteadOfDataSourceInLayout) ? [collectionView numberOfSections] : [dataSource numberOfSectionsInCollectionView:collectionView]); const UIEdgeInsets contentInset = collectionView.ig_contentInset; const CGRect contentInsetAdjustedCollectionViewBounds = UIEdgeInsetsInsetRect(collectionView.bounds, contentInset); _sectionData.resize(sectionCount); CGFloat itemCoordInScrollDirection = 0.0; CGFloat itemCoordInFixedDirection = 0.0; CGFloat nextRowCoordInScrollDirection = 0.0; // union item frames and optionally the header to find a bounding box of the entire section CGRect rollingSectionBounds = CGRectZero; // populate last valid section information const NSInteger lastValidSection = _minimumInvalidatedSection - 1; if (lastValidSection >= 0 && lastValidSection < sectionCount) { itemCoordInScrollDirection = _sectionData[lastValidSection].lastItemCoordInScrollDirection; itemCoordInFixedDirection = _sectionData[lastValidSection].lastItemCoordInFixedDirection; nextRowCoordInScrollDirection = _sectionData[lastValidSection].lastNextRowCoordInScrollDirection; rollingSectionBounds = _sectionData[lastValidSection].bounds; } for (NSInteger section = _minimumInvalidatedSection; section < sectionCount; section++) { const NSInteger itemCount = (IGListExperimentEnabled(_experiments, IGListExperimentUseCollectionViewInsteadOfDataSourceInLayout) ? [collectionView numberOfItemsInSection:section] : [dataSource collectionView:collectionView numberOfItemsInSection:section]); const BOOL itemsEmpty = itemCount == 0; const BOOL hideHeaderWhenItemsEmpty = itemsEmpty && !self.showHeaderWhenEmpty; _sectionData[section].itemBounds = std::vector(itemCount); const CGSize headerSize = [delegate collectionView:collectionView layout:self referenceSizeForHeaderInSection:section]; const CGSize footerSize = [delegate collectionView:collectionView layout:self referenceSizeForFooterInSection:section]; const UIEdgeInsets insets = [delegate collectionView:collectionView layout:self insetForSectionAtIndex:section]; const CGFloat lineSpacing = [delegate collectionView:collectionView layout:self minimumLineSpacingForSectionAtIndex:section]; const CGFloat interitemSpacing = [delegate collectionView:collectionView layout:self minimumInteritemSpacingForSectionAtIndex:section]; const CGSize paddedCollectionViewSize = UIEdgeInsetsInsetRect(contentInsetAdjustedCollectionViewBounds, insets).size; const UICollectionViewScrollDirection fixedDirection = self.scrollDirection == UICollectionViewScrollDirectionHorizontal ? UICollectionViewScrollDirectionVertical : UICollectionViewScrollDirectionHorizontal; const CGFloat paddedLengthInFixedDirection = CGSizeGetLengthInDirection(paddedCollectionViewSize, fixedDirection); const CGFloat headerLengthInScrollDirection = hideHeaderWhenItemsEmpty ? 0 : CGSizeGetLengthInDirection(headerSize, self.scrollDirection); const CGFloat footerLengthInScrollDirection = hideHeaderWhenItemsEmpty ? 0 : CGSizeGetLengthInDirection(footerSize, self.scrollDirection); const BOOL headerExists = headerLengthInScrollDirection > 0; const BOOL footerExists = footerLengthInScrollDirection > 0; // start the section accounting for the header size // header length in scroll direction is subtracted from the sectionBounds when calculating the header bounds after items are done // this bumps the first row of items over enough to make room for the header itemCoordInScrollDirection += headerLengthInScrollDirection; nextRowCoordInScrollDirection += headerLengthInScrollDirection; // add the leading inset in fixed direction in case the section falls on the same row as the previous // if the section is newlined then the coord in fixed direction is reset itemCoordInFixedDirection += UIEdgeInsetsLeadingInsetInDirection(insets, fixedDirection); // the farthest in the fixed direction the frame of an item in this section can go const CGFloat maxCoordinateInFixedDirection = CGRectGetLengthInDirection(contentInsetAdjustedCollectionViewBounds, fixedDirection) - UIEdgeInsetsTrailingInsetInDirection(insets, fixedDirection); for (NSInteger item = 0; item < itemCount; item++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section]; const CGSize size = [delegate collectionView:collectionView layout:self sizeForItemAtIndexPath:indexPath]; IGAssert(CGSizeGetLengthInDirection(size, fixedDirection) <= paddedLengthInFixedDirection || fabs(CGSizeGetLengthInDirection(size, fixedDirection) - paddedLengthInFixedDirection) < FLT_EPSILON, @"%@ of item %li in section %li (%.0f pt) must be less than or equal to container (%.0f pt) accounting for section insets %@", self.scrollDirection == UICollectionViewScrollDirectionVertical ? @"Width" : @"Height", (long)item, (long)section, CGSizeGetLengthInDirection(size, fixedDirection), CGRectGetLengthInDirection(contentInsetAdjustedCollectionViewBounds, fixedDirection), NSStringFromUIEdgeInsets(insets)); CGFloat itemLengthInFixedDirection = MIN(CGSizeGetLengthInDirection(size, fixedDirection), paddedLengthInFixedDirection); // if the origin and length in fixed direction of the item busts the size of the container // or if this is the first item and the header has a non-zero size // newline to the next row and reset // define epsilon to avoid float overflow issue const CGFloat epsilon = 1.0; if (itemCoordInFixedDirection + itemLengthInFixedDirection > maxCoordinateInFixedDirection + epsilon || (item == 0 && headerExists)) { itemCoordInScrollDirection = nextRowCoordInScrollDirection; itemCoordInFixedDirection = UIEdgeInsetsLeadingInsetInDirection(insets, fixedDirection); // if newlining, always append line spacing unless its the very first item of the section if (item > 0) { itemCoordInScrollDirection += lineSpacing; } } const CGFloat distanceToEdge = paddedLengthInFixedDirection - (itemCoordInFixedDirection + itemLengthInFixedDirection); if (self.stretchToEdge && distanceToEdge > 0 && distanceToEdge <= epsilon) { itemLengthInFixedDirection = paddedLengthInFixedDirection - itemCoordInFixedDirection; } const CGRect rawFrame = (self.scrollDirection == UICollectionViewScrollDirectionVertical) ? CGRectMake(itemCoordInFixedDirection, itemCoordInScrollDirection + insets.top, itemLengthInFixedDirection, size.height) : CGRectMake(itemCoordInScrollDirection + insets.left, itemCoordInFixedDirection, size.width, itemLengthInFixedDirection); const CGRect frame = IGListRectIntegralScaled(rawFrame); _sectionData[section].itemBounds[item] = frame; // track the max size of the row to find the coord of the next row, adjust for leading inset while iterating items nextRowCoordInScrollDirection = MAX(CGRectGetMaxInDirection(frame, self.scrollDirection) - UIEdgeInsetsLeadingInsetInDirection(insets, self.scrollDirection), nextRowCoordInScrollDirection); // increase the rolling coord in fixed direction appropriately and add item spacing for all items on the same row itemCoordInFixedDirection += itemLengthInFixedDirection + interitemSpacing; // union the rolling section bounds if (item == 0) { rollingSectionBounds = frame; } else { rollingSectionBounds = CGRectUnion(rollingSectionBounds, frame); } } const CGRect headerBounds = self.scrollDirection == UICollectionViewScrollDirectionVertical ? CGRectMake(insets.left, itemsEmpty ? CGRectGetMaxY(rollingSectionBounds) : CGRectGetMinY(rollingSectionBounds) - headerSize.height, paddedLengthInFixedDirection, hideHeaderWhenItemsEmpty ? 0 : headerSize.height) : CGRectMake(itemsEmpty ? CGRectGetMaxX(rollingSectionBounds) : CGRectGetMinX(rollingSectionBounds) - headerSize.width, insets.top, hideHeaderWhenItemsEmpty ? 0 : headerSize.width, paddedLengthInFixedDirection); _sectionData[section].headerBounds = headerBounds; if (itemsEmpty) { rollingSectionBounds = headerBounds; } const CGRect footerBounds = (self.scrollDirection == UICollectionViewScrollDirectionVertical) ? CGRectMake(insets.left, CGRectGetMaxY(rollingSectionBounds), paddedLengthInFixedDirection, hideHeaderWhenItemsEmpty ? 0 : footerSize.height) : CGRectMake(CGRectGetMaxX(rollingSectionBounds) + insets.right, insets.top, hideHeaderWhenItemsEmpty ? 0 : footerSize.width, paddedLengthInFixedDirection); _sectionData[section].footerBounds = footerBounds; // union the header before setting the bounds of the section // only do this when the header has a size, otherwise the union stretches to box empty space if (headerExists) { rollingSectionBounds = CGRectUnion(rollingSectionBounds, headerBounds); } if (footerExists) { rollingSectionBounds = CGRectUnion(rollingSectionBounds, footerBounds); } _sectionData[section].bounds = rollingSectionBounds; _sectionData[section].insets = insets; // bump the coord for the next section with the right insets itemCoordInFixedDirection += UIEdgeInsetsTrailingInsetInDirection(insets, fixedDirection); // find the farthest point in the section and add the trailing inset to find the next row's coord nextRowCoordInScrollDirection = MAX(nextRowCoordInScrollDirection, CGRectGetMaxInDirection(rollingSectionBounds, self.scrollDirection) + UIEdgeInsetsTrailingInsetInDirection(insets, self.scrollDirection)); // keep track of coordinates for partial invalidation _sectionData[section].lastItemCoordInScrollDirection = itemCoordInScrollDirection; _sectionData[section].lastItemCoordInFixedDirection = itemCoordInFixedDirection; _sectionData[section].lastNextRowCoordInScrollDirection = nextRowCoordInScrollDirection; } _minimumInvalidatedSection = NSNotFound; } - (NSRange)_rangeOfSectionsInRect:(CGRect)rect { NSRange result = NSMakeRange(NSNotFound, 0); const NSInteger sectionCount = _sectionData.size(); for (NSInteger section = 0; section < sectionCount; section++) { IGListSectionEntry entry = _sectionData[section]; if (entry.isValid() && CGRectIntersectsRect(entry.bounds, rect)) { const NSRange sectionRange = NSMakeRange(section, 1); if (result.location == NSNotFound) { result = sectionRange; } else { result = NSUnionRange(result, sectionRange); } } } return result; } - (void)_resetSupplementaryAttributesCache { [_supplementaryAttributesCache enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableDictionary * _Nonnull attributesCache, BOOL * _Nonnull stop) { [attributesCache removeAllObjects]; }]; } #pragma mark - Minimum Invalidated Section - (void)didModifySection:(NSInteger)modifiedSection { _minimumInvalidatedSection = IGListMergeMinimumInvalidatedSection(_minimumInvalidatedSection, modifiedSection); } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListCollectionViewLayoutCompatible.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import NS_ASSUME_NONNULL_BEGIN /** A protocol for layouts that defines interaction with an IGListCollectionView, for recieving updated section indexes. */ NS_SWIFT_NAME(ListCollectionViewLayoutCompatible) @protocol IGListCollectionViewLayoutCompatible /** Called to notify the layout that a specific section was modified before invalidation. This can be used to optimize layout re-calculation. @note When updating a collection view (ex: calling `-insertSections`), `-invalidateLayoutWithContext` gets called on the layout object. However, the invalidation context doesn't provide details on which index paths are being modified, which typically forces a full layout re-calculation. Layouts can use this method to keep track of which section actually needs to be updated on the following `-invalidateLayoutWithContext`. See `IGListCollectionView`. @param modifiedSection The section that was modified. */ - (void)didModifySection:(NSInteger)modifiedSection; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListDisplayDelegate.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListAdapter; @class IGListSectionController; NS_ASSUME_NONNULL_BEGIN /** Implement this protocol to receive display events for a section controller when it is on screen. */ NS_SWIFT_NAME(ListDisplayDelegate) @protocol IGListDisplayDelegate /** Tells the delegate that the specified section controller is about to be displayed. @param listAdapter The list adapter for the section controller. @param sectionController The section controller about to be displayed. */ - (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController; /** Tells the delegate that the specified section controller is no longer being displayed. @param listAdapter The list adapter for the section controller. @param sectionController The section controller that is no longer displayed. */ - (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController; /** Tells the delegate that a cell in the specified list is about to be displayed. @param listAdapter The list adapter in which the cell will display. @param sectionController The section controller that is displaying the cell. @param cell The cell about to be displayed. @param index The index of the cell in the section. */ - (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController cell:(UICollectionViewCell *)cell atIndex:(NSInteger)index; /** Tells the delegate that a cell in the specified list is no longer being displayed. @param listAdapter The list adapter in which the cell was displayed. @param sectionController The section controller that is no longer displaying the cell. @param cell The cell that is no longer displayed. @param index The index of the cell in the section. */ - (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController cell:(UICollectionViewCell *)cell atIndex:(NSInteger)index; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListGenericSectionController.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import NS_ASSUME_NONNULL_BEGIN /** This class adds a helper layer to `IGListSectionController` to automatically store a generic object in `didUpdateToObject:`. */ NS_SWIFT_NAME(ListGenericSectionController) @interface IGListGenericSectionController<__covariant ObjectType> : IGListSectionController /** The object mapped to this section controller. Matches the object provided in `[IGListAdapterDataSource listAdapter:sectionControllerForObject:]` when this section controller was created and returned. @note This object is briefly `nil` between initialization and the first call to `didUpdateToObject:`. After that, it is safe to assume that this is non-`nil`. */ @property (nonatomic, strong, nullable, readonly) ObjectType object; /** Updates the section controller to a new object. @param object The object mapped to this section controller. @note This `IGListSectionController` subclass sets its object in this method, so any overrides **must call super**. */ - (void)didUpdateToObject:(id)object NS_REQUIRES_SUPER; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListGenericSectionController.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListGenericSectionController.h" @implementation IGListGenericSectionController - (void)didUpdateToObject:(id)object { _object = object; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListKit.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import /** * Project version number for IGListKit. */ FOUNDATION_EXPORT double IGListKitVersionNumber; /** * Project version string for IGListKit. */ FOUNDATION_EXPORT const unsigned char IGListKitVersionString[]; #if TARGET_OS_EMBEDDED || TARGET_OS_SIMULATOR // iOS and tvOS only: #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #endif // Shared (iOS, tvOS, macOS compatible): #import #import #import #import #import #import #import #import #import #import #import ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListReloadDataUpdater.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import NS_ASSUME_NONNULL_BEGIN /** An `IGListReloadDataUpdater` is a concrete type that conforms to `IGListUpdatingDelegate`. It is an out-of-box updater for `IGListAdapter` objects to use. @note This updater performs simple, synchronous updates using `-[UICollectionView reloadData]`. */ IGLK_SUBCLASSING_RESTRICTED NS_SWIFT_NAME(ListReloadDataUpdater) @interface IGListReloadDataUpdater : NSObject @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListReloadDataUpdater.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @implementation IGListReloadDataUpdater #pragma mark - IGListUpdatingDelegate - (NSPointerFunctions *)objectLookupPointerFunctions { return [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsObjectPersonality]; } - (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock fromObjects:(NSArray *)fromObjects toObjectsBlock:(IGListToObjectBlock)toObjectsBlock animated:(BOOL)animated objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock completion:(IGListUpdatingCompletion)completion { if (toObjectsBlock != nil) { NSArray *toObjects = toObjectsBlock() ?: @[]; objectTransitionBlock(toObjects); } [self _synchronousReloadDataWithCollectionView:collectionViewBlock()]; if (completion) { completion(YES); } } - (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock animated:(BOOL)animated itemUpdates:(IGListItemUpdateBlock)itemUpdates completion:(IGListUpdatingCompletion)completion { itemUpdates(); [self _synchronousReloadDataWithCollectionView:collectionViewBlock()]; if (completion) { completion(YES); } } - (void)insertItemsIntoCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths { [self _synchronousReloadDataWithCollectionView:collectionView]; } - (void)deleteItemsFromCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths { [self _synchronousReloadDataWithCollectionView:collectionView]; } - (void)moveItemInCollectionView:(UICollectionView *)collectionView fromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { [self _synchronousReloadDataWithCollectionView:collectionView]; } - (void)reloadItemInCollectionView:(UICollectionView *)collectionView fromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { [self _synchronousReloadDataWithCollectionView:collectionView]; } - (void)moveSectionInCollectionView:(UICollectionView *)collectionView fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex { [self _synchronousReloadDataWithCollectionView:collectionView]; } - (void)reloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock completion:(IGListUpdatingCompletion)completion { reloadUpdateBlock(); [self _synchronousReloadDataWithCollectionView:collectionViewBlock()]; if (completion) { completion(YES); } } - (void)reloadCollectionView:(UICollectionView *)collectionView sections:(NSIndexSet *)sections { [self _synchronousReloadDataWithCollectionView:collectionView]; } - (void)_synchronousReloadDataWithCollectionView:(UICollectionView *)collectionView { [collectionView reloadData]; [collectionView layoutIfNeeded]; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListScrollDelegate.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListAdapter; @class IGListSectionController; NS_ASSUME_NONNULL_BEGIN /** Implement this protocol to receive display events for a section controller when it is on screen. */ NS_SWIFT_NAME(ListScrollDelegate) @protocol IGListScrollDelegate /** Tells the delegate that the section controller was scrolled on screen. @param listAdapter The list adapter whose collection view was scrolled. @param sectionController The visible section controller that was scrolled. */ - (void)listAdapter:(IGListAdapter *)listAdapter didScrollSectionController:(IGListSectionController *)sectionController; /** Tells the delegate that the section controller will be dragged on screen. @param listAdapter The list adapter whose collection view will drag. @param sectionController The visible section controller that will drag. */ - (void)listAdapter:(IGListAdapter *)listAdapter willBeginDraggingSectionController:(IGListSectionController *)sectionController; /** Tells the delegate that the section controller did end dragging on screen. @param listAdapter The list adapter whose collection view ended dragging. @param sectionController The visible section controller that ended dragging. @param decelerate 'Yes' if the scrolling movement will continue, but decelerate, after a touch-up gesture during a dragging operation. If the value is 'No', scrolling stops immediately upon touch-up. */ - (void)listAdapter:(IGListAdapter *)listAdapter didEndDraggingSectionController:(IGListSectionController *)sectionController willDecelerate:(BOOL)decelerate; @optional /** Tells the delegate that the section controller did end decelerating on screen. @param listAdapter The list adapter whose collection view ended decelerating. @param sectionController The visible section controller that ended decelerating. @note This method is `@optional` until the next breaking-change release. */ - (void)listAdapter:(IGListAdapter *)listAdapter didEndDeceleratingSectionController:(IGListSectionController *)sectionController; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListSectionController.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import #import #import #import NS_ASSUME_NONNULL_BEGIN /** The base class for section controllers used in a list. This class is intended to be subclassed. */ NS_SWIFT_NAME(ListSectionController) @interface IGListSectionController : NSObject /** Returns the number of items in the section. @return A count of items in the list. @note The count returned is used to drive the number of cells displayed for this section controller. The default implementation returns 1. **Calling super is not required.** */ - (NSInteger)numberOfItems; /** The specific size for the item at the specified index. @param index The row index of the item. @return The size for the item at index. @note The returned size is not guaranteed to be used. The implementation may query sections for their layout information at will, or use its own layout metrics. For example, consider a dynamic-text sized list versus a fixed height-and-width grid. The former will ask each section for a size, and the latter will likely not. The default implementation returns size zero. **Calling super is not required.** */ - (CGSize)sizeForItemAtIndex:(NSInteger)index; /** Return a dequeued cell for a given index. @param index The index of the requested row. @return A configured `UICollectionViewCell` subclass. @note This is your opportunity to do any cell setup and configuration. The infrastructure requests a cell when it will be used on screen. You should never allocate new cells in this method, instead use the provided adapter to call one of the dequeue methods on the IGListCollectionContext. The default implementation will assert. **You must override this method without calling super.** @warning Don't call this method to obtain a reference to currently dequeued cells: a new cell will be dequeued and returned, rather than the existing cell that you may have intended to retrieve. Instead, you can call `-cellForItemAtIndex:sectionController:` on `IGListCollectionContext` to obtain active cell references. */ - (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index; /** Updates the section controller to a new object. @param object The object mapped to this section controller. @note When this method is called, all available contexts and configurations have been set for the section controller. This method will only be called when the object instance has changed, including from `nil` or a previous object. **Calling super is not required.** */ - (void)didUpdateToObject:(id)object; /** Tells the section controller that the cell at the specified index path was selected. @param index The index of the selected cell. @note The default implementation does nothing. **Calling super is not required.** */ - (void)didSelectItemAtIndex:(NSInteger)index; /** Tells the section controller that the cell at the specified index path was deselected. @param index The index of the deselected cell. @note The default implementation does nothing. **Calling super is not required.** */ - (void)didDeselectItemAtIndex:(NSInteger)index; /** Tells the section controller that the cell at the specified index path was highlighted. @param index The index of the highlighted cell. @note The default implementation does nothing. **Calling super is not required.** */ - (void)didHighlightItemAtIndex:(NSInteger)index; /** Tells the section controller that the cell at the specified index path was unhighlighted. @param index The index of the unhighlighted cell. @note The default implementation does nothing. **Calling super is not required.** */ - (void)didUnhighlightItemAtIndex:(NSInteger)index; /** Identifies whether an object can be moved through interactive reordering. @param index The index of the object in the list. @return `YES` if the object is allowed to move, otherwise `NO`. @note Interactive reordering is supported both for items within a single section, as well as for reordering sections themselves when sections contain only one item. The default implementation returns false. */ - (BOOL)canMoveItemAtIndex:(NSInteger)index; /** Notifies the section that a list object should move within a section as the result of interactive reordering. @param sourceIndex The starting index of the object. @param destinationIndex The ending index of the object. @note this method must be implemented if interactive reordering is enabled. */ - (void)moveObjectFromIndex:(NSInteger)sourceIndex toIndex:(NSInteger)destinationIndex NS_AVAILABLE_IOS(9_0); /** The view controller housing the adapter that created this section controller. @note Use this view controller to push, pop, present, or do other custom transitions. @warning It is considered very bad practice to cast this to a known view controller and call methods on it other than for navigations and transitions. */ @property (nonatomic, weak, nullable, readonly) UIViewController *viewController; /** A context object for interacting with the collection. Use this property for accessing the collection size, dequeuing cells, reloading, inserting, deleting, etc. */ @property (nonatomic, weak, nullable, readonly) id collectionContext; /** Returns the section within the list for this section controller. @note This value also relates to the section within a `UICollectionView` that this section controller's cells belong. It also relates to the `-[NSIndexPath section]` value for individual cells within the collection view. */ @property (nonatomic, assign, readonly) NSInteger section; /** Returns `YES` if the section controller is the first section in the list, `NO` otherwise. */ @property (nonatomic, assign, readonly) BOOL isFirstSection; /** Returns `YES` if the section controller is the last section in the list, `NO` otherwise. */ @property (nonatomic, assign, readonly) BOOL isLastSection; /** The margins used to lay out content in the section controller. @see `-[UICollectionViewFlowLayout sectionInset]`. */ @property (nonatomic, assign) UIEdgeInsets inset; /** The minimum spacing to use between rows of items. @see `-[UICollectionViewFlowLayout minimumLineSpacing]`. */ @property (nonatomic, assign) CGFloat minimumLineSpacing; /** The minimum spacing to use between items in the same row. @see `-[UICollectionViewFlowLayout minimumInteritemSpacing]`. */ @property (nonatomic, assign) CGFloat minimumInteritemSpacing; /** The supplementary view source for the section controller. Can be `nil`. @return An object that conforms to `IGListSupplementaryViewSource` or `nil`. @note You may wish to return `self` if your section controller implements this protocol. */ @property (nonatomic, weak, nullable) id supplementaryViewSource; /** An object that handles display events for the section controller. Can be `nil`. @return An object that conforms to `IGListDisplayDelegate` or `nil`. @note You may wish to return `self` if your section controller implements this protocol. */ @property (nonatomic, weak, nullable) id displayDelegate; /** An object that handles working range events for the section controller. Can be `nil`. @return An object that conforms to `IGListWorkingRangeDelegate` or `nil`. @note You may wish to return `self` if your section controller implements this protocol. */ @property (nonatomic, weak, nullable) id workingRangeDelegate; /** An object that handles scroll events for the section controller. Can be `nil`. @return An object that conforms to `IGListScrollDelegate` or `nil`. @note You may wish to return `self` if your section controller implements this protocol. */ @property (nonatomic, weak, nullable) id scrollDelegate; /** An object that handles transition events for the section controller. Can be `nil`. @return An object that conforms to `IGListTransitionDelegat` or `nil`. @note You may wish to return `self` if your section controller implements this protocol. */ @property (nonatomic, weak, nullable) id transitionDelegate; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListSectionController.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListSectionControllerInternal.h" #import #import static NSString * const kIGListSectionControllerThreadKey = @"kIGListSectionControllerThreadKey"; @interface IGListSectionControllerThreadContext : NSObject @property (nonatomic, weak) UIViewController *viewController; @property (nonatomic, weak) id collectionContext; @end @implementation IGListSectionControllerThreadContext @end static NSMutableArray *threadContextStack(void) { IGAssertMainThread(); NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary]; NSMutableArray *stack = threadDictionary[kIGListSectionControllerThreadKey]; if (stack == nil) { stack = [NSMutableArray new]; threadDictionary[kIGListSectionControllerThreadKey] = stack; } return stack; } void IGListSectionControllerPushThread(UIViewController *viewController, id collectionContext) { IGListSectionControllerThreadContext *context = [IGListSectionControllerThreadContext new]; context.viewController = viewController; context.collectionContext = collectionContext; [threadContextStack() addObject:context]; } void IGListSectionControllerPopThread(void) { NSMutableArray *stack = threadContextStack(); IGAssert(stack.count > 0, @"IGListSectionController thread stack is empty"); [stack removeLastObject]; } @implementation IGListSectionController - (instancetype)init { if (self = [super init]) { IGListSectionControllerThreadContext *context = [threadContextStack() lastObject]; _viewController = context.viewController; _collectionContext = context.collectionContext; if (_collectionContext == nil) { IGLKLog(@"Warning: Creating %@ outside of -[IGListAdapterDataSource listAdapter:sectionControllerForObject:]. Collection context and view controller will be set later.", NSStringFromClass([self class])); } _minimumInteritemSpacing = 0.0; _minimumLineSpacing = 0.0; _inset = UIEdgeInsetsZero; _section = NSNotFound; } return self; } - (NSInteger)numberOfItems { return 1; } - (CGSize)sizeForItemAtIndex:(NSInteger)index { return CGSizeZero; } - (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index { IGFailAssert(@"Section controller %@ must override %s:", self, __PRETTY_FUNCTION__); return nil; } - (void)didUpdateToObject:(id)object {} - (void)didSelectItemAtIndex:(NSInteger)index {} - (void)didDeselectItemAtIndex:(NSInteger)index {} - (void)didHighlightItemAtIndex:(NSInteger)index {} - (void)didUnhighlightItemAtIndex:(NSInteger)index {} - (BOOL)canMoveItemAtIndex:(NSInteger)index { return NO; } - (BOOL)canMoveItemAtIndex:(NSInteger)sourceItemIndex toIndex:(NSInteger)destinationItemIndex { return [self canMoveItemAtIndex:sourceItemIndex]; } - (void)moveObjectFromIndex:(NSInteger)sourceIndex toIndex:(NSInteger)destinationIndex { IGFailAssert(@"Section controller %@ must override %s if interactive reordering is enabled.", self, __PRETTY_FUNCTION__); } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListSingleSectionController.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import NS_ASSUME_NONNULL_BEGIN /** A block used to configure cells. @param item The model with which to configure the cell. @param cell The cell to configure. */ NS_SWIFT_NAME(ListSingleSectionCellConfigureBlock) typedef void (^IGListSingleSectionCellConfigureBlock)(id item, __kindof UICollectionViewCell *cell); /** A block that returns the size for the cell given the collection context. @param item The model for the section. @param collectionContext The collection context for the section. @return The size for the cell. */ NS_SWIFT_NAME(ListSingleSectionCellSizeBlock) typedef CGSize (^IGListSingleSectionCellSizeBlock)(id item, id _Nullable collectionContext); @class IGListSingleSectionController; /** A delegate that can receive selection events on an `IGListSingleSectionController`. */ NS_SWIFT_NAME(ListSingleSectionControllerDelegate) @protocol IGListSingleSectionControllerDelegate /** Tells the delegate that the section controller was selected. @param sectionController The section controller that was selected. @param object The model for the given section. */ - (void)didSelectSectionController:(IGListSingleSectionController *)sectionController withObject:(id)object; @optional /** Tells the delegate that the section controller was deselected. @param sectionController The section controller that was deselected. @param object The model for the given section. @note Method is `@optional` until the 4.0.0 release where it will become required. */ - (void)didDeselectSectionController:(IGListSingleSectionController *)sectionController withObject:(id)object; @end /** This section controller is meant to make building simple, single-cell lists easier. By providing the type of cell, a block to configure the cell, and a block to return the size of a cell, you can use an `IGListAdapter`-powered list with a simpler architecture. */ IGLK_SUBCLASSING_RESTRICTED NS_SWIFT_NAME(ListSingleSectionController) @interface IGListSingleSectionController : IGListSectionController /** Creates a new section controller for a given cell type that will always have only one cell when present in a list. @param cellClass The `UICollectionViewCell` subclass for the single cell. @param configureBlock A block that configures the cell with the item given to the section controller. @param sizeBlock A block that returns the size for the cell given the collection context. @return A new section controller. @warning Be VERY CAREFUL not to create retain cycles by holding strong references to: the object that owns the adapter (usually `self`) or the `IGListAdapter`. Pass in locally scoped objects or use `weak` references! */ - (instancetype)initWithCellClass:(Class)cellClass configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock; /** Creates a new section controller for a given nib name and bundle that will always have only one cell when present in a list. @param nibName The name of the nib file for the single cell. @param bundle The bundle in which to search for the nib file. If `nil`, this method looks for the file in the main bundle. @param configureBlock A block that configures the cell with the item given to the section controller. @param sizeBlock A block that returns the size for the cell given the collection context. @return A new section controller. @warning Be VERY CAREFUL not to create retain cycles by holding strong references to: the object that owns the adapter (usually `self`) or the `IGListAdapter`. Pass in locally scoped objects or use `weak` references! */ - (instancetype)initWithNibName:(NSString *)nibName bundle:(nullable NSBundle *)bundle configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock; /** Creates a new section controller for a given storyboard cell identifier that will always have only one cell when present in a list. @param identifier The identifier of the cell prototype in storyboard. @param configureBlock A block that configures the cell with the item given to the section controller. @param sizeBlock A block that returns the size for the cell given the collection context. @return A new section controller. @warning Be VERY CAREFUL not to create retain cycles by holding strong references to: the object that owns the adapter (usually `self`) or the `IGListAdapter`. Pass in locally scoped objects or use `weak` references! */ - (instancetype)initWithStoryboardCellIdentifier:(NSString *)identifier configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock; /** An optional delegate that handles selection and deselection. */ @property (nonatomic, weak, nullable) id selectionDelegate; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListSingleSectionController.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListSingleSectionController.h" #import @interface IGListSingleSectionController () @property (nonatomic, strong, readonly) NSString *nibName; @property (nonatomic, strong, readonly) NSBundle *bundle; @property (nonatomic, strong, readonly) NSString *identifier; @property (nonatomic, strong, readonly) Class cellClass; @property (nonatomic, strong, readonly) IGListSingleSectionCellConfigureBlock configureBlock; @property (nonatomic, strong, readonly) IGListSingleSectionCellSizeBlock sizeBlock; @property (nonatomic, strong) id item; @end @implementation IGListSingleSectionController - (instancetype)initWithCellClass:(Class)cellClass configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock { IGParameterAssert(cellClass != nil); IGParameterAssert(configureBlock != nil); IGParameterAssert(sizeBlock != nil); if (self = [super init]) { _cellClass = cellClass; _configureBlock = [configureBlock copy]; _sizeBlock = [sizeBlock copy]; } return self; } - (instancetype)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock { IGParameterAssert(nibName != nil); IGParameterAssert(configureBlock != nil); IGParameterAssert(sizeBlock != nil); if (self = [super init]) { _nibName = [nibName copy]; _bundle = bundle; _configureBlock = [configureBlock copy]; _sizeBlock = [sizeBlock copy]; } return self; } - (instancetype)initWithStoryboardCellIdentifier:(NSString *)identifier configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock { IGParameterAssert(identifier.length > 0); IGParameterAssert(configureBlock != nil); IGParameterAssert(sizeBlock != nil); if (self = [super init]) { _identifier = [identifier copy]; _configureBlock = [configureBlock copy]; _sizeBlock = [sizeBlock copy]; } return self; } #pragma mark - IGListSectionController Overrides - (NSInteger)numberOfItems { return 1; } - (CGSize)sizeForItemAtIndex:(NSInteger)index { return self.sizeBlock(self.item, self.collectionContext); } - (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index { IGParameterAssert(index == 0); id cell; id collectionContext = self.collectionContext; if ([self.nibName length] > 0) { cell = [collectionContext dequeueReusableCellWithNibName:self.nibName bundle:self.bundle forSectionController:self atIndex:index]; } else if ([self.identifier length] > 0) { cell = [collectionContext dequeueReusableCellFromStoryboardWithIdentifier:self.identifier forSectionController:self atIndex:index]; } else { cell = [collectionContext dequeueReusableCellOfClass:self.cellClass forSectionController:self atIndex:index]; } self.configureBlock(self.item, cell); return cell; } - (void)didUpdateToObject:(id)object { self.item = object; } - (void)didSelectItemAtIndex:(NSInteger)index { [self.selectionDelegate didSelectSectionController:self withObject:self.item]; } - (void)didDeselectItemAtIndex:(NSInteger)index { if ([self.selectionDelegate respondsToSelector:@selector(didDeselectSectionController:withObject:)]) { [self.selectionDelegate didDeselectSectionController:self withObject:self.item]; } } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListSupplementaryViewSource.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import NS_ASSUME_NONNULL_BEGIN /** Conform to this protocol to provide information about a list's supplementary views. This data is used in `IGListAdapter` which then configures and maintains a `UICollectionView`. The supplementary API reflects that in `UICollectionView`, `UICollectionViewLayout`, and `UICollectionViewDataSource`. */ NS_SWIFT_NAME(ListSupplementaryViewSource) @protocol IGListSupplementaryViewSource /** Asks the SupplementaryViewSource for an array of supported element kinds. @return An array of element kind strings that the supplementary source handles. */ - (NSArray *)supportedElementKinds; /** Asks the SupplementaryViewSource for a configured supplementary view for the specified kind and index. @param elementKind The kind of supplementary view being requested @param index The index for the supplementary veiw being requested. @note This is your opportunity to do any supplementary view setup and configuration. @warning You should never allocate new views in this method. Instead deque a view from the `IGListCollectionContext`. */ - (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index; /** Asks the SupplementaryViewSource for the size of a supplementary view for the given kind and index path. @param elementKind The kind of supplementary view. @param index The index of the requested view. @return The size for the supplementary view. */ - (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListTransitionDelegate.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** Conform to `IGListTransitionDelegate` to provide customized layout information for a collection view. */ @protocol IGListTransitionDelegate /** Asks the delegate to customize and return the starting layout information for an item being inserted into the collection view. @param listAdapter The adapter controlling the list. @param attributes The starting layout information for an item being inserted into the collection view. @param sectionController The section controller to perform the transition on. @param index The index of the item being inserted. */ - (UICollectionViewLayoutAttributes *)listAdapter:(IGListAdapter *)listAdapter customizedInitialLayoutAttributes:(UICollectionViewLayoutAttributes *)attributes sectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; /** Asks the delegate to customize and return the final layout information for an item that is about to be removed from the collection view. @param listAdapter The adapter controlling the list. @param attributes The final layout information for an item that is about to be removed from the collection view. @param sectionController The section controller to perform the transition on. @param index The index of the item being deleted. */ - (UICollectionViewLayoutAttributes *)listAdapter:(IGListAdapter *)listAdapter customizedFinalLayoutAttributes:(UICollectionViewLayoutAttributes *)attributes sectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListUpdatingDelegate.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @protocol IGListDiffable; NS_ASSUME_NONNULL_BEGIN /** A completion block to execute when updates are finished. @param finished Specifies whether or not the update finished. */ NS_SWIFT_NAME(ListUpdatingCompletion) typedef void (^IGListUpdatingCompletion)(BOOL finished); /** A block to be called when the adapter applies changes to the collection view. @param toObjects The new objects in the collection. */ NS_SWIFT_NAME(ListObjectTransitionBlock) typedef void (^IGListObjectTransitionBlock)(NSArray *toObjects); /// A block that contains all of the updates. NS_SWIFT_NAME(ListItemUpdateBlock) typedef void (^IGListItemUpdateBlock)(void); /// A block to be called when an adapter reloads the collection view. NS_SWIFT_NAME(ListReloadUpdateBlock) typedef void (^IGListReloadUpdateBlock)(void); /// A block that returns an array of objects to transition to. NS_SWIFT_NAME(ListToObjectBlock) typedef NSArray * _Nullable (^IGListToObjectBlock)(void); /// A block that returns a collection view to perform updates on. NS_SWIFT_NAME(ListCollectionViewBlock) typedef UICollectionView * _Nullable (^IGListCollectionViewBlock)(void); /** Implement this protocol in order to handle both section and row based update events. Implementation should forward or coalesce these events to a backing store or collection. */ NS_SWIFT_NAME(ListUpdatingDelegate) @protocol IGListUpdatingDelegate /** Asks the delegate for the pointer functions for looking up an object in a collection. @return Pointer functions for looking up an object in a collection. @note Since the updating delegate is responsible for transitioning between object sets, it becomes the "source of truth" for how objects and their corresponding section controllers are mapped. This allows the updater to control if objects are looked up by pointer, or more traditionally, with `-hash`/`-isEqual`. For behavior similar to `NSDictionary`, simply return `+[NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsObjectPersonality]`. */ - (NSPointerFunctions *)objectLookupPointerFunctions; /** Tells the delegate to perform a section transition from an old array of objects to a new one. @param collectionViewBlock A block returning the collecion view to perform updates on. @param fromObjects The previous objects in the collection view. Objects must conform to `IGListDiffable`. @param toObjectsBlock A block returning the new objects in the collection view. Objects must conform to `IGListDiffable`. @param animated A flag indicating if the transition should be animated. @param objectTransitionBlock A block that must be called when the adapter applies changes to the collection view. @param completion A completion block to execute when the update is finished. @note Implementations determine how to transition between objects. You can perform a diff on the objects, reload each section, or simply call `-reloadData` on the collection view. In the end, the collection view must be setup with a section for each object in the `toObjects` array. The `objectTransitionBlock` block should be called prior to making any `UICollectionView` updates, passing in the `toObjects` that the updater is applying. */ - (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock fromObjects:(nullable NSArray> *)fromObjects toObjectsBlock:(nullable IGListToObjectBlock)toObjectsBlock animated:(BOOL)animated objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock completion:(nullable IGListUpdatingCompletion)completion; /** Tells the delegate to perform item inserts at the given index paths. @param collectionView The collection view on which to perform the transition. @param indexPaths The index paths to insert items into. */ - (void)insertItemsIntoCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths; /** Tells the delegate to perform item deletes at the given index paths. @param collectionView The collection view on which to perform the transition. @param indexPaths The index paths to delete items from. */ - (void)deleteItemsFromCollectionView:(UICollectionView *)collectionView indexPaths:(NSArray *)indexPaths; /** Tells the delegate to move an item from and to given index paths. @param collectionView The collection view on which to perform the transition. @param fromIndexPath The source index path of the item to move. @param toIndexPath The destination index path of the item to move. */ - (void)moveItemInCollectionView:(UICollectionView *)collectionView fromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; /** Tells the delegate to reload an item from and to given index paths. @param collectionView The collection view on which to perform the transition. @param fromIndexPath The source index path of the item to reload. @param toIndexPath The destination index path of the item to reload. @note Since UICollectionView is unable to handle calling -[UICollectionView reloadItemsAtIndexPaths:] safely while also executing insert and delete operations in the same batch updates, the updater must know about the origin and destination of the reload to perform a safe transition. */ - (void)reloadItemInCollectionView:(UICollectionView *)collectionView fromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; /** Tells the delegate to move a section from and to given indexes. @param collectionView The collection view on which to perform the transition. @param fromIndex The source index of the section to move. @param toIndex The destination index of the section to move. */ - (void)moveSectionInCollectionView:(UICollectionView *)collectionView fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex; /** Completely reload data in the collection. @param collectionViewBlock A block returning the collecion view to reload. @param reloadUpdateBlock A block that must be called when the adapter reloads the collection view. @param completion A completion block to execute when the reload is finished. */ - (void)reloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock completion:(nullable IGListUpdatingCompletion)completion; /** Completely reload each section in the collection view. @param collectionView The collection view to reload. @param sections The sections to reload. */ - (void)reloadCollectionView:(UICollectionView *)collectionView sections:(NSIndexSet *)sections; /** Perform an item update block in the collection view. @param collectionViewBlock A block returning the collecion view to perform updates on. @param animated A flag indicating if the transition should be animated. @param itemUpdates A block containing all of the updates. @param completion A completion block to execute when the update is finished. */ - (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock animated:(BOOL)animated itemUpdates:(IGListItemUpdateBlock)itemUpdates completion:(nullable IGListUpdatingCompletion)completion; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/IGListWorkingRangeDelegate.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListAdapter; @class IGListSectionController; NS_ASSUME_NONNULL_BEGIN /** Implement this protocol to receive working range events for a list. The working range is a range *near* the viewport in which you can begin preparing content for display. For example, you could begin decoding images, or warming text caches. */ NS_SWIFT_NAME(ListWorkingRangeDelegate) @protocol IGListWorkingRangeDelegate /** Notifies the delegate that an section controller will enter the working range. @param listAdapter The adapter controlling the list. @param sectionController The section controller entering the range. */ - (void)listAdapter:(IGListAdapter *)listAdapter sectionControllerWillEnterWorkingRange:(IGListSectionController *)sectionController; /** Notifies the delegate that an section controller exited the working range. @param listAdapter The adapter controlling the list. @param sectionController The section controller that exited the range. */ - (void)listAdapter:(IGListAdapter *)listAdapter sectionControllerDidExitWorkingRange:(IGListSectionController *)sectionController; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListAdapter+DebugDescription.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @interface IGListAdapter (DebugDescription) - (NSArray *)debugDescriptionLines; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListAdapter+DebugDescription.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListAdapter+DebugDescription.h" #import "IGListAdapterInternal.h" #import "IGListAdapterUpdater+DebugDescription.h" #import "IGListDebuggingUtilities.h" #import "IGListSectionMap+DebugDescription.h" #import "UICollectionView+DebugDescription.h" @implementation IGListAdapter (DebugDescription) - (NSString *)debugDescription { NSMutableArray *lines = [NSMutableArray arrayWithObject:[NSString stringWithFormat:@"IGListAdapter %p:", self]]; [lines addObjectsFromArray:IGListDebugIndentedLines([self debugDescriptionLines])]; return [lines componentsJoinedByString:@"\n"]; } - (NSArray *)debugDescriptionLines { NSMutableArray *debug = [NSMutableArray new]; #if IGLK_DEBUG_DESCRIPTION_ENABLED [debug addObject:[NSString stringWithFormat:@"Updater type: %@", NSStringFromClass(self.updater.class)]]; [debug addObject:[NSString stringWithFormat:@"Data source: %@", self.dataSource]]; [debug addObject:[NSString stringWithFormat:@"Collection view delegate: %@", self.collectionViewDelegate]]; [debug addObject:[NSString stringWithFormat:@"Scroll view delegate: %@", self.scrollViewDelegate]]; [debug addObject:[NSString stringWithFormat:@"Is in update block: %@", IGListDebugBOOL(self.isInUpdateBlock)]]; [debug addObject:[NSString stringWithFormat:@"View controller: %@", self.viewController]]; if (@available(iOS 10.0, tvOS 10, *)) { [debug addObject:[NSString stringWithFormat:@"Is prefetching enabled: %@", IGListDebugBOOL(self.collectionView.isPrefetchingEnabled)]]; } if (self.registeredCellIdentifiers.count > 0) { [debug addObject:@"Registered cell identifiers:"]; [debug addObject:[self.registeredCellIdentifiers description]]; } if (self.registeredNibNames.count > 0) { [debug addObject:@"Registered nib names:"]; [debug addObject:[self.registeredNibNames description]]; } if (self.registeredSupplementaryViewIdentifiers.count > 0) { [debug addObject:@"Registered supplementary view identifiers:"]; [debug addObject:[self.registeredSupplementaryViewIdentifiers description]]; } if (self.registeredSupplementaryViewNibNames.count > 0) { [debug addObject:@"Registered supplementary view nib names:"]; [debug addObject:self.registeredSupplementaryViewNibNames]; } if ([self.updater isKindOfClass:[IGListAdapterUpdater class]]) { [debug addObject:[NSString stringWithFormat:@"IGListAdapterUpdater instance %p:", self.updater]]; [debug addObjectsFromArray:IGListDebugIndentedLines([(IGListAdapterUpdater *)self.updater debugDescriptionLines])]; } [debug addObject:@"Section map details:"]; [debug addObjectsFromArray:IGListDebugIndentedLines([self.sectionMap debugDescriptionLines])]; if (self.previousSectionMap != nil) { [debug addObject:@"Previous section map details:"]; [debug addObjectsFromArray:IGListDebugIndentedLines([self.previousSectionMap debugDescriptionLines])]; } [debug addObject:@"Collection view details:"]; [debug addObjectsFromArray:IGListDebugIndentedLines([self.collectionView debugDescriptionLines])]; #endif // #if IGLK_DEBUG_DESCRIPTION_ENABLED return debug; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListAdapter+UICollectionView.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import @interface IGListAdapter (UICollectionView) < UICollectionViewDataSource, IGListCollectionViewDelegateLayout > @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListAdapter+UICollectionView.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListAdapter+UICollectionView.h" #import #import #import #import @implementation IGListAdapter (UICollectionView) #pragma mark - UICollectionViewDataSource - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return self.sectionMap.objects.count; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { IGListSectionController * sectionController = [self sectionControllerForSection:section]; IGAssert(sectionController != nil, @"Nil section controller for section %li for item %@. Check your -diffIdentifier and -isEqual: implementations.", (long)section, [self.sectionMap objectForSection:section]); const NSInteger numberOfItems = [sectionController numberOfItems]; IGAssert(numberOfItems >= 0, @"Cannot return negative number of items %li for section controller %@.", (long)numberOfItems, sectionController); return numberOfItems; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { id performanceDelegate = self.performanceDelegate; [performanceDelegate listAdapterWillCallDequeueCell:self]; IGListSectionController *sectionController = [self sectionControllerForSection:indexPath.section]; // flag that a cell is being dequeued in case it tries to access a cell in the process _isDequeuingCell = YES; UICollectionViewCell *cell = [sectionController cellForItemAtIndex:indexPath.item]; _isDequeuingCell = NO; IGAssert(cell != nil, @"Returned a nil cell at indexPath <%@> from section controller: <%@>", indexPath, sectionController); // associate the section controller with the cell so that we know which section controller is using it [self mapView:cell toSectionController:sectionController]; [performanceDelegate listAdapter:self didCallDequeueCell:cell onSectionController:sectionController atIndex:indexPath.item]; return cell; } - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { IGListSectionController *sectionController = [self sectionControllerForSection:indexPath.section]; id supplementarySource = [sectionController supplementaryViewSource]; UICollectionReusableView *view = [supplementarySource viewForSupplementaryElementOfKind:kind atIndex:indexPath.item]; IGAssert(view != nil, @"Returned a nil supplementary view at indexPath <%@> from section controller: <%@>, supplementary source: <%@>", indexPath, sectionController, supplementarySource); // associate the section controller with the cell so that we know which section controller is using it [self mapView:view toSectionController:sectionController]; return view; } - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath { const NSInteger sectionIndex = indexPath.section; const NSInteger itemIndex = indexPath.item; IGListSectionController *sectionController = [self sectionControllerForSection:sectionIndex]; return [sectionController canMoveItemAtIndex:itemIndex]; } - (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath { const NSInteger sourceSectionIndex = sourceIndexPath.section; const NSInteger destinationSectionIndex = destinationIndexPath.section; const NSInteger sourceItemIndex = sourceIndexPath.item; const NSInteger destinationItemIndex = destinationIndexPath.item; IGListSectionController *sourceSectionController = [self sectionControllerForSection:sourceSectionIndex]; IGListSectionController *destinationSectionController = [self sectionControllerForSection:destinationSectionIndex]; // this is a move within a section if (sourceSectionController == destinationSectionController) { if ([sourceSectionController canMoveItemAtIndex:sourceItemIndex toIndex:destinationItemIndex]) { [self moveInSectionControllerInteractive:sourceSectionController fromIndex:sourceItemIndex toIndex:destinationItemIndex]; } else { // otherwise this is a move of an _item_ from one section to another section // we need to revert the change as it's too late to cancel [self revertInvalidInteractiveMoveFromIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; } return; } // this is a reordering of sections themselves if ([sourceSectionController numberOfItems] == 1 && [destinationSectionController numberOfItems] == 1) { // perform view changes in the collection view [self moveSectionControllerInteractive:sourceSectionController fromIndex:sourceSectionIndex toIndex:destinationSectionIndex]; return; } // otherwise this is a move of an _item_ from one section to another section // this is not currently supported, so we need to revert the change as it's too late to cancel [self revertInvalidInteractiveMoveFromIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; } #pragma mark - UICollectionViewDelegate - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { // forward this method to the delegate b/c this implementation will steal the message from the proxy id collectionViewDelegate = self.collectionViewDelegate; if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)]) { [collectionViewDelegate collectionView:collectionView didSelectItemAtIndexPath:indexPath]; } IGListSectionController * sectionController = [self sectionControllerForSection:indexPath.section]; [sectionController didSelectItemAtIndex:indexPath.item]; } - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { // forward this method to the delegate b/c this implementation will steal the message from the proxy id collectionViewDelegate = self.collectionViewDelegate; if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didDeselectItemAtIndexPath:)]) { [collectionViewDelegate collectionView:collectionView didDeselectItemAtIndexPath:indexPath]; } IGListSectionController * sectionController = [self sectionControllerForSection:indexPath.section]; [sectionController didDeselectItemAtIndex:indexPath.item]; } - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { id performanceDelegate = self.performanceDelegate; [performanceDelegate listAdapterWillCallDisplayCell:self]; // forward this method to the delegate b/c this implementation will steal the message from the proxy id collectionViewDelegate = self.collectionViewDelegate; if ([collectionViewDelegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]) { [collectionViewDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; } IGListSectionController *sectionController = [self sectionControllerForView:cell]; // if the section controller relationship was destroyed, reconnect it // this happens with iOS 10 UICollectionView display range changes if (sectionController == nil) { sectionController = [self sectionControllerForSection:indexPath.section]; [self mapView:cell toSectionController:sectionController]; } id object = [self.sectionMap objectForSection:indexPath.section]; [self.displayHandler willDisplayCell:cell forListAdapter:self sectionController:sectionController object:object indexPath:indexPath]; _isSendingWorkingRangeDisplayUpdates = YES; [self.workingRangeHandler willDisplayItemAtIndexPath:indexPath forListAdapter:self]; _isSendingWorkingRangeDisplayUpdates = NO; [performanceDelegate listAdapter:self didCallDisplayCell:cell onSectionController:sectionController atIndex:indexPath.item]; } - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { id performanceDelegate = self.performanceDelegate; [performanceDelegate listAdapterWillCallEndDisplayCell:self]; // forward this method to the delegate b/c this implementation will steal the message from the proxy id collectionViewDelegate = self.collectionViewDelegate; if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)]) { [collectionViewDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; } IGListSectionController *sectionController = [self sectionControllerForView:cell]; [self.displayHandler didEndDisplayingCell:cell forListAdapter:self sectionController:sectionController indexPath:indexPath]; [self.workingRangeHandler didEndDisplayingItemAtIndexPath:indexPath forListAdapter:self]; // break the association between the cell and the section controller [self removeMapForView:cell]; [performanceDelegate listAdapter:self didCallEndDisplayCell:cell onSectionController:sectionController atIndex:indexPath.item]; } - (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { id collectionViewDelegate = self.collectionViewDelegate; if ([collectionViewDelegate respondsToSelector:@selector(collectionView:willDisplaySupplementaryView:forElementKind:atIndexPath:)]) { [collectionViewDelegate collectionView:collectionView willDisplaySupplementaryView:view forElementKind:elementKind atIndexPath:indexPath]; } IGListSectionController *sectionController = [self sectionControllerForView:view]; // if the section controller relationship was destroyed, reconnect it // this happens with iOS 10 UICollectionView display range changes if (sectionController == nil) { sectionController = [self.sectionMap sectionControllerForSection:indexPath.section]; [self mapView:view toSectionController:sectionController]; } id object = [self.sectionMap objectForSection:indexPath.section]; [self.displayHandler willDisplaySupplementaryView:view forListAdapter:self sectionController:sectionController object:object indexPath:indexPath]; } - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { id collectionViewDelegate = self.collectionViewDelegate; if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didEndDisplayingSupplementaryView:forElementOfKind:atIndexPath:)]) { [collectionViewDelegate collectionView:collectionView didEndDisplayingSupplementaryView:view forElementOfKind:elementKind atIndexPath:indexPath]; } IGListSectionController *sectionController = [self sectionControllerForView:view]; [self.displayHandler didEndDisplayingSupplementaryView:view forListAdapter:self sectionController:sectionController indexPath:indexPath]; [self removeMapForView:view]; } - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath { // forward this method to the delegate b/c this implementation will steal the message from the proxy id collectionViewDelegate = self.collectionViewDelegate; if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didHighlightItemAtIndexPath:)]) { [collectionViewDelegate collectionView:collectionView didHighlightItemAtIndexPath:indexPath]; } IGListSectionController * sectionController = [self sectionControllerForSection:indexPath.section]; [sectionController didHighlightItemAtIndex:indexPath.item]; } - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath { // forward this method to the delegate b/c this implementation will steal the message from the proxy id collectionViewDelegate = self.collectionViewDelegate; if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didUnhighlightItemAtIndexPath:)]) { [collectionViewDelegate collectionView:collectionView didUnhighlightItemAtIndexPath:indexPath]; } IGListSectionController * sectionController = [self sectionControllerForSection:indexPath.section]; [sectionController didUnhighlightItemAtIndex:indexPath.item]; } #pragma mark - UICollectionViewDelegateFlowLayout - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); CGSize size = [self sizeForItemAtIndexPath:indexPath]; IGAssert(!isnan(size.height), @"IGListAdapter returned NaN height = %f for item at indexPath <%@>", size.height, indexPath); IGAssert(!isnan(size.width), @"IGListAdapter returned NaN width = %f for item at indexPath <%@>", size.width, indexPath); return size; } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); return [[self sectionControllerForSection:section] inset]; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); return [[self sectionControllerForSection:section] minimumLineSpacing]; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); return [[self sectionControllerForSection:section] minimumInteritemSpacing]; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; return [self sizeForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath]; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { IGAssert(![self.collectionViewDelegate respondsToSelector:_cmd], @"IGListAdapter is consuming method also implemented by the collectionViewDelegate: %@", NSStringFromSelector(_cmd)); NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; return [self sizeForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:indexPath]; } #pragma mark - IGListCollectionViewDelegateLayout - (UICollectionViewLayoutAttributes *)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout customizedInitialLayoutAttributes:(UICollectionViewLayoutAttributes *)attributes atIndexPath:(NSIndexPath *)indexPath { IGListSectionController *sectionController = [self sectionControllerForSection:indexPath.section]; if (sectionController.transitionDelegate) { return [sectionController.transitionDelegate listAdapter:self customizedInitialLayoutAttributes:attributes sectionController:sectionController atIndex:indexPath.item]; } return attributes; } - (UICollectionViewLayoutAttributes *)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout customizedFinalLayoutAttributes:(UICollectionViewLayoutAttributes *)attributes atIndexPath:(NSIndexPath *)indexPath { IGListSectionController *sectionController = [self sectionControllerForSection:indexPath.section]; if (sectionController.transitionDelegate) { return [sectionController.transitionDelegate listAdapter:self customizedFinalLayoutAttributes:attributes sectionController:sectionController atIndex:indexPath.item]; } return attributes; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListAdapterInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import "IGListAdapter+UICollectionView.h" #import "IGListAdapterProxy.h" #import "IGListDisplayHandler.h" #import "IGListSectionMap.h" #import "IGListWorkingRangeHandler.h" NS_ASSUME_NONNULL_BEGIN /// Generate a string representation of a reusable view class when registering with a UICollectionView. NS_INLINE NSString *IGListReusableViewIdentifier(Class viewClass, NSString * _Nullable kind, NSString * _Nullable givenReuseIdentifier) { return [NSString stringWithFormat:@"%@%@%@", kind ?: @"", givenReuseIdentifier ?: @"", NSStringFromClass(viewClass)]; } @interface IGListAdapter () < IGListCollectionContext, IGListBatchContext > { __weak UICollectionView *_collectionView; BOOL _isDequeuingCell; BOOL _isSendingWorkingRangeDisplayUpdates; } @property (nonatomic, strong) id updater; @property (nonatomic, strong, readonly) IGListSectionMap *sectionMap; @property (nonatomic, strong, readonly) IGListDisplayHandler *displayHandler; @property (nonatomic, strong, readonly) IGListWorkingRangeHandler *workingRangeHandler; @property (nonatomic, strong, nullable) IGListAdapterProxy *delegateProxy; @property (nonatomic, strong, nullable) UIView *emptyBackgroundView; // we need to special case interactive section moves that are moved to the last position @property (nonatomic) BOOL isLastInteractiveMoveToLastSectionIndex; /** When making object updates inside a batch update block, delete operations must use the section /before/ any moves take place. This includes when other objects are deleted or inserted ahead of the section controller making the mutations. In order to account for this we must track when the adapter is in the middle of an update block as well as the section controller mapping prior to the transition. Note that the previous section controller map is destroyed as soon as a transition is finished so there is no dangling objects or section controllers. */ @property (nonatomic, assign) BOOL isInUpdateBlock; @property (nonatomic, strong, nullable) IGListSectionMap *previousSectionMap; /** Set of cell identifiers registered with the list context. Identifiers are constructed with the `IGListReusableViewIdentifier` function. */ @property (nonatomic, strong) NSMutableSet *registeredCellIdentifiers; @property (nonatomic, strong) NSMutableSet *registeredNibNames; @property (nonatomic, strong) NSMutableSet *registeredSupplementaryViewIdentifiers; @property (nonatomic, strong) NSMutableSet *registeredSupplementaryViewNibNames; - (void)mapView:(__kindof UIView *)view toSectionController:(IGListSectionController *)sectionController; - (nullable IGListSectionController *)sectionControllerForView:(__kindof UIView *)view; - (void)removeMapForView:(__kindof UIView *)view; - (NSArray *)indexPathsFromSectionController:(IGListSectionController *)sectionController indexes:(NSIndexSet *)indexes usePreviousIfInUpdateBlock:(BOOL)usePreviousIfInUpdateBlock; - (nullable NSIndexPath *)indexPathForSectionController:(IGListSectionController *)controller index:(NSInteger)index usePreviousIfInUpdateBlock:(BOOL)usePreviousIfInUpdateBlock; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListAdapterProxy.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import @class IGListAdapter; NS_ASSUME_NONNULL_BEGIN /** A proxy that sends a custom set of selectors to an IGListAdapter object and the rest to a UICollectionViewDelegate target. */ IGLK_SUBCLASSING_RESTRICTED @interface IGListAdapterProxy : NSProxy /** Create a new proxy object with targets and interceptor. @param collectionViewTarget A UICollectionViewDelegate conforming object that receives non-intercepted messages. @param scrollViewTarget A UIScrollViewDelegate conforming object that receives non-intercepted messages. @param interceptor An IGListAdapter object that intercepts a set of messages. @return A new IGListAdapterProxy object. */ - (instancetype)initWithCollectionViewTarget:(nullable id)collectionViewTarget scrollViewTarget:(nullable id)scrollViewTarget interceptor:(IGListAdapter *)interceptor; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListAdapterProxy.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListAdapterProxy.h" #import #import "IGListCollectionViewDelegateLayout.h" /** Define messages that you want the IGListAdapter object to intercept. Pattern copied from https://github.com/facebook/AsyncDisplayKit/blob/7b112a2dcd0391ddf3671f9dcb63521f554b78bd/AsyncDisplayKit/ASCollectionView.mm#L34-L53 */ static BOOL isInterceptedSelector(SEL sel) { return ( // UIScrollViewDelegate sel == @selector(scrollViewDidScroll:) || sel == @selector(scrollViewWillBeginDragging:) || sel == @selector(scrollViewDidEndDragging:willDecelerate:) || sel == @selector(scrollViewDidEndDecelerating:) || // UICollectionViewDelegate sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) || sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) || sel == @selector(collectionView:didSelectItemAtIndexPath:) || sel == @selector(collectionView:didDeselectItemAtIndexPath:) || sel == @selector(collectionView:didHighlightItemAtIndexPath:) || sel == @selector(collectionView:didUnhighlightItemAtIndexPath:) || // UICollectionViewDelegateFlowLayout sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) || sel == @selector(collectionView:layout:insetForSectionAtIndex:) || sel == @selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:) || sel == @selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:) || sel == @selector(collectionView:layout:referenceSizeForFooterInSection:) || sel == @selector(collectionView:layout:referenceSizeForHeaderInSection:) || // IGListCollectionViewDelegateLayout sel == @selector(collectionView:layout:customizedInitialLayoutAttributes:atIndexPath:) || sel == @selector(collectionView:layout:customizedFinalLayoutAttributes:atIndexPath:) ); } @interface IGListAdapterProxy () { __weak id _collectionViewTarget; __weak id _scrollViewTarget; __weak IGListAdapter *_interceptor; } @end @implementation IGListAdapterProxy - (instancetype)initWithCollectionViewTarget:(nullable id)collectionViewTarget scrollViewTarget:(nullable id)scrollViewTarget interceptor:(IGListAdapter *)interceptor { IGParameterAssert(interceptor != nil); // -[NSProxy init] is undefined if (self) { _collectionViewTarget = collectionViewTarget; _scrollViewTarget = scrollViewTarget; _interceptor = interceptor; } return self; } - (BOOL)respondsToSelector:(SEL)aSelector { return isInterceptedSelector(aSelector) || [_collectionViewTarget respondsToSelector:aSelector] || [_scrollViewTarget respondsToSelector:aSelector]; } - (id)forwardingTargetForSelector:(SEL)aSelector { if (isInterceptedSelector(aSelector)) { return _interceptor; } // since UICollectionViewDelegate is a superset of UIScrollViewDelegate, first check if the method exists in // _scrollViewTarget, otherwise use the _collectionViewTarget return [_scrollViewTarget respondsToSelector:aSelector] ? _scrollViewTarget : _collectionViewTarget; } // handling unimplemented methods and nil target/interceptor // https://github.com/Flipboard/FLAnimatedImage/blob/76a31aefc645cc09463a62d42c02954a30434d7d/FLAnimatedImage/FLAnimatedImage.m#L786-L807 - (void)forwardInvocation:(NSInvocation *)invocation { void *nullPointer = NULL; [invocation setReturnValue:&nullPointer]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { return [NSObject instanceMethodSignatureForSelector:@selector(init)]; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListAdapterUpdater+DebugDescription.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @interface IGListAdapterUpdater (DebugDescription) - (NSArray *)debugDescriptionLines; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListAdapterUpdater+DebugDescription.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListAdapterUpdater+DebugDescription.h" #import "IGListAdapterUpdaterInternal.h" #import "IGListBatchUpdateData+DebugDescription.h" #import "IGListDebuggingUtilities.h" #if IGLK_DEBUG_DESCRIPTION_ENABLED static NSMutableArray *linesFromObjects(NSArray *objects) { NSMutableArray *lines = [NSMutableArray new]; for (id object in objects) { [lines addObject:[NSString stringWithFormat:@"Object %p of type %@ with identifier %@", object, NSStringFromClass([object class]), [object diffIdentifier]]]; } return lines; } #endif // #if IGLK_DEBUG_DESCRIPTION_ENABLED @implementation IGListAdapterUpdater (DebugDescription) - (NSArray *)debugDescriptionLines { NSMutableArray *debug = [NSMutableArray new]; #if IGLK_DEBUG_DESCRIPTION_ENABLED [debug addObject:[NSString stringWithFormat:@"Moves as deletes+inserts: %@", IGListDebugBOOL(self.movesAsDeletesInserts)]]; [debug addObject:[NSString stringWithFormat:@"Allows background reloading: %@", IGListDebugBOOL(self.allowsBackgroundReloading)]]; [debug addObject:[NSString stringWithFormat:@"Has queued reload data: %@", IGListDebugBOOL(self.hasQueuedReloadData)]]; [debug addObject:[NSString stringWithFormat:@"Queued update is animated: %@", IGListDebugBOOL(self.queuedUpdateIsAnimated)]]; NSString *stateString; switch (self.state) { case IGListBatchUpdateStateIdle: stateString = @"Idle"; break; case IGListBatchUpdateStateQueuedBatchUpdate: stateString = @"Queued batch update"; break; case IGListBatchUpdateStateExecutedBatchUpdateBlock: stateString = @"Executed batch update block"; break; case IGListBatchUpdateStateExecutingBatchUpdateBlock: stateString = @"Executing batch update block"; break; } [debug addObject:[NSString stringWithFormat:@"State: %@", stateString]]; if (self.applyingUpdateData != nil) { [debug addObject:@"Batch update data:"]; [debug addObjectsFromArray:IGListDebugIndentedLines([self.applyingUpdateData debugDescriptionLines])]; } if (self.fromObjects != nil) { [debug addObject:@"From objects:"]; [debug addObjectsFromArray:IGListDebugIndentedLines(linesFromObjects(self.fromObjects))]; } if (self.toObjectsBlock != nil) { [debug addObject:@"To objects:"]; [debug addObjectsFromArray:IGListDebugIndentedLines(linesFromObjects(self.toObjectsBlock()))]; } if (self.pendingTransitionToObjects != nil) { [debug addObject:@"Pending objects:"]; [debug addObjectsFromArray:IGListDebugIndentedLines(linesFromObjects(self.pendingTransitionToObjects))]; } #endif // #if IGLK_DEBUG_DESCRIPTION_ENABLED return debug; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListAdapterUpdaterInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import #import #import "IGListAdapterUpdater.h" #import "IGListBatchUpdateState.h" #import "IGListBatchUpdates.h" NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXTERN void convertReloadToDeleteInsert(NSMutableIndexSet *reloads, NSMutableIndexSet *deletes, NSMutableIndexSet *inserts, IGListIndexSetResult *result, NSArray> *fromObjects); @interface IGListAdapterUpdater () @property (nonatomic, copy, nullable) NSArray *fromObjects; @property (nonatomic, copy, nullable) IGListToObjectBlock toObjectsBlock; @property (nonatomic, copy, nullable) NSArray *pendingTransitionToObjects; @property (nonatomic, strong) NSMutableArray *completionBlocks; @property (nonatomic, assign) BOOL queuedUpdateIsAnimated; @property (nonatomic, strong) IGListBatchUpdates *batchUpdates; @property (nonatomic, copy, nullable) IGListObjectTransitionBlock objectTransitionBlock; @property (nonatomic, copy, nullable) IGListReloadUpdateBlock reloadUpdates; @property (nonatomic, assign, getter=hasQueuedReloadData) BOOL queuedReloadData; @property (nonatomic, assign) IGListBatchUpdateState state; @property (nonatomic, strong, nullable) IGListBatchUpdateData *applyingUpdateData; - (void)performReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock; - (void)performBatchUpdatesWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock; - (void)cleanStateBeforeUpdates; - (BOOL)hasChanges; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListArrayUtilsInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #ifndef IGListArrayUtilsInternal_h #define IGListArrayUtilsInternal_h #import static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray> *objects) { if (objects == nil) { return nil; } NSMapTable *identifierMap = [NSMapTable strongToStrongObjectsMapTable]; NSMutableArray *uniqueObjects = [NSMutableArray new]; for (id object in objects) { id diffIdentifier = [object diffIdentifier]; id previousObject = [identifierMap objectForKey:diffIdentifier]; if (diffIdentifier != nil && previousObject == nil) { [identifierMap setObject:object forKey:diffIdentifier]; [uniqueObjects addObject:object]; } else { IGLKLog(@"Duplicate identifier %@ for object %@ with object %@", diffIdentifier, object, previousObject); } } return uniqueObjects; } #endif /* IGListArrayUtilsInternal_h */ ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListBatchUpdateData+DebugDescription.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @interface IGListBatchUpdateData (DebugDescription) - (NSArray *)debugDescriptionLines; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListBatchUpdateData+DebugDescription.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListBatchUpdateData+DebugDescription.h" @implementation IGListBatchUpdateData (DebugDescription) - (NSArray *)debugDescriptionLines { NSMutableArray *debug = [NSMutableArray new]; #if IGLK_DEBUG_DESCRIPTION_ENABLED [debug addObject:[NSString stringWithFormat:@"Insert sections: %@", self.insertSections]]; [debug addObject:[NSString stringWithFormat:@"Delete sections: %@", self.deleteSections]]; for (IGListMoveIndex *move in self.moveSections) { [debug addObject:[NSString stringWithFormat:@"Move from section %li to %li", (long)move.from, (long)move.to]]; } for (NSIndexPath *path in self.deleteIndexPaths) { [debug addObject:[NSString stringWithFormat:@"Delete section %li item %li", (long)path.section, (long)path.item]]; } for (NSIndexPath *path in self.insertIndexPaths) { [debug addObject:[NSString stringWithFormat:@"Insert section %li item %li", (long)path.section, (long)path.item]]; } for (IGListMoveIndexPath *move in self.moveIndexPaths) { [debug addObject:[NSString stringWithFormat:@"Move from section %li item %li to section %li item %li", (long)move.from.section, (long)move.from.item, (long)move.to.section, (long)move.to.item]]; } #endif // #if IGLK_DEBUG_DESCRIPTION_ENABLED return debug; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListBatchUpdateState.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import typedef NS_ENUM (NSInteger, IGListBatchUpdateState) { IGListBatchUpdateStateIdle, IGListBatchUpdateStateQueuedBatchUpdate, IGListBatchUpdateStateExecutingBatchUpdateBlock, IGListBatchUpdateStateExecutedBatchUpdateBlock, }; ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListBatchUpdates.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import @class IGListMoveIndexPath; @class IGListReloadIndexPath; IGLK_SUBCLASSING_RESTRICTED @interface IGListBatchUpdates : NSObject @property (nonatomic, strong, readonly) NSMutableIndexSet *sectionReloads; @property (nonatomic, strong, readonly) NSMutableArray *itemInserts; @property (nonatomic, strong, readonly) NSMutableArray *itemDeletes; @property (nonatomic, strong, readonly) NSMutableArray *itemReloads; @property (nonatomic, strong, readonly) NSMutableArray *itemMoves; @property (nonatomic, strong, readonly) NSMutableArray *itemUpdateBlocks; @property (nonatomic, strong, readonly) NSMutableArray *itemCompletionBlocks; - (BOOL)hasChanges; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListBatchUpdates.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListBatchUpdates.h" @implementation IGListBatchUpdates - (instancetype)init { if (self = [super init]) { _sectionReloads = [NSMutableIndexSet new]; _itemInserts = [NSMutableArray new]; _itemMoves = [NSMutableArray new]; _itemDeletes = [NSMutableArray new]; _itemReloads = [NSMutableArray new]; _itemUpdateBlocks = [NSMutableArray new]; _itemCompletionBlocks = [NSMutableArray new]; } return self; } - (BOOL)hasChanges { return [self.itemUpdateBlocks count] > 0 || [self.sectionReloads count] > 0 || [self.itemInserts count] > 0 || [self.itemMoves count] > 0 || [self.itemReloads count] > 0 || [self.itemDeletes count] > 0; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListBindingSectionController+DebugDescription.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @interface IGListBindingSectionController (DebugDescription) - (NSArray *)debugDescriptionLines; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListBindingSectionController+DebugDescription.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListBindingSectionController+DebugDescription.h" #import "IGListDebuggingUtilities.h" @implementation IGListBindingSectionController (DebugDescription) - (NSString *)debugDescription { NSMutableArray *lines = [NSMutableArray arrayWithObject:[NSString stringWithFormat:@"IGListBindingSectionController %p:", self]]; [lines addObjectsFromArray:IGListDebugIndentedLines([self debugDescriptionLines])]; return [lines componentsJoinedByString:@"\n"]; } - (NSArray *)debugDescriptionLines { NSMutableArray *debug = [NSMutableArray new]; #if IGLK_DEBUG_DESCRIPTION_ENABLED [debug addObject:[NSString stringWithFormat:@"Data source: %@", self.dataSource]]; [debug addObject:[NSString stringWithFormat:@"Selection delegate: %@", self.selectionDelegate]]; [debug addObject:[NSString stringWithFormat:@"Object: %@", self.object]]; [debug addObject:@"View models:"]; for (id viewModel in self.viewModels) { [debug addObject:[NSString stringWithFormat:@"%@: %@", viewModel, viewModel.diffIdentifier]]; } [debug addObject:[NSString stringWithFormat:@"Number of items: %ld", (long)self.numberOfItems]]; [debug addObject:[NSString stringWithFormat:@"View controller: %@", self.viewController]]; [debug addObject:[NSString stringWithFormat:@"Collection context: %@", self.collectionContext]]; [debug addObject:[NSString stringWithFormat:@"Section: %ld", (long)self.section]]; [debug addObject:[NSString stringWithFormat:@"Is first section: %@", IGListDebugBOOL(self.isFirstSection)]]; [debug addObject:[NSString stringWithFormat:@"Is last section: %@", IGListDebugBOOL(self.isLastSection)]]; [debug addObject:[NSString stringWithFormat:@"Supplementary view source: %@", self.supplementaryViewSource]]; [debug addObject:[NSString stringWithFormat:@"Display delegate: %@", self.displayDelegate]]; [debug addObject:[NSString stringWithFormat:@"Working range delegate: %@", self.workingRangeDelegate]]; [debug addObject:[NSString stringWithFormat:@"Scroll delegate: %@", self.scrollDelegate]]; #endif // #if IGLK_DEBUG_DESCRIPTION_ENABLED return debug; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListCollectionViewLayoutInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ static CGRect IGListRectIntegralScaled(CGRect rect) { CGFloat scale = [[UIScreen mainScreen] scale]; return CGRectMake(floorf(rect.origin.x * scale) / scale, floorf(rect.origin.y * scale) / scale, ceilf(rect.size.width * scale) / scale, ceilf(rect.size.height * scale) / scale); } ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListDebugger.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import @class IGListAdapter; IGLK_SUBCLASSING_RESTRICTED @interface IGListDebugger : NSObject + (void)trackAdapter:(IGListAdapter *)adapter; + (NSArray *)adapterDescriptions; + (void)clear; + (NSString *)dump; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListDebugger.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListDebugger.h" #import "IGListAdapter+DebugDescription.h" @implementation IGListDebugger static NSHashTable *livingAdaptersTable = nil; + (void)trackAdapter:(IGListAdapter *)adapter { #if IGLK_DEBUG_DESCRIPTION_ENABLED if (livingAdaptersTable == nil) { livingAdaptersTable = [NSHashTable weakObjectsHashTable]; } [livingAdaptersTable addObject:adapter]; #endif // #if IGLK_DEBUG_DESCRIPTION_ENABLED } + (NSArray *)adapterDescriptions { NSMutableArray *descriptions = [NSMutableArray new]; for (IGListAdapter *adapter in livingAdaptersTable) { [descriptions addObject:[adapter debugDescription]]; } return descriptions; } + (void)clear { [livingAdaptersTable removeAllObjects]; } + (NSString *)dump { return [[self adapterDescriptions] componentsJoinedByString:@"\n"]; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListDebuggingUtilities.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListAdapter; extern NSString *IGListDebugBOOL(BOOL b); extern NSArray *IGListDebugIndentedLines(NSArray *lines); ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListDebuggingUtilities.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListDebuggingUtilities.h" NSString *IGListDebugBOOL(BOOL b) { return b ? @"Yes" : @"No"; } NSArray *IGListDebugIndentedLines(NSArray *lines) { NSMutableArray *newLines = [NSMutableArray new]; for (NSString *line in lines) { [newLines addObject:[NSString stringWithFormat:@" %@", line]]; } return newLines; } ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListDisplayHandler.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import @class IGListAdapter; @class IGListSectionController; NS_ASSUME_NONNULL_BEGIN IGLK_SUBCLASSING_RESTRICTED @interface IGListDisplayHandler : NSObject /** Counted set of the currently visible section controllers. */ @property (nonatomic, strong, readonly) NSCountedSet *visibleListSections; /** Tells the handler that a cell will be displayed in the IGListAdapter. @param cell A cell that will be displayed. @param listAdapter The adapter the cell will display in. @param sectionController The section controller that manages the cell. @param object The object that powers the section controller. @param indexPath The index path of the cell in the UICollectionView. */ - (void)willDisplayCell:(UICollectionViewCell *)cell forListAdapter:(IGListAdapter *)listAdapter sectionController:(IGListSectionController *)sectionController object:(id)object indexPath:(NSIndexPath *)indexPath; /** Tells the handler that a cell did end display in the IGListAdapter. @param cell A cell that will be displayed. @param listAdapter The adapter the cell will display in. @param sectionController The section controller that manages the cell. @param indexPath The index path of the cell in the UICollectionView. */ - (void)didEndDisplayingCell:(UICollectionViewCell *)cell forListAdapter:(IGListAdapter *)listAdapter sectionController:(IGListSectionController *)sectionController indexPath:(NSIndexPath *)indexPath; /** Tells the handler that a supplementary view will be displayed in the IGListAdapter. @param view A supplementary view that will be displayed. @param listAdapter The adapter the supplementary view will display in. @param sectionController The section controller that manages the supplementary view. @param object The object that powers the section controller. @param indexPath The index path of the supplementary view in the UICollectionView. */ - (void)willDisplaySupplementaryView:(UICollectionReusableView *)view forListAdapter:(IGListAdapter *)listAdapter sectionController:(IGListSectionController *)sectionController object:(id)object indexPath:(NSIndexPath *)indexPath; /** Tells the handler that a supplementary view did end display in the IGListAdapter. @param view A supplementary view that will be displayed. @param listAdapter The adapter the supplementary view will display in. @param sectionController The section controller that manages the supplementary view. @param indexPath The index path of the supplementary view in the UICollectionView. */ - (void)didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forListAdapter:(IGListAdapter *)listAdapter sectionController:(IGListSectionController *)sectionController indexPath:(NSIndexPath *)indexPath; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListDisplayHandler.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListDisplayHandler.h" #import #import #import #import @interface IGListDisplayHandler () @property (nonatomic, strong) NSMapTable *visibleViewObjectMap; @end @implementation IGListDisplayHandler - (instancetype)init { if (self = [super init]) { _visibleListSections = [NSCountedSet new]; _visibleViewObjectMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:0]; } return self; } - (id)_pluckObjectForView:(UICollectionReusableView *)view { NSMapTable *viewObjectMap = self.visibleViewObjectMap; id object = [viewObjectMap objectForKey:view]; [viewObjectMap removeObjectForKey:view]; return object; } - (void)_willDisplayReusableView:(UICollectionReusableView *)view forListAdapter:(IGListAdapter *)listAdapter sectionController:(IGListSectionController *)sectionController object:(id)object indexPath:(NSIndexPath *)indexPath { IGParameterAssert(view != nil); IGParameterAssert(listAdapter != nil); IGParameterAssert(object != nil); IGParameterAssert(indexPath != nil); [self.visibleViewObjectMap setObject:object forKey:view]; NSCountedSet *visibleListSections = self.visibleListSections; if ([visibleListSections countForObject:sectionController] == 0) { [sectionController.displayDelegate listAdapter:listAdapter willDisplaySectionController:sectionController]; [listAdapter.delegate listAdapter:listAdapter willDisplayObject:object atIndex:indexPath.section]; } [visibleListSections addObject:sectionController]; } - (void)_didEndDisplayingReusableView:(UICollectionReusableView *)view forListAdapter:(IGListAdapter *)listAdapter sectionController:(IGListSectionController *)sectionController object:(id)object indexPath:(NSIndexPath *)indexPath { IGParameterAssert(view != nil); IGParameterAssert(listAdapter != nil); IGParameterAssert(indexPath != nil); if (object == nil || sectionController == nil) { return; } const NSInteger section = indexPath.section; NSCountedSet *visibleSections = self.visibleListSections; [visibleSections removeObject:sectionController]; if ([visibleSections countForObject:sectionController] == 0) { [sectionController.displayDelegate listAdapter:listAdapter didEndDisplayingSectionController:sectionController]; [listAdapter.delegate listAdapter:listAdapter didEndDisplayingObject:object atIndex:section]; } } - (void)willDisplaySupplementaryView:(UICollectionReusableView *)view forListAdapter:(IGListAdapter *)listAdapter sectionController:(IGListSectionController *)sectionController object:(id)object indexPath:(NSIndexPath *)indexPath { [self _willDisplayReusableView:view forListAdapter:listAdapter sectionController:sectionController object:object indexPath:indexPath]; } - (void)didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forListAdapter:(IGListAdapter *)listAdapter sectionController:(IGListSectionController *)sectionController indexPath:(NSIndexPath *)indexPath { // if cell display events break, don't send display events when the object has disappeared id object = [self _pluckObjectForView:view]; [self _didEndDisplayingReusableView:view forListAdapter:listAdapter sectionController:sectionController object:object indexPath:indexPath]; } - (void)willDisplayCell:(UICollectionViewCell *)cell forListAdapter:(IGListAdapter *)listAdapter sectionController:(IGListSectionController *)sectionController object:(id)object indexPath:(NSIndexPath *)indexPath { id displayDelegate = [sectionController displayDelegate]; [displayDelegate listAdapter:listAdapter willDisplaySectionController:sectionController cell:cell atIndex:indexPath.item]; [self _willDisplayReusableView:cell forListAdapter:listAdapter sectionController:sectionController object:object indexPath:indexPath]; } - (void)didEndDisplayingCell:(UICollectionViewCell *)cell forListAdapter:(IGListAdapter *)listAdapter sectionController:(IGListSectionController *)sectionController indexPath:(NSIndexPath *)indexPath { // if cell display events break, don't send cell events to the displayDelegate when the object has disappeared id object = [self _pluckObjectForView:cell]; if (object == nil) { return; } [sectionController.displayDelegate listAdapter:listAdapter didEndDisplayingSectionController:sectionController cell:cell atIndex:indexPath.item]; [self _didEndDisplayingReusableView:cell forListAdapter:listAdapter sectionController:sectionController object:object indexPath:indexPath]; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListReloadIndexPath.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import NS_ASSUME_NONNULL_BEGIN /** An object with index path information for reloading an item during a batch update. */ IGLK_SUBCLASSING_RESTRICTED @interface IGListReloadIndexPath : NSObject /** The index path of the item before batch updates are applied. */ @property (nonatomic, strong, readonly) NSIndexPath *fromIndexPath; /** The index path of the item after batch updates are applied. */ @property (nonatomic, strong, readonly) NSIndexPath *toIndexPath; /** Creates a new reload object. @param fromIndexPath The index path of the item before batch updates. @param toIndexPath The index path of the item after batch updates. @return A new reload object. */ - (instancetype)initWithFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath NS_DESIGNATED_INITIALIZER; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListReloadIndexPath.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListReloadIndexPath.h" @implementation IGListReloadIndexPath - (instancetype)initWithFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { if (self = [super init]) { _fromIndexPath = fromIndexPath; _toIndexPath = toIndexPath; } return self; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListSectionControllerInternal.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListSectionController.h" FOUNDATION_EXTERN void IGListSectionControllerPushThread(UIViewController *viewController, id collectionContext); FOUNDATION_EXTERN void IGListSectionControllerPopThread(void); @interface IGListSectionController() @property (nonatomic, weak, readwrite) id collectionContext; @property (nonatomic, weak, readwrite) UIViewController *viewController; @property (nonatomic, assign, readwrite) NSInteger section; @property (nonatomic, assign, readwrite) BOOL isFirstSection; @property (nonatomic, assign, readwrite) BOOL isLastSection; /* Provides a way for specialized section controllers (like the stacked section controller) to reject invalid moves */ - (BOOL)canMoveItemAtIndex:(NSInteger)sourceItemIndex toIndex:(NSInteger)destinationItemIndex; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListSectionMap+DebugDescription.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import "IGListSectionMap.h" @interface IGListSectionMap (DebugDescription) - (NSArray *)debugDescriptionLines; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListSectionMap+DebugDescription.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListSectionMap+DebugDescription.h" #import "IGListBindingSectionController.h" @implementation IGListSectionMap (DebugDescription) - (NSArray *)debugDescriptionLines { NSMutableArray *debug = [NSMutableArray new]; #if IGLK_DEBUG_DESCRIPTION_ENABLED [self enumerateUsingBlock:^(id object, IGListSectionController *sectionController, NSInteger section, BOOL *stop) { if ([sectionController isKindOfClass:[IGListBindingSectionController class]]) { [debug addObject:[sectionController debugDescription]]; } else { [debug addObject:[NSString stringWithFormat:@"Object and section controller at section: %li:", (long)section]]; [debug addObject:[NSString stringWithFormat:@" %@", object]]; [debug addObject:[NSString stringWithFormat:@" %@", sectionController]]; } }]; #endif // #if IGLK_DEBUG_DESCRIPTION_ENABLED return debug; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListSectionMap.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import @class IGListSectionController; NS_ASSUME_NONNULL_BEGIN /** The IGListSectionMap provides a way to map a collection of objects to a collection of section controllers and achieve constant-time lookups O(1). IGListSectionMap is a mutable object and does not guarantee thread safety. */ IGLK_SUBCLASSING_RESTRICTED @interface IGListSectionMap : NSObject - (instancetype)initWithMapTable:(NSMapTable *)mapTable NS_DESIGNATED_INITIALIZER; /** The objects stored in the map. */ @property (nonatomic, strong, readonly) NSArray *objects; /** Update the map with objects and the section controller counterparts. @param objects The objects in the collection. @param sectionControllers The section controllers that map to each object. */ - (void)updateWithObjects:(NSArray > *)objects sectionControllers:(NSArray *)sectionControllers; /** Fetch a section controller given a section. @param section The section index of the section controller. @return A section controller. */ - (nullable IGListSectionController *)sectionControllerForSection:(NSInteger)section; /** Fetch the object for a section @param section The section index of the object. @return The object corresponding to the section. */ - (nullable id)objectForSection:(NSInteger)section; /** Fetch a section controller given an object. Can return nil. @param object The object that maps to a section controller. @return A section controller. */ - (nullable id)sectionControllerForObject:(id)object; /** Look up the section index for a section controller. @param sectionController The list to look up. @return The section index of the given section controller if it exists, NSNotFound otherwise. */ - (NSInteger)sectionForSectionController:(id)sectionController; /** Look up the section index for an object. @param object The object to look up. @return The section index of the given object if it exists, NSNotFound otherwise. */ - (NSInteger)sectionForObject:(id)object; /** Remove all saved objects and section controllers. */ - (void)reset; /** Update an object with a new instance. */ - (void)updateObject:(id)object; /** Applies a given block object to the entries of the section controller map. @param block A block object to operate on entries in the section controller map. */ - (void)enumerateUsingBlock:(void (^)(id object, IGListSectionController *sectionController, NSInteger section, BOOL *stop))block; /** :nodoc: */ - (instancetype)init NS_UNAVAILABLE; /** :nodoc: */ + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListSectionMap.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListSectionMap.h" #import #import "IGListSectionControllerInternal.h" @interface IGListSectionMap () // both of these maps allow fast lookups of objects, list objects, and indexes @property (nonatomic, strong, readonly, nonnull) NSMapTable *objectToSectionControllerMap; @property (nonatomic, strong, readonly, nonnull) NSMapTable *sectionControllerToSectionMap; @property (nonatomic, strong, nonnull) NSMutableArray *mObjects; @end @implementation IGListSectionMap - (instancetype)initWithMapTable:(NSMapTable *)mapTable { IGParameterAssert(mapTable != nil); if (self = [super init]) { _objectToSectionControllerMap = [mapTable copy]; // lookup list objects by pointer equality _sectionControllerToSectionMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory | NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory capacity:0]; _mObjects = [NSMutableArray new]; } return self; } #pragma mark - Public API - (NSArray *)objects { return [self.mObjects copy]; } - (NSInteger)sectionForSectionController:(IGListSectionController *)sectionController { IGParameterAssert(sectionController != nil); NSNumber *index = [self.sectionControllerToSectionMap objectForKey:sectionController]; return index != nil ? [index integerValue] : NSNotFound; } - (IGListSectionController *)sectionControllerForSection:(NSInteger)section { return [self.objectToSectionControllerMap objectForKey:[self objectForSection:section]]; } - (void)updateWithObjects:(NSArray *)objects sectionControllers:(NSArray *)sectionControllers { IGParameterAssert(objects.count == sectionControllers.count); [self reset]; self.mObjects = [objects mutableCopy]; id firstObject = objects.firstObject; id lastObject = objects.lastObject; [objects enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) { IGListSectionController *sectionController = sectionControllers[idx]; // set the index of the list for easy reverse lookup [self.sectionControllerToSectionMap setObject:@(idx) forKey:sectionController]; [self.objectToSectionControllerMap setObject:sectionController forKey:object]; sectionController.isFirstSection = (object == firstObject); sectionController.isLastSection = (object == lastObject); sectionController.section = (NSInteger)idx; }]; } - (nullable IGListSectionController *)sectionControllerForObject:(id)object { IGParameterAssert(object != nil); return [self.objectToSectionControllerMap objectForKey:object]; } - (nullable id)objectForSection:(NSInteger)section { NSArray *objects = self.mObjects; if (section < objects.count) { return objects[section]; } else { return nil; } } - (NSInteger)sectionForObject:(id)object { IGParameterAssert(object != nil); id sectionController = [self sectionControllerForObject:object]; if (sectionController == nil) { return NSNotFound; } else { return [self sectionForSectionController:sectionController]; } } - (void)reset { [self enumerateUsingBlock:^(id _Nonnull object, IGListSectionController * _Nonnull sectionController, NSInteger section, BOOL * _Nonnull stop) { sectionController.section = NSNotFound; sectionController.isFirstSection = NO; sectionController.isLastSection = NO; }]; [self.sectionControllerToSectionMap removeAllObjects]; [self.objectToSectionControllerMap removeAllObjects]; } - (void)updateObject:(id)object { IGParameterAssert(object != nil); const NSInteger section = [self sectionForObject:object]; id sectionController = [self sectionControllerForObject:object]; [self.sectionControllerToSectionMap setObject:@(section) forKey:sectionController]; [self.objectToSectionControllerMap setObject:sectionController forKey:object]; self.mObjects[section] = object; } - (void)enumerateUsingBlock:(void (^)(id object, IGListSectionController *sectionController, NSInteger section, BOOL *stop))block { IGParameterAssert(block != nil); BOOL stop = NO; NSArray *objects = self.objects; for (NSInteger section = 0; section < objects.count; section++) { id object = objects[section]; IGListSectionController *sectionController = [self sectionControllerForObject:object]; block(object, sectionController, section, &stop); if (stop) { break; } } } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { IGListSectionMap *copy = [[IGListSectionMap allocWithZone:zone] initWithMapTable:self.objectToSectionControllerMap]; if (copy != nil) { copy->_sectionControllerToSectionMap = [self.sectionControllerToSectionMap copy]; copy->_mObjects = [self.mObjects mutableCopy]; } return copy; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListWorkingRangeHandler.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListAdapter; @interface IGListWorkingRangeHandler : NSObject /** Initializes the working range handler. @param workingRangeSize the number of sections beyond the visible viewport that should be considered within the working range. Applies equally in both directions above and below the viewport. */ - (instancetype)initWithWorkingRangeSize:(NSInteger)workingRangeSize; /** Tells the handler that a cell will be displayed in the IGListKit infra. @param indexPath The index path of the cell in the UICollectionView. @param listAdapter The adapter managing the infra. */ - (void)willDisplayItemAtIndexPath:(NSIndexPath *)indexPath forListAdapter:(IGListAdapter *)listAdapter; /** Tells the handler that a cell did end display in the IGListKit infra. @param indexPath The index path of the cell in the UICollectionView. @param listAdapter The adapter managing the infra. */ - (void)didEndDisplayingItemAtIndexPath:(NSIndexPath *)indexPath forListAdapter:(IGListAdapter *)listAdapter; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/IGListWorkingRangeHandler.mm ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "IGListWorkingRangeHandler.h" #import #import #import #import #import struct _IGListWorkingRangeHandlerIndexPath { NSInteger section; NSInteger row; size_t hash; bool operator==(const _IGListWorkingRangeHandlerIndexPath &other) const { return (section == other.section && row == other.row); } }; struct _IGListWorkingRangeHandlerSectionControllerWrapper { IGListSectionController *sectionController; bool operator==(const _IGListWorkingRangeHandlerSectionControllerWrapper &other) const { return (sectionController == other.sectionController); } }; struct _IGListWorkingRangeHandlerIndexPathHash { size_t operator()(const _IGListWorkingRangeHandlerIndexPath& o) const { return (size_t)o.hash; } }; struct _IGListWorkingRangeHashID { size_t operator()(const _IGListWorkingRangeHandlerSectionControllerWrapper &o) const { return (size_t)[o.sectionController hash]; } }; typedef std::unordered_set<_IGListWorkingRangeHandlerSectionControllerWrapper, _IGListWorkingRangeHashID> _IGListWorkingRangeSectionControllerSet; typedef std::unordered_set<_IGListWorkingRangeHandlerIndexPath, _IGListWorkingRangeHandlerIndexPathHash> _IGListWorkingRangeIndexPathSet; @interface IGListWorkingRangeHandler () @property (nonatomic, assign, readonly) NSInteger workingRangeSize; @end @implementation IGListWorkingRangeHandler { _IGListWorkingRangeIndexPathSet _visibleSectionIndices; _IGListWorkingRangeSectionControllerSet _workingRangeSectionControllers; } - (instancetype)initWithWorkingRangeSize:(NSInteger)workingRangeSize { if (self = [super init]) { _workingRangeSize = workingRangeSize; } return self; } - (void)willDisplayItemAtIndexPath:(NSIndexPath *)indexPath forListAdapter:(IGListAdapter *)listAdapter { IGParameterAssert(indexPath != nil); IGParameterAssert(listAdapter != nil); _visibleSectionIndices.insert({ .section = indexPath.section, .row = indexPath.row, .hash = indexPath.hash }); [self _updateWorkingRangesWithListAdapter:listAdapter]; } - (void)didEndDisplayingItemAtIndexPath:(NSIndexPath *)indexPath forListAdapter:(IGListAdapter *)listAdapter { IGParameterAssert(indexPath != nil); IGParameterAssert(listAdapter != nil); _visibleSectionIndices.erase({ .section = indexPath.section, .row = indexPath.row, .hash = indexPath.hash }); [self _updateWorkingRangesWithListAdapter:listAdapter]; } #pragma mark - Working Ranges - (void)_updateWorkingRangesWithListAdapter:(IGListAdapter *)listAdapter { IGAssertMainThread(); // This method is optimized C++ to improve straight-line speed of these operations. Change at your peril. // We use a std::set because it is ordered. std::set visibleSectionSet = std::set(); for (const _IGListWorkingRangeHandlerIndexPath &indexPath : _visibleSectionIndices) { visibleSectionSet.insert(indexPath.section); } NSInteger start; NSInteger end; if (visibleSectionSet.size() == 0) { // We're now devoid of any visible sections. Bail start = 0; end = 0; } else { start = MAX(*visibleSectionSet.begin() - _workingRangeSize, 0); end = MIN(*visibleSectionSet.rbegin() + 1 + _workingRangeSize, (NSInteger)listAdapter.objects.count); } // Build the current set of working range section controllers _IGListWorkingRangeSectionControllerSet workingRangeSectionControllers (visibleSectionSet.size()); for (NSInteger idx = start; idx < end; idx++) { id item = [listAdapter objectAtSection:idx]; IGListSectionController *sectionController = [listAdapter sectionControllerForObject:item]; workingRangeSectionControllers.insert({sectionController}); } IGAssert(workingRangeSectionControllers.size() < 1000, @"This algorithm is way too slow with so many objects:%lu", workingRangeSectionControllers.size()); // Tell any new section controllers that they have entered the working range for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : workingRangeSectionControllers) { // Check if the item exists in the old working range item array. auto it = _workingRangeSectionControllers.find(wrapper); if (it == _workingRangeSectionControllers.end()) { // The section controller isn't in the existing list, so it's new. id workingRangeDelegate = wrapper.sectionController.workingRangeDelegate; [workingRangeDelegate listAdapter:listAdapter sectionControllerWillEnterWorkingRange:wrapper.sectionController]; } } // Tell any removed section controllers that they have exited the working range for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : _workingRangeSectionControllers) { // Check if the item exists in the new list of section controllers auto it = workingRangeSectionControllers.find(wrapper); if (it == workingRangeSectionControllers.end()) { // If the item does not exist in the new list, then it's been removed. id workingRangeDelegate = wrapper.sectionController.workingRangeDelegate; [workingRangeDelegate listAdapter:listAdapter sectionControllerDidExitWorkingRange:wrapper.sectionController]; } } _workingRangeSectionControllers = workingRangeSectionControllers; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/UICollectionView+DebugDescription.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @interface UICollectionView (DebugDescription) - (NSArray *)debugDescriptionLines; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/UICollectionView+DebugDescription.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "UICollectionView+DebugDescription.h" #import @implementation UICollectionView (DebugDescription) - (NSArray *)debugDescriptionLines { NSMutableArray *debug = [NSMutableArray new]; #if IGLK_DEBUG_DESCRIPTION_ENABLED [debug addObject:[NSString stringWithFormat:@"Class: %@, instance: %p", NSStringFromClass(self.class), self]]; [debug addObject:[NSString stringWithFormat:@"Data source: %@", self.dataSource]]; [debug addObject:[NSString stringWithFormat:@"Delegate: %@", self.delegate]]; [debug addObject:[NSString stringWithFormat:@"Layout: %@", self.collectionViewLayout]]; [debug addObject:[NSString stringWithFormat:@"Frame: %@, bounds: %@", NSStringFromCGRect(self.frame), NSStringFromCGRect(self.bounds)]]; const NSInteger sections = [self numberOfSections]; [debug addObject:[NSString stringWithFormat:@"Number of sections: %lld", (long long)sections]]; for (NSInteger section = 0; section < sections; section++) { [debug addObject:[NSString stringWithFormat:@" %lld items in section %lld", (long long)[self numberOfItemsInSection:section], (long long)section]]; } [debug addObject:@"Visible cell details:"]; NSArray *visibleIndexPaths = [[self indexPathsForVisibleItems] sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *path in visibleIndexPaths) { [debug addObject:[NSString stringWithFormat:@" Visible cell at section %lld, item %lld:", (long long)path.section, (long long)path.item]]; [debug addObject:[NSString stringWithFormat:@" %@", [[self cellForItemAtIndexPath:path] description] ?: @""]]; } #endif // #if IGLK_DEBUG_DESCRIPTION_ENABLED return debug; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/UICollectionView+IGListBatchUpdateData.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @class IGListBatchUpdateData; @interface UICollectionView (IGListBatchUpdateData) - (void)ig_applyBatchUpdateData:(IGListBatchUpdateData *)updateData; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/UICollectionView+IGListBatchUpdateData.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "UICollectionView+IGListBatchUpdateData.h" #import @implementation UICollectionView (IGListBatchUpdateData) - (void)ig_applyBatchUpdateData:(IGListBatchUpdateData *)updateData { [self deleteItemsAtIndexPaths:updateData.deleteIndexPaths]; [self insertItemsAtIndexPaths:updateData.insertIndexPaths]; [self reloadItemsAtIndexPaths:updateData.updateIndexPaths]; for (IGListMoveIndexPath *move in updateData.moveIndexPaths) { [self moveItemAtIndexPath:move.from toIndexPath:move.to]; } for (IGListMoveIndex *move in updateData.moveSections) { [self moveSection:move.from toSection:move.to]; } [self deleteSections:updateData.deleteSections]; [self insertSections:updateData.insertSections]; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/UICollectionViewLayout+InteractiveReordering.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import #import NS_ASSUME_NONNULL_BEGIN @interface UICollectionViewLayout (InteractiveReordering) - (void)ig_hijackLayoutInteractiveReorderingMethodForAdapter:(IGListAdapter *)adapter; - (nullable NSIndexPath *)updatedTargetForInteractivelyMovingItem:(NSIndexPath *)previousIndexPath toIndexPath:(NSIndexPath *)originalTarget adapter:(IGListAdapter *)adapter; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/UICollectionViewLayout+InteractiveReordering.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "UICollectionViewLayout+InteractiveReordering.h" #import #import #import @implementation UICollectionViewLayout (InteractiveReordering) static void * kIGListAdapterKey = &kIGListAdapterKey; + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class layoutClass = [self class]; // override implementation for targetIndexPathForInteractivelyMovingItem:withPosition: SEL userMoveSelector = @selector(targetIndexPathForInteractivelyMovingItem:withPosition:); SEL overrideSelector = @selector(ig_targetIndexPathForInteractivelyMovingItem:withPosition:); Method userLayoutMethod = class_getInstanceMethod(layoutClass, userMoveSelector); Method overrideLayoutMethod = class_getInstanceMethod(layoutClass, overrideSelector); method_exchangeImplementations(userLayoutMethod, overrideLayoutMethod); // override implementation for // invalidationContextForInteractivelyMovingItems:withTargetPosition:previousIndexPaths:previousPosition: SEL userInvalidationSelector = @selector(invalidationContextForInteractivelyMovingItems:withTargetPosition:previousIndexPaths:previousPosition:); SEL overrideInvalidationSelector = @selector(ig_invalidationContextForInteractivelyMovingItems:withTargetPosition:previousIndexPaths:previousPosition:); Method userInvalidationMethod = class_getInstanceMethod(layoutClass, userInvalidationSelector); Method overrideInvalidationMethod = class_getInstanceMethod(layoutClass, overrideInvalidationSelector); method_exchangeImplementations(userInvalidationMethod, overrideInvalidationMethod); // override implementation for // invalidationContextForInteractivelyMovingItems:withTargetPosition:previousIndexPaths:previousPosition: SEL userEndInvalidationSelector = @selector(invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:previousIndexPaths:movementCancelled:); SEL overrideEndInvalidationSelector = @selector(ig_invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:previousIndexPaths:movementCancelled:); Method userEndInvalidationMethod = class_getInstanceMethod(layoutClass, userEndInvalidationSelector); Method overrideEndInvalidationMethod = class_getInstanceMethod(layoutClass, overrideEndInvalidationSelector); method_exchangeImplementations(userEndInvalidationMethod, overrideEndInvalidationMethod); }); } - (void)ig_hijackLayoutInteractiveReorderingMethodForAdapter:(IGListAdapter *)adapter { objc_setAssociatedObject(self, kIGListAdapterKey, adapter, OBJC_ASSOCIATION_ASSIGN); } - (NSIndexPath *)ig_targetIndexPathForInteractivelyMovingItem:(NSIndexPath *)previousIndexPath withPosition:(CGPoint)position NS_AVAILABLE_IOS(9_0) { // call looks recursive, but through swizzling is calling the original implementation for // targetIndexPathForInteractivelyMovingItem:withPosition: NSIndexPath *originalTarget = [self ig_targetIndexPathForInteractivelyMovingItem:previousIndexPath withPosition:position]; IGListAdapter *adapter = (IGListAdapter *)objc_getAssociatedObject(self, kIGListAdapterKey); if (adapter == nil) { return originalTarget; } NSIndexPath *updatedTarget = [self updatedTargetForInteractivelyMovingItem:previousIndexPath toIndexPath:originalTarget adapter:adapter]; if (updatedTarget) { return updatedTarget; } return originalTarget; } - (nullable NSIndexPath *)updatedTargetForInteractivelyMovingItem:(NSIndexPath *)previousIndexPath toIndexPath:(NSIndexPath *)originalTarget adapter:(IGListAdapter *)adapter { const NSInteger sourceSectionIndex = previousIndexPath.section; NSInteger destinationSectionIndex = originalTarget.section; NSInteger destinationItemIndex = originalTarget.item; IGListSectionController *sourceSectionController = [adapter sectionControllerForSection:sourceSectionIndex]; IGListSectionController *destinationSectionController = [adapter sectionControllerForSection:destinationSectionIndex]; adapter.isLastInteractiveMoveToLastSectionIndex = NO; // this is a reordering of sections themselves if ([sourceSectionController numberOfItems] == 1 && [destinationSectionController numberOfItems] == 1) { if (destinationItemIndex == 1) { // the "item" representing our section was dropped // into the end of a destination section rather than the beginning // so it really belongs one section after the section where it landed if (destinationSectionIndex < [[adapter objects] count] - 1) { destinationSectionIndex += 1; destinationItemIndex = 0; } else { // if we're moving an item to the last spot, our index would exceed the number of sections available // so we have to special case this scenario. iOS doesnt allow an item move to "create" a new section adapter.isLastInteractiveMoveToLastSectionIndex = YES; } NSIndexPath *updatedTarget = [NSIndexPath indexPathForItem:destinationItemIndex inSection:destinationSectionIndex]; return updatedTarget; } } return nil; } - (UICollectionViewLayoutInvalidationContext *)ig_invalidationContextForInteractivelyMovingItems:(NSArray *)targetIndexPaths withTargetPosition:(CGPoint)targetPosition previousIndexPaths:(NSArray *)previousIndexPaths previousPosition:(CGPoint)previousPosition { // call looks recursive, but through swizzling is calling the original implementation for // invalidationContextForInteractivelyMovingItems:withTargetPosition:previousIndexPaths:previousPosition: UICollectionViewLayoutInvalidationContext *originalContext = [self ig_invalidationContextForInteractivelyMovingItems:targetIndexPaths withTargetPosition:targetPosition previousIndexPaths:previousIndexPaths previousPosition:previousPosition]; return [self ig_cleanupInvalidationContext:originalContext]; } - (UICollectionViewLayoutInvalidationContext *)ig_invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:(NSArray *)indexPaths previousIndexPaths:(NSArray *)previousIndexPaths movementCancelled:(BOOL)movementCancelled { // call looks recursive, but through swizzling is calling the original implementation for // invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:previousIndexPaths:movementCancelled: UICollectionViewLayoutInvalidationContext *originalContext = [self ig_invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:indexPaths previousIndexPaths:previousIndexPaths movementCancelled:movementCancelled]; return [self ig_cleanupInvalidationContext:originalContext]; } - (UICollectionViewLayoutInvalidationContext *)ig_cleanupInvalidationContext:(UICollectionViewLayoutInvalidationContext *)originalContext { IGListAdapter *adapter = (IGListAdapter *)objc_getAssociatedObject(self, kIGListAdapterKey); if (adapter == nil || !self.collectionView) { return originalContext; } const NSInteger numSections = [adapter numberOfSectionsInCollectionView:(UICollectionView * _Nonnull)self.collectionView]; // protect against invalidating an index path that no longer exists // (like item 1 in the last section after interactively reordering an item to the end of a list of 1 item sections) if ([originalContext.invalidatedItemIndexPaths count] > 0) { NSUInteger indexToRemove = NSNotFound; indexToRemove = [originalContext.invalidatedItemIndexPaths indexOfObjectPassingTest: ^BOOL(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (obj.section == numSections-1) { IGListSectionController *section = [adapter sectionControllerForSection:obj.section]; return obj.item > [section numberOfItems] - 1; } return NO; }]; if (indexToRemove != NSNotFound) { NSMutableArray *invalidatedItemIndexPaths = [originalContext.invalidatedItemIndexPaths mutableCopy]; [invalidatedItemIndexPaths removeObjectAtIndex:indexToRemove]; UICollectionViewLayoutInvalidationContext *modifiedContext; if ([originalContext isKindOfClass:[UICollectionViewFlowLayoutInvalidationContext class]]) { // UICollectionViewFlowLayout has a special invalidation context subclass UICollectionViewFlowLayoutInvalidationContext *flowModifiedContext = [[self.class invalidationContextClass] new]; flowModifiedContext.invalidateFlowLayoutDelegateMetrics = [(UICollectionViewFlowLayoutInvalidationContext *)originalContext invalidateFlowLayoutDelegateMetrics]; flowModifiedContext.invalidateFlowLayoutAttributes = [(UICollectionViewFlowLayoutInvalidationContext *)originalContext invalidateFlowLayoutAttributes]; modifiedContext = flowModifiedContext; } else { modifiedContext = [[self.class invalidationContextClass] new]; } [modifiedContext invalidateItemsAtIndexPaths:invalidatedItemIndexPaths]; [originalContext.invalidatedSupplementaryIndexPaths enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSArray * _Nonnull obj, BOOL * _Nonnull stop) { [modifiedContext invalidateSupplementaryElementsOfKind:key atIndexPaths:obj]; }]; [originalContext.invalidatedDecorationIndexPaths enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSArray * _Nonnull obj, BOOL * _Nonnull stop) { [modifiedContext invalidateDecorationElementsOfKind:key atIndexPaths:obj]; }]; modifiedContext.contentOffsetAdjustment = originalContext.contentOffsetAdjustment; modifiedContext.contentSizeAdjustment = originalContext.contentSizeAdjustment; return modifiedContext; } } return originalContext; } @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/UIScrollView+IGListKit.h ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import @interface UIScrollView (IGListKit) - (UIEdgeInsets) ig_contentInset; @end ================================================ FILE: JetChat/Pods/IGListKit/Source/IGListKit/Internal/UIScrollView+IGListKit.m ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "UIScrollView+IGListKit.h" @implementation UIScrollView (IGListKit) - (UIEdgeInsets) ig_contentInset { if (@available(iOS 11.0, tvOS 11.0, *)) { return self.adjustedContentInset; } else { return self.contentInset; } } @end ================================================ FILE: JetChat/Pods/IGListKit.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 00D8EECF24D59CAE6154030BA3DEE195 /* IGListDisplayDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = AE34A0AC11A6284D61E5EAF57F9E57C3 /* IGListDisplayDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 01E08BBED18704A4E678834927B164E5 /* IGListAdapterUpdater+DebugDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 3823BF42DF59AC3C268857B0546BBCA7 /* IGListAdapterUpdater+DebugDescription.h */; settings = {ATTRIBUTES = (Private, ); }; }; 05E830839CDD81941193D642966F6864 /* IGListBindingSectionController+DebugDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = E0A3CEF4643766F24AA02213CB3E6C35 /* IGListBindingSectionController+DebugDescription.m */; }; 0F46EF0FB383BD634788C36C2D4F1ED6 /* IGListSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE90137D1998AFD99452896AF7752F74 /* IGListSectionController.m */; }; 10CE3BED45EF90FB47CA51EB6EAC1D79 /* IGListArrayUtilsInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = E218631DD6734D657BFF89EBFEB60448 /* IGListArrayUtilsInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 15A0C8B73D68C30CBEFC9CEF16F2B992 /* IGListKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = C8EF76A697528EEEE488BFA0F17F976E /* IGListKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19284AE6E0FDB4AF8BDCA70EF9E0643E /* IGListWorkingRangeHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 63B4F603D8F363926716C5F2B5CCB36D /* IGListWorkingRangeHandler.mm */; }; 20678F0AA5EF8C4561671341AB213C68 /* IGListAdapterProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 35DFC4D5B375DE3C895B43210C2BE673 /* IGListAdapterProxy.h */; settings = {ATTRIBUTES = (Private, ); }; }; 20A54B8CA1D62C9EF31AE7FCFE7182EE /* IGListBatchUpdates.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B6BC88735C17BB24434BF8B8E5632A5 /* IGListBatchUpdates.m */; }; 23267ACF6633F832FE1FC2A82A245D9D /* IGListWorkingRangeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B7F61CD007007796F94020CB59EB5D7 /* IGListWorkingRangeHandler.h */; settings = {ATTRIBUTES = (Private, ); }; }; 25E6E52F611840941C6872C7317D9A65 /* IGListDisplayHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A7A797C2B5DBE959ED812736F04428 /* IGListDisplayHandler.m */; }; 287F7998F968B6FD12D39728F40AAA69 /* IGListKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = E91AC57F25B02C5F13C48C6C219A19DD /* IGListKit-dummy.m */; }; 291AD3AD4B64C989D6FDD75EC62F4200 /* IGListBatchContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F93019F1AEF22C00D37A24D075C2547 /* IGListBatchContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2C7D33CB0026884D5530951A4B4984B9 /* IGListAdapterPerformanceDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = FFE667515CBADE23A2B69C59523FE355 /* IGListAdapterPerformanceDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2CBC483648CD891B4A3D6B109EFABA9D /* IGListSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = E747EFC2F632F73BA77481E4DC2D7319 /* IGListSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2F96DECCD2172DBD1BD18453A7E4AB1C /* IGListDisplayHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 272436B4C13EB52FBF53B123511BFCB1 /* IGListDisplayHandler.h */; settings = {ATTRIBUTES = (Private, ); }; }; 2FCE5208B5FE11B36A1D20CC76477FB6 /* IGListCollectionViewLayoutCompatible.h in Headers */ = {isa = PBXBuildFile; fileRef = 23078DECFA8BB459564DE91C02447890 /* IGListCollectionViewLayoutCompatible.h */; settings = {ATTRIBUTES = (Public, ); }; }; 31FBA3D8B80DF597DD2117DC1BCFFF94 /* IGListReloadIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 65A86458078CCBE07FFE2F5BC3C47896 /* IGListReloadIndexPath.m */; }; 339CC7529A1CB7845AF899CBEA59790F /* IGListGenericSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = 80CA79323A56DED063EAE61EFA39E9F3 /* IGListGenericSectionController.m */; }; 35C03326F9E7CF9BA971E04A887602EB /* IGListDebuggingUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 90AC138465BD599D3B2BCE89ACE1752F /* IGListDebuggingUtilities.m */; }; 3A768ABD7CC269A78CF22785F210F7FF /* UICollectionView+DebugDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 47832CBECE03B2C33D3359F6972931AA /* UICollectionView+DebugDescription.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3D906418205B53287328F5D82152FA80 /* IGListBindingSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = 52714997587F48C24307AB7612F9CACA /* IGListBindingSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 408AB729D6ABE8A01FDD99D349B2E4A4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA25A968128B1BFB14311354D251F7D1 /* Foundation.framework */; }; 412DB79BB7685882ECE4CD783E71CD37 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59AB62FE4BF490C9328C66C5CEE03395 /* UIKit.framework */; }; 4B3027D1216593F18734639D31C08604 /* IGListBatchUpdateData+DebugDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = DEE1BAAF6A9AC27405491E3347C14D12 /* IGListBatchUpdateData+DebugDescription.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5071DCE1BB0BDBB81A41A7209B244894 /* IGListCollectionViewLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = CC6A0EDB95C12A26BFC07918A04339C2 /* IGListCollectionViewLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; 524050F4143C30DCB448E3BB23F2751A /* IGListDebugger.m in Sources */ = {isa = PBXBuildFile; fileRef = 81846F252C9F2B471F26A812A1BC9EF7 /* IGListDebugger.m */; }; 54C9547BF6E7ABED2DDF80223B397E46 /* IGListAdapterMoveDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E7B4A287CD4489BA03728A7DC45CB480 /* IGListAdapterMoveDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5894D82734BCC132943F8D499303DE0A /* IGListBindingSectionControllerDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AD9621C062F9C79A1DB0C4F0BA938B0 /* IGListBindingSectionControllerDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 590EB8DCC56FE067F14A7B45059C075C /* IGListCollectionViewLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = C628A8F96DB565469E515C8CCFCCB2E4 /* IGListCollectionViewLayout.mm */; }; 59CE90BD34A5275CDC7455692A8A3AF7 /* IGListSectionMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 3CC8A7F4FC5AF76CFFC2870E5385584A /* IGListSectionMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5C81F6CF7FFF93C6FB11D58A8FDF3DB1 /* UICollectionViewLayout+InteractiveReordering.m in Sources */ = {isa = PBXBuildFile; fileRef = D5D41058691CD6A374A28AF4A5BCD6FD /* UICollectionViewLayout+InteractiveReordering.m */; }; 5CC045F50E7344D52F71E5187FB32A5D /* IGListBindingSectionControllerSelectionDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 380B9E1EC70CC3CA70EE0CE16B01AEE7 /* IGListBindingSectionControllerSelectionDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5F010BCB98B62E7DE25D4D01374DB423 /* IGListAdapterUpdaterInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = B930E3CD5DCECBC66891E0558197AE59 /* IGListAdapterUpdaterInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 66FC7655431ABAD92264A53171185724 /* IGListScrollDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 193B3D5854467EF50B686347B1288E60 /* IGListScrollDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68056A58BB6CF71C0A261C82AB3D669F /* IGListAdapterDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 1426A8FE1BC8DA4B32B7311E1A6D4FF6 /* IGListAdapterDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6B1234AB83FF379FDC1653BA17121B73 /* IGListKit.h in Headers */ = {isa = PBXBuildFile; fileRef = D0F6937220581929145C8B1B88CB4E51 /* IGListKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6BBA17D852117737A603C569AF074B5A /* IGListMoveIndexPathInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 71F287B0FF878E91B396F91CED8FB839 /* IGListMoveIndexPathInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 6D1F1FE148B459962FFB42635226E94A /* IGListIndexSetResultInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 28A54EB092063A06A598C0122BFF9D57 /* IGListIndexSetResultInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 6DE7CB794E88B822088773BABFBF83FF /* IGListAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 94F1D3844203726DA391D556D63191D5 /* IGListAdapter.m */; }; 6F90C02A0D0907BFFA5AE716BA58C90C /* IGListCollectionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D1A7A4E91924FB53983A7A3F0CD180B /* IGListCollectionContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; 700AC00EFE11C06134A2DB32F0A5D6ED /* IGListAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = 16908C4157F8C939FEE87B682394BC24 /* IGListAdapter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 72C668484EB6F1E848B2787D395AB2A5 /* IGListAdapterDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 063A5988DABE4EFB6204EA3889A71ADB /* IGListAdapterDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 748999B5A01BDCDCB77E3BD60E49032C /* IGListAdapter+DebugDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DD4DED4A41189E3AE046E7B533D8C38 /* IGListAdapter+DebugDescription.h */; settings = {ATTRIBUTES = (Private, ); }; }; 796B55F2170B8924B08FB1DF05BA09C6 /* IGListAdapterUpdaterDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = FC8F99BB50EF1E40D2CA9EB4B6D34154 /* IGListAdapterUpdaterDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 837EF61F55713CA43E12FB6B8466E968 /* IGListReloadIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = CB4C7DD12F6A1855F89DE78D766AAA47 /* IGListReloadIndexPath.h */; settings = {ATTRIBUTES = (Private, ); }; }; 83D3CCECB9B772B5FC138B355AFE296D /* IGListIndexPathResultInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1350853F608218FBFE4BE1D84B950959 /* IGListIndexPathResultInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 85A10A32F13940714AE26AC7C8C4CDA2 /* UICollectionView+DebugDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = FA74A7979C82186A27B1A519E4AD79CB /* UICollectionView+DebugDescription.m */; }; 8964079800CA53BC7D0BE36413B1DA74 /* IGListBindingSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = BC08D98EDCA81E34988B1E3E10B10348 /* IGListBindingSectionController.m */; }; 89FE92DBAF2183E4E65D5F78144331AA /* IGListCollectionScrollingTraits.h in Headers */ = {isa = PBXBuildFile; fileRef = 698190404480FED32681B4B5A3E55073 /* IGListCollectionScrollingTraits.h */; settings = {ATTRIBUTES = (Public, ); }; }; 968CFE27158615D5E5CE548459C40B5D /* IGListReloadDataUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = C2870D9D95104F5E65B2E13A46B22841 /* IGListReloadDataUpdater.m */; }; 9D674BE6F590F50914DEED68A3B7356D /* IGListAdapterUpdateListener.h in Headers */ = {isa = PBXBuildFile; fileRef = F6BF1C47ECB0912C3C500A94B5C5D140 /* IGListAdapterUpdateListener.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DA4EDCC87D1BD54E00F1849385B85EF /* IGListBatchUpdates.h in Headers */ = {isa = PBXBuildFile; fileRef = F72279261964733411540354FFE149F6 /* IGListBatchUpdates.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9EDCC6FAA805C232557FB79DFB57DF7C /* IGListSectionMap+DebugDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 7FCADB39D5D4A99CF546F760FFFEEF61 /* IGListSectionMap+DebugDescription.h */; settings = {ATTRIBUTES = (Private, ); }; }; A02F17211658A92A69072350AFF7DD5F /* IGListDebuggingUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AE5852C99446C693B3D65FE32DE4054 /* IGListDebuggingUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; }; A3DCFBA9C5CD86C0704EFF03583B8900 /* IGListAdapter+DebugDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 56086A29B27DE75D5A5EC7391825121C /* IGListAdapter+DebugDescription.m */; }; A4CEF1AF773B6C67B57BD262391E0967 /* IGListSupplementaryViewSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 5250DF799124CFF01B7C8670AD305E52 /* IGListSupplementaryViewSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; A61C1E6A4819E8FCF03053272B28E932 /* IGListTransitionDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = ECBE67B083E813F8DC6E24C82BB83337 /* IGListTransitionDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; A9D5B3D28EA5A5240821D109F5330919 /* IGListSectionControllerInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 17708A1E8A80310069268E74C94EADBF /* IGListSectionControllerInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; ACBA45C9A25507999B8F6FD03B270640 /* IGListBindingSectionController+DebugDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = BC0AAB5D5FB600A592F32F757A8A42BA /* IGListBindingSectionController+DebugDescription.h */; settings = {ATTRIBUTES = (Private, ); }; }; B17CAEC48975381B521BB00AC3FA7688 /* IGListReloadDataUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A187F8E56255E0F0098361E1DF22A48 /* IGListReloadDataUpdater.h */; settings = {ATTRIBUTES = (Public, ); }; }; B6B10A8366E9F8CA95134F7FB5249B4B /* UICollectionView+IGListBatchUpdateData.h in Headers */ = {isa = PBXBuildFile; fileRef = E9045CCCDD512318ACEFEDDEA7FC5273 /* UICollectionView+IGListBatchUpdateData.h */; settings = {ATTRIBUTES = (Private, ); }; }; B6FB268940FA4EB7426D9133A435A929 /* IGListGenericSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = CEE80663C4D17068D06E64D3C13957A7 /* IGListGenericSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; B73068D4B3550EA0D7CA9A0960345C2B /* IGListBatchUpdateState.h in Headers */ = {isa = PBXBuildFile; fileRef = 81FAEBD87499AF65CC01B5043590F3A8 /* IGListBatchUpdateState.h */; settings = {ATTRIBUTES = (Private, ); }; }; B994A61DF8AA246F263DD558728ADD71 /* IGListMoveIndexInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 62472F10BDA8D0CF1909B47DCEDB6041 /* IGListMoveIndexInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; BCF304EE58DBDBE7EB3860FE310B2C17 /* IGListAdapterUpdater+DebugDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = BC21B2C17D19027AC026ABB728721743 /* IGListAdapterUpdater+DebugDescription.m */; }; C369067B04D5A03F396B35C1B070B603 /* IGListAdapter+UICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = ABE0E4BD8B3D9A057E119D6024A9B7F9 /* IGListAdapter+UICollectionView.m */; }; C6D659ED98FD91D6250BADADEC070190 /* IGListAdapterProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 97733A8BA453D5D4E25FB83EBA2B3961 /* IGListAdapterProxy.m */; }; C8A13466974B3313D9B432CD82A83D0C /* UIScrollView+IGListKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 97C642F80CE617AEFB32CCA1186D59B6 /* UIScrollView+IGListKit.h */; settings = {ATTRIBUTES = (Private, ); }; }; CAB21CB8CF3C59BAFC9A4FD68A810E39 /* UIScrollView+IGListKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D34987D98EC88A8DB5E128B8132443D /* UIScrollView+IGListKit.m */; }; CB455F74F02B72C8692C1B389DAFD7DE /* IGListBindable.h in Headers */ = {isa = PBXBuildFile; fileRef = A62EEB3D608E944C7C3E292536A0B1D7 /* IGListBindable.h */; settings = {ATTRIBUTES = (Public, ); }; }; CD27C095B2D32203F0968C687FAA923C /* IGListAdapterUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = AD022F2E6AE27ACB3AF6EFDD734C5D68 /* IGListAdapterUpdater.h */; settings = {ATTRIBUTES = (Public, ); }; }; CEAB8C388D8DA8702987EE293B5D1340 /* IGListAdapter+UICollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = 688296734A654AFD9960993818B27084 /* IGListAdapter+UICollectionView.h */; settings = {ATTRIBUTES = (Private, ); }; }; CFA2DB1D9E4A741C47E055809F672853 /* IGListBatchUpdateData+DebugDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = EBADECFA857960F2329E14AC278DC7F9 /* IGListBatchUpdateData+DebugDescription.m */; }; D111876D68620B70CD8DADBC808E397A /* UICollectionViewLayout+InteractiveReordering.h in Headers */ = {isa = PBXBuildFile; fileRef = 4494EE211F18FF3CBB86A10485F6B66B /* UICollectionViewLayout+InteractiveReordering.h */; settings = {ATTRIBUTES = (Private, ); }; }; D150E49F4D65F964BF5F92CAC59855FF /* IGListCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B6A39C929A3E81DFF85DB578EC6D015 /* IGListCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2ADEA8C2074604A21B513E30EA8ACA3 /* IGListSingleSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = 438474C77ECC1664F1BEE4EFD0BAD336 /* IGListSingleSectionController.m */; }; D41DE3B1749E169822B166F1E26329A1 /* UICollectionView+IGListBatchUpdateData.m in Sources */ = {isa = PBXBuildFile; fileRef = 53F7FD263036347D36767921EE34E92D /* UICollectionView+IGListBatchUpdateData.m */; }; D5D5819079EBA2A4AF83B670C09A5E24 /* IGListWorkingRangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 59CFD541C9E1C7C9846907FD5B135F9A /* IGListWorkingRangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; D940E0F5D90FF644A16219A2BACDC8DF /* IGListSingleSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = DCBBC230FB9736A95CEBB36DCBBC55ED /* IGListSingleSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D9ECF466EF530ACCB864BEB10ED2C656 /* IGListDebugger.h in Headers */ = {isa = PBXBuildFile; fileRef = FFCF97DA06E2CA95E0D391278754AFD7 /* IGListDebugger.h */; settings = {ATTRIBUTES = (Private, ); }; }; DA31EB23253AD08E7EF77B30238828FA /* IGListCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 392727C2C656BBDBAA108A13947A9A63 /* IGListCollectionView.m */; }; DDC86B48AAE2538DAB200D9FF8073E1E /* IGListAdapterUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C6C88E72428CCA3F9D53A7C6A5E4255 /* IGListAdapterUpdater.m */; }; DEB4FB4540A43D6B3CC2F22928887B3C /* IGListCollectionViewDelegateLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D38245CA903B41DD85BF637375D2A97 /* IGListCollectionViewDelegateLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; DFC34C6C22A62E979CE48965DC7EC019 /* IGListAdapterInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 03BF964D9ADFF1437056716D779AAB09 /* IGListAdapterInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; E7185EC5ECC56BE56BB4890D7392A8C9 /* IGListSectionMap.m in Sources */ = {isa = PBXBuildFile; fileRef = D9728425465E444E5B6B43474BDE4A4E /* IGListSectionMap.m */; }; F26498A7A947D54EB8A8800A6CFB0786 /* IGListCollectionViewLayoutInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 19E44DA2D0BF454144CFBB68316E4166 /* IGListCollectionViewLayoutInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; F94A8C7A4C3360D6C237C0EAAD133CDA /* IGListUpdatingDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E72DD50A0DEFCD99CC89213D0E98C41 /* IGListUpdatingDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; FB2C0646EF20359B5DD30DE172CF8BF2 /* IGListSectionMap+DebugDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 832301249C38E290714D95EEF53FB3A8 /* IGListSectionMap+DebugDescription.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 62FF8776F5035EB9D871C3BBF79FB2E5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 68BB7FFF06B526B638EAF3987F64CB1C /* IGListDiffKit.xcodeproj */; proxyType = 1; remoteGlobalIDString = 9074A5DFB260E6240F743D74D3F432DD; remoteInfo = IGListDiffKit; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 017682A1559F97D4B1A82EFCD9B40A09 /* IGListKit */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = IGListKit; path = IGListKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03BF964D9ADFF1437056716D779AAB09 /* IGListAdapterInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapterInternal.h; path = Source/IGListKit/Internal/IGListAdapterInternal.h; sourceTree = ""; }; 063A5988DABE4EFB6204EA3889A71ADB /* IGListAdapterDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapterDelegate.h; path = Source/IGListKit/IGListAdapterDelegate.h; sourceTree = ""; }; 099850C93EBB6DD568CC066921A40594 /* IGListKit-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "IGListKit-Info.plist"; sourceTree = ""; }; 0E72DD50A0DEFCD99CC89213D0E98C41 /* IGListUpdatingDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListUpdatingDelegate.h; path = Source/IGListKit/IGListUpdatingDelegate.h; sourceTree = ""; }; 1350853F608218FBFE4BE1D84B950959 /* IGListIndexPathResultInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListIndexPathResultInternal.h; path = Source/IGListDiffKit/Internal/IGListIndexPathResultInternal.h; sourceTree = ""; }; 1426A8FE1BC8DA4B32B7311E1A6D4FF6 /* IGListAdapterDataSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapterDataSource.h; path = Source/IGListKit/IGListAdapterDataSource.h; sourceTree = ""; }; 16908C4157F8C939FEE87B682394BC24 /* IGListAdapter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapter.h; path = Source/IGListKit/IGListAdapter.h; sourceTree = ""; }; 17708A1E8A80310069268E74C94EADBF /* IGListSectionControllerInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListSectionControllerInternal.h; path = Source/IGListKit/Internal/IGListSectionControllerInternal.h; sourceTree = ""; }; 193B3D5854467EF50B686347B1288E60 /* IGListScrollDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListScrollDelegate.h; path = Source/IGListKit/IGListScrollDelegate.h; sourceTree = ""; }; 19E44DA2D0BF454144CFBB68316E4166 /* IGListCollectionViewLayoutInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListCollectionViewLayoutInternal.h; path = Source/IGListKit/Internal/IGListCollectionViewLayoutInternal.h; sourceTree = ""; }; 1AD9621C062F9C79A1DB0C4F0BA938B0 /* IGListBindingSectionControllerDataSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListBindingSectionControllerDataSource.h; path = Source/IGListKit/IGListBindingSectionControllerDataSource.h; sourceTree = ""; }; 23078DECFA8BB459564DE91C02447890 /* IGListCollectionViewLayoutCompatible.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListCollectionViewLayoutCompatible.h; path = Source/IGListKit/IGListCollectionViewLayoutCompatible.h; sourceTree = ""; }; 272436B4C13EB52FBF53B123511BFCB1 /* IGListDisplayHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListDisplayHandler.h; path = Source/IGListKit/Internal/IGListDisplayHandler.h; sourceTree = ""; }; 28A54EB092063A06A598C0122BFF9D57 /* IGListIndexSetResultInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListIndexSetResultInternal.h; path = Source/IGListDiffKit/Internal/IGListIndexSetResultInternal.h; sourceTree = ""; }; 35DFC4D5B375DE3C895B43210C2BE673 /* IGListAdapterProxy.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapterProxy.h; path = Source/IGListKit/Internal/IGListAdapterProxy.h; sourceTree = ""; }; 380B9E1EC70CC3CA70EE0CE16B01AEE7 /* IGListBindingSectionControllerSelectionDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListBindingSectionControllerSelectionDelegate.h; path = Source/IGListKit/IGListBindingSectionControllerSelectionDelegate.h; sourceTree = ""; }; 3823BF42DF59AC3C268857B0546BBCA7 /* IGListAdapterUpdater+DebugDescription.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "IGListAdapterUpdater+DebugDescription.h"; path = "Source/IGListKit/Internal/IGListAdapterUpdater+DebugDescription.h"; sourceTree = ""; }; 392727C2C656BBDBAA108A13947A9A63 /* IGListCollectionView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListCollectionView.m; path = Source/IGListKit/IGListCollectionView.m; sourceTree = ""; }; 3CC8A7F4FC5AF76CFFC2870E5385584A /* IGListSectionMap.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListSectionMap.h; path = Source/IGListKit/Internal/IGListSectionMap.h; sourceTree = ""; }; 3F93019F1AEF22C00D37A24D075C2547 /* IGListBatchContext.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListBatchContext.h; path = Source/IGListKit/IGListBatchContext.h; sourceTree = ""; }; 438474C77ECC1664F1BEE4EFD0BAD336 /* IGListSingleSectionController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListSingleSectionController.m; path = Source/IGListKit/IGListSingleSectionController.m; sourceTree = ""; }; 4494EE211F18FF3CBB86A10485F6B66B /* UICollectionViewLayout+InteractiveReordering.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UICollectionViewLayout+InteractiveReordering.h"; path = "Source/IGListKit/Internal/UICollectionViewLayout+InteractiveReordering.h"; sourceTree = ""; }; 47832CBECE03B2C33D3359F6972931AA /* UICollectionView+DebugDescription.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UICollectionView+DebugDescription.h"; path = "Source/IGListKit/Internal/UICollectionView+DebugDescription.h"; sourceTree = ""; }; 4B6BC88735C17BB24434BF8B8E5632A5 /* IGListBatchUpdates.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListBatchUpdates.m; path = Source/IGListKit/Internal/IGListBatchUpdates.m; sourceTree = ""; }; 4DD4DED4A41189E3AE046E7B533D8C38 /* IGListAdapter+DebugDescription.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "IGListAdapter+DebugDescription.h"; path = "Source/IGListKit/Internal/IGListAdapter+DebugDescription.h"; sourceTree = ""; }; 51A7A797C2B5DBE959ED812736F04428 /* IGListDisplayHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListDisplayHandler.m; path = Source/IGListKit/Internal/IGListDisplayHandler.m; sourceTree = ""; }; 5250DF799124CFF01B7C8670AD305E52 /* IGListSupplementaryViewSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListSupplementaryViewSource.h; path = Source/IGListKit/IGListSupplementaryViewSource.h; sourceTree = ""; }; 52714997587F48C24307AB7612F9CACA /* IGListBindingSectionController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListBindingSectionController.h; path = Source/IGListKit/IGListBindingSectionController.h; sourceTree = ""; }; 53F7FD263036347D36767921EE34E92D /* UICollectionView+IGListBatchUpdateData.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UICollectionView+IGListBatchUpdateData.m"; path = "Source/IGListKit/Internal/UICollectionView+IGListBatchUpdateData.m"; sourceTree = ""; }; 56086A29B27DE75D5A5EC7391825121C /* IGListAdapter+DebugDescription.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "IGListAdapter+DebugDescription.m"; path = "Source/IGListKit/Internal/IGListAdapter+DebugDescription.m"; sourceTree = ""; }; 562F1B0AFBC215CCD48168B2F1BA5114 /* IGListKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IGListKit.debug.xcconfig; sourceTree = ""; }; 59AB62FE4BF490C9328C66C5CEE03395 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 59CFD541C9E1C7C9846907FD5B135F9A /* IGListWorkingRangeDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListWorkingRangeDelegate.h; path = Source/IGListKit/IGListWorkingRangeDelegate.h; sourceTree = ""; }; 5AE5852C99446C693B3D65FE32DE4054 /* IGListDebuggingUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListDebuggingUtilities.h; path = Source/IGListKit/Internal/IGListDebuggingUtilities.h; sourceTree = ""; }; 5D1A7A4E91924FB53983A7A3F0CD180B /* IGListCollectionContext.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListCollectionContext.h; path = Source/IGListKit/IGListCollectionContext.h; sourceTree = ""; }; 5D34987D98EC88A8DB5E128B8132443D /* UIScrollView+IGListKit.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIScrollView+IGListKit.m"; path = "Source/IGListKit/Internal/UIScrollView+IGListKit.m"; sourceTree = ""; }; 5D38245CA903B41DD85BF637375D2A97 /* IGListCollectionViewDelegateLayout.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListCollectionViewDelegateLayout.h; path = Source/IGListKit/IGListCollectionViewDelegateLayout.h; sourceTree = ""; }; 62472F10BDA8D0CF1909B47DCEDB6041 /* IGListMoveIndexInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListMoveIndexInternal.h; path = Source/IGListDiffKit/Internal/IGListMoveIndexInternal.h; sourceTree = ""; }; 63B4F603D8F363926716C5F2B5CCB36D /* IGListWorkingRangeHandler.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = IGListWorkingRangeHandler.mm; path = Source/IGListKit/Internal/IGListWorkingRangeHandler.mm; sourceTree = ""; }; 65A86458078CCBE07FFE2F5BC3C47896 /* IGListReloadIndexPath.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListReloadIndexPath.m; path = Source/IGListKit/Internal/IGListReloadIndexPath.m; sourceTree = ""; }; 688296734A654AFD9960993818B27084 /* IGListAdapter+UICollectionView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "IGListAdapter+UICollectionView.h"; path = "Source/IGListKit/Internal/IGListAdapter+UICollectionView.h"; sourceTree = ""; }; 68BB7FFF06B526B638EAF3987F64CB1C /* IGListDiffKit */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = IGListDiffKit; path = IGListDiffKit.xcodeproj; sourceTree = ""; }; 698190404480FED32681B4B5A3E55073 /* IGListCollectionScrollingTraits.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListCollectionScrollingTraits.h; path = Source/IGListKit/IGListCollectionScrollingTraits.h; sourceTree = ""; }; 6A187F8E56255E0F0098361E1DF22A48 /* IGListReloadDataUpdater.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListReloadDataUpdater.h; path = Source/IGListKit/IGListReloadDataUpdater.h; sourceTree = ""; }; 71F287B0FF878E91B396F91CED8FB839 /* IGListMoveIndexPathInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListMoveIndexPathInternal.h; path = Source/IGListDiffKit/Internal/IGListMoveIndexPathInternal.h; sourceTree = ""; }; 7FCADB39D5D4A99CF546F760FFFEEF61 /* IGListSectionMap+DebugDescription.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "IGListSectionMap+DebugDescription.h"; path = "Source/IGListKit/Internal/IGListSectionMap+DebugDescription.h"; sourceTree = ""; }; 80CA79323A56DED063EAE61EFA39E9F3 /* IGListGenericSectionController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListGenericSectionController.m; path = Source/IGListKit/IGListGenericSectionController.m; sourceTree = ""; }; 81846F252C9F2B471F26A812A1BC9EF7 /* IGListDebugger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListDebugger.m; path = Source/IGListKit/Internal/IGListDebugger.m; sourceTree = ""; }; 81FAEBD87499AF65CC01B5043590F3A8 /* IGListBatchUpdateState.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListBatchUpdateState.h; path = Source/IGListKit/Internal/IGListBatchUpdateState.h; sourceTree = ""; }; 82B3FEA19DB6F2D790DB5E72DE9BEAC0 /* IGListKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IGListKit.release.xcconfig; sourceTree = ""; }; 832301249C38E290714D95EEF53FB3A8 /* IGListSectionMap+DebugDescription.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "IGListSectionMap+DebugDescription.m"; path = "Source/IGListKit/Internal/IGListSectionMap+DebugDescription.m"; sourceTree = ""; }; 8C6C88E72428CCA3F9D53A7C6A5E4255 /* IGListAdapterUpdater.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListAdapterUpdater.m; path = Source/IGListKit/IGListAdapterUpdater.m; sourceTree = ""; }; 90AC138465BD599D3B2BCE89ACE1752F /* IGListDebuggingUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListDebuggingUtilities.m; path = Source/IGListKit/Internal/IGListDebuggingUtilities.m; sourceTree = ""; }; 94F1D3844203726DA391D556D63191D5 /* IGListAdapter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListAdapter.m; path = Source/IGListKit/IGListAdapter.m; sourceTree = ""; }; 97733A8BA453D5D4E25FB83EBA2B3961 /* IGListAdapterProxy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListAdapterProxy.m; path = Source/IGListKit/Internal/IGListAdapterProxy.m; sourceTree = ""; }; 97C642F80CE617AEFB32CCA1186D59B6 /* UIScrollView+IGListKit.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIScrollView+IGListKit.h"; path = "Source/IGListKit/Internal/UIScrollView+IGListKit.h"; sourceTree = ""; }; 9B6A39C929A3E81DFF85DB578EC6D015 /* IGListCollectionView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListCollectionView.h; path = Source/IGListKit/IGListCollectionView.h; sourceTree = ""; }; 9B7F61CD007007796F94020CB59EB5D7 /* IGListWorkingRangeHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListWorkingRangeHandler.h; path = Source/IGListKit/Internal/IGListWorkingRangeHandler.h; sourceTree = ""; }; A62EEB3D608E944C7C3E292536A0B1D7 /* IGListBindable.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListBindable.h; path = Source/IGListKit/IGListBindable.h; sourceTree = ""; }; ABE0E4BD8B3D9A057E119D6024A9B7F9 /* IGListAdapter+UICollectionView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "IGListAdapter+UICollectionView.m"; path = "Source/IGListKit/Internal/IGListAdapter+UICollectionView.m"; sourceTree = ""; }; AD022F2E6AE27ACB3AF6EFDD734C5D68 /* IGListAdapterUpdater.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapterUpdater.h; path = Source/IGListKit/IGListAdapterUpdater.h; sourceTree = ""; }; AE34A0AC11A6284D61E5EAF57F9E57C3 /* IGListDisplayDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListDisplayDelegate.h; path = Source/IGListKit/IGListDisplayDelegate.h; sourceTree = ""; }; B930E3CD5DCECBC66891E0558197AE59 /* IGListAdapterUpdaterInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapterUpdaterInternal.h; path = Source/IGListKit/Internal/IGListAdapterUpdaterInternal.h; sourceTree = ""; }; BC08D98EDCA81E34988B1E3E10B10348 /* IGListBindingSectionController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListBindingSectionController.m; path = Source/IGListKit/IGListBindingSectionController.m; sourceTree = ""; }; BC0AAB5D5FB600A592F32F757A8A42BA /* IGListBindingSectionController+DebugDescription.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "IGListBindingSectionController+DebugDescription.h"; path = "Source/IGListKit/Internal/IGListBindingSectionController+DebugDescription.h"; sourceTree = ""; }; BC21B2C17D19027AC026ABB728721743 /* IGListAdapterUpdater+DebugDescription.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "IGListAdapterUpdater+DebugDescription.m"; path = "Source/IGListKit/Internal/IGListAdapterUpdater+DebugDescription.m"; sourceTree = ""; }; BF8F661B3B9DCBA4CF0A93E5AE3253FE /* IGListKit.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = IGListKit.modulemap; sourceTree = ""; }; C2870D9D95104F5E65B2E13A46B22841 /* IGListReloadDataUpdater.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListReloadDataUpdater.m; path = Source/IGListKit/IGListReloadDataUpdater.m; sourceTree = ""; }; C628A8F96DB565469E515C8CCFCCB2E4 /* IGListCollectionViewLayout.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = IGListCollectionViewLayout.mm; path = Source/IGListKit/IGListCollectionViewLayout.mm; sourceTree = ""; }; C8EF76A697528EEEE488BFA0F17F976E /* IGListKit-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IGListKit-umbrella.h"; sourceTree = ""; }; CB4C7DD12F6A1855F89DE78D766AAA47 /* IGListReloadIndexPath.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListReloadIndexPath.h; path = Source/IGListKit/Internal/IGListReloadIndexPath.h; sourceTree = ""; }; CC6A0EDB95C12A26BFC07918A04339C2 /* IGListCollectionViewLayout.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListCollectionViewLayout.h; path = Source/IGListKit/IGListCollectionViewLayout.h; sourceTree = ""; }; CE90137D1998AFD99452896AF7752F74 /* IGListSectionController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListSectionController.m; path = Source/IGListKit/IGListSectionController.m; sourceTree = ""; }; CEE80663C4D17068D06E64D3C13957A7 /* IGListGenericSectionController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListGenericSectionController.h; path = Source/IGListKit/IGListGenericSectionController.h; sourceTree = ""; }; D0F6937220581929145C8B1B88CB4E51 /* IGListKit.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListKit.h; path = Source/IGListKit/IGListKit.h; sourceTree = ""; }; D5D41058691CD6A374A28AF4A5BCD6FD /* UICollectionViewLayout+InteractiveReordering.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UICollectionViewLayout+InteractiveReordering.m"; path = "Source/IGListKit/Internal/UICollectionViewLayout+InteractiveReordering.m"; sourceTree = ""; }; D9728425465E444E5B6B43474BDE4A4E /* IGListSectionMap.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = IGListSectionMap.m; path = Source/IGListKit/Internal/IGListSectionMap.m; sourceTree = ""; }; DA25A968128B1BFB14311354D251F7D1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; DCBBC230FB9736A95CEBB36DCBBC55ED /* IGListSingleSectionController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListSingleSectionController.h; path = Source/IGListKit/IGListSingleSectionController.h; sourceTree = ""; }; DEE1BAAF6A9AC27405491E3347C14D12 /* IGListBatchUpdateData+DebugDescription.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "IGListBatchUpdateData+DebugDescription.h"; path = "Source/IGListKit/Internal/IGListBatchUpdateData+DebugDescription.h"; sourceTree = ""; }; E0A3CEF4643766F24AA02213CB3E6C35 /* IGListBindingSectionController+DebugDescription.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "IGListBindingSectionController+DebugDescription.m"; path = "Source/IGListKit/Internal/IGListBindingSectionController+DebugDescription.m"; sourceTree = ""; }; E1D8E9CC1E713664772DEE930321C3F7 /* IGListKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IGListKit-prefix.pch"; sourceTree = ""; }; E218631DD6734D657BFF89EBFEB60448 /* IGListArrayUtilsInternal.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListArrayUtilsInternal.h; path = Source/IGListKit/Internal/IGListArrayUtilsInternal.h; sourceTree = ""; }; E747EFC2F632F73BA77481E4DC2D7319 /* IGListSectionController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListSectionController.h; path = Source/IGListKit/IGListSectionController.h; sourceTree = ""; }; E7B4A287CD4489BA03728A7DC45CB480 /* IGListAdapterMoveDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapterMoveDelegate.h; path = Source/IGListKit/IGListAdapterMoveDelegate.h; sourceTree = ""; }; E9045CCCDD512318ACEFEDDEA7FC5273 /* UICollectionView+IGListBatchUpdateData.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UICollectionView+IGListBatchUpdateData.h"; path = "Source/IGListKit/Internal/UICollectionView+IGListBatchUpdateData.h"; sourceTree = ""; }; E91AC57F25B02C5F13C48C6C219A19DD /* IGListKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "IGListKit-dummy.m"; sourceTree = ""; }; EBADECFA857960F2329E14AC278DC7F9 /* IGListBatchUpdateData+DebugDescription.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "IGListBatchUpdateData+DebugDescription.m"; path = "Source/IGListKit/Internal/IGListBatchUpdateData+DebugDescription.m"; sourceTree = ""; }; ECBE67B083E813F8DC6E24C82BB83337 /* IGListTransitionDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListTransitionDelegate.h; path = Source/IGListKit/IGListTransitionDelegate.h; sourceTree = ""; }; F6BF1C47ECB0912C3C500A94B5C5D140 /* IGListAdapterUpdateListener.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapterUpdateListener.h; path = Source/IGListKit/IGListAdapterUpdateListener.h; sourceTree = ""; }; F72279261964733411540354FFE149F6 /* IGListBatchUpdates.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListBatchUpdates.h; path = Source/IGListKit/Internal/IGListBatchUpdates.h; sourceTree = ""; }; FA74A7979C82186A27B1A519E4AD79CB /* UICollectionView+DebugDescription.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UICollectionView+DebugDescription.m"; path = "Source/IGListKit/Internal/UICollectionView+DebugDescription.m"; sourceTree = ""; }; FC8F99BB50EF1E40D2CA9EB4B6D34154 /* IGListAdapterUpdaterDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapterUpdaterDelegate.h; path = Source/IGListKit/IGListAdapterUpdaterDelegate.h; sourceTree = ""; }; FFCF97DA06E2CA95E0D391278754AFD7 /* IGListDebugger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListDebugger.h; path = Source/IGListKit/Internal/IGListDebugger.h; sourceTree = ""; }; FFE667515CBADE23A2B69C59523FE355 /* IGListAdapterPerformanceDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = IGListAdapterPerformanceDelegate.h; path = Source/IGListKit/IGListAdapterPerformanceDelegate.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ DEC09E6BD01E73B616D6EB3CE41AF16E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 408AB729D6ABE8A01FDD99D349B2E4A4 /* Foundation.framework in Frameworks */, 412DB79BB7685882ECE4CD783E71CD37 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2321ADD6EE282B76CD0B58A7E9FE6734 /* Dependencies */ = { isa = PBXGroup; children = ( 68BB7FFF06B526B638EAF3987F64CB1C /* IGListDiffKit */, ); name = Dependencies; sourceTree = ""; }; 50C8EC082A9D41F0D2FAFA252A2D4CAA /* Support Files */ = { isa = PBXGroup; children = ( BF8F661B3B9DCBA4CF0A93E5AE3253FE /* IGListKit.modulemap */, E91AC57F25B02C5F13C48C6C219A19DD /* IGListKit-dummy.m */, 099850C93EBB6DD568CC066921A40594 /* IGListKit-Info.plist */, E1D8E9CC1E713664772DEE930321C3F7 /* IGListKit-prefix.pch */, C8EF76A697528EEEE488BFA0F17F976E /* IGListKit-umbrella.h */, 562F1B0AFBC215CCD48168B2F1BA5114 /* IGListKit.debug.xcconfig */, 82B3FEA19DB6F2D790DB5E72DE9BEAC0 /* IGListKit.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/IGListKit"; sourceTree = ""; }; 72A30D2CF979C03B7F1EF23616514CF0 /* IGListKit */ = { isa = PBXGroup; children = ( 16908C4157F8C939FEE87B682394BC24 /* IGListAdapter.h */, 94F1D3844203726DA391D556D63191D5 /* IGListAdapter.m */, 4DD4DED4A41189E3AE046E7B533D8C38 /* IGListAdapter+DebugDescription.h */, 56086A29B27DE75D5A5EC7391825121C /* IGListAdapter+DebugDescription.m */, 688296734A654AFD9960993818B27084 /* IGListAdapter+UICollectionView.h */, ABE0E4BD8B3D9A057E119D6024A9B7F9 /* IGListAdapter+UICollectionView.m */, 1426A8FE1BC8DA4B32B7311E1A6D4FF6 /* IGListAdapterDataSource.h */, 063A5988DABE4EFB6204EA3889A71ADB /* IGListAdapterDelegate.h */, 03BF964D9ADFF1437056716D779AAB09 /* IGListAdapterInternal.h */, E7B4A287CD4489BA03728A7DC45CB480 /* IGListAdapterMoveDelegate.h */, FFE667515CBADE23A2B69C59523FE355 /* IGListAdapterPerformanceDelegate.h */, 35DFC4D5B375DE3C895B43210C2BE673 /* IGListAdapterProxy.h */, 97733A8BA453D5D4E25FB83EBA2B3961 /* IGListAdapterProxy.m */, F6BF1C47ECB0912C3C500A94B5C5D140 /* IGListAdapterUpdateListener.h */, AD022F2E6AE27ACB3AF6EFDD734C5D68 /* IGListAdapterUpdater.h */, 8C6C88E72428CCA3F9D53A7C6A5E4255 /* IGListAdapterUpdater.m */, 3823BF42DF59AC3C268857B0546BBCA7 /* IGListAdapterUpdater+DebugDescription.h */, BC21B2C17D19027AC026ABB728721743 /* IGListAdapterUpdater+DebugDescription.m */, FC8F99BB50EF1E40D2CA9EB4B6D34154 /* IGListAdapterUpdaterDelegate.h */, B930E3CD5DCECBC66891E0558197AE59 /* IGListAdapterUpdaterInternal.h */, E218631DD6734D657BFF89EBFEB60448 /* IGListArrayUtilsInternal.h */, 3F93019F1AEF22C00D37A24D075C2547 /* IGListBatchContext.h */, DEE1BAAF6A9AC27405491E3347C14D12 /* IGListBatchUpdateData+DebugDescription.h */, EBADECFA857960F2329E14AC278DC7F9 /* IGListBatchUpdateData+DebugDescription.m */, F72279261964733411540354FFE149F6 /* IGListBatchUpdates.h */, 4B6BC88735C17BB24434BF8B8E5632A5 /* IGListBatchUpdates.m */, 81FAEBD87499AF65CC01B5043590F3A8 /* IGListBatchUpdateState.h */, A62EEB3D608E944C7C3E292536A0B1D7 /* IGListBindable.h */, 52714997587F48C24307AB7612F9CACA /* IGListBindingSectionController.h */, BC08D98EDCA81E34988B1E3E10B10348 /* IGListBindingSectionController.m */, BC0AAB5D5FB600A592F32F757A8A42BA /* IGListBindingSectionController+DebugDescription.h */, E0A3CEF4643766F24AA02213CB3E6C35 /* IGListBindingSectionController+DebugDescription.m */, 1AD9621C062F9C79A1DB0C4F0BA938B0 /* IGListBindingSectionControllerDataSource.h */, 380B9E1EC70CC3CA70EE0CE16B01AEE7 /* IGListBindingSectionControllerSelectionDelegate.h */, 5D1A7A4E91924FB53983A7A3F0CD180B /* IGListCollectionContext.h */, 698190404480FED32681B4B5A3E55073 /* IGListCollectionScrollingTraits.h */, 9B6A39C929A3E81DFF85DB578EC6D015 /* IGListCollectionView.h */, 392727C2C656BBDBAA108A13947A9A63 /* IGListCollectionView.m */, 5D38245CA903B41DD85BF637375D2A97 /* IGListCollectionViewDelegateLayout.h */, CC6A0EDB95C12A26BFC07918A04339C2 /* IGListCollectionViewLayout.h */, C628A8F96DB565469E515C8CCFCCB2E4 /* IGListCollectionViewLayout.mm */, 23078DECFA8BB459564DE91C02447890 /* IGListCollectionViewLayoutCompatible.h */, 19E44DA2D0BF454144CFBB68316E4166 /* IGListCollectionViewLayoutInternal.h */, FFCF97DA06E2CA95E0D391278754AFD7 /* IGListDebugger.h */, 81846F252C9F2B471F26A812A1BC9EF7 /* IGListDebugger.m */, 5AE5852C99446C693B3D65FE32DE4054 /* IGListDebuggingUtilities.h */, 90AC138465BD599D3B2BCE89ACE1752F /* IGListDebuggingUtilities.m */, AE34A0AC11A6284D61E5EAF57F9E57C3 /* IGListDisplayDelegate.h */, 272436B4C13EB52FBF53B123511BFCB1 /* IGListDisplayHandler.h */, 51A7A797C2B5DBE959ED812736F04428 /* IGListDisplayHandler.m */, CEE80663C4D17068D06E64D3C13957A7 /* IGListGenericSectionController.h */, 80CA79323A56DED063EAE61EFA39E9F3 /* IGListGenericSectionController.m */, 1350853F608218FBFE4BE1D84B950959 /* IGListIndexPathResultInternal.h */, 28A54EB092063A06A598C0122BFF9D57 /* IGListIndexSetResultInternal.h */, D0F6937220581929145C8B1B88CB4E51 /* IGListKit.h */, 62472F10BDA8D0CF1909B47DCEDB6041 /* IGListMoveIndexInternal.h */, 71F287B0FF878E91B396F91CED8FB839 /* IGListMoveIndexPathInternal.h */, 6A187F8E56255E0F0098361E1DF22A48 /* IGListReloadDataUpdater.h */, C2870D9D95104F5E65B2E13A46B22841 /* IGListReloadDataUpdater.m */, CB4C7DD12F6A1855F89DE78D766AAA47 /* IGListReloadIndexPath.h */, 65A86458078CCBE07FFE2F5BC3C47896 /* IGListReloadIndexPath.m */, 193B3D5854467EF50B686347B1288E60 /* IGListScrollDelegate.h */, E747EFC2F632F73BA77481E4DC2D7319 /* IGListSectionController.h */, CE90137D1998AFD99452896AF7752F74 /* IGListSectionController.m */, 17708A1E8A80310069268E74C94EADBF /* IGListSectionControllerInternal.h */, 3CC8A7F4FC5AF76CFFC2870E5385584A /* IGListSectionMap.h */, D9728425465E444E5B6B43474BDE4A4E /* IGListSectionMap.m */, 7FCADB39D5D4A99CF546F760FFFEEF61 /* IGListSectionMap+DebugDescription.h */, 832301249C38E290714D95EEF53FB3A8 /* IGListSectionMap+DebugDescription.m */, DCBBC230FB9736A95CEBB36DCBBC55ED /* IGListSingleSectionController.h */, 438474C77ECC1664F1BEE4EFD0BAD336 /* IGListSingleSectionController.m */, 5250DF799124CFF01B7C8670AD305E52 /* IGListSupplementaryViewSource.h */, ECBE67B083E813F8DC6E24C82BB83337 /* IGListTransitionDelegate.h */, 0E72DD50A0DEFCD99CC89213D0E98C41 /* IGListUpdatingDelegate.h */, 59CFD541C9E1C7C9846907FD5B135F9A /* IGListWorkingRangeDelegate.h */, 9B7F61CD007007796F94020CB59EB5D7 /* IGListWorkingRangeHandler.h */, 63B4F603D8F363926716C5F2B5CCB36D /* IGListWorkingRangeHandler.mm */, 47832CBECE03B2C33D3359F6972931AA /* UICollectionView+DebugDescription.h */, FA74A7979C82186A27B1A519E4AD79CB /* UICollectionView+DebugDescription.m */, E9045CCCDD512318ACEFEDDEA7FC5273 /* UICollectionView+IGListBatchUpdateData.h */, 53F7FD263036347D36767921EE34E92D /* UICollectionView+IGListBatchUpdateData.m */, 4494EE211F18FF3CBB86A10485F6B66B /* UICollectionViewLayout+InteractiveReordering.h */, D5D41058691CD6A374A28AF4A5BCD6FD /* UICollectionViewLayout+InteractiveReordering.m */, 97C642F80CE617AEFB32CCA1186D59B6 /* UIScrollView+IGListKit.h */, 5D34987D98EC88A8DB5E128B8132443D /* UIScrollView+IGListKit.m */, 50C8EC082A9D41F0D2FAFA252A2D4CAA /* Support Files */, ); name = IGListKit; path = IGListKit; sourceTree = ""; }; 972FB1F3215716CBA8742AF423394F54 = { isa = PBXGroup; children = ( 2321ADD6EE282B76CD0B58A7E9FE6734 /* Dependencies */, 97E1F7ECEAF7D5B088D961130DBE6316 /* Frameworks */, 72A30D2CF979C03B7F1EF23616514CF0 /* IGListKit */, B727F9F06FDD92DE3FCEA5041DA1A12F /* Products */, ); sourceTree = ""; }; 97E1F7ECEAF7D5B088D961130DBE6316 /* Frameworks */ = { isa = PBXGroup; children = ( F0113027A6AC3ABDB828CEB50D68D69D /* iOS */, ); name = Frameworks; sourceTree = ""; }; B727F9F06FDD92DE3FCEA5041DA1A12F /* Products */ = { isa = PBXGroup; children = ( 017682A1559F97D4B1A82EFCD9B40A09 /* IGListKit */, ); name = Products; sourceTree = ""; }; F0113027A6AC3ABDB828CEB50D68D69D /* iOS */ = { isa = PBXGroup; children = ( DA25A968128B1BFB14311354D251F7D1 /* Foundation.framework */, 59AB62FE4BF490C9328C66C5CEE03395 /* UIKit.framework */, ); name = iOS; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 95694B667489204AF5F823DAEA3681EB /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 700AC00EFE11C06134A2DB32F0A5D6ED /* IGListAdapter.h in Headers */, 748999B5A01BDCDCB77E3BD60E49032C /* IGListAdapter+DebugDescription.h in Headers */, CEAB8C388D8DA8702987EE293B5D1340 /* IGListAdapter+UICollectionView.h in Headers */, 68056A58BB6CF71C0A261C82AB3D669F /* IGListAdapterDataSource.h in Headers */, 72C668484EB6F1E848B2787D395AB2A5 /* IGListAdapterDelegate.h in Headers */, DFC34C6C22A62E979CE48965DC7EC019 /* IGListAdapterInternal.h in Headers */, 54C9547BF6E7ABED2DDF80223B397E46 /* IGListAdapterMoveDelegate.h in Headers */, 2C7D33CB0026884D5530951A4B4984B9 /* IGListAdapterPerformanceDelegate.h in Headers */, 20678F0AA5EF8C4561671341AB213C68 /* IGListAdapterProxy.h in Headers */, 9D674BE6F590F50914DEED68A3B7356D /* IGListAdapterUpdateListener.h in Headers */, CD27C095B2D32203F0968C687FAA923C /* IGListAdapterUpdater.h in Headers */, 01E08BBED18704A4E678834927B164E5 /* IGListAdapterUpdater+DebugDescription.h in Headers */, 796B55F2170B8924B08FB1DF05BA09C6 /* IGListAdapterUpdaterDelegate.h in Headers */, 5F010BCB98B62E7DE25D4D01374DB423 /* IGListAdapterUpdaterInternal.h in Headers */, 10CE3BED45EF90FB47CA51EB6EAC1D79 /* IGListArrayUtilsInternal.h in Headers */, 291AD3AD4B64C989D6FDD75EC62F4200 /* IGListBatchContext.h in Headers */, 4B3027D1216593F18734639D31C08604 /* IGListBatchUpdateData+DebugDescription.h in Headers */, 9DA4EDCC87D1BD54E00F1849385B85EF /* IGListBatchUpdates.h in Headers */, B73068D4B3550EA0D7CA9A0960345C2B /* IGListBatchUpdateState.h in Headers */, CB455F74F02B72C8692C1B389DAFD7DE /* IGListBindable.h in Headers */, 3D906418205B53287328F5D82152FA80 /* IGListBindingSectionController.h in Headers */, ACBA45C9A25507999B8F6FD03B270640 /* IGListBindingSectionController+DebugDescription.h in Headers */, 5894D82734BCC132943F8D499303DE0A /* IGListBindingSectionControllerDataSource.h in Headers */, 5CC045F50E7344D52F71E5187FB32A5D /* IGListBindingSectionControllerSelectionDelegate.h in Headers */, 6F90C02A0D0907BFFA5AE716BA58C90C /* IGListCollectionContext.h in Headers */, 89FE92DBAF2183E4E65D5F78144331AA /* IGListCollectionScrollingTraits.h in Headers */, D150E49F4D65F964BF5F92CAC59855FF /* IGListCollectionView.h in Headers */, DEB4FB4540A43D6B3CC2F22928887B3C /* IGListCollectionViewDelegateLayout.h in Headers */, 5071DCE1BB0BDBB81A41A7209B244894 /* IGListCollectionViewLayout.h in Headers */, 2FCE5208B5FE11B36A1D20CC76477FB6 /* IGListCollectionViewLayoutCompatible.h in Headers */, F26498A7A947D54EB8A8800A6CFB0786 /* IGListCollectionViewLayoutInternal.h in Headers */, D9ECF466EF530ACCB864BEB10ED2C656 /* IGListDebugger.h in Headers */, A02F17211658A92A69072350AFF7DD5F /* IGListDebuggingUtilities.h in Headers */, 00D8EECF24D59CAE6154030BA3DEE195 /* IGListDisplayDelegate.h in Headers */, 2F96DECCD2172DBD1BD18453A7E4AB1C /* IGListDisplayHandler.h in Headers */, B6FB268940FA4EB7426D9133A435A929 /* IGListGenericSectionController.h in Headers */, 83D3CCECB9B772B5FC138B355AFE296D /* IGListIndexPathResultInternal.h in Headers */, 6D1F1FE148B459962FFB42635226E94A /* IGListIndexSetResultInternal.h in Headers */, 6B1234AB83FF379FDC1653BA17121B73 /* IGListKit.h in Headers */, 15A0C8B73D68C30CBEFC9CEF16F2B992 /* IGListKit-umbrella.h in Headers */, B994A61DF8AA246F263DD558728ADD71 /* IGListMoveIndexInternal.h in Headers */, 6BBA17D852117737A603C569AF074B5A /* IGListMoveIndexPathInternal.h in Headers */, B17CAEC48975381B521BB00AC3FA7688 /* IGListReloadDataUpdater.h in Headers */, 837EF61F55713CA43E12FB6B8466E968 /* IGListReloadIndexPath.h in Headers */, 66FC7655431ABAD92264A53171185724 /* IGListScrollDelegate.h in Headers */, 2CBC483648CD891B4A3D6B109EFABA9D /* IGListSectionController.h in Headers */, A9D5B3D28EA5A5240821D109F5330919 /* IGListSectionControllerInternal.h in Headers */, 59CE90BD34A5275CDC7455692A8A3AF7 /* IGListSectionMap.h in Headers */, 9EDCC6FAA805C232557FB79DFB57DF7C /* IGListSectionMap+DebugDescription.h in Headers */, D940E0F5D90FF644A16219A2BACDC8DF /* IGListSingleSectionController.h in Headers */, A4CEF1AF773B6C67B57BD262391E0967 /* IGListSupplementaryViewSource.h in Headers */, A61C1E6A4819E8FCF03053272B28E932 /* IGListTransitionDelegate.h in Headers */, F94A8C7A4C3360D6C237C0EAAD133CDA /* IGListUpdatingDelegate.h in Headers */, D5D5819079EBA2A4AF83B670C09A5E24 /* IGListWorkingRangeDelegate.h in Headers */, 23267ACF6633F832FE1FC2A82A245D9D /* IGListWorkingRangeHandler.h in Headers */, 3A768ABD7CC269A78CF22785F210F7FF /* UICollectionView+DebugDescription.h in Headers */, B6B10A8366E9F8CA95134F7FB5249B4B /* UICollectionView+IGListBatchUpdateData.h in Headers */, D111876D68620B70CD8DADBC808E397A /* UICollectionViewLayout+InteractiveReordering.h in Headers */, C8A13466974B3313D9B432CD82A83D0C /* UIScrollView+IGListKit.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 6C5D6FE2744F299C4B5885492E7CFF8C /* IGListKit */ = { isa = PBXNativeTarget; buildConfigurationList = 2025E6AF3C16FA7CC6A4895B9A438EC5 /* Build configuration list for PBXNativeTarget "IGListKit" */; buildPhases = ( 95694B667489204AF5F823DAEA3681EB /* Headers */, 0FA1EF6AAF807984D33F3F1759FF2443 /* Sources */, DEC09E6BD01E73B616D6EB3CE41AF16E /* Frameworks */, 23E356D431C114E83CBF714786288593 /* Resources */, ); buildRules = ( ); dependencies = ( 13E7746DBCC3BB53CA2952C375607B97 /* PBXTargetDependency */, ); name = IGListKit; productName = IGListKit; productReference = 017682A1559F97D4B1A82EFCD9B40A09 /* IGListKit */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ CAB46DA50D863A9116D12B2D36D7C68C /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = BE82EBF77FB73AAC8B39FB7783249946 /* Build configuration list for PBXProject "IGListKit" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 972FB1F3215716CBA8742AF423394F54; productRefGroup = B727F9F06FDD92DE3FCEA5041DA1A12F /* Products */; projectDirPath = ""; projectReferences = ( { ProjectRef = 68BB7FFF06B526B638EAF3987F64CB1C /* IGListDiffKit */; }, ); projectRoot = ""; targets = ( 6C5D6FE2744F299C4B5885492E7CFF8C /* IGListKit */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 23E356D431C114E83CBF714786288593 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 0FA1EF6AAF807984D33F3F1759FF2443 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 6DE7CB794E88B822088773BABFBF83FF /* IGListAdapter.m in Sources */, A3DCFBA9C5CD86C0704EFF03583B8900 /* IGListAdapter+DebugDescription.m in Sources */, C369067B04D5A03F396B35C1B070B603 /* IGListAdapter+UICollectionView.m in Sources */, C6D659ED98FD91D6250BADADEC070190 /* IGListAdapterProxy.m in Sources */, DDC86B48AAE2538DAB200D9FF8073E1E /* IGListAdapterUpdater.m in Sources */, BCF304EE58DBDBE7EB3860FE310B2C17 /* IGListAdapterUpdater+DebugDescription.m in Sources */, CFA2DB1D9E4A741C47E055809F672853 /* IGListBatchUpdateData+DebugDescription.m in Sources */, 20A54B8CA1D62C9EF31AE7FCFE7182EE /* IGListBatchUpdates.m in Sources */, 8964079800CA53BC7D0BE36413B1DA74 /* IGListBindingSectionController.m in Sources */, 05E830839CDD81941193D642966F6864 /* IGListBindingSectionController+DebugDescription.m in Sources */, DA31EB23253AD08E7EF77B30238828FA /* IGListCollectionView.m in Sources */, 590EB8DCC56FE067F14A7B45059C075C /* IGListCollectionViewLayout.mm in Sources */, 524050F4143C30DCB448E3BB23F2751A /* IGListDebugger.m in Sources */, 35C03326F9E7CF9BA971E04A887602EB /* IGListDebuggingUtilities.m in Sources */, 25E6E52F611840941C6872C7317D9A65 /* IGListDisplayHandler.m in Sources */, 339CC7529A1CB7845AF899CBEA59790F /* IGListGenericSectionController.m in Sources */, 287F7998F968B6FD12D39728F40AAA69 /* IGListKit-dummy.m in Sources */, 968CFE27158615D5E5CE548459C40B5D /* IGListReloadDataUpdater.m in Sources */, 31FBA3D8B80DF597DD2117DC1BCFFF94 /* IGListReloadIndexPath.m in Sources */, 0F46EF0FB383BD634788C36C2D4F1ED6 /* IGListSectionController.m in Sources */, E7185EC5ECC56BE56BB4890D7392A8C9 /* IGListSectionMap.m in Sources */, FB2C0646EF20359B5DD30DE172CF8BF2 /* IGListSectionMap+DebugDescription.m in Sources */, D2ADEA8C2074604A21B513E30EA8ACA3 /* IGListSingleSectionController.m in Sources */, 19284AE6E0FDB4AF8BDCA70EF9E0643E /* IGListWorkingRangeHandler.mm in Sources */, 85A10A32F13940714AE26AC7C8C4CDA2 /* UICollectionView+DebugDescription.m in Sources */, D41DE3B1749E169822B166F1E26329A1 /* UICollectionView+IGListBatchUpdateData.m in Sources */, 5C81F6CF7FFF93C6FB11D58A8FDF3DB1 /* UICollectionViewLayout+InteractiveReordering.m in Sources */, CAB21CB8CF3C59BAFC9A4FD68A810E39 /* UIScrollView+IGListKit.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 13E7746DBCC3BB53CA2952C375607B97 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = IGListDiffKit; targetProxy = 62FF8776F5035EB9D871C3BBF79FB2E5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 494D3D979A75EA97849240E03EBCE047 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; 509F57C4DEE72121D2EB0F3210F0EED2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; B81D07816BD0445B0699506DD86D9238 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 562F1B0AFBC215CCD48168B2F1BA5114 /* IGListKit.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/IGListKit/IGListKit-prefix.pch"; INFOPLIST_FILE = "Target Support Files/IGListKit/IGListKit-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/IGListKit/IGListKit.modulemap"; PRODUCT_MODULE_NAME = IGListKit; PRODUCT_NAME = IGListKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; E0EDBD9844827574388C2B602CDE3F7D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 82B3FEA19DB6F2D790DB5E72DE9BEAC0 /* IGListKit.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/IGListKit/IGListKit-prefix.pch"; INFOPLIST_FILE = "Target Support Files/IGListKit/IGListKit-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/IGListKit/IGListKit.modulemap"; PRODUCT_MODULE_NAME = IGListKit; PRODUCT_NAME = IGListKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2025E6AF3C16FA7CC6A4895B9A438EC5 /* Build configuration list for PBXNativeTarget "IGListKit" */ = { isa = XCConfigurationList; buildConfigurations = ( B81D07816BD0445B0699506DD86D9238 /* Debug */, E0EDBD9844827574388C2B602CDE3F7D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BE82EBF77FB73AAC8B39FB7783249946 /* Build configuration list for PBXProject "IGListKit" */ = { isa = XCConfigurationList; buildConfigurations = ( 509F57C4DEE72121D2EB0F3210F0EED2 /* Debug */, 494D3D979A75EA97849240E03EBCE047 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = CAB46DA50D863A9116D12B2D36D7C68C /* Project object */; } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Categories/IQNSArray+Sort.swift ================================================ // // IQNSArray+Sort.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit /** UIView.subviews sorting category. */ @available(iOSApplicationExtension, unavailable) internal extension Array where Element: UIView { /** Returns the array by sorting the UIView's by their tag property. */ func sortedArrayByTag() -> [Element] { return sorted(by: { (obj1: Element, obj2: Element) -> Bool in return (obj1.tag < obj2.tag) }) } /** Returns the array by sorting the UIView's by their tag property. */ func sortedArrayByPosition() -> [Element] { return sorted(by: { (obj1: Element, obj2: Element) -> Bool in if obj1.frame.minY != obj2.frame.minY { return obj1.frame.minY < obj2.frame.minY } else { return obj1.frame.minX < obj2.frame.minX } }) } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Categories/IQUIScrollView+Additions.swift ================================================ // // IQUIScrollView+Additions.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit @available(iOSApplicationExtension, unavailable) @objc public extension UIScrollView { private struct AssociatedKeys { static var shouldIgnoreScrollingAdjustment = "shouldIgnoreScrollingAdjustment" static var shouldIgnoreContentInsetAdjustment = "shouldIgnoreContentInsetAdjustment" static var shouldRestoreScrollViewContentOffset = "shouldRestoreScrollViewContentOffset" } /** If YES, then scrollview will ignore scrolling (simply not scroll it) for adjusting textfield position. Default is NO. */ var shouldIgnoreScrollingAdjustment: Bool { get { return objc_getAssociatedObject(self, &AssociatedKeys.shouldIgnoreScrollingAdjustment) as? Bool ?? false } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.shouldIgnoreScrollingAdjustment, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** If YES, then scrollview will ignore content inset adjustment (simply not updating it) when keyboard is shown. Default is NO. */ var shouldIgnoreContentInsetAdjustment: Bool { get { return objc_getAssociatedObject(self, &AssociatedKeys.shouldIgnoreContentInsetAdjustment) as? Bool ?? false } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.shouldIgnoreContentInsetAdjustment, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** To set customized distance from keyboard for textField/textView. Can't be less than zero */ var shouldRestoreScrollViewContentOffset: Bool { get { return objc_getAssociatedObject(self, &AssociatedKeys.shouldRestoreScrollViewContentOffset) as? Bool ?? false } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.shouldRestoreScrollViewContentOffset, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } @available(iOSApplicationExtension, unavailable) internal extension UITableView { func previousIndexPath(of indexPath: IndexPath) -> IndexPath? { var previousRow = indexPath.row - 1 var previousSection = indexPath.section //Fixing indexPath if previousRow < 0 { previousSection -= 1 if previousSection >= 0 { previousRow = self.numberOfRows(inSection: previousSection) - 1 } } if previousRow >= 0, previousSection >= 0 { return IndexPath(row: previousRow, section: previousSection) } else { return nil } } } @available(iOSApplicationExtension, unavailable) internal extension UICollectionView { func previousIndexPath(of indexPath: IndexPath) -> IndexPath? { var previousRow = indexPath.row - 1 var previousSection = indexPath.section //Fixing indexPath if previousRow < 0 { previousSection -= 1 if previousSection >= 0 { previousRow = self.numberOfItems(inSection: previousSection) - 1 } } if previousRow >= 0, previousSection >= 0 { return IndexPath(item: previousRow, section: previousSection) } else { return nil } } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Categories/IQUITextFieldView+Additions.swift ================================================ // // IQUITextFieldView+Additions.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit /** Uses default keyboard distance for textField. */ @available(iOSApplicationExtension, unavailable) public let kIQUseDefaultKeyboardDistance = CGFloat.greatestFiniteMagnitude /** UIView category for managing UITextField/UITextView */ @available(iOSApplicationExtension, unavailable) @objc public extension UIView { private struct AssociatedKeys { static var keyboardDistanceFromTextField = "keyboardDistanceFromTextField" static var ignoreSwitchingByNextPrevious = "ignoreSwitchingByNextPrevious" static var enableMode = "enableMode" static var shouldResignOnTouchOutsideMode = "shouldResignOnTouchOutsideMode" } /** To set customized distance from keyboard for textField/textView. Can't be less than zero */ var keyboardDistanceFromTextField: CGFloat { get { return objc_getAssociatedObject(self, &AssociatedKeys.keyboardDistanceFromTextField) as? CGFloat ?? kIQUseDefaultKeyboardDistance } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.keyboardDistanceFromTextField, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** If shouldIgnoreSwitchingByNextPrevious is true then library will ignore this textField/textView while moving to other textField/textView using keyboard toolbar next previous buttons. Default is false */ var ignoreSwitchingByNextPrevious: Bool { get { return objc_getAssociatedObject(self, &AssociatedKeys.ignoreSwitchingByNextPrevious) as? Bool ?? false } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.ignoreSwitchingByNextPrevious, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** Override Enable/disable managing distance between keyboard and textField behaviour for this particular textField. */ var enableMode: IQEnableMode { get { return objc_getAssociatedObject(self, &AssociatedKeys.enableMode) as? IQEnableMode ?? .default } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.enableMode, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** Override resigns Keyboard on touching outside of UITextField/View behaviour for this particular textField. */ var shouldResignOnTouchOutsideMode: IQEnableMode { get { return objc_getAssociatedObject(self, &AssociatedKeys.shouldResignOnTouchOutsideMode) as? IQEnableMode ?? .default } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.shouldResignOnTouchOutsideMode, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Categories/IQUIView+Hierarchy.swift ================================================ // // IQUIView+Hierarchy.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import UIKit /** UIView hierarchy category. */ @available(iOSApplicationExtension, unavailable) @objc public extension UIView { // MARK: viewControllers /** Returns the UIViewController object that manages the receiver. */ func viewContainingController() -> UIViewController? { var nextResponder: UIResponder? = self repeat { nextResponder = nextResponder?.next if let viewController = nextResponder as? UIViewController { return viewController } } while nextResponder != nil return nil } /** Returns the topMost UIViewController object in hierarchy. */ func topMostController() -> UIViewController? { var controllersHierarchy = [UIViewController]() if var topController = window?.rootViewController { controllersHierarchy.append(topController) while let presented = topController.presentedViewController { topController = presented controllersHierarchy.append(presented) } var matchController: UIResponder? = viewContainingController() while let mController = matchController as? UIViewController, controllersHierarchy.contains(mController) == false { repeat { matchController = matchController?.next } while matchController != nil && matchController is UIViewController == false } return matchController as? UIViewController } else { return viewContainingController() } } /** Returns the UIViewController object that is actually the parent of this object. Most of the time it's the viewController object which actually contains it, but result may be different if it's viewController is added as childViewController of another viewController. */ func parentContainerViewController() -> UIViewController? { var matchController = viewContainingController() var parentContainerViewController: UIViewController? if var navController = matchController?.navigationController { while let parentNav = navController.navigationController { navController = parentNav } var parentController: UIViewController = navController while let parent = parentController.parent, (parent.isKind(of: UINavigationController.self) == false && parent.isKind(of: UITabBarController.self) == false && parent.isKind(of: UISplitViewController.self) == false) { parentController = parent } if navController == parentController { parentContainerViewController = navController.topViewController } else { parentContainerViewController = parentController } } else if let tabController = matchController?.tabBarController { if let navController = tabController.selectedViewController as? UINavigationController { parentContainerViewController = navController.topViewController } else { parentContainerViewController = tabController.selectedViewController } } else { while let parentController = matchController?.parent, (parentController.isKind(of: UINavigationController.self) == false && parentController.isKind(of: UITabBarController.self) == false && parentController.isKind(of: UISplitViewController.self) == false) { matchController = parentController } parentContainerViewController = matchController } let finalController = parentContainerViewController?.parentIQContainerViewController() ?? parentContainerViewController return finalController } // MARK: Superviews/Subviews/Siglings /** Returns the superView of provided class type. @param classType class type of the object which is to be search in above hierarchy and return @param belowView view object in upper hierarchy where method should stop searching and return nil */ func superviewOfClassType(_ classType: UIView.Type, belowView: UIView? = nil) -> UIView? { var superView = superview while let unwrappedSuperView = superView { if unwrappedSuperView.isKind(of: classType) { //If it's UIScrollView, then validating for special cases if unwrappedSuperView.isKind(of: UIScrollView.self) { let classNameString = NSStringFromClass(type(of: unwrappedSuperView.self)) // If it's not UITableViewWrapperView class, this is internal class which is actually manage in UITableview. The speciality of this class is that it's superview is UITableView. // If it's not UITableViewCellScrollView class, this is internal class which is actually manage in UITableviewCell. The speciality of this class is that it's superview is UITableViewCell. //If it's not _UIQueuingScrollView class, actually we validate for _ prefix which usually used by Apple internal classes if unwrappedSuperView.superview?.isKind(of: UITableView.self) == false, unwrappedSuperView.superview?.isKind(of: UITableViewCell.self) == false, classNameString.hasPrefix("_") == false { return superView } } else { return superView } } else if unwrappedSuperView == belowView { return nil } superView = unwrappedSuperView.superview } return nil } /** Returns all siblings of the receiver which canBecomeFirstResponder. */ internal func responderSiblings() -> [UIView] { //Array of (UITextField/UITextView's). var tempTextFields = [UIView]() // Getting all siblings if let siblings = superview?.subviews { for textField in siblings { if (textField == self || textField.ignoreSwitchingByNextPrevious == false), textField.IQcanBecomeFirstResponder() { tempTextFields.append(textField) } } } return tempTextFields } /** Returns all deep subViews of the receiver which canBecomeFirstResponder. */ internal func deepResponderViews() -> [UIView] { //Array of (UITextField/UITextView's). var textfields = [UIView]() for textField in subviews { if (textField == self || textField.ignoreSwitchingByNextPrevious == false), textField.IQcanBecomeFirstResponder() { textfields.append(textField) } //Sometimes there are hidden or disabled views and textField inside them still recorded, so we added some more validations here (Bug ID: #458) //Uncommented else (Bug ID: #625) else if textField.subviews.count != 0, isUserInteractionEnabled, !isHidden, alpha != 0.0 { for deepView in textField.deepResponderViews() { textfields.append(deepView) } } } //subviews are returning in opposite order. Sorting according the frames 'y'. return textfields.sorted(by: { (view1: UIView, view2: UIView) -> Bool in let frame1 = view1.convert(view1.bounds, to: self) let frame2 = view2.convert(view2.bounds, to: self) if frame1.minY != frame2.minY { return frame1.minY < frame2.minY } else { return frame1.minX < frame2.minX } }) } private func IQcanBecomeFirstResponder() -> Bool { var IQcanBecomeFirstResponder = false if self.conforms(to: UITextInput.self) { // Setting toolbar to keyboard. if let textView = self as? UITextView { IQcanBecomeFirstResponder = textView.isEditable } else if let textField = self as? UITextField { IQcanBecomeFirstResponder = textField.isEnabled } } if IQcanBecomeFirstResponder { IQcanBecomeFirstResponder = isUserInteractionEnabled && !isHidden && alpha != 0.0 && !isAlertViewTextField() && textFieldSearchBar() == nil } return IQcanBecomeFirstResponder } // MARK: Special TextFields /** Returns searchBar if receiver object is UISearchBarTextField, otherwise return nil. */ internal func textFieldSearchBar() -> UISearchBar? { var responder: UIResponder? = self.next while let bar = responder { if let searchBar = bar as? UISearchBar { return searchBar } else if bar is UIViewController { break } responder = bar.next } return nil } /** Returns YES if the receiver object is UIAlertSheetTextField, otherwise return NO. */ internal func isAlertViewTextField() -> Bool { var alertViewController: UIResponder? = viewContainingController() var isAlertViewTextField = false while let controller = alertViewController, !isAlertViewTextField { if controller.isKind(of: UIAlertController.self) { isAlertViewTextField = true break } alertViewController = controller.next } return isAlertViewTextField } private func depth() -> Int { var depth: Int = 0 if let superView = superview { depth = superView.depth()+1 } return depth } } @available(iOSApplicationExtension, unavailable) extension NSObject { internal func _IQDescription() -> String { return "<\(self) \(Unmanaged.passUnretained(self).toOpaque())>" } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Categories/IQUIViewController+Additions.swift ================================================ // // IQUIViewController+Additions.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import UIKit @available(iOSApplicationExtension, unavailable) @objc public extension UIViewController { private struct AssociatedKeys { static var IQLayoutGuideConstraint = "IQLayoutGuideConstraint" } /** This method is provided to override by viewController's if the library lifts a viewController which you doesn't want to lift . This may happen if you have implemented side menu feature in your app and the library try to lift the side menu controller. Overriding this method in side menu class to return correct controller should fix the problem. */ func parentIQContainerViewController() -> UIViewController? { return self } /** To set customized distance from keyboard for textField/textView. Can't be less than zero @deprecated Due to change in core-logic of handling distance between textField and keyboard distance, this layout contraint tweak is no longer needed and things will just work out of the box regardless of constraint pinned with safeArea/layoutGuide/superview */ @available(*, deprecated, message: "Due to change in core-logic of handling distance between textField and keyboard distance, this layout contraint tweak is no longer needed and things will just work out of the box regardless of constraint pinned with safeArea/layoutGuide/superview.") @IBOutlet var IQLayoutGuideConstraint: NSLayoutConstraint? { get { return objc_getAssociatedObject(self, &AssociatedKeys.IQLayoutGuideConstraint) as? NSLayoutConstraint } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.IQLayoutGuideConstraint, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstants.swift ================================================ // // IQKeyboardManagerConstants.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation // MARK: IQAutoToolbarManageBehaviour /** `IQAutoToolbarBySubviews` Creates Toolbar according to subview's hirarchy of Textfield's in view. `IQAutoToolbarByTag` Creates Toolbar according to tag property of TextField's. `IQAutoToolbarByPosition` Creates Toolbar according to the y,x position of textField in it's superview coordinate. */ @available(iOSApplicationExtension, unavailable) @objc public enum IQAutoToolbarManageBehaviour: Int { case bySubviews case byTag case byPosition } /** `IQPreviousNextDisplayModeDefault` Show NextPrevious when there are more than 1 textField otherwise hide. `IQPreviousNextDisplayModeAlwaysHide` Do not show NextPrevious buttons in any case. `IQPreviousNextDisplayModeAlwaysShow` Always show nextPrevious buttons, if there are more than 1 textField then both buttons will be visible but will be shown as disabled. */ @available(iOSApplicationExtension, unavailable) @objc public enum IQPreviousNextDisplayMode: Int { case `default` case alwaysHide case alwaysShow } /** `IQEnableModeDefault` Pick default settings. `IQEnableModeEnabled` setting is enabled. `IQEnableModeDisabled` setting is disabled. */ @available(iOSApplicationExtension, unavailable) @objc public enum IQEnableMode: Int { case `default` case enabled case disabled } /* /---------------------------------------------------------------------------------------------------\ \---------------------------------------------------------------------------------------------------/ | iOS Notification Mechanism | /---------------------------------------------------------------------------------------------------\ \---------------------------------------------------------------------------------------------------/ ------------------------------------------------------------ When UITextField become first responder ------------------------------------------------------------ - UITextFieldTextDidBeginEditingNotification (UITextField) - UIKeyboardWillShowNotification - UIKeyboardDidShowNotification ------------------------------------------------------------ When UITextView become first responder ------------------------------------------------------------ - UIKeyboardWillShowNotification - UITextViewTextDidBeginEditingNotification (UITextView) - UIKeyboardDidShowNotification ------------------------------------------------------------ When switching focus from UITextField to another UITextField ------------------------------------------------------------ - UITextFieldTextDidEndEditingNotification (UITextField1) - UITextFieldTextDidBeginEditingNotification (UITextField2) - UIKeyboardWillShowNotification - UIKeyboardDidShowNotification ------------------------------------------------------------ When switching focus from UITextView to another UITextView ------------------------------------------------------------ - UITextViewTextDidEndEditingNotification: (UITextView1) - UIKeyboardWillShowNotification - UITextViewTextDidBeginEditingNotification: (UITextView2) - UIKeyboardDidShowNotification ------------------------------------------------------------ When switching focus from UITextField to UITextView ------------------------------------------------------------ - UITextFieldTextDidEndEditingNotification (UITextField) - UIKeyboardWillShowNotification - UITextViewTextDidBeginEditingNotification (UITextView) - UIKeyboardDidShowNotification ------------------------------------------------------------ When switching focus from UITextView to UITextField ------------------------------------------------------------ - UITextViewTextDidEndEditingNotification (UITextView) - UITextFieldTextDidBeginEditingNotification (UITextField) - UIKeyboardWillShowNotification - UIKeyboardDidShowNotification ------------------------------------------------------------ When opening/closing UIKeyboard Predictive bar ------------------------------------------------------------ - UIKeyboardWillShowNotification - UIKeyboardDidShowNotification ------------------------------------------------------------ On orientation change ------------------------------------------------------------ - UIApplicationWillChangeStatusBarOrientationNotification - UIKeyboardWillHideNotification - UIKeyboardDidHideNotification - UIApplicationDidChangeStatusBarOrientationNotification - UIKeyboardWillShowNotification - UIKeyboardDidShowNotification - UIKeyboardWillShowNotification - UIKeyboardDidShowNotification */ ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstantsInternal.swift ================================================ // // IQKeyboardManagerConstantsInternal.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import UIKit ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager+Debug.swift ================================================ // // IQKeyboardManager+Debug.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit // MARK: Debugging & Developer options @available(iOSApplicationExtension, unavailable) public extension IQKeyboardManager { private struct AssociatedKeys { static var enableDebugging = "enableDebugging" } @objc var enableDebugging: Bool { get { return objc_getAssociatedObject(self, &AssociatedKeys.enableDebugging) as? Bool ?? false } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.enableDebugging, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** @warning Use below methods to completely enable/disable notifications registered by library internally. Please keep in mind that library is totally dependent on NSNotification of UITextField, UITextField, Keyboard etc. If you do unregisterAllNotifications then library will not work at all. You should only use below methods if you want to completedly disable all library functions. You should use below methods at your own risk. */ @objc func registerAllNotifications() { // Registering for keyboard notification. NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: UIResponder.keyboardDidShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil) // Registering for UITextField notification. registerTextFieldViewClass(UITextField.self, didBeginEditingNotificationName: UITextField.textDidBeginEditingNotification.rawValue, didEndEditingNotificationName: UITextField.textDidEndEditingNotification.rawValue) // Registering for UITextView notification. registerTextFieldViewClass(UITextView.self, didBeginEditingNotificationName: UITextView.textDidBeginEditingNotification.rawValue, didEndEditingNotificationName: UITextView.textDidEndEditingNotification.rawValue) // Registering for orientation changes notification NotificationCenter.default.addObserver(self, selector: #selector(self.willChangeStatusBarOrientation(_:)), name: UIApplication.willChangeStatusBarOrientationNotification, object: UIApplication.shared) } @objc func unregisterAllNotifications() { // Unregistering for keyboard notification. NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidHideNotification, object: nil) // Unregistering for UITextField notification. unregisterTextFieldViewClass(UITextField.self, didBeginEditingNotificationName: UITextField.textDidBeginEditingNotification.rawValue, didEndEditingNotificationName: UITextField.textDidEndEditingNotification.rawValue) // Unregistering for UITextView notification. unregisterTextFieldViewClass(UITextView.self, didBeginEditingNotificationName: UITextView.textDidBeginEditingNotification.rawValue, didEndEditingNotificationName: UITextView.textDidEndEditingNotification.rawValue) // Unregistering for orientation changes notification NotificationCenter.default.removeObserver(self, name: UIApplication.willChangeStatusBarOrientationNotification, object: UIApplication.shared) } struct Static { static var indentation = 0 } internal func showLog(_ logString: String, indentation: Int = 0) { guard enableDebugging else { return } if indentation < 0 { Static.indentation = max(0, Static.indentation + indentation) } var preLog = "IQKeyboardManager" for _ in 0 ... Static.indentation { preLog += "|\t" } print(preLog + logString) if indentation > 0 { Static.indentation += indentation } } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager+Internal.swift ================================================ // // IQKeyboardManager+Internal.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit @available(iOSApplicationExtension, unavailable) internal extension IQKeyboardManager { /** Get all UITextField/UITextView siblings of textFieldView. */ func responderViews() -> [UIView]? { var superConsideredView: UIView? //If find any consider responderView in it's upper hierarchy then will get deepResponderView. for disabledClass in toolbarPreviousNextAllowedClasses { superConsideredView = textFieldView?.superviewOfClassType(disabledClass) if superConsideredView != nil { break } } //If there is a superConsideredView in view's hierarchy, then fetching all it's subview that responds. No sorting for superConsideredView, it's by subView position. (Enhancement ID: #22) if let view = superConsideredView { return view.deepResponderViews() } else { //Otherwise fetching all the siblings guard let textFields = textFieldView?.responderSiblings() else { return nil } //Sorting textFields according to behaviour switch toolbarManageBehaviour { //If autoToolbar behaviour is bySubviews, then returning it. case .bySubviews: return textFields //If autoToolbar behaviour is by tag, then sorting it according to tag property. case .byTag: return textFields.sortedArrayByTag() //If autoToolbar behaviour is by tag, then sorting it according to tag property. case .byPosition: return textFields.sortedArrayByPosition() } } } func privateIsEnabled() -> Bool { var isEnabled = enable let enableMode = textFieldView?.enableMode if enableMode == .enabled { isEnabled = true } else if enableMode == .disabled { isEnabled = false } else if var textFieldViewController = textFieldView?.viewContainingController() { //If it is searchBar textField embedded in Navigation Bar if textFieldView?.textFieldSearchBar() != nil, let navController = textFieldViewController as? UINavigationController, let topController = navController.topViewController { textFieldViewController = topController } //If viewController is kind of enable viewController class, then assuming it's enabled. if !isEnabled, enabledDistanceHandlingClasses.contains(where: { textFieldViewController.isKind(of: $0) }) { isEnabled = true } if isEnabled { //If viewController is kind of disabled viewController class, then assuming it's disabled. if disabledDistanceHandlingClasses.contains(where: { textFieldViewController.isKind(of: $0) }) { isEnabled = false } //Special Controllers if isEnabled { let classNameString = NSStringFromClass(type(of: textFieldViewController.self)) //_UIAlertControllerTextFieldViewController if classNameString.contains("UIAlertController"), classNameString.hasSuffix("TextFieldViewController") { isEnabled = false } } } } return isEnabled } func privateIsEnableAutoToolbar() -> Bool { guard var textFieldViewController = textFieldView?.viewContainingController() else { return enableAutoToolbar } //If it is searchBar textField embedded in Navigation Bar if textFieldView?.textFieldSearchBar() != nil, let navController = textFieldViewController as? UINavigationController, let topController = navController.topViewController { textFieldViewController = topController } var enableToolbar = enableAutoToolbar if !enableToolbar, enabledToolbarClasses.contains(where: { textFieldViewController.isKind(of: $0) }) { enableToolbar = true } if enableToolbar { //If found any toolbar disabled classes then return. if disabledToolbarClasses.contains(where: { textFieldViewController.isKind(of: $0) }) { enableToolbar = true } //Special Controllers if enableToolbar { let classNameString = NSStringFromClass(type(of: textFieldViewController.self)) //_UIAlertControllerTextFieldViewController if classNameString.contains("UIAlertController"), classNameString.hasSuffix("TextFieldViewController") { enableToolbar = false } } } return enableToolbar } func privateShouldResignOnTouchOutside() -> Bool { var shouldResign = shouldResignOnTouchOutside let enableMode = textFieldView?.shouldResignOnTouchOutsideMode if enableMode == .enabled { shouldResign = true } else if enableMode == .disabled { shouldResign = false } else if var textFieldViewController = textFieldView?.viewContainingController() { //If it is searchBar textField embedded in Navigation Bar if textFieldView?.textFieldSearchBar() != nil, let navController = textFieldViewController as? UINavigationController, let topController = navController.topViewController { textFieldViewController = topController } //If viewController is kind of enable viewController class, then assuming shouldResignOnTouchOutside is enabled. if !shouldResign, enabledTouchResignedClasses.contains(where: { textFieldViewController.isKind(of: $0) }) { shouldResign = true } if shouldResign { //If viewController is kind of disable viewController class, then assuming shouldResignOnTouchOutside is disable. if disabledTouchResignedClasses.contains(where: { textFieldViewController.isKind(of: $0) }) { shouldResign = false } //Special Controllers if shouldResign { let classNameString = NSStringFromClass(type(of: textFieldViewController.self)) //_UIAlertControllerTextFieldViewController if classNameString.contains("UIAlertController"), classNameString.hasSuffix("TextFieldViewController") { shouldResign = false } } } } return shouldResign } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager+OrientationNotification.swift ================================================ // // IQKeyboardManager+OrientationNotification.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit // MARK: UIStatusBar Notification methods @available(iOSApplicationExtension, unavailable) internal extension IQKeyboardManager { /** UIApplicationWillChangeStatusBarOrientationNotification. Need to set the textView to it's original position. If any frame changes made. (Bug ID: #92)*/ @objc func willChangeStatusBarOrientation(_ notification: Notification) { let currentStatusBarOrientation: UIInterfaceOrientation #if swift(>=5.1) if #available(iOS 13, *) { currentStatusBarOrientation = keyWindow()?.windowScene?.interfaceOrientation ?? UIInterfaceOrientation.unknown } else { currentStatusBarOrientation = UIApplication.shared.statusBarOrientation } #else currentStatusBarOrientation = UIApplication.shared.statusBarOrientation #endif guard let statusBarOrientation = notification.userInfo?[UIApplication.statusBarOrientationUserInfoKey] as? Int, currentStatusBarOrientation.rawValue != statusBarOrientation else { return } let startTime = CACurrentMediaTime() showLog("****** \(#function) started ******", indentation: 1) //If textViewContentInsetChanged is saved then restore it. if let textView = textFieldView as? UITextView, textView.responds(to: #selector(getter: UITextView.isEditable)) { if isTextViewContentInsetChanged { self.isTextViewContentInsetChanged = false if textView.contentInset != self.startingTextViewContentInsets { UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in self.showLog("Restoring textView.contentInset to: \(self.startingTextViewContentInsets)") //Setting textField to it's initial contentInset textView.contentInset = self.startingTextViewContentInsets textView.scrollIndicatorInsets = self.startingTextViewScrollIndicatorInsets }, completion: { (_) -> Void in }) } } } restorePosition() let elapsedTime = CACurrentMediaTime() - startTime showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1) } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager+Position.swift ================================================ // // IQKeyboardManager+Position.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit @available(iOSApplicationExtension, unavailable) public extension IQKeyboardManager { private struct AssociatedKeys { static var movedDistance = "movedDistance" static var movedDistanceChanged = "movedDistanceChanged" static var lastScrollView = "lastScrollView" static var startingContentOffset = "startingContentOffset" static var startingScrollIndicatorInsets = "startingScrollIndicatorInsets" static var startingContentInsets = "startingContentInsets" static var startingTextViewContentInsets = "startingTextViewContentInsets" static var startingTextViewScrollIndicatorInsets = "startingTextViewScrollIndicatorInsets" static var isTextViewContentInsetChanged = "isTextViewContentInsetChanged" static var hasPendingAdjustRequest = "hasPendingAdjustRequest" } /** moved distance to the top used to maintain distance between keyboard and textField. Most of the time this will be a positive value. */ @objc private(set) var movedDistance: CGFloat { get { return objc_getAssociatedObject(self, &AssociatedKeys.movedDistance) as? CGFloat ?? 0.0 } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.movedDistance, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) movedDistanceChanged?(movedDistance) } } /** Will be called then movedDistance will be changed */ @objc var movedDistanceChanged: ((CGFloat) -> Void)? { get { return objc_getAssociatedObject(self, &AssociatedKeys.movedDistanceChanged) as? ((CGFloat) -> Void) } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.movedDistanceChanged, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) movedDistanceChanged?(movedDistance) } } /** Variable to save lastScrollView that was scrolled. */ internal weak var lastScrollView: UIScrollView? { get { return (objc_getAssociatedObject(self, &AssociatedKeys.lastScrollView) as? WeakObjectContainer)?.object as? UIScrollView } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.lastScrollView, WeakObjectContainer(object: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** LastScrollView's initial contentOffset. */ internal var startingContentOffset: CGPoint { get { return objc_getAssociatedObject(self, &AssociatedKeys.startingContentOffset) as? CGPoint ?? IQKeyboardManager.kIQCGPointInvalid } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.startingContentOffset, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** LastScrollView's initial scrollIndicatorInsets. */ internal var startingScrollIndicatorInsets: UIEdgeInsets { get { return objc_getAssociatedObject(self, &AssociatedKeys.startingScrollIndicatorInsets) as? UIEdgeInsets ?? .init() } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.startingScrollIndicatorInsets, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** LastScrollView's initial contentInsets. */ internal var startingContentInsets: UIEdgeInsets { get { return objc_getAssociatedObject(self, &AssociatedKeys.startingContentInsets) as? UIEdgeInsets ?? .init() } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.startingContentInsets, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** used to adjust contentInset of UITextView. */ internal var startingTextViewContentInsets: UIEdgeInsets { get { return objc_getAssociatedObject(self, &AssociatedKeys.startingTextViewContentInsets) as? UIEdgeInsets ?? .init() } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.startingTextViewContentInsets, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** used to adjust scrollIndicatorInsets of UITextView. */ internal var startingTextViewScrollIndicatorInsets: UIEdgeInsets { get { return objc_getAssociatedObject(self, &AssociatedKeys.startingTextViewScrollIndicatorInsets) as? UIEdgeInsets ?? .init() } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.startingTextViewScrollIndicatorInsets, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** used with textView to detect a textFieldView contentInset is changed or not. (Bug ID: #92)*/ internal var isTextViewContentInsetChanged: Bool { get { return objc_getAssociatedObject(self, &AssociatedKeys.isTextViewContentInsetChanged) as? Bool ?? false } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.isTextViewContentInsetChanged, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** To know if we have any pending request to adjust view position. */ private var hasPendingAdjustRequest: Bool { get { return objc_getAssociatedObject(self, &AssociatedKeys.hasPendingAdjustRequest) as? Bool ?? false } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.hasPendingAdjustRequest, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } internal func optimizedAdjustPosition() { if !hasPendingAdjustRequest { hasPendingAdjustRequest = true OperationQueue.main.addOperation { self.adjustPosition() self.hasPendingAdjustRequest = false } } } /* Adjusting RootViewController's frame according to interface orientation. */ private func adjustPosition() { // We are unable to get textField object while keyboard showing on WKWebView's textField. (Bug ID: #11) guard hasPendingAdjustRequest, let textFieldView = textFieldView, let rootController = textFieldView.parentContainerViewController(), let window = keyWindow(), let textFieldViewRectInWindow = textFieldView.superview?.convert(textFieldView.frame, to: window), let textFieldViewRectInRootSuperview = textFieldView.superview?.convert(textFieldView.frame, to: rootController.view?.superview) else { return } let startTime = CACurrentMediaTime() showLog("****** \(#function) started ******", indentation: 1) // Getting RootViewOrigin. var rootViewOrigin = rootController.view.frame.origin //Maintain keyboardDistanceFromTextField var specialKeyboardDistanceFromTextField = textFieldView.keyboardDistanceFromTextField if let searchBar = textFieldView.textFieldSearchBar() { specialKeyboardDistanceFromTextField = searchBar.keyboardDistanceFromTextField } let newKeyboardDistanceFromTextField = (specialKeyboardDistanceFromTextField == kIQUseDefaultKeyboardDistance) ? keyboardDistanceFromTextField : specialKeyboardDistanceFromTextField var kbSize = keyboardFrame.size do { var kbFrame = keyboardFrame kbFrame.origin.y -= newKeyboardDistanceFromTextField kbFrame.size.height += newKeyboardDistanceFromTextField //Calculating actual keyboard covered size respect to window, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506) let intersectRect = kbFrame.intersection(window.frame) if intersectRect.isNull { kbSize = CGSize(width: kbFrame.size.width, height: 0) } else { kbSize = intersectRect.size } } let statusBarHeight: CGFloat #if swift(>=5.1) if #available(iOS 13, *) { statusBarHeight = window.windowScene?.statusBarManager?.statusBarFrame.height ?? 0 } else { statusBarHeight = UIApplication.shared.statusBarFrame.height } #else statusBarHeight = UIApplication.shared.statusBarFrame.height #endif let navigationBarAreaHeight: CGFloat = statusBarHeight + ( rootController.navigationController?.navigationBar.frame.height ?? 0) let layoutAreaHeight: CGFloat = rootController.view.layoutMargins.bottom let topLayoutGuide: CGFloat = max(navigationBarAreaHeight, layoutAreaHeight) + 5 let bottomLayoutGuide: CGFloat = (textFieldView is UIScrollView && textFieldView.responds(to: #selector(getter: UITextView.isEditable))) ? 0 : rootController.view.layoutMargins.bottom //Validation of textView for case where there is a tab bar at the bottom or running on iPhone X and textView is at the bottom. // Move positive = textField is hidden. // Move negative = textField is showing. // Calculating move position. var move: CGFloat = min(textFieldViewRectInRootSuperview.minY-(topLayoutGuide), textFieldViewRectInWindow.maxY-(window.frame.height-kbSize.height)+bottomLayoutGuide) showLog("Need to move: \(move)") var superScrollView: UIScrollView? var superView = textFieldView.superviewOfClassType(UIScrollView.self) as? UIScrollView //Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285) while let view = superView { if view.isScrollEnabled, !view.shouldIgnoreScrollingAdjustment { superScrollView = view break } else { // Getting it's superScrollView. // (Enhancement ID: #21, #24) superView = view.superviewOfClassType(UIScrollView.self) as? UIScrollView } } //If there was a lastScrollView. // (Bug ID: #34) if let lastScrollView = lastScrollView { //If we can't find current superScrollView, then setting lastScrollView to it's original form. if superScrollView == nil { if lastScrollView.contentInset != self.startingContentInsets { showLog("Restoring contentInset to: \(startingContentInsets)") UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in lastScrollView.contentInset = self.startingContentInsets lastScrollView.scrollIndicatorInsets = self.startingScrollIndicatorInsets }) } if lastScrollView.shouldRestoreScrollViewContentOffset, !lastScrollView.contentOffset.equalTo(startingContentOffset) { showLog("Restoring contentOffset to: \(startingContentOffset)") let animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil // (Bug ID: #1365, #1508, #1541) if animatedContentOffset { lastScrollView.setContentOffset(startingContentOffset, animated: UIView.areAnimationsEnabled) } else { lastScrollView.contentOffset = startingContentOffset } } startingContentInsets = UIEdgeInsets() startingScrollIndicatorInsets = UIEdgeInsets() startingContentOffset = CGPoint.zero self.lastScrollView = nil } else if superScrollView != lastScrollView { //If both scrollView's are different, then reset lastScrollView to it's original frame and setting current scrollView as last scrollView. if lastScrollView.contentInset != self.startingContentInsets { showLog("Restoring contentInset to: \(startingContentInsets)") UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in lastScrollView.contentInset = self.startingContentInsets lastScrollView.scrollIndicatorInsets = self.startingScrollIndicatorInsets }) } if lastScrollView.shouldRestoreScrollViewContentOffset, !lastScrollView.contentOffset.equalTo(startingContentOffset) { showLog("Restoring contentOffset to: \(startingContentOffset)") let animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil // (Bug ID: #1365, #1508, #1541) if animatedContentOffset { lastScrollView.setContentOffset(startingContentOffset, animated: UIView.areAnimationsEnabled) } else { lastScrollView.contentOffset = startingContentOffset } } self.lastScrollView = superScrollView if let scrollView = superScrollView { startingContentInsets = scrollView.contentInset startingContentOffset = scrollView.contentOffset #if swift(>=5.1) if #available(iOS 11.1, *) { startingScrollIndicatorInsets = scrollView.verticalScrollIndicatorInsets } else { startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets } #else _startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets #endif } showLog("Saving ScrollView New contentInset: \(startingContentInsets) and contentOffset: \(startingContentOffset)") } //Else the case where superScrollView == lastScrollView means we are on same scrollView after switching to different textField. So doing nothing, going ahead } else if let unwrappedSuperScrollView = superScrollView { //If there was no lastScrollView and we found a current scrollView. then setting it as lastScrollView. lastScrollView = unwrappedSuperScrollView startingContentInsets = unwrappedSuperScrollView.contentInset startingContentOffset = unwrappedSuperScrollView.contentOffset #if swift(>=5.1) if #available(iOS 11.1, *) { startingScrollIndicatorInsets = unwrappedSuperScrollView.verticalScrollIndicatorInsets } else { startingScrollIndicatorInsets = unwrappedSuperScrollView.scrollIndicatorInsets } #else _startingScrollIndicatorInsets = unwrappedSuperScrollView.scrollIndicatorInsets #endif showLog("Saving ScrollView contentInset: \(startingContentInsets) and contentOffset: \(startingContentOffset)") } // Special case for ScrollView. // If we found lastScrollView then setting it's contentOffset to show textField. if let lastScrollView = lastScrollView { //Saving var lastView = textFieldView var superScrollView = self.lastScrollView while let scrollView = superScrollView { var shouldContinue = false if move > 0 { shouldContinue = move > (-scrollView.contentOffset.y - scrollView.contentInset.top) } else if let tableView = scrollView.superviewOfClassType(UITableView.self) as? UITableView { shouldContinue = scrollView.contentOffset.y > 0 if shouldContinue, let tableCell = textFieldView.superviewOfClassType(UITableViewCell.self) as? UITableViewCell, let indexPath = tableView.indexPath(for: tableCell), let previousIndexPath = tableView.previousIndexPath(of: indexPath) { let previousCellRect = tableView.rectForRow(at: previousIndexPath) if !previousCellRect.isEmpty { let previousCellRectInRootSuperview = tableView.convert(previousCellRect, to: rootController.view.superview) move = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide) } } } else if let collectionView = scrollView.superviewOfClassType(UICollectionView.self) as? UICollectionView { shouldContinue = scrollView.contentOffset.y > 0 if shouldContinue, let collectionCell = textFieldView.superviewOfClassType(UICollectionViewCell.self) as? UICollectionViewCell, let indexPath = collectionView.indexPath(for: collectionCell), let previousIndexPath = collectionView.previousIndexPath(of: indexPath), let attributes = collectionView.layoutAttributesForItem(at: previousIndexPath) { let previousCellRect = attributes.frame if !previousCellRect.isEmpty { let previousCellRectInRootSuperview = collectionView.convert(previousCellRect, to: rootController.view.superview) move = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide) } } } else { shouldContinue = textFieldViewRectInRootSuperview.origin.y < topLayoutGuide if shouldContinue { move = min(0, textFieldViewRectInRootSuperview.origin.y - topLayoutGuide) } } //Looping in upper hierarchy until we don't found any scrollView in it's upper hirarchy till UIWindow object. if shouldContinue { var tempScrollView = scrollView.superviewOfClassType(UIScrollView.self) as? UIScrollView var nextScrollView: UIScrollView? while let view = tempScrollView { if view.isScrollEnabled, !view.shouldIgnoreScrollingAdjustment { nextScrollView = view break } else { tempScrollView = view.superviewOfClassType(UIScrollView.self) as? UIScrollView } } //Getting lastViewRect. if let lastViewRect = lastView.superview?.convert(lastView.frame, to: scrollView) { //Calculating the expected Y offset from move and scrollView's contentOffset. var shouldOffsetY = scrollView.contentOffset.y - min(scrollView.contentOffset.y, -move) //Rearranging the expected Y offset according to the view. shouldOffsetY = min(shouldOffsetY, lastViewRect.origin.y) //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type //nextScrollView == nil If processing scrollView is last scrollView in upper hierarchy (there is no other scrollView upper hierrchy.) //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type //shouldOffsetY >= 0 shouldOffsetY must be greater than in order to keep distance from navigationBar (Bug ID: #92) if (textFieldView is UIScrollView && textFieldView.responds(to: #selector(getter: UITextView.isEditable))), nextScrollView == nil, shouldOffsetY >= 0 { // Converting Rectangle according to window bounds. if let currentTextFieldViewRect = textFieldView.superview?.convert(textFieldView.frame, to: window) { //Calculating expected fix distance which needs to be managed from navigation bar let expectedFixDistance = currentTextFieldViewRect.minY - topLayoutGuide //Now if expectedOffsetY (superScrollView.contentOffset.y + expectedFixDistance) is lower than current shouldOffsetY, which means we're in a position where navigationBar up and hide, then reducing shouldOffsetY with expectedOffsetY (superScrollView.contentOffset.y + expectedFixDistance) shouldOffsetY = min(shouldOffsetY, scrollView.contentOffset.y + expectedFixDistance) //Setting move to 0 because now we don't want to move any view anymore (All will be managed by our contentInset logic. move = 0 } else { //Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY. move -= (shouldOffsetY-scrollView.contentOffset.y) } } else { //Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY. move -= (shouldOffsetY-scrollView.contentOffset.y) } let newContentOffset = CGPoint(x: scrollView.contentOffset.x, y: shouldOffsetY) if scrollView.contentOffset.equalTo(newContentOffset) == false { showLog("old contentOffset: \(scrollView.contentOffset) new contentOffset: \(newContentOffset)") self.showLog("Remaining Move: \(move)") //Getting problem while using `setContentOffset:animated:`, So I used animation API. UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in let animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: scrollView) != nil // (Bug ID: #1365, #1508, #1541) if animatedContentOffset { scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled) } else { scrollView.contentOffset = newContentOffset } }, completion: { _ in if scrollView is UITableView || scrollView is UICollectionView { //This will update the next/previous states self.addToolbarIfRequired() } }) } } // Getting next lastView & superScrollView. lastView = scrollView superScrollView = nextScrollView } else { move = 0 break } } //Updating contentInset if let lastScrollViewRect = lastScrollView.superview?.convert(lastScrollView.frame, to: window), lastScrollView.shouldIgnoreContentInsetAdjustment == false { var bottomInset: CGFloat = (kbSize.height)-(window.frame.height-lastScrollViewRect.maxY) var bottomScrollIndicatorInset = bottomInset - newKeyboardDistanceFromTextField // Update the insets so that the scroll vew doesn't shift incorrectly when the offset is near the bottom of the scroll view. bottomInset = max(startingContentInsets.bottom, bottomInset) bottomScrollIndicatorInset = max(startingScrollIndicatorInsets.bottom, bottomScrollIndicatorInset) if #available(iOS 11, *) { bottomInset -= lastScrollView.safeAreaInsets.bottom bottomScrollIndicatorInset -= lastScrollView.safeAreaInsets.bottom } var movedInsets = lastScrollView.contentInset movedInsets.bottom = bottomInset if lastScrollView.contentInset != movedInsets { showLog("old ContentInset: \(lastScrollView.contentInset) new ContentInset: \(movedInsets)") UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in lastScrollView.contentInset = movedInsets var newScrollIndicatorInset: UIEdgeInsets #if swift(>=5.1) if #available(iOS 11.1, *) { newScrollIndicatorInset = lastScrollView.verticalScrollIndicatorInsets } else { newScrollIndicatorInset = lastScrollView.scrollIndicatorInsets } #else newScrollIndicatorInset = lastScrollView.scrollIndicatorInsets #endif newScrollIndicatorInset.bottom = bottomScrollIndicatorInset lastScrollView.scrollIndicatorInsets = newScrollIndicatorInset }) } } } //Going ahead. No else if. //Special case for UITextView(Readjusting textView.contentInset when textView hight is too big to fit on screen) //_lastScrollView If not having inside any scrollView, (now contentInset manages the full screen textView. //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type if let textView = textFieldView as? UIScrollView, textView.isScrollEnabled, textFieldView.responds(to: #selector(getter: UITextView.isEditable)) { // CGRect rootSuperViewFrameInWindow = [_rootViewController.view.superview convertRect:_rootViewController.view.superview.bounds toView:keyWindow]; // // CGFloat keyboardOverlapping = CGRectGetMaxY(rootSuperViewFrameInWindow) - keyboardYPosition; // // CGFloat textViewHeight = MIN(CGRectGetHeight(_textFieldView.frame), (CGRectGetHeight(rootSuperViewFrameInWindow)-topLayoutGuide-keyboardOverlapping)); let keyboardYPosition = window.frame.height - (kbSize.height-newKeyboardDistanceFromTextField) var rootSuperViewFrameInWindow = window.frame if let rootSuperview = rootController.view.superview { rootSuperViewFrameInWindow = rootSuperview.convert(rootSuperview.bounds, to: window) } let keyboardOverlapping = rootSuperViewFrameInWindow.maxY - keyboardYPosition let textViewHeight = min(textView.frame.height, rootSuperViewFrameInWindow.height-topLayoutGuide-keyboardOverlapping) if textView.frame.size.height-textView.contentInset.bottom>textViewHeight { //_isTextViewContentInsetChanged, If frame is not change by library in past, then saving user textView properties (Bug ID: #92) if !self.isTextViewContentInsetChanged { self.startingTextViewContentInsets = textView.contentInset #if swift(>=5.1) if #available(iOS 11.1, *) { self.startingTextViewScrollIndicatorInsets = textView.verticalScrollIndicatorInsets } else { self.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets } #else self.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets #endif } self.isTextViewContentInsetChanged = true var newContentInset = textView.contentInset newContentInset.bottom = textView.frame.size.height-textViewHeight if #available(iOS 11, *) { newContentInset.bottom -= textView.safeAreaInsets.bottom } if textView.contentInset != newContentInset { self.showLog("\(textFieldView) Old UITextView.contentInset: \(textView.contentInset) New UITextView.contentInset: \(newContentInset)") UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in textView.contentInset = newContentInset textView.scrollIndicatorInsets = newContentInset }, completion: { (_) -> Void in }) } } } // +Positive or zero. if move >= 0 { rootViewOrigin.y = max(rootViewOrigin.y - move, min(0, -(kbSize.height-newKeyboardDistanceFromTextField))) if rootController.view.frame.origin.equalTo(rootViewOrigin) == false { showLog("Moving Upward") UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in var rect = rootController.view.frame rect.origin = rootViewOrigin rootController.view.frame = rect //Animating content if needed (Bug ID: #204) if self.layoutIfNeededOnUpdate { //Animating content (Bug ID: #160) rootController.view.setNeedsLayout() rootController.view.layoutIfNeeded() } self.showLog("Set \(rootController) origin to: \(rootViewOrigin)") }) } movedDistance = (topViewBeginOrigin.y-rootViewOrigin.y) } else { // -Negative let disturbDistance: CGFloat = rootViewOrigin.y-topViewBeginOrigin.y // disturbDistance Negative = frame disturbed. // disturbDistance positive = frame not disturbed. if disturbDistance <= 0 { rootViewOrigin.y -= max(move, disturbDistance) if rootController.view.frame.origin.equalTo(rootViewOrigin) == false { showLog("Moving Downward") // Setting adjusted rootViewRect // Setting adjusted rootViewRect UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in var rect = rootController.view.frame rect.origin = rootViewOrigin rootController.view.frame = rect //Animating content if needed (Bug ID: #204) if self.layoutIfNeededOnUpdate { //Animating content (Bug ID: #160) rootController.view.setNeedsLayout() rootController.view.layoutIfNeeded() } self.showLog("Set \(rootController) origin to: \(rootViewOrigin)") }) } movedDistance = (topViewBeginOrigin.y-rootViewOrigin.y) } } let elapsedTime = CACurrentMediaTime() - startTime showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1) } internal func restorePosition() { hasPendingAdjustRequest = false // Setting rootViewController frame to it's original position. // (Bug ID: #18) guard topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == false, let rootViewController = rootViewController else { return } if rootViewController.view.frame.origin.equalTo(self.topViewBeginOrigin) == false { //Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations. UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in self.showLog("Restoring \(rootViewController) origin to: \(self.topViewBeginOrigin)") // Setting it's new frame var rect = rootViewController.view.frame rect.origin = self.topViewBeginOrigin rootViewController.view.frame = rect //Animating content if needed (Bug ID: #204) if self.layoutIfNeededOnUpdate { //Animating content (Bug ID: #160) rootViewController.view.setNeedsLayout() rootViewController.view.layoutIfNeeded() } }) } self.movedDistance = 0 if rootViewController.navigationController?.interactivePopGestureRecognizer?.state == .began { self.rootViewControllerWhilePopGestureRecognizerActive = rootViewController self.topViewBeginOriginWhilePopGestureRecognizerActive = self.topViewBeginOrigin } self.rootViewController = nil } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager+Toolbar.swift ================================================ // // IQKeyboardManager+Toolbar.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit @available(iOSApplicationExtension, unavailable) public extension IQKeyboardManager { /** Default tag for toolbar with Done button -1002. */ private static let kIQDoneButtonToolbarTag = -1002 /** Default tag for toolbar with Previous/Next buttons -1005. */ private static let kIQPreviousNextButtonToolbarTag = -1005 /** Add toolbar if it is required to add on textFields and it's siblings. */ internal func addToolbarIfRequired() { //Either there is no inputAccessoryView or if accessoryView is not appropriate for current situation(There is Previous/Next/Done toolbar). guard let siblings = responderViews(), !siblings.isEmpty, let textField = textFieldView, textField.responds(to: #selector(setter: UITextField.inputAccessoryView)), (textField.inputAccessoryView == nil || textField.inputAccessoryView?.tag == IQKeyboardManager.kIQPreviousNextButtonToolbarTag || textField.inputAccessoryView?.tag == IQKeyboardManager.kIQDoneButtonToolbarTag) else { return } let startTime = CACurrentMediaTime() showLog("****** \(#function) started ******", indentation: 1) showLog("Found \(siblings.count) responder sibling(s)") let rightConfiguration: IQBarButtonItemConfiguration if let doneBarButtonItemImage = toolbarDoneBarButtonItemImage { rightConfiguration = IQBarButtonItemConfiguration(image: doneBarButtonItemImage, action: #selector(self.doneAction(_:))) } else if let doneBarButtonItemText = toolbarDoneBarButtonItemText { rightConfiguration = IQBarButtonItemConfiguration(title: doneBarButtonItemText, action: #selector(self.doneAction(_:))) } else { rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: #selector(self.doneAction(_:))) } rightConfiguration.accessibilityLabel = toolbarDoneBarButtonItemAccessibilityLabel ?? "Done" // If only one object is found, then adding only Done button. if (siblings.count <= 1 && previousNextDisplayMode == .default) || previousNextDisplayMode == .alwaysHide { textField.addKeyboardToolbarWithTarget(target: self, titleText: (shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder: nil), rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: nil, nextBarButtonConfiguration: nil) textField.inputAccessoryView?.tag = IQKeyboardManager.kIQDoneButtonToolbarTag // (Bug ID: #78) } else if previousNextDisplayMode == .default || previousNextDisplayMode == .alwaysShow { let prevConfiguration: IQBarButtonItemConfiguration if let doneBarButtonItemImage = toolbarPreviousBarButtonItemImage { prevConfiguration = IQBarButtonItemConfiguration(image: doneBarButtonItemImage, action: #selector(self.previousAction(_:))) } else if let doneBarButtonItemText = toolbarPreviousBarButtonItemText { prevConfiguration = IQBarButtonItemConfiguration(title: doneBarButtonItemText, action: #selector(self.previousAction(_:))) } else { prevConfiguration = IQBarButtonItemConfiguration(image: (UIImage.keyboardPreviousImage() ?? UIImage()), action: #selector(self.previousAction(_:))) } prevConfiguration.accessibilityLabel = toolbarPreviousBarButtonItemAccessibilityLabel ?? "Previous" let nextConfiguration: IQBarButtonItemConfiguration if let doneBarButtonItemImage = toolbarNextBarButtonItemImage { nextConfiguration = IQBarButtonItemConfiguration(image: doneBarButtonItemImage, action: #selector(self.nextAction(_:))) } else if let doneBarButtonItemText = toolbarNextBarButtonItemText { nextConfiguration = IQBarButtonItemConfiguration(title: doneBarButtonItemText, action: #selector(self.nextAction(_:))) } else { nextConfiguration = IQBarButtonItemConfiguration(image: (UIImage.keyboardNextImage() ?? UIImage()), action: #selector(self.nextAction(_:))) } nextConfiguration.accessibilityLabel = toolbarNextBarButtonItemAccessibilityLabel ?? "Next" textField.addKeyboardToolbarWithTarget(target: self, titleText: (shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder: nil), rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration) textField.inputAccessoryView?.tag = IQKeyboardManager.kIQPreviousNextButtonToolbarTag // (Bug ID: #78) } let toolbar = textField.keyboardToolbar //Setting toolbar tintColor // (Enhancement ID: #30) toolbar.tintColor = shouldToolbarUsesTextFieldTintColor ? textField.tintColor : toolbarTintColor // Setting toolbar to keyboard. if let textFieldView = textField as? UITextInput { //Bar style according to keyboard appearance switch textFieldView.keyboardAppearance { case .dark?: toolbar.barStyle = .black toolbar.barTintColor = nil default: toolbar.barStyle = .default toolbar.barTintColor = toolbarBarTintColor } } //Setting toolbar title font. // (Enhancement ID: #30) if shouldShowToolbarPlaceholder, !textField.shouldHideToolbarPlaceholder { //Updating placeholder font to toolbar. //(Bug ID: #148, #272) if toolbar.titleBarButton.title == nil || toolbar.titleBarButton.title != textField.drawingToolbarPlaceholder { toolbar.titleBarButton.title = textField.drawingToolbarPlaceholder } //Setting toolbar title font. // (Enhancement ID: #30) toolbar.titleBarButton.titleFont = placeholderFont //Setting toolbar title color. // (Enhancement ID: #880) toolbar.titleBarButton.titleColor = placeholderColor //Setting toolbar button title color. // (Enhancement ID: #880) toolbar.titleBarButton.selectableTitleColor = placeholderButtonColor } else { toolbar.titleBarButton.title = nil } //In case of UITableView (Special), the next/previous buttons has to be refreshed everytime. (Bug ID: #56) textField.keyboardToolbar.previousBarButton.isEnabled = (siblings.first != textField) // If firstTextField, then previous should not be enabled. textField.keyboardToolbar.nextBarButton.isEnabled = (siblings.last != textField) // If lastTextField then next should not be enaled. let elapsedTime = CACurrentMediaTime() - startTime showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1) } /** Remove any toolbar if it is IQToolbar. */ internal func removeToolbarIfRequired() { // (Bug ID: #18) guard let siblings = responderViews(), !siblings.isEmpty, let textField = textFieldView, textField.responds(to: #selector(setter: UITextField.inputAccessoryView)), (textField.inputAccessoryView == nil || textField.inputAccessoryView?.tag == IQKeyboardManager.kIQPreviousNextButtonToolbarTag || textField.inputAccessoryView?.tag == IQKeyboardManager.kIQDoneButtonToolbarTag) else { return } let startTime = CACurrentMediaTime() showLog("****** \(#function) started ******", indentation: 1) showLog("Found \(siblings.count) responder sibling(s)") for view in siblings { if let toolbar = view.inputAccessoryView as? IQToolbar { //setInputAccessoryView: check (Bug ID: #307) if view.responds(to: #selector(setter: UITextField.inputAccessoryView)), (toolbar.tag == IQKeyboardManager.kIQDoneButtonToolbarTag || toolbar.tag == IQKeyboardManager.kIQPreviousNextButtonToolbarTag) { if let textField = view as? UITextField { textField.inputAccessoryView = nil } else if let textView = view as? UITextView { textView.inputAccessoryView = nil } view.reloadInputViews() } } } let elapsedTime = CACurrentMediaTime() - startTime showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1) } /** reloadInputViews to reload toolbar buttons enable/disable state on the fly Enhancement ID #434. */ @objc func reloadInputViews() { //If enabled then adding toolbar. if privateIsEnableAutoToolbar() { self.addToolbarIfRequired() } else { self.removeToolbarIfRequired() } } } // MARK: Previous next button actions @available(iOSApplicationExtension, unavailable) public extension IQKeyboardManager { /** Returns YES if can navigate to previous responder textField/textView, otherwise NO. */ @objc var canGoPrevious: Bool { //If it is not first textField. then it's previous object canBecomeFirstResponder. guard let textFields = responderViews(), let textFieldRetain = textFieldView, let index = textFields.firstIndex(of: textFieldRetain), index > 0 else { return false } return true } /** Returns YES if can navigate to next responder textField/textView, otherwise NO. */ @objc var canGoNext: Bool { //If it is not first textField. then it's previous object canBecomeFirstResponder. guard let textFields = responderViews(), let textFieldRetain = textFieldView, let index = textFields.firstIndex(of: textFieldRetain), index < textFields.count-1 else { return false } return true } /** Navigate to previous responder textField/textView. */ @objc @discardableResult func goPrevious() -> Bool { //If it is not first textField. then it's previous object becomeFirstResponder. guard let textFields = responderViews(), let textFieldRetain = textFieldView, let index = textFields.firstIndex(of: textFieldRetain), index > 0 else { return false } let nextTextField = textFields[index-1] let isAcceptAsFirstResponder = nextTextField.becomeFirstResponder() // If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96) if isAcceptAsFirstResponder == false { //If next field refuses to become first responder then restoring old textField as first responder. textFieldRetain.becomeFirstResponder() showLog("Refuses to become first responder: \(nextTextField)") } return isAcceptAsFirstResponder } /** Navigate to next responder textField/textView. */ @objc @discardableResult func goNext() -> Bool { //If it is not first textField. then it's previous object becomeFirstResponder. guard let textFields = responderViews(), let textFieldRetain = textFieldView, let index = textFields.firstIndex(of: textFieldRetain), index < textFields.count-1 else { return false } let nextTextField = textFields[index+1] let isAcceptAsFirstResponder = nextTextField.becomeFirstResponder() // If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96) if isAcceptAsFirstResponder == false { //If next field refuses to become first responder then restoring old textField as first responder. textFieldRetain.becomeFirstResponder() showLog("Refuses to become first responder: \(nextTextField)") } return isAcceptAsFirstResponder } /** previousAction. */ @objc internal func previousAction (_ barButton: IQBarButtonItem) { //If user wants to play input Click sound. if shouldPlayInputClicks { //Play Input Click Sound. UIDevice.current.playInputClick() } guard canGoPrevious, let textFieldRetain = textFieldView else { return } let isAcceptAsFirstResponder = goPrevious() var invocation = barButton.invocation var sender = textFieldRetain //Handling search bar special case do { if let searchBar = textFieldRetain.textFieldSearchBar() { invocation = searchBar.keyboardToolbar.previousBarButton.invocation sender = searchBar } } if isAcceptAsFirstResponder { invocation?.invoke(from: sender) } } /** nextAction. */ @objc internal func nextAction (_ barButton: IQBarButtonItem) { //If user wants to play input Click sound. if shouldPlayInputClicks { //Play Input Click Sound. UIDevice.current.playInputClick() } guard canGoNext, let textFieldRetain = textFieldView else { return } let isAcceptAsFirstResponder = goNext() var invocation = barButton.invocation var sender = textFieldRetain //Handling search bar special case do { if let searchBar = textFieldRetain.textFieldSearchBar() { invocation = searchBar.keyboardToolbar.nextBarButton.invocation sender = searchBar } } if isAcceptAsFirstResponder { invocation?.invoke(from: sender) } } /** doneAction. Resigning current textField. */ @objc internal func doneAction (_ barButton: IQBarButtonItem) { //If user wants to play input Click sound. if shouldPlayInputClicks { //Play Input Click Sound. UIDevice.current.playInputClick() } guard let textFieldRetain = textFieldView else { return } //Resign textFieldView. let isResignedFirstResponder = resignFirstResponder() var invocation = barButton.invocation var sender = textFieldRetain //Handling search bar special case do { if let searchBar = textFieldRetain.textFieldSearchBar() { invocation = searchBar.keyboardToolbar.doneBarButton.invocation sender = searchBar } } if isResignedFirstResponder { invocation?.invoke(from: sender) } } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager+UIKeyboardNotification.swift ================================================ // // IQKeyboardManager+UIKeyboardNotification.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit // MARK: UIKeyboard Notifications @available(iOSApplicationExtension, unavailable) public extension IQKeyboardManager { private struct AssociatedKeys { static var keyboardShowing = "keyboardShowing" static var keyboardShowNotification = "keyboardShowNotification" static var keyboardFrame = "keyboardFrame" static var animationDuration = "animationDuration" static var animationCurve = "animationCurve" } /** Boolean to know if keyboard is showing. */ @objc private(set) var keyboardShowing: Bool { get { return objc_getAssociatedObject(self, &AssociatedKeys.keyboardShowing) as? Bool ?? false } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.keyboardShowing, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** To save keyboardWillShowNotification. Needed for enable keyboard functionality. */ internal var keyboardShowNotification: Notification? { get { return objc_getAssociatedObject(self, &AssociatedKeys.keyboardShowNotification) as? Notification } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.keyboardShowNotification, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** To save keyboard rame. */ internal var keyboardFrame: CGRect { get { return objc_getAssociatedObject(self, &AssociatedKeys.keyboardFrame) as? CGRect ?? .zero } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.keyboardFrame, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** To save keyboard animation duration. */ internal var animationDuration: TimeInterval { get { return objc_getAssociatedObject(self, &AssociatedKeys.animationDuration) as? TimeInterval ?? 0.25 } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.animationDuration, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** To mimic the keyboard animation */ internal var animationCurve: UIView.AnimationOptions { get { return objc_getAssociatedObject(self, &AssociatedKeys.animationCurve) as? UIView.AnimationOptions ?? .curveEaseOut } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.animationCurve, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /* UIKeyboardWillShowNotification. */ @objc internal func keyboardWillShow(_ notification: Notification?) { keyboardShowNotification = notification // Boolean to know keyboard is showing/hiding keyboardShowing = true let oldKBFrame = keyboardFrame if let info = notification?.userInfo { // Getting keyboard animation. if let curve = info[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt { animationCurve = UIView.AnimationOptions(rawValue: curve).union(.beginFromCurrentState) } else { animationCurve = UIView.AnimationOptions.curveEaseOut.union(.beginFromCurrentState) } // Getting keyboard animation duration animationDuration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25 // Getting UIKeyboardSize. if let kbFrame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect { keyboardFrame = kbFrame showLog("UIKeyboard Frame: \(keyboardFrame)") } } guard privateIsEnabled() else { restorePosition() topViewBeginOrigin = IQKeyboardManager.kIQCGPointInvalid return } let startTime = CACurrentMediaTime() showLog("****** \(#function) started ******", indentation: 1) // (Bug ID: #5) if let textFieldView = textFieldView, topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) { // keyboard is not showing(At the beginning only). We should save rootViewRect. rootViewController = textFieldView.parentContainerViewController() if let controller = rootViewController { if rootViewControllerWhilePopGestureRecognizerActive == controller { topViewBeginOrigin = topViewBeginOriginWhilePopGestureRecognizerActive } else { topViewBeginOrigin = controller.view.frame.origin } rootViewControllerWhilePopGestureRecognizerActive = nil topViewBeginOriginWhilePopGestureRecognizerActive = IQKeyboardManager.kIQCGPointInvalid self.showLog("Saving \(controller) beginning origin: \(self.topViewBeginOrigin)") } } //If last restored keyboard size is different(any orientation accure), then refresh. otherwise not. if keyboardFrame.equalTo(oldKBFrame) == false { //If textFieldView is inside UITableViewController then let UITableViewController to handle it (Bug ID: #37) (Bug ID: #76) See note:- https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html If it is UIAlertView textField then do not affect anything (Bug ID: #70). if keyboardShowing, let textFieldView = textFieldView, textFieldView.isAlertViewTextField() == false { // keyboard is already showing. adjust position. optimizedAdjustPosition() } } let elapsedTime = CACurrentMediaTime() - startTime showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1) } /* UIKeyboardDidShowNotification. */ @objc internal func keyboardDidShow(_ notification: Notification?) { guard privateIsEnabled(), let textFieldView = textFieldView, let parentController = textFieldView.parentContainerViewController(), (parentController.modalPresentationStyle == UIModalPresentationStyle.formSheet || parentController.modalPresentationStyle == UIModalPresentationStyle.pageSheet), textFieldView.isAlertViewTextField() == false else { return } let startTime = CACurrentMediaTime() showLog("****** \(#function) started ******", indentation: 1) self.optimizedAdjustPosition() let elapsedTime = CACurrentMediaTime() - startTime showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1) } /* UIKeyboardWillHideNotification. So setting rootViewController to it's default frame. */ @objc internal func keyboardWillHide(_ notification: Notification?) { //If it's not a fake notification generated by [self setEnable:NO]. if notification != nil { keyboardShowNotification = nil } // Boolean to know keyboard is showing/hiding keyboardShowing = false if let info = notification?.userInfo { // Getting keyboard animation. if let curve = info[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt { animationCurve = UIView.AnimationOptions(rawValue: curve).union(.beginFromCurrentState) } else { animationCurve = UIView.AnimationOptions.curveEaseOut.union(.beginFromCurrentState) } // Getting keyboard animation duration animationDuration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25 } //If not enabled then do nothing. guard privateIsEnabled() else { return } let startTime = CACurrentMediaTime() showLog("****** \(#function) started ******", indentation: 1) //Commented due to #56. Added all the conditions below to handle WKWebView's textFields. (Bug ID: #56) // We are unable to get textField object while keyboard showing on WKWebView's textField. (Bug ID: #11) // if (_textFieldView == nil) return //Restoring the contentOffset of the lastScrollView if let lastScrollView = lastScrollView { UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in if lastScrollView.contentInset != self.startingContentInsets { self.showLog("Restoring contentInset to: \(self.startingContentInsets)") lastScrollView.contentInset = self.startingContentInsets lastScrollView.scrollIndicatorInsets = self.startingScrollIndicatorInsets } if lastScrollView.shouldRestoreScrollViewContentOffset, !lastScrollView.contentOffset.equalTo(self.startingContentOffset) { self.showLog("Restoring contentOffset to: \(self.startingContentOffset)") let animatedContentOffset = self.textFieldView?.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil // (Bug ID: #1365, #1508, #1541) if animatedContentOffset { lastScrollView.setContentOffset(self.startingContentOffset, animated: UIView.areAnimationsEnabled) } else { lastScrollView.contentOffset = self.startingContentOffset } } // TODO: restore scrollView state // This is temporary solution. Have to implement the save and restore scrollView state var superScrollView: UIScrollView? = lastScrollView while let scrollView = superScrollView { let contentSize = CGSize(width: max(scrollView.contentSize.width, scrollView.frame.width), height: max(scrollView.contentSize.height, scrollView.frame.height)) let minimumY = contentSize.height - scrollView.frame.height if minimumY < scrollView.contentOffset.y { let newContentOffset = CGPoint(x: scrollView.contentOffset.x, y: minimumY) if scrollView.contentOffset.equalTo(newContentOffset) == false { let animatedContentOffset = self.textFieldView?.superviewOfClassType(UIStackView.self, belowView: scrollView) != nil // (Bug ID: #1365, #1508, #1541) if animatedContentOffset { scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled) } else { scrollView.contentOffset = newContentOffset } self.showLog("Restoring contentOffset to: \(self.startingContentOffset)") } } superScrollView = scrollView.superviewOfClassType(UIScrollView.self) as? UIScrollView } }) } restorePosition() //Reset all values lastScrollView = nil keyboardFrame = CGRect.zero startingContentInsets = UIEdgeInsets() startingScrollIndicatorInsets = UIEdgeInsets() startingContentOffset = CGPoint.zero // topViewBeginRect = CGRectZero //Commented due to #82 let elapsedTime = CACurrentMediaTime() - startTime showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1) } @objc internal func keyboardDidHide(_ notification: Notification) { let startTime = CACurrentMediaTime() showLog("****** \(#function) started ******", indentation: 1) topViewBeginOrigin = IQKeyboardManager.kIQCGPointInvalid keyboardFrame = CGRect.zero let elapsedTime = CACurrentMediaTime() - startTime showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1) } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager+UITextFieldViewNotification.swift ================================================ // // IQKeyboardManager+UITextFieldViewNotification.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit // MARK: UITextField/UITextView Notifications @available(iOSApplicationExtension, unavailable) internal extension IQKeyboardManager { private struct AssociatedKeys { static var textFieldView = "textFieldView" static var topViewBeginOrigin = "topViewBeginOrigin" static var rootViewController = "rootViewController" static var rootViewControllerWhilePopGestureRecognizerActive = "rootViewControllerWhilePopGestureRecognizerActive" static var topViewBeginOriginWhilePopGestureRecognizerActive = "topViewBeginOriginWhilePopGestureRecognizerActive" } /** To save UITextField/UITextView object voa textField/textView notifications. */ weak var textFieldView: UIView? { get { return (objc_getAssociatedObject(self, &AssociatedKeys.textFieldView) as? WeakObjectContainer)?.object as? UIView } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.textFieldView, WeakObjectContainer(object: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } var topViewBeginOrigin: CGPoint { get { return objc_getAssociatedObject(self, &AssociatedKeys.topViewBeginOrigin) as? CGPoint ?? IQKeyboardManager.kIQCGPointInvalid } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.topViewBeginOrigin, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** To save rootViewController */ weak var rootViewController: UIViewController? { get { return (objc_getAssociatedObject(self, &AssociatedKeys.rootViewController) as? WeakObjectContainer)?.object as? UIViewController } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.rootViewController, WeakObjectContainer(object: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** To overcome with popGestureRecognizer issue Bug ID: #1361 */ weak var rootViewControllerWhilePopGestureRecognizerActive: UIViewController? { get { return (objc_getAssociatedObject(self, &AssociatedKeys.rootViewControllerWhilePopGestureRecognizerActive) as? WeakObjectContainer)?.object as? UIViewController } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.rootViewControllerWhilePopGestureRecognizerActive, WeakObjectContainer(object: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } var topViewBeginOriginWhilePopGestureRecognizerActive: CGPoint { get { return objc_getAssociatedObject(self, &AssociatedKeys.topViewBeginOriginWhilePopGestureRecognizerActive) as? CGPoint ?? IQKeyboardManager.kIQCGPointInvalid } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.topViewBeginOriginWhilePopGestureRecognizerActive, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } /** UITextFieldTextDidBeginEditingNotification, UITextViewTextDidBeginEditingNotification. Fetching UITextFieldView object. */ @objc func textFieldViewDidBeginEditing(_ notification: Notification) { let startTime = CACurrentMediaTime() showLog("****** \(#function) started ******", indentation: 1) // Getting object textFieldView = notification.object as? UIView if overrideKeyboardAppearance, let textInput = textFieldView as? UITextInput, textInput.keyboardAppearance != keyboardAppearance { //Setting textField keyboard appearance and reloading inputViews. if let textFieldView = textFieldView as? UITextField { textFieldView.keyboardAppearance = keyboardAppearance } else if let textFieldView = textFieldView as? UITextView { textFieldView.keyboardAppearance = keyboardAppearance } textFieldView?.reloadInputViews() } //If autoToolbar enable, then add toolbar on all the UITextField/UITextView's if required. if privateIsEnableAutoToolbar() { //UITextView special case. Keyboard Notification is firing before textView notification so we need to resign it first and then again set it as first responder to add toolbar on it. if let textView = textFieldView as? UIScrollView, textView.responds(to: #selector(getter: UITextView.isEditable)), textView.inputAccessoryView == nil { UIView.animate(withDuration: 0.00001, delay: 0, options: animationCurve, animations: { () -> Void in self.addToolbarIfRequired() }, completion: { (_) -> Void in //On textView toolbar didn't appear on first time, so forcing textView to reload it's inputViews. textView.reloadInputViews() }) } else { //Adding toolbar addToolbarIfRequired() } } else { removeToolbarIfRequired() } resignFirstResponderGesture.isEnabled = privateShouldResignOnTouchOutside() textFieldView?.window?.addGestureRecognizer(resignFirstResponderGesture) // (Enhancement ID: #14) if privateIsEnabled() == false { restorePosition() topViewBeginOrigin = IQKeyboardManager.kIQCGPointInvalid } else { if topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) { // (Bug ID: #5) rootViewController = textFieldView?.parentContainerViewController() if let controller = rootViewController { if rootViewControllerWhilePopGestureRecognizerActive == controller { topViewBeginOrigin = topViewBeginOriginWhilePopGestureRecognizerActive } else { topViewBeginOrigin = controller.view.frame.origin } rootViewControllerWhilePopGestureRecognizerActive = nil topViewBeginOriginWhilePopGestureRecognizerActive = IQKeyboardManager.kIQCGPointInvalid self.showLog("Saving \(controller) beginning origin: \(self.topViewBeginOrigin)") } } //If textFieldView is inside ignored responder then do nothing. (Bug ID: #37, #74, #76) //See notes:- https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html If it is UIAlertView textField then do not affect anything (Bug ID: #70). if keyboardShowing, let textFieldView = textFieldView, textFieldView.isAlertViewTextField() == false { // keyboard is already showing. adjust position. optimizedAdjustPosition() } } let elapsedTime = CACurrentMediaTime() - startTime showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1) } /** UITextFieldTextDidEndEditingNotification, UITextViewTextDidEndEditingNotification. Removing fetched object. */ @objc func textFieldViewDidEndEditing(_ notification: Notification) { let startTime = CACurrentMediaTime() showLog("****** \(#function) started ******", indentation: 1) //Removing gesture recognizer (Enhancement ID: #14) textFieldView?.window?.removeGestureRecognizer(resignFirstResponderGesture) // We check if there's a change in original frame or not. if let textView = textFieldView as? UIScrollView, textView.responds(to: #selector(getter: UITextView.isEditable)) { if isTextViewContentInsetChanged { self.isTextViewContentInsetChanged = false if textView.contentInset != self.startingTextViewContentInsets { self.showLog("Restoring textView.contentInset to: \(self.startingTextViewContentInsets)") UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in //Setting textField to it's initial contentInset textView.contentInset = self.startingTextViewContentInsets textView.scrollIndicatorInsets = self.startingTextViewScrollIndicatorInsets }, completion: { (_) -> Void in }) } } } //Setting object to nil textFieldView = nil let elapsedTime = CACurrentMediaTime() - startTime showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1) } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager.swift ================================================ // // IQKeyboardManager.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit import CoreGraphics import QuartzCore // MARK: IQToolbar tags /** Codeless drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView. Neither need to write any code nor any setup required and much more. A generic version of KeyboardManagement. https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html */ @available(iOSApplicationExtension, unavailable) @objc public final class IQKeyboardManager: NSObject { /** Returns the default singleton instance. */ @objc public static let shared = IQKeyboardManager() /** Invalid point value. */ internal static let kIQCGPointInvalid = CGPoint.init(x: CGFloat.greatestFiniteMagnitude, y: CGFloat.greatestFiniteMagnitude) // MARK: UIKeyboard handling /** Enable/disable managing distance between keyboard and textField. Default is YES(Enabled when class loads in `+(void)load` method). */ @objc public var enable = false { didSet { //If not enable, enable it. if enable, !oldValue { //If keyboard is currently showing. Sending a fake notification for keyboardWillHide to retain view's original position. if let notification = keyboardShowNotification { keyboardWillShow(notification) } showLog("Enabled") } else if !enable, oldValue { //If not disable, desable it. keyboardWillHide(nil) showLog("Disabled") } } } /** To set keyboard distance from textField. can't be less than zero. Default is 10.0. */ @objc public var keyboardDistanceFromTextField: CGFloat = 10.0 // MARK: IQToolbar handling /** Automatic add the IQToolbar functionality. Default is YES. */ @objc public var enableAutoToolbar = true { didSet { privateIsEnableAutoToolbar() ? addToolbarIfRequired() : removeToolbarIfRequired() let enableToolbar = enableAutoToolbar ? "Yes" : "NO" showLog("enableAutoToolbar: \(enableToolbar)") } } /** /** IQAutoToolbarBySubviews: Creates Toolbar according to subview's hirarchy of Textfield's in view. IQAutoToolbarByTag: Creates Toolbar according to tag property of TextField's. IQAutoToolbarByPosition: Creates Toolbar according to the y,x position of textField in it's superview coordinate. Default is IQAutoToolbarBySubviews. */ AutoToolbar managing behaviour. Default is IQAutoToolbarBySubviews. */ @objc public var toolbarManageBehaviour = IQAutoToolbarManageBehaviour.bySubviews /** If YES, then uses textField's tintColor property for IQToolbar, otherwise tint color is default. Default is NO. */ @objc public var shouldToolbarUsesTextFieldTintColor = false /** This is used for toolbar.tintColor when textfield.keyboardAppearance is UIKeyboardAppearanceDefault. If shouldToolbarUsesTextFieldTintColor is YES then this property is ignored. Default is nil and uses black color. */ @objc public var toolbarTintColor: UIColor? /** This is used for toolbar.barTintColor. Default is nil. */ @objc public var toolbarBarTintColor: UIColor? /** IQPreviousNextDisplayModeDefault: Show NextPrevious when there are more than 1 textField otherwise hide. IQPreviousNextDisplayModeAlwaysHide: Do not show NextPrevious buttons in any case. IQPreviousNextDisplayModeAlwaysShow: Always show nextPrevious buttons, if there are more than 1 textField then both buttons will be visible but will be shown as disabled. */ @objc public var previousNextDisplayMode = IQPreviousNextDisplayMode.default /** Toolbar previous/next/done button icon, If nothing is provided then check toolbarDoneBarButtonItemText to draw done button. */ @objc public var toolbarPreviousBarButtonItemImage: UIImage? @objc public var toolbarNextBarButtonItemImage: UIImage? @objc public var toolbarDoneBarButtonItemImage: UIImage? /** Toolbar previous/next/done button text, If nothing is provided then system default 'UIBarButtonSystemItemDone' will be used. */ @objc public var toolbarPreviousBarButtonItemText: String? @objc public var toolbarPreviousBarButtonItemAccessibilityLabel: String? @objc public var toolbarNextBarButtonItemText: String? @objc public var toolbarNextBarButtonItemAccessibilityLabel: String? @objc public var toolbarDoneBarButtonItemText: String? @objc public var toolbarDoneBarButtonItemAccessibilityLabel: String? /** If YES, then it add the textField's placeholder text on IQToolbar. Default is YES. */ @objc public var shouldShowToolbarPlaceholder = true /** Placeholder Font. Default is nil. */ @objc public var placeholderFont: UIFont? /** Placeholder Color. Default is nil. Which means lightGray */ @objc public var placeholderColor: UIColor? /** Placeholder Button Color when it's treated as button. Default is nil. */ @objc public var placeholderButtonColor: UIColor? // MARK: UIKeyboard appearance overriding /** Override the keyboardAppearance for all textField/textView. Default is NO. */ @objc public var overrideKeyboardAppearance = false /** If overrideKeyboardAppearance is YES, then all the textField keyboardAppearance is set using this property. */ @objc public var keyboardAppearance = UIKeyboardAppearance.default // MARK: UITextField/UITextView Next/Previous/Resign handling /** Resigns Keyboard on touching outside of UITextField/View. Default is NO. */ @objc public var shouldResignOnTouchOutside = false { didSet { resignFirstResponderGesture.isEnabled = privateShouldResignOnTouchOutside() let shouldResign = shouldResignOnTouchOutside ? "Yes" : "NO" showLog("shouldResignOnTouchOutside: \(shouldResign)") } } /** TapGesture to resign keyboard on view's touch. It's a readonly property and exposed only for adding/removing dependencies if your added gesture does have collision with this one */ @objc lazy public var resignFirstResponderGesture: UITapGestureRecognizer = { let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tapRecognized(_:))) tapGesture.cancelsTouchesInView = false tapGesture.delegate = self return tapGesture }() /*******************************************/ /** Resigns currently first responder field. */ @objc @discardableResult public func resignFirstResponder() -> Bool { guard let textFieldRetain = textFieldView else { return false } //Resigning first responder guard textFieldRetain.resignFirstResponder() else { showLog("Refuses to resign first responder: \(textFieldRetain)") // If it refuses then becoming it as first responder again. (Bug ID: #96) //If it refuses to resign then becoming it first responder again for getting notifications callback. textFieldRetain.becomeFirstResponder() return false } return true } // MARK: UISound handling /** If YES, then it plays inputClick sound on next/previous/done click. */ @objc public var shouldPlayInputClicks = true // MARK: UIAnimation handling /** If YES, then calls 'setNeedsLayout' and 'layoutIfNeeded' on any frame update of to viewController's view. */ @objc public var layoutIfNeededOnUpdate = false // MARK: Class Level disabling methods /** Disable distance handling within the scope of disabled distance handling viewControllers classes. Within this scope, 'enabled' property is ignored. Class should be kind of UIViewController. */ @objc public var disabledDistanceHandlingClasses = [UIViewController.Type]() /** Enable distance handling within the scope of enabled distance handling viewControllers classes. Within this scope, 'enabled' property is ignored. Class should be kind of UIViewController. If same Class is added in disabledDistanceHandlingClasses list, then enabledDistanceHandlingClasses will be ignored. */ @objc public var enabledDistanceHandlingClasses = [UIViewController.Type]() /** Disable automatic toolbar creation within the scope of disabled toolbar viewControllers classes. Within this scope, 'enableAutoToolbar' property is ignored. Class should be kind of UIViewController. */ @objc public var disabledToolbarClasses = [UIViewController.Type]() /** Enable automatic toolbar creation within the scope of enabled toolbar viewControllers classes. Within this scope, 'enableAutoToolbar' property is ignored. Class should be kind of UIViewController. If same Class is added in disabledToolbarClasses list, then enabledToolbarClasses will be ignore. */ @objc public var enabledToolbarClasses = [UIViewController.Type]() /** Allowed subclasses of UIView to add all inner textField, this will allow to navigate between textField contains in different superview. Class should be kind of UIView. */ @objc public var toolbarPreviousNextAllowedClasses = [UIView.Type]() /** Disabled classes to ignore 'shouldResignOnTouchOutside' property, Class should be kind of UIViewController. */ @objc public var disabledTouchResignedClasses = [UIViewController.Type]() /** Enabled classes to forcefully enable 'shouldResignOnTouchOutsite' property. Class should be kind of UIViewController. If same Class is added in disabledTouchResignedClasses list, then enabledTouchResignedClasses will be ignored. */ @objc public var enabledTouchResignedClasses = [UIViewController.Type]() /** if shouldResignOnTouchOutside is enabled then you can customise the behaviour to not recognise gesture touches on some specific view subclasses. Class should be kind of UIView. Default is [UIControl, UINavigationBar] */ @objc public var touchResignedGestureIgnoreClasses = [UIView.Type]() // MARK: Third Party Library support /// Add TextField/TextView Notifications customised Notifications. For example while using YYTextView https://github.com/ibireme/YYText /** Add/Remove customised Notification for third party customised TextField/TextView. Please be aware that the Notification object must be idential to UITextField/UITextView Notification objects and customised TextField/TextView support must be idential to UITextField/UITextView. @param didBeginEditingNotificationName This should be identical to UITextViewTextDidBeginEditingNotification @param didEndEditingNotificationName This should be identical to UITextViewTextDidEndEditingNotification */ @objc public func registerTextFieldViewClass(_ aClass: UIView.Type, didBeginEditingNotificationName: String, didEndEditingNotificationName: String) { NotificationCenter.default.addObserver(self, selector: #selector(self.textFieldViewDidBeginEditing(_:)), name: Notification.Name(rawValue: didBeginEditingNotificationName), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.textFieldViewDidEndEditing(_:)), name: Notification.Name(rawValue: didEndEditingNotificationName), object: nil) } @objc public func unregisterTextFieldViewClass(_ aClass: UIView.Type, didBeginEditingNotificationName: String, didEndEditingNotificationName: String) { NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: didBeginEditingNotificationName), object: nil) NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: didEndEditingNotificationName), object: nil) } /**************************************************************************************/ internal struct WeakObjectContainer { weak var object: AnyObject? } /**************************************************************************************/ // MARK: Initialization/Deinitialization /* Singleton Object Initialization. */ override init() { super.init() self.registerAllNotifications() //Creating gesture for @shouldResignOnTouchOutside. (Enhancement ID: #14) resignFirstResponderGesture.isEnabled = shouldResignOnTouchOutside //Loading IQToolbar, IQTitleBarButtonItem, IQBarButtonItem to fix first time keyboard appearance delay (Bug ID: #550) //If you experience exception breakpoint issue at below line then try these solutions https://stackoverflow.com/questions/27375640/all-exception-break-point-is-stopping-for-no-reason-on-simulator let textField = UITextField() textField.addDoneOnKeyboardWithTarget(nil, action: #selector(self.doneAction(_:))) textField.addPreviousNextDoneOnKeyboardWithTarget(nil, previousAction: #selector(self.previousAction(_:)), nextAction: #selector(self.nextAction(_:)), doneAction: #selector(self.doneAction(_:))) disabledDistanceHandlingClasses.append(UITableViewController.self) disabledDistanceHandlingClasses.append(UIAlertController.self) disabledToolbarClasses.append(UIAlertController.self) disabledTouchResignedClasses.append(UIAlertController.self) toolbarPreviousNextAllowedClasses.append(UITableView.self) toolbarPreviousNextAllowedClasses.append(UICollectionView.self) toolbarPreviousNextAllowedClasses.append(IQPreviousNextView.self) touchResignedGestureIgnoreClasses.append(UIControl.self) touchResignedGestureIgnoreClasses.append(UINavigationBar.self) } deinit { // Disable the keyboard manager. enable = false } /** Getting keyWindow. */ internal func keyWindow() -> UIWindow? { if let keyWindow = textFieldView?.window { return keyWindow } else { struct Static { /** @abstract Save keyWindow object for reuse. @discussion Sometimes [[UIApplication sharedApplication] keyWindow] is returning nil between the app. */ static weak var keyWindow: UIWindow? } var originalKeyWindow: UIWindow? #if swift(>=5.1) if #available(iOS 13, *) { originalKeyWindow = UIApplication.shared.connectedScenes .compactMap { $0 as? UIWindowScene } .flatMap { $0.windows } .first(where: { $0.isKeyWindow }) } else { originalKeyWindow = UIApplication.shared.keyWindow } #else originalKeyWindow = UIApplication.shared.keyWindow #endif //If original key window is not nil and the cached keywindow is also not original keywindow then changing keywindow. if let originalKeyWindow = originalKeyWindow { Static.keyWindow = originalKeyWindow } //Return KeyWindow return Static.keyWindow } } // MARK: Public Methods /* Refreshes textField/textView position if any external changes is explicitly made by user. */ @objc public func reloadLayoutIfNeeded() { guard privateIsEnabled(), keyboardShowing, topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == false, let textFieldView = textFieldView, textFieldView.isAlertViewTextField() == false else { return } optimizedAdjustPosition() } } @available(iOSApplicationExtension, unavailable) extension IQKeyboardManager: UIGestureRecognizerDelegate { /** Resigning on tap gesture. (Enhancement ID: #14)*/ @objc internal func tapRecognized(_ gesture: UITapGestureRecognizer) { if gesture.state == .ended { //Resigning currently responder textField. resignFirstResponder() } } /** Note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES. */ @objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } /** To not detect touch events in a subclass of UIControl, these may have added their own selector for specific work */ @objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { // Should not recognize gesture if the clicked view is either UIControl or UINavigationBar( IQTextFieldViewInfoModal? { for modal in textFieldInfoCache { if let view = modal.textFieldView { if view == textField { return modal } } } return nil } private func updateReturnKeyTypeOnTextField(_ view: UIView) { var superConsideredView: UIView? //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347) for disabledClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses { superConsideredView = view.superviewOfClassType(disabledClass) if superConsideredView != nil { break } } var textFields = [UIView]() //If there is a tableView in view's hierarchy, then fetching all it's subview that responds. if let unwrappedTableView = superConsideredView { // (Enhancement ID: #22) textFields = unwrappedTableView.deepResponderViews() } else { //Otherwise fetching all the siblings textFields = view.responderSiblings() //Sorting textFields according to behaviour switch IQKeyboardManager.shared.toolbarManageBehaviour { //If needs to sort it by tag case .byTag: textFields = textFields.sortedArrayByTag() //If needs to sort it by Position case .byPosition: textFields = textFields.sortedArrayByPosition() default: break } } if let lastView = textFields.last { if let textField = view as? UITextField { //If it's the last textField in responder view, else next textField.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next } else if let textView = view as? UITextView { //If it's the last textField in responder view, else next textView.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next } } } // MARK: Registering/Unregistering textFieldView /** Should pass UITextField/UITextView intance. Assign textFieldView delegate to self, change it's returnKeyType. @param view UITextField/UITextView object to register. */ @objc public func addTextFieldView(_ view: UIView) { let modal = IQTextFieldViewInfoModal(textFieldView: view, textFieldDelegate: nil, textViewDelegate: nil) if let textField = view as? UITextField { modal.originalReturnKeyType = textField.returnKeyType modal.textFieldDelegate = textField.delegate textField.delegate = self } else if let textView = view as? UITextView { modal.originalReturnKeyType = textView.returnKeyType modal.textViewDelegate = textView.delegate textView.delegate = self } textFieldInfoCache.append(modal) } /** Should pass UITextField/UITextView intance. Restore it's textFieldView delegate and it's returnKeyType. @param view UITextField/UITextView object to unregister. */ @objc public func removeTextFieldView(_ view: UIView) { if let modal = textFieldViewCachedInfo(view) { if let textField = view as? UITextField { textField.returnKeyType = modal.originalReturnKeyType textField.delegate = modal.textFieldDelegate } else if let textView = view as? UITextView { textView.returnKeyType = modal.originalReturnKeyType textView.delegate = modal.textViewDelegate } if let index = textFieldInfoCache.firstIndex(where: { $0.textFieldView == view}) { textFieldInfoCache.remove(at: index) } } } /** Add all the UITextField/UITextView responderView's. @param view UIView object to register all it's responder subviews. */ @objc public func addResponderFromView(_ view: UIView) { let textFields = view.deepResponderViews() for textField in textFields { addTextFieldView(textField) } } /** Remove all the UITextField/UITextView responderView's. @param view UIView object to unregister all it's responder subviews. */ @objc public func removeResponderFromView(_ view: UIView) { let textFields = view.deepResponderViews() for textField in textFields { removeTextFieldView(textField) } } @discardableResult private func goToNextResponderOrResign(_ view: UIView) -> Bool { var superConsideredView: UIView? //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347) for disabledClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses { superConsideredView = view.superviewOfClassType(disabledClass) if superConsideredView != nil { break } } var textFields = [UIView]() //If there is a tableView in view's hierarchy, then fetching all it's subview that responds. if let unwrappedTableView = superConsideredView { // (Enhancement ID: #22) textFields = unwrappedTableView.deepResponderViews() } else { //Otherwise fetching all the siblings textFields = view.responderSiblings() //Sorting textFields according to behaviour switch IQKeyboardManager.shared.toolbarManageBehaviour { //If needs to sort it by tag case .byTag: textFields = textFields.sortedArrayByTag() //If needs to sort it by Position case .byPosition: textFields = textFields.sortedArrayByPosition() default: break } } //Getting index of current textField. if let index = textFields.firstIndex(of: view) { //If it is not last textField. then it's next object becomeFirstResponder. if index < (textFields.count - 1) { let nextTextField = textFields[index+1] nextTextField.becomeFirstResponder() return false } else { view.resignFirstResponder() return true } } else { return true } } } // MARK: UITextFieldDelegate @available(iOSApplicationExtension, unavailable) extension IQKeyboardReturnKeyHandler: UITextFieldDelegate { @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldBeginEditing(_:))) { return unwrapDelegate.textFieldShouldBeginEditing?(textField) ?? false } } } return true } @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:))) { return unwrapDelegate.textFieldShouldEndEditing?(textField) ?? false } } } return true } @objc public func textFieldDidBeginEditing(_ textField: UITextField) { updateReturnKeyTypeOnTextField(textField) var aDelegate: UITextFieldDelegate? = delegate if aDelegate == nil { if let modal = textFieldViewCachedInfo(textField) { aDelegate = modal.textFieldDelegate } } aDelegate?.textFieldDidBeginEditing?(textField) } @objc public func textFieldDidEndEditing(_ textField: UITextField) { var aDelegate: UITextFieldDelegate? = delegate if aDelegate == nil { if let modal = textFieldViewCachedInfo(textField) { aDelegate = modal.textFieldDelegate } } aDelegate?.textFieldDidEndEditing?(textField) } @available(iOS 10.0, *) @objc public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) { var aDelegate: UITextFieldDelegate? = delegate if aDelegate == nil { if let modal = textFieldViewCachedInfo(textField) { aDelegate = modal.textFieldDelegate } } aDelegate?.textFieldDidEndEditing?(textField, reason: reason) } @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textField(_:shouldChangeCharactersIn:replacementString:))) { return unwrapDelegate.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? false } } } return true } @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldClear(_:))) { return unwrapDelegate.textFieldShouldClear?(textField) ?? false } } } return true } @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { var shouldReturn = true if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldReturn(_:))) { shouldReturn = unwrapDelegate.textFieldShouldReturn?(textField) ?? false } } } if shouldReturn { goToNextResponderOrResign(textField) return true } else { return goToNextResponderOrResign(textField) } } } // MARK: UITextViewDelegate @available(iOSApplicationExtension, unavailable) extension IQKeyboardReturnKeyHandler: UITextViewDelegate { @objc public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate { if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldBeginEditing(_:))) { return unwrapDelegate.textViewShouldBeginEditing?(textView) ?? false } } } return true } @objc public func textViewShouldEndEditing(_ textView: UITextView) -> Bool { if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate { if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldEndEditing(_:))) { return unwrapDelegate.textViewShouldEndEditing?(textView) ?? false } } } return true } @objc public func textViewDidBeginEditing(_ textView: UITextView) { updateReturnKeyTypeOnTextField(textView) var aDelegate: UITextViewDelegate? = delegate if aDelegate == nil { if let modal = textFieldViewCachedInfo(textView) { aDelegate = modal.textViewDelegate } } aDelegate?.textViewDidBeginEditing?(textView) } @objc public func textViewDidEndEditing(_ textView: UITextView) { var aDelegate: UITextViewDelegate? = delegate if aDelegate == nil { if let modal = textFieldViewCachedInfo(textView) { aDelegate = modal.textViewDelegate } } aDelegate?.textViewDidEndEditing?(textView) } @objc public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { var shouldReturn = true if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate { if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textView(_:shouldChangeTextIn:replacementText:))) { shouldReturn = (unwrapDelegate.textView?(textView, shouldChangeTextIn: range, replacementText: text)) ?? false } } } if shouldReturn, text == "\n" { shouldReturn = goToNextResponderOrResign(textView) } return shouldReturn } @objc public func textViewDidChange(_ textView: UITextView) { var aDelegate: UITextViewDelegate? = delegate if aDelegate == nil { if let modal = textFieldViewCachedInfo(textView) { aDelegate = modal.textViewDelegate } } aDelegate?.textViewDidChange?(textView) } @objc public func textViewDidChangeSelection(_ textView: UITextView) { var aDelegate: UITextViewDelegate? = delegate if aDelegate == nil { if let modal = textFieldViewCachedInfo(textView) { aDelegate = modal.textViewDelegate } } aDelegate?.textViewDidChangeSelection?(textView) } @available(iOS 10.0, *) @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange, UITextItemInteraction) -> Bool)) { return unwrapDelegate.textView?(aTextView, shouldInteractWith: URL, in: characterRange, interaction: interaction) ?? false } } } return true } @available(iOS 10.0, *) @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange, UITextItemInteraction) -> Bool)) { return unwrapDelegate.textView?(aTextView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) ?? false } } } return true } @available(iOS, deprecated: 10.0) @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange) -> Bool)) { return unwrapDelegate.textView?(aTextView, shouldInteractWith: URL, in: characterRange) ?? false } } } return true } @available(iOS, deprecated: 10.0) @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool { if delegate == nil { if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange) -> Bool)) { return unwrapDelegate.textView?(aTextView, shouldInteractWith: textAttachment, in: characterRange) ?? false } } } return true } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQTextView/IQTextView.swift ================================================ // // IQTextView.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import UIKit /** @abstract UITextView with placeholder support */ @available(iOSApplicationExtension, unavailable) open class IQTextView: UITextView { @objc required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextView.textDidChangeNotification, object: self) } @objc override public init(frame: CGRect, textContainer: NSTextContainer?) { super.init(frame: frame, textContainer: textContainer) NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextView.textDidChangeNotification, object: self) } @objc override open func awakeFromNib() { super.awakeFromNib() NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextView.textDidChangeNotification, object: self) } deinit { IQ_PlaceholderLabel.removeFromSuperview() } private var placeholderInsets: UIEdgeInsets { return UIEdgeInsets(top: self.textContainerInset.top, left: self.textContainerInset.left + self.textContainer.lineFragmentPadding, bottom: self.textContainerInset.bottom, right: self.textContainerInset.right + self.textContainer.lineFragmentPadding) } private var placeholderExpectedFrame: CGRect { let placeholderInsets = self.placeholderInsets let maxWidth = self.frame.width-placeholderInsets.left-placeholderInsets.right let expectedSize = IQ_PlaceholderLabel.sizeThatFits(CGSize(width: maxWidth, height: self.frame.height-placeholderInsets.top-placeholderInsets.bottom)) return CGRect(x: placeholderInsets.left, y: placeholderInsets.top, width: maxWidth, height: expectedSize.height) } lazy var IQ_PlaceholderLabel: UILabel = { let label = UILabel() label.autoresizingMask = [.flexibleWidth, .flexibleHeight] label.lineBreakMode = .byWordWrapping label.numberOfLines = 0 label.font = self.font label.textAlignment = self.textAlignment label.backgroundColor = UIColor.clear #if swift(>=5.1) label.textColor = UIColor.systemGray #else label.textColor = UIColor.lightText #endif label.alpha = 0 self.addSubview(label) return label }() /** @abstract To set textView's placeholder text color. */ @IBInspectable open var placeholderTextColor: UIColor? { get { return IQ_PlaceholderLabel.textColor } set { IQ_PlaceholderLabel.textColor = newValue } } /** @abstract To set textView's placeholder text. Default is nil. */ @IBInspectable open var placeholder: String? { get { return IQ_PlaceholderLabel.text } set { IQ_PlaceholderLabel.text = newValue refreshPlaceholder() } } /** @abstract To set textView's placeholder attributed text. Default is nil. */ open var attributedPlaceholder: NSAttributedString? { get { return IQ_PlaceholderLabel.attributedText } set { IQ_PlaceholderLabel.attributedText = newValue refreshPlaceholder() } } @objc override open func layoutSubviews() { super.layoutSubviews() IQ_PlaceholderLabel.frame = placeholderExpectedFrame } @objc internal func refreshPlaceholder() { if !text.isEmpty || !attributedText.string.isEmpty { IQ_PlaceholderLabel.alpha = 0 } else { IQ_PlaceholderLabel.alpha = 1 } } @objc override open var text: String! { didSet { refreshPlaceholder() } } open override var attributedText: NSAttributedString! { didSet { refreshPlaceholder() } } @objc override open var font: UIFont? { didSet { if let unwrappedFont = font { IQ_PlaceholderLabel.font = unwrappedFont } else { IQ_PlaceholderLabel.font = UIFont.systemFont(ofSize: 12) } } } @objc override open var textAlignment: NSTextAlignment { didSet { IQ_PlaceholderLabel.textAlignment = textAlignment } } @objc override weak open var delegate: UITextViewDelegate? { get { refreshPlaceholder() return super.delegate } set { super.delegate = newValue } } @objc override open var intrinsicContentSize: CGSize { guard !hasText else { return super.intrinsicContentSize } var newSize = super.intrinsicContentSize let placeholderInsets = self.placeholderInsets newSize.height = placeholderExpectedFrame.height + placeholderInsets.top + placeholderInsets.bottom return newSize } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQBarButtonItem.swift ================================================ // // IQBarButtonItem.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation - UIKit contains Foundation import UIKit @available(iOSApplicationExtension, unavailable) open class IQBarButtonItem: UIBarButtonItem { private static var _classInitialize: Void = classInitialize() @objc public override init() { _ = IQBarButtonItem._classInitialize super.init() } @objc public required init?(coder aDecoder: NSCoder) { _ = IQBarButtonItem._classInitialize super.init(coder: aDecoder) } private class func classInitialize() { let appearanceProxy = self.appearance() let states: [UIControl.State] states = [.normal, .highlighted, .disabled, .selected, .application, .reserved] for state in states { appearanceProxy.setBackgroundImage(nil, for: state, barMetrics: .default) appearanceProxy.setBackgroundImage(nil, for: state, style: .done, barMetrics: .default) appearanceProxy.setBackgroundImage(nil, for: state, style: .plain, barMetrics: .default) appearanceProxy.setBackButtonBackgroundImage(nil, for: state, barMetrics: .default) } appearanceProxy.setTitlePositionAdjustment(UIOffset(), for: .default) appearanceProxy.setBackgroundVerticalPositionAdjustment(0, for: .default) appearanceProxy.setBackButtonBackgroundVerticalPositionAdjustment(0, for: .default) } @objc override open var tintColor: UIColor? { didSet { var textAttributes = [NSAttributedString.Key: Any]() textAttributes[.foregroundColor] = tintColor if let attributes = titleTextAttributes(for: .normal) { for (key, value) in attributes { textAttributes[key] = value } } setTitleTextAttributes(textAttributes, for: .normal) } } /** Boolean to know if it's a system item or custom item, we are having a limitation that we cannot override a designated initializer, so we are manually setting this property once in initialization */ @objc internal var isSystemItem = false /** Additional target & action to do get callback action. Note that setting custom target & selector doesn't affect native functionality, this is just an additional target to get a callback. @param target Target object. @param action Target Selector. */ @objc open func setTarget(_ target: AnyObject?, action: Selector?) { if let target = target, let action = action { invocation = IQInvocation(target, action) } else { invocation = nil } } /** Customized Invocation to be called when button is pressed. invocation is internally created using setTarget:action: method. */ @objc open var invocation: IQInvocation? deinit { target = nil invocation = nil } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQInvocation.swift ================================================ // // IQInvocation.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import UIKit @available(iOSApplicationExtension, unavailable) @objc public final class IQInvocation: NSObject { @objc public weak var target: AnyObject? @objc public var action: Selector @objc public init(_ target: AnyObject, _ action: Selector) { self.target = target self.action = action } @objc public func invoke(from: Any) { if let target = target { UIApplication.shared.sendAction(action, to: target, from: from, for: UIEvent()) } } deinit { target = nil } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQPreviousNextView.swift ================================================ // // IQPreviousNextView.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import UIKit @available(iOSApplicationExtension, unavailable) @objc public class IQPreviousNextView: UIView { } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQTitleBarButtonItem.swift ================================================ // // IQTitleBarButtonItem.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import UIKit @available(iOSApplicationExtension, unavailable) open class IQTitleBarButtonItem: IQBarButtonItem { @objc open var titleFont: UIFont? { didSet { if let unwrappedFont = titleFont { titleButton?.titleLabel?.font = unwrappedFont } else { titleButton?.titleLabel?.font = UIFont.systemFont(ofSize: 13) } } } @objc override open var title: String? { didSet { titleButton?.setTitle(title, for: .normal) } } /** titleColor to be used for displaying button text when displaying title (disabled state). */ @objc open var titleColor: UIColor? { didSet { if let color = titleColor { titleButton?.setTitleColor(color, for: .disabled) } else { titleButton?.setTitleColor(UIColor.lightGray, for: .disabled) } } } /** selectableTitleColor to be used for displaying button text when button is enabled. */ @objc open var selectableTitleColor: UIColor? { didSet { if let color = selectableTitleColor { titleButton?.setTitleColor(color, for: .normal) } else { #if swift(>=5.1) titleButton?.setTitleColor(UIColor.systemBlue, for: .normal) #else titleButton?.setTitleColor(UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1), for: .normal) #endif } } } /** Customized Invocation to be called on title button action. titleInvocation is internally created using setTitleTarget:action: method. */ @objc override open var invocation: IQInvocation? { didSet { if let target = invocation?.target, let action = invocation?.action { self.isEnabled = true titleButton?.isEnabled = true titleButton?.addTarget(target, action: action, for: .touchUpInside) } else { self.isEnabled = false titleButton?.isEnabled = false titleButton?.removeTarget(nil, action: nil, for: .touchUpInside) } } } internal var titleButton: UIButton? private var _titleView: UIView? override init() { super.init() } @objc public convenience init(title: String?) { self.init(title: nil, style: .plain, target: nil, action: nil) _titleView = UIView() _titleView?.backgroundColor = UIColor.clear titleButton = UIButton(type: .system) titleButton?.isEnabled = false titleButton?.titleLabel?.numberOfLines = 3 titleButton?.setTitleColor(UIColor.lightGray, for: .disabled) #if swift(>=5.1) titleButton?.setTitleColor(UIColor.systemBlue, for: .normal) #else titleButton?.setTitleColor(UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1), for: .normal) #endif titleButton?.backgroundColor = UIColor.clear titleButton?.titleLabel?.textAlignment = .center titleButton?.setTitle(title, for: .normal) titleFont = UIFont.systemFont(ofSize: 13.0) titleButton?.titleLabel?.font = self.titleFont _titleView?.addSubview(titleButton!) if #available(iOS 11, *) { let layoutDefaultLowPriority = UILayoutPriority(rawValue: UILayoutPriority.defaultLow.rawValue-1) let layoutDefaultHighPriority = UILayoutPriority(rawValue: UILayoutPriority.defaultHigh.rawValue-1) _titleView?.translatesAutoresizingMaskIntoConstraints = false _titleView?.setContentHuggingPriority(layoutDefaultLowPriority, for: .vertical) _titleView?.setContentHuggingPriority(layoutDefaultLowPriority, for: .horizontal) _titleView?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .vertical) _titleView?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .horizontal) titleButton?.translatesAutoresizingMaskIntoConstraints = false titleButton?.setContentHuggingPriority(layoutDefaultLowPriority, for: .vertical) titleButton?.setContentHuggingPriority(layoutDefaultLowPriority, for: .horizontal) titleButton?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .vertical) titleButton?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .horizontal) let top = NSLayoutConstraint.init(item: titleButton!, attribute: .top, relatedBy: .equal, toItem: _titleView, attribute: .top, multiplier: 1, constant: 0) let bottom = NSLayoutConstraint.init(item: titleButton!, attribute: .bottom, relatedBy: .equal, toItem: _titleView, attribute: .bottom, multiplier: 1, constant: 0) let leading = NSLayoutConstraint.init(item: titleButton!, attribute: .leading, relatedBy: .equal, toItem: _titleView, attribute: .leading, multiplier: 1, constant: 0) let trailing = NSLayoutConstraint.init(item: titleButton!, attribute: .trailing, relatedBy: .equal, toItem: _titleView, attribute: .trailing, multiplier: 1, constant: 0) _titleView?.addConstraints([top, bottom, leading, trailing]) } else { _titleView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] titleButton?.autoresizingMask = [.flexibleWidth, .flexibleHeight] } customView = _titleView } @objc required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } deinit { customView = nil titleButton?.removeTarget(nil, action: nil, for: .touchUpInside) _titleView = nil titleButton = nil } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQToolbar.swift ================================================ // // IQToolbar.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import UIKit /** @abstract IQToolbar for IQKeyboardManager. */ @available(iOSApplicationExtension, unavailable) open class IQToolbar: UIToolbar, UIInputViewAudioFeedback { private static var _classInitialize: Void = classInitialize() private class func classInitialize() { let appearanceProxy = self.appearance() appearanceProxy.barTintColor = nil let positions: [UIBarPosition] = [.any, .bottom, .top, .topAttached] for position in positions { appearanceProxy.setBackgroundImage(nil, forToolbarPosition: position, barMetrics: .default) appearanceProxy.setShadowImage(nil, forToolbarPosition: .any) } //Background color appearanceProxy.backgroundColor = nil } /** Previous bar button of toolbar. */ private var privatePreviousBarButton: IQBarButtonItem? @objc open var previousBarButton: IQBarButtonItem { get { if privatePreviousBarButton == nil { privatePreviousBarButton = IQBarButtonItem(image: nil, style: .plain, target: nil, action: nil) } return privatePreviousBarButton! } set (newValue) { privatePreviousBarButton = newValue } } /** Next bar button of toolbar. */ private var privateNextBarButton: IQBarButtonItem? @objc open var nextBarButton: IQBarButtonItem { get { if privateNextBarButton == nil { privateNextBarButton = IQBarButtonItem(image: nil, style: .plain, target: nil, action: nil) } return privateNextBarButton! } set (newValue) { privateNextBarButton = newValue } } /** Title bar button of toolbar. */ private var privateTitleBarButton: IQTitleBarButtonItem? @objc open var titleBarButton: IQTitleBarButtonItem { get { if privateTitleBarButton == nil { privateTitleBarButton = IQTitleBarButtonItem(title: nil) privateTitleBarButton?.accessibilityLabel = "Title" } return privateTitleBarButton! } set (newValue) { privateTitleBarButton = newValue } } /** Done bar button of toolbar. */ private var privateDoneBarButton: IQBarButtonItem? @objc open var doneBarButton: IQBarButtonItem { get { if privateDoneBarButton == nil { privateDoneBarButton = IQBarButtonItem(title: nil, style: .done, target: nil, action: nil) } return privateDoneBarButton! } set (newValue) { privateDoneBarButton = newValue } } /** Fixed space bar button of toolbar. */ private var privateFixedSpaceBarButton: IQBarButtonItem? @objc open var fixedSpaceBarButton: IQBarButtonItem { get { if privateFixedSpaceBarButton == nil { privateFixedSpaceBarButton = IQBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) } privateFixedSpaceBarButton!.isSystemItem = true if #available(iOS 10, *) { privateFixedSpaceBarButton!.width = 6 } else { privateFixedSpaceBarButton!.width = 20 } return privateFixedSpaceBarButton! } set (newValue) { privateFixedSpaceBarButton = newValue } } override init(frame: CGRect) { _ = IQToolbar._classInitialize super.init(frame: frame) sizeToFit() autoresizingMask = .flexibleWidth self.isTranslucent = true } @objc required public init?(coder aDecoder: NSCoder) { _ = IQToolbar._classInitialize super.init(coder: aDecoder) sizeToFit() autoresizingMask = .flexibleWidth self.isTranslucent = true } @objc override open func sizeThatFits(_ size: CGSize) -> CGSize { var sizeThatFit = super.sizeThatFits(size) sizeThatFit.height = 44 return sizeThatFit } @objc override open var tintColor: UIColor! { didSet { if let unwrappedItems = items { for item in unwrappedItems { item.tintColor = tintColor } } } } @objc override open func layoutSubviews() { super.layoutSubviews() if #available(iOS 11, *) { return } else if let customTitleView = titleBarButton.customView { var leftRect = CGRect.null var rightRect = CGRect.null var isTitleBarButtonFound = false let sortedSubviews = self.subviews.sorted(by: { (view1: UIView, view2: UIView) -> Bool in if view1.frame.minX != view2.frame.minX { return view1.frame.minX < view2.frame.minX } else { return view1.frame.minY < view2.frame.minY } }) for barButtonItemView in sortedSubviews { if isTitleBarButtonFound { rightRect = barButtonItemView.frame break } else if barButtonItemView === customTitleView { isTitleBarButtonFound = true //If it's UIToolbarButton or UIToolbarTextButton (which actually UIBarButtonItem) } else if barButtonItemView.isKind(of: UIControl.self) { leftRect = barButtonItemView.frame } } let titleMargin: CGFloat = 16 let maxWidth: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX) let maxHeight = self.frame.height let sizeThatFits = customTitleView.sizeThatFits(CGSize(width: maxWidth, height: maxHeight)) var titleRect: CGRect if sizeThatFits.width > 0, sizeThatFits.height > 0 { let width = min(sizeThatFits.width, maxWidth) let height = min(sizeThatFits.height, maxHeight) var xPosition: CGFloat if !leftRect.isNull { xPosition = titleMargin + leftRect.maxX + ((maxWidth - width)/2) } else { xPosition = titleMargin } let yPosition = (maxHeight - height)/2 titleRect = CGRect(x: xPosition, y: yPosition, width: width, height: height) } else { var xPosition: CGFloat if !leftRect.isNull { xPosition = titleMargin + leftRect.maxX } else { xPosition = titleMargin } let width: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX) titleRect = CGRect(x: xPosition, y: 0, width: width, height: maxHeight) } customTitleView.frame = titleRect } } @objc open var enableInputClicksWhenVisible: Bool { return true } deinit { items = nil privatePreviousBarButton = nil privateNextBarButton = nil privateTitleBarButton = nil privateDoneBarButton = nil privateFixedSpaceBarButton = nil } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQToolbar/IQUIView+IQKeyboardToolbar.swift ================================================ // // IQUIView+IQKeyboardToolbar.swift // https://github.com/hackiftekhar/IQKeyboardManager // Copyright (c) 2013-20 Iftekhar Qurashi. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import UIKit /** IQBarButtonItemConfiguration for creating toolbar with bar button items */ @available(iOSApplicationExtension, unavailable) @objc public final class IQBarButtonItemConfiguration: NSObject { @objc public init(barButtonSystemItem: UIBarButtonItem.SystemItem, action: Selector) { self.barButtonSystemItem = barButtonSystemItem self.image = nil self.title = nil self.action = action super.init() } @objc public init(image: UIImage, action: Selector) { self.barButtonSystemItem = nil self.image = image self.title = nil self.action = action super.init() } @objc public init(title: String, action: Selector) { self.barButtonSystemItem = nil self.image = nil self.title = title self.action = action super.init() } public let barButtonSystemItem: UIBarButtonItem.SystemItem? //System Item to be used to instantiate bar button. @objc public let image: UIImage? //Image to show on bar button item if it's not a system item. @objc public let title: String? //Title to show on bar button item if it's not a system item. @objc public let action: Selector? //action for bar button item. Usually 'doneAction:(IQBarButtonItem*)item'. } /** UIImage category methods to get next/prev images */ // swiftlint:disable nesting // swiftlint:disable line_length @available(iOSApplicationExtension, unavailable) @objc public extension UIImage { static func keyboardLeftImage() -> UIImage? { struct Static { static var keyboardLeftImage: UIImage? } if Static.keyboardLeftImage == nil { let base64Data = "iVBORw0KGgoAAAANSUhEUgAAACQAAAA/CAYAAACIEWrAAAAAAXNSR0IArs4c6QAABtFJREFUaAXFmV1oHFUUx++d3SSbj/0k6Uc2u7Ob7QeVSqBSP7AUm1JpS0tb+6nFYhELxfahDxVU9KmgD0UU7ENRLLRQodRqNbVJY5IGXwRBEPHBh2x2ZpPQaDC7W2qSzc5c/3ebDTN3d5Pd7Gw6L3PPOcM5vzn33I+5Q8gTvJqbm52RYPAdIEg5DFuusdz3dq/X7XA6ewiVTvrcnvBkMvE9GNgTAQoGg16pztFLKX02mwhKOrwe99rJZPL2sgO1tbX5aiWpDzDPGHuFEvq01+2ZpEZltdutra3NjpranxC0Q4zFCLsVVZRjdtFQLTmycuUKZq/pA8zGvBiM3IiqynHoM8sCFGoJrSIO1o9u2SDCIDPXAXMCeo3bqg4UCARaJYkMEELXiTCEkauAOQm9nrPNj/+cwso7aiZQS6VBdFMeDDLz1ZAaM8Hw2FXLUHj1apnaawYIpWHxJRkjl5GZ09Az0VYVIFmWw6iXAWRGFgMynV2KxpWzhWD4s5Z3GeaZNXZGeTflwzDyGWDOFIPhQJZmqN3vX0clG7qJtHLnpktnFwFz3qQrIFgGJK+WN+D1+jGaVolxGNM/jsbVd0V9IdkSoEggsJFJlE96K8Qgus4uDMfVD0R9MbniGgr7/R1YsXkB58FgEH04HFdKhuGQFWUIo2kTZaQXQ9snvjGG9nsY2h+J+sXkJQO1BwKbMYv0YNX2ikF0ws4Pq8pFUV+KvCSgkD/0PCaMbnSTWwyCzJwDzKeivlS5bCBsOV/EsL6LAE5jEMYvSs4C5pJRX267LKBwILAVw/oOgjQZAz1mYaejinrZqF9Ku+QdY0SWOzkMaqbRGAgwOjJzKqqqXxj1S22jDBa/wsHgDqxNtwFTb3w6C0PYyWFVvWrUV9JetMsibfIuRuktkDuMgQCjYRdzYnhEvW7UV9peEKg9GNyDOeYmYOpMgRjLYD9zHDA3THoLhKIzdSgQ2k+p9A1imGEImUXNHEM3WQ7D36dghlAzhyRKeFfU8IcMV1rTtSOxePy2QWdpMw8oEggdwxp0DVFE2wy66SBg+LCv2mUa9mFZfhORrmA0mWCwz5zWdW0/uolPiFW95msIMGckQr8EjAkSo2mKMH0vMtNTVZI559lMtAdC5zCSPhEDAuaRppG9yqg6INqqJVNk5m1k5nMxAGAYYLYro8qywXAGiWYyvYSxUREIXUdtdnIKelM9ic9ZLWeXDnxdRmppdnMeEAMgUTex0XoN+lnRVg05C8Qd828pW5FvKUwD3w0pylE8lq4GhNHnPBBX+v3+tjpbTT+lZK3xId5GprqQqUNozog2K2UTEHfMDwdqJBtOKsh6MRAmxru6Ql+Jkdi0aLNKzgPijvnxia2e9WFhfUoMhC1qb1rP7BsZGZkSbVbI8xOj0Vnsn9gDMjO9DcH/MOp5G925o1aydeFko0G0WSEXBOKOh8bH/57OpDuxbPwuBsKM0Omw195taWkxbWXF55YiFwXizsbGxibSWqYTFf2b6ByZ2uqsb+jmZ82irRK5YA2JDkOekEdykXuA2CzaMP5+YanUzujkZDLfVr6mJCDu1ufzubxOZzeq6AUxDGrtVz1FXo4lYgnRVq5cMhB3zLvH1dD4I2poS14gdOuMru3A6Ps3z1aGYsEaEv1MTEw8fDQzvRP6QdGG4bep1mbv52fRebYyFGUBcb/j4+OPpmbTuzFz4yzIfCHdHQ6cK/IzabOldKlsIO4ao++/tK7tQe3cE0OhOzcSh+N+9mxaNJYgl1VDBfzVtcsyvtnobtGG+euvWV3rjMfjY6JtIXlJGTI4nMH/iQPI1A8GXbaJN13Pz6j5gi3aFpIrBeK+01E1dhAL77d5gShd47DZB/mZdZ6tiKLSLjO6tUeCoes4qjlsVPI2uk/RCNumKMqwaBNlKzKU85nBr4JXkamvc4rcHW8t87NrvjPN6YrdrQTiMTTU1OtY+67lBaQk+9+Dn2Xn2QwKq4G4a21IVd5Apq4Y4jxuUuonNvv97Jl2nnHukSJ6K9Q0EpQvYwZ/S3SGmhrPMH27qqp/ijbTV6porFTGT90u/NxdgXnKtEtATTXZKD3scTb1JFKpcWOcqgLxQIC643F7fNi6PGcMjHYjZvUjrkZPb/Jh8kHOVnUgHiiRTHQjUy5kyrx1obSBSuSI1+Xqm0ylsjP6sgBxKGTqHn6D1yNTpq0LslSPXxNH3c6mAXTfqJUTI4+76IXT3AvY5L1f4MFUhrBdy5ahHAAy1e91uzD46Es53dydYv7qWnYgHhxQgx6XexZQ2+dgZojGDuCf2p0nAsQhEqnkzz63awpz0hacve+LjqjZA7H/AWSbJ/TPf3CuAAAAAElFTkSuQmCC" if let data = Data(base64Encoded: base64Data, options: .ignoreUnknownCharacters) { Static.keyboardLeftImage = UIImage(data: data, scale: 3) } //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448) Static.keyboardLeftImage = Static.keyboardLeftImage?.imageFlippedForRightToLeftLayoutDirection() } return Static.keyboardLeftImage } static func keyboardRightImage() -> UIImage? { struct Static { static var keyboardRightImage: UIImage? } if Static.keyboardRightImage == nil { let base64Data = "iVBORw0KGgoAAAANSUhEUgAAACQAAAA/CAYAAACIEWrAAAAAAXNSR0IArs4c6QAABu5JREFUaAXFmXtsFEUcx2f3rj0Kvd29k9LHtXfXqyjGV2J8EF/hIQgp4VnahPgIxviH0ZgYNSbGmBg1McaYGGOM+o8k+EINMQjIo6UoBAVEEBGQXnvbS1ttw91epUDbu/E7lb3bm22Pu97uOQnszO+3ne/nvjM7sw9CMsXRFAi83jhnTnUmVPqacEXSGfIHPhMEoYUSejpJyKJIJNJfehxCRIiWwZktDIYBCESY56BCZ319ve9/AQr5/c8CY7VRXBDIXJfo6Kyrq2swxktRZ0NWFgoEPocza3lBDF9P6rKwsGegp4fP2dVmQzYWjkTaCCVf8iKADIou0un3+0N8zq42A2JlvEvt2QBHPv2vmfkfFvrLiNAZqq+fm4naV9OBmEISTj0MpzaZ5AShXhAd+xrr6q435SwO6Je9sVsRc+ojDNdjxiCrw8GBcUoXq6p6is9Z1TY6pPeZglOPQ/1DPaAfAVnjFMQODN/Neszqo2OqDmNa/DuPJM/G+nSn8RxYOgux9Upl5a748PBfxpwV9SmBWOexhLbdIyserEvzs8QEYSYRxFZJUfZommbpip4TaAJKi+/0SnIlEYS7jVBwqQJutXkkqT2WSPQZc8XUrwo0AZXQdntkaQYg7jWKYU4hJrZJlXKnNqxFjbnp1vMCmoDStL2KJDsBdT8n5hJFoRXAP8Q0TeVyBTfzBmI9xxNah1eRU9j7FnJKLrTbZLf7QDyRiHC5gpoFAbGe4cJ+TPRRTPTFRiU4V45/rV5FOYRzuo25QuoFA7HOsST8qCjyBcyhpUYxAJVRSloVSToMp7qMuXzr0wJincc17SCc0uDUMqMYg8JEb/W65aNYNs4Zc/nUpw3EOodTh+DUEFb15QDBKpAuTiJi8ZSl4wA/m47mUSkKiPUPwcNeWR6ghDRzUA60W+DUSTh1Og+WiVOKBmK9YBIfVRQlCqdW8FC4J16nyPJpgOe1IVsCxKAgeAxOReDUyiwoTCik13olz9lYIn6SnZurWAbERODUcY+idMGpVYBK30mwOm5d1sCpMMBPlAzoCtRvsiSdEdmDAweF/Go4pcKpX6eCstQhXQRr0O9w6hTWqTWIpTXYUMKpVXCqD079op9vPKZPNgatqGP4/pAl9wlRENnTTFqHQaG9wiN5/oZTR3it9Il8woo2nDrjUeRjcGod+nPqfTIoYDVjnToPp37W4+xoKxATgFN/ym7lCKZ4C6xJQ7EcqJZjsx7BOQdZmxXbgZhIPBE/h9uTn1BdD4gyFssUYQmgkoDaz2IlAWJCEAxLlcpBDFULoMpZLFOERdgXBWxF+4z7TyZvYy1YH1wginQvoNLrlC6XIvT5rDHVEzYeRYdINhrXJ10LK7yapPSbUgI58AC6CQAbdAj9SCntpmOjC9X+/kipgJxN/uBmALTqEOkjpecujY8t6uvv72WxUgBNvO6B1iSve8jxkdHLSwYGBgZ1QLuByuHMFoit1AUzR3psNJl8ADDnMzF7HXLhveXXuB9qNgqyOubMkXFCl0aj0Rifs8WhIAnOcPjJVsA8yAsC5xAZTixTYzHNnLPBIbwsrcA68y0u7Qd4QThzIDFyYflQLDbM5/S2pQ5VV1fPcjkc27BLLdAF9CMej/YPXxxpHhoa+kePTXa0DKiqqqpylqtiO0TuMwvRDlzaKwYHB0fMueyIJUBer1eSKmbuwJzJekPCpODM7tFUclVfX9/FbOnJW0UDhTwembil79H9XWYJujOlCmuiJHrJnJs8UhQQXhd7MF92YYe+ne8eE3hbWI20IH6Zz+Vqm3bcXCcbcz6f7xo8M7Nd2wSDgdoKGHaXWBAM639aDtXU1FS5nGV78Pe3sE6MBc58BRi2gY4Z4/nWCwZin6/EctdeCNxoEqHkC8A8hPi4KZdnoCCgQCBQi/nSjnkzj+8fzmwGzKOIJ/lcIe285xD7XOUgwj48QZhgUpR8AphHioVh4HkBsc9U7HMV3LnO9Gsp/bhb7dmIOF71FV+uOmSNtbUBwVnWgb2pkZejNPVBWFWfRBx3oNaUnEDssxTuxdvhTMAkl6LvhXvVp03xIgNTDhnmzLXss9RkMHg+f6erN2I5DPstkzrEPkOJoqMdw1TH/+AUpW91q5EX+LhVbRNQoDZwA54t2aVdYxahbwDmJXPcukgWUFNDw01UxHZAyBxeArv2q7i0X+HjVrfTQI0+3634wrMHMLPNIvRlwLxmjlsfmQDCCnwb3iTtxpzx8hK4tF/Epf0mH7er7Qw1NNyBzndh11Z4kVSKPtfdq77Nx+1sO7GiVeCNpBN3e9mFpp4BzLvZQftbExhNfv89mD87IOfGJollhjwV7o28b798DoWgLzgfD3bnAfdEjtNsT/0LGvgrBSkuN9gAAAAASUVORK5CYII=" if let data = Data(base64Encoded: base64Data, options: .ignoreUnknownCharacters) { Static.keyboardRightImage = UIImage(data: data, scale: 3) } //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448) Static.keyboardRightImage = Static.keyboardRightImage?.imageFlippedForRightToLeftLayoutDirection() } return Static.keyboardRightImage } static func keyboardUpImage() -> UIImage? { struct Static { static var keyboardUpImage: UIImage? } if Static.keyboardUpImage == nil { let base64Data = "iVBORw0KGgoAAAANSUhEUgAAAD8AAAAkCAYAAAA+TuKHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAGmklEQVRoBd1ZWWwbRRie2bVz27s2adPGxzqxqAQCIRA3CDVJGxpKaEtRoSAVISQQggdeQIIHeIAHkOCBFyQeKlARhaYHvUJa0ksVoIgKUKFqKWqdeG2nR1Lsdeo0h73D54iku7NO6ySOk3alyPN//+zM/81/7MyEkDl66j2eJXWK8vocTT82rTgXk/t8vqBNEI9QSp9zOeVkPJnomgs7ik5eUZQ6OxGOEEq9WcKUksdlWbqU0LRfi70ARSXv8Xi8dkE8CsJ+I1FK6BNYgCgW4A8jPtvtopFHqNeWCLbDIF6fkxQjK91O1z9IgRM59bMAFoV8YEFgka1EyBJfMhkH5L9ACFstS9IpRMDJyfoVEp918sGamoVCme0QyN3GG87wAKcTOBYA4hrJKf+VSCb+nsBnqYHVnr2ntra2mpWWH0BVu52fhRH2XSZDmsA/xensokC21Pv9T3J4wcWrq17gob1er7tEhMcJuYsfGoS3hdTweuBpxaM0iCJph8fLuX7DJMPWnI2GOzi8YOKseD4gB+RSQezMRRx5vRPEn88Sz7IIx8KHgT3FCBniWJUyke6o8/uXc3jBxIKTd7vdTsFJfkSo38NbCY/vPRsOPwt81KgLqeoBXc+sBjZsxLF4ZfgM7goqSqMRL1S7oOSrq6sdLodjH0rYfbyByPEOePwZ4CO8Liv3RCL70Wctr8+mA2NkT53P91iu92aCFYx8TU1NpbOi8gfs2R7iDYLxnXqYPg3c5Fm+Xygcbs/omXXATZGBBagQqNAe9Psf4d+ZiVwQ8qjqFVVl5dmi9ShvDEL90IieXtVDevic5ruOyYiAXYiA9YSxsZow0YnSKkKFjoAn8OAENsPGjKs9qnp5iSDuBXFLXsLjR4fSIy29vb2DU7UThW4d8n0zxjXtRVAYNaJnlocikWNTHZPvP1PPl2LLujM3cfbzwJXUyukQzxrZraptRCcbEDm60Wh4S0IE7McByVJQjf3yac+EfEm9ouxAcWu2TsS6koOplr6+vstWXf5IKBrejBR4ybIAlLpE1JE6j8eyh8h/dEKmS95e7w9sy57G+MkQ6sdYMrmiv79/gNdNR0YEbGKUvIIFQMRffRBtbkG0HQj6fHdcRafWmg55Gzy+BR5vtUzF2O96kjSH4nHNopsB0B0Ob6SEvcYvAPYS1UwQDyqLFcu5IZ/pTMUkjxfEoD/wLVY9+z02PXDL8RE9s0y9qMZNigIJcU37TZblfj7aUAMqURLXuqqq9sQHBi5NZbqpkBfh8a9BPLtDMz3wyImh9GhTLBab0uSmQfIQcNQ95pJkDVG3wtgdC1KFA+HaSodjdzKZ/Neou1Y7X/JC0K98BeIvWAdjp+jwUKN6/nyfVVd4JK4lunDrkwJhc6Gl1GGjwhqnLO3UNC2Rz8z5kKfw+EYQf5EfEKF+Wh+kDd0XYxd43WzKiIBfEAEjiIAm0zyUSFiU1XJF+feJy5evW3euR57C41+A+MumSbICY2dGmd6gnlPPWXRFABABP7llCXsA2mCcDjVAJoK4qryycsfAwEDSqOPb1yQPj38O4q/yL4F4aCiTXhqNRmMWXREBFMGjslOywUbToQeyyy4IrVVO53bUgEk/uZOSr/MHPsOd0hs8F4R6mI2ONKi9vRFeNxdyIqkddknOMhA2nyuy+wAqtEol8rbEYCLnZisneXj8UxB/00KGkUiGsqU90WiPRTeHACLgoNsp4eBDHzaagRS4RbCzle6ysq3xVIq/LiMW8ti5fYRVfMs4yFibsdgI05eqqhqy6OYBEE9qnSiCLhRB7tRHFzDR1oIasBU1wHTAMpHHjcmHIP4OzwXf8XMkk24IR6NneN18klEE97mc0gJwuN9oF+SFNlF8vNJR1YYacGVcN0Eet6XvY6Pw3rhi/Bc5fiEzShp7eiOnx7H5/IsI6EAELEIE3Gu0EymwyCbQZocktWEfMHa3MEa+zqe8KwjCB8bO/7f70kxvVGPqyRy6eQshAtpdsuTDN/9us5F0MQ4zTS5BaIsPDQ3jO+5/G+fjj82dIDF2CZeKjd3R6J8W3Y0BYFca+JJQssFqLuvSUqlmESHSiZywGzsgx+OZNFnWE4scN+I3WJshAnYjAm5FBNxptp16y+y2hICLEtOVMXJcI0xvDveGi/ofU7NxBZN0XIpuIIy0mUZkZNNZVf1kDAt6lZagEhjGnxbweh8wdbw5hOwdxHbwY/j9BpTM9xi4MGzFvZhpk3Bz8J5gkb19ym7cJr5w/wEmUjzJqoNVhwAAAABJRU5ErkJggg==" if let data = Data(base64Encoded: base64Data, options: .ignoreUnknownCharacters) { Static.keyboardUpImage = UIImage(data: data, scale: 3) } //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448) Static.keyboardUpImage = Static.keyboardUpImage?.imageFlippedForRightToLeftLayoutDirection() } return Static.keyboardUpImage } static func keyboardDownImage() -> UIImage? { struct Static { static var keyboardDownImage: UIImage? } if Static.keyboardDownImage == nil { let base64Data = "iVBORw0KGgoAAAANSUhEUgAAAD8AAAAkCAYAAAA+TuKHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAGp0lEQVRoBd1ZCWhcRRiemff25WrydmOtuXbfZlMo4lEpKkppm6TpZUovC4UqKlQoUhURqQcUBcWDIkhVUCuI9SpJa+2h0VZjUawUEUUUirLNXqmxSnc32WaT7O4bv0nd5R1bc+2maR8s7z9m5v+/+f/5Z94sIf89jW73Yp/bfUuWvwLfDp/H8zhwObLYmCCaPJ6FjLJPCWNHNU1bkFVeQW/Zp2l7KWUvNmlaB3DJAhvz1ntvI5R1EUpnUUKdEifHGuvr519BwKUmj/cDYNtwARNd5/NoH4GWKIhzlFKXCSzn/xCut/jD4V9N8suPYYj4ewC+2e46f55Rwp/geExKSmdzJn2l1WrXmuSXF8MQ8XfyAeeEn9KTyV3MHwq9RTh50IqLEjJHUkh3Y13dPKvuMuApIr6bUHKP1VeE+Y8MIa09Z8/+JQlltD/+Q7VaFcW6X2VsjFmbRRnbUFFZeai/v/+cUTeDaYqIv4GlfL/NR879I3qmORwOnxG6UfCCiMbjJ51VagKdlgs+91BaKVO6oVJVD8bj8WhOPkMJn1t7jTL6gNU9pHpgKJ1q7u3tjWR1OfBCEOuPf+9Sq4YwAW3ZBqNvSqsYpeuc5WUHYolE3KSbQYzP430FwB+yuoSCFtKHaXP4z3DIqDOBFwpkwHfVThXLgrYaG6IGOAmT1pZVVHw8MDDQb9TNBLrJre0E8EdtvnAeSRPeHOwN9lh1NvCiASbgG5fqRLDJEmMHsSU6GFuDGrAfNWDAqLuUNE5uL6A2bbf5wPkZrmdaAuGw36aDIC940TAajx1HBijIgEWmjpRWS4ytrnKq+1EDEibdJWAa3dqzjLGnrKaxxvt4OtXS09v7u1WX5S8KXjRABnQ7VbUCEV+Y7SDeWAJX4dfuLCnZFzt//rxRN500jqo74NvTVptY42fTnLcGI5FTVp2R/1/womEsHj/mwgxg27vd2BH8bCrLq0rKyjoTicSgUTcdNIrbkwD+nM2WOJ3qmaVI9d9sOotgTPCiPTLgi+oqdTbOAbea+lM6xyHLK8pnVXSiCCZNuiIyjZr2GArSS1YTOKie45n0UqT6L1ZdPn5c4EVHHIS6sA3WYLZvNg6E9L9GZmwZzgEdqAFDRl0xaET8EQB/2To21ngsQ0kbIv6zVXcxftzgxQDIgM+qVbUeGbDAPCCtxbfxUhdjHdGhoWGzrnAcIr4NwHflGbGf6PqyQCj0Yx7dRUUTAi9GwQQccapOL7bBm4yjIiPqSElpC5VYRzKZLPgE4M5hK0rt67CDZDM9A+k0XxmIhE6apONgJgxejBmLxw65VHUu/LjRaANeNZQpyhJZUToGBwdHjLqp0Ij4FgB/0wocaxw7DV8F4CcmM/6kwMMQRwYcrFad87DvXW8yTKlbkZVFSmlJB3bBlEk3CQYRvxfA3wbw0Vun7BAAPqjrmfaecPjbrGyib2sKTbS/LG5F4NhGe0d+fDiTuSMSiUx6F8Bn6V343N6TB3gSyb/aHwx22+2OX2KazfF3y7VMnw4FcUvCP8lJcgRtVph0yEu8pTnRBAiv270JwN+1AscQw5zr66YKXLgyVfBijBQc2YQ0PCIY4wPH2yQPERNTYpSPRSPid0qUvY/+1mU5QjJ8PVL96FhjjEdfCPDCzggyAKnPP7cZpWQFlsZ+yPGdMPaDiK/F6fEjbKeypXVK5/pGfyTYZZFPmi0UeOHAcCZI1+Oa6JjVG0SwHbcrnZDn7sytbQSPiLdLTBJXy+Z2nKcR8U09odDhfP0mKyskeBIggaERPb0WGfC1zSFK1gDcXsitER1t6m3wrkTEbRmC5ZTRCd+MiB+wjTlFwVSrfV7zdXV15aWy0oWKvNjWgJMOfyiAIklwYXLhwfd4G/47OAxnTMVRAKec3u0PB8SkFfyxFpSCGMBHTkpWHPsU2bEEKe8xDUrJdfhKnItzgiiEXKvXWhijR9CuzNgOwHWc1+87HQ5+aJQXki4KeOGgOOFJDkdnqeJowSGlweg00vsGHJAa1UpnTJKIAF5u1AM4R8S3APgeo7zQdFHS3uikz+VSSWXVlwBo+hoUbUR0ITfVHQEcEd+K4rbbOE4xaJPhYhg4HY3GcYG4HFB/so5vBT6q53TbdAAXtooe+SzghoaGakWSu2FwflZmfWMffxjAX7XKi8VPG3gBoKam5uoKpeQEDjBz7YD4dpwUd9rlxZMUPe2Nrvf19f2dTKdasap7jHIsiR3TDdxsfxq5xtpazad5g02al+Na6plpND0zTHk8Hp+4iLyU3vwLp0orLWXqrZQAAAAASUVORK5CYII=" if let data = Data(base64Encoded: base64Data, options: .ignoreUnknownCharacters) { Static.keyboardDownImage = UIImage(data: data, scale: 3) } //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448) Static.keyboardDownImage = Static.keyboardDownImage?.imageFlippedForRightToLeftLayoutDirection() } return Static.keyboardDownImage } static func keyboardPreviousImage() -> UIImage? { if #available(iOS 10, *) { return keyboardUpImage() } else { return keyboardLeftImage() } } static func keyboardNextImage() -> UIImage? { if #available(iOS 10, *) { return keyboardDownImage() } else { return keyboardRightImage() } } } /** UIView category methods to add IQToolbar on UIKeyboard. */ @available(iOSApplicationExtension, unavailable) @objc public extension UIView { private struct AssociatedKeys { static var keyboardToolbar = "keyboardToolbar" static var shouldHideToolbarPlaceholder = "shouldHideToolbarPlaceholder" static var toolbarPlaceholder = "toolbarPlaceholder" } // MARK: Toolbar /** IQToolbar references for better customization control. */ var keyboardToolbar: IQToolbar { var toolbar = inputAccessoryView as? IQToolbar if toolbar == nil { toolbar = objc_getAssociatedObject(self, &AssociatedKeys.keyboardToolbar) as? IQToolbar } if let unwrappedToolbar = toolbar { return unwrappedToolbar } else { let frame = CGRect(origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: 44)) let newToolbar = IQToolbar(frame: frame) objc_setAssociatedObject(self, &AssociatedKeys.keyboardToolbar, newToolbar, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return newToolbar } } // MARK: Toolbar title /** If `shouldHideToolbarPlaceholder` is YES, then title will not be added to the toolbar. Default to NO. */ var shouldHideToolbarPlaceholder: Bool { get { return objc_getAssociatedObject(self, &AssociatedKeys.shouldHideToolbarPlaceholder) as? Bool ?? false } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.shouldHideToolbarPlaceholder, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) self.keyboardToolbar.titleBarButton.title = self.drawingToolbarPlaceholder } } /** `toolbarPlaceholder` to override default `placeholder` text when drawing text on toolbar. */ var toolbarPlaceholder: String? { get { return objc_getAssociatedObject(self, &AssociatedKeys.toolbarPlaceholder) as? String } set(newValue) { objc_setAssociatedObject(self, &AssociatedKeys.toolbarPlaceholder, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) self.keyboardToolbar.titleBarButton.title = self.drawingToolbarPlaceholder } } /** `drawingToolbarPlaceholder` will be actual text used to draw on toolbar. This would either `placeholder` or `toolbarPlaceholder`. */ var drawingToolbarPlaceholder: String? { if self.shouldHideToolbarPlaceholder { return nil } else if self.toolbarPlaceholder?.isEmpty == false { return self.toolbarPlaceholder } else if self.responds(to: #selector(getter: UITextField.placeholder)) { if let textField = self as? UITextField { return textField.placeholder } else if let textView = self as? IQTextView { return textView.placeholder } else { return nil } } else { return nil } } // MARK: Private helper // swiftlint:disable nesting private static func flexibleBarButtonItem () -> IQBarButtonItem { struct Static { static let nilButton = IQBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) } Static.nilButton.isSystemItem = true return Static.nilButton } // MARK: Common func addKeyboardToolbarWithTarget(target: AnyObject?, titleText: String?, rightBarButtonConfiguration: IQBarButtonItemConfiguration?, previousBarButtonConfiguration: IQBarButtonItemConfiguration? = nil, nextBarButtonConfiguration: IQBarButtonItemConfiguration? = nil) { //If can't set InputAccessoryView. Then return if self.responds(to: #selector(setter: UITextField.inputAccessoryView)) { // Creating a toolBar for phoneNumber keyboard let toolbar = self.keyboardToolbar var items: [IQBarButtonItem] = [] if let prevConfig = previousBarButtonConfiguration { var prev = toolbar.previousBarButton if prevConfig.barButtonSystemItem == nil, !prev.isSystemItem { prev.title = prevConfig.title prev.accessibilityLabel = prevConfig.accessibilityLabel prev.image = prevConfig.image prev.target = target prev.action = prevConfig.action } else { if let systemItem = prevConfig.barButtonSystemItem { prev = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: prevConfig.action) prev.isSystemItem = true } else if let image = prevConfig.image { prev = IQBarButtonItem(image: image, style: .plain, target: target, action: prevConfig.action) } else { prev = IQBarButtonItem(title: prevConfig.title, style: .plain, target: target, action: prevConfig.action) } prev.invocation = toolbar.previousBarButton.invocation prev.accessibilityLabel = prevConfig.accessibilityLabel prev.isEnabled = toolbar.previousBarButton.isEnabled prev.tag = toolbar.previousBarButton.tag toolbar.previousBarButton = prev } items.append(prev) } if previousBarButtonConfiguration != nil, nextBarButtonConfiguration != nil { items.append(toolbar.fixedSpaceBarButton) } if let nextConfig = nextBarButtonConfiguration { var next = toolbar.nextBarButton if nextConfig.barButtonSystemItem == nil, !next.isSystemItem { next.title = nextConfig.title next.accessibilityLabel = nextConfig.accessibilityLabel next.image = nextConfig.image next.target = target next.action = nextConfig.action } else { if let systemItem = nextConfig.barButtonSystemItem { next = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: nextConfig.action) next.isSystemItem = true } else if let image = nextConfig.image { next = IQBarButtonItem(image: image, style: .plain, target: target, action: nextConfig.action) } else { next = IQBarButtonItem(title: nextConfig.title, style: .plain, target: target, action: nextConfig.action) } next.invocation = toolbar.nextBarButton.invocation next.accessibilityLabel = nextConfig.accessibilityLabel next.isEnabled = toolbar.nextBarButton.isEnabled next.tag = toolbar.nextBarButton.tag toolbar.nextBarButton = next } items.append(next) } //Title bar button item do { //Flexible space items.append(UIView.flexibleBarButtonItem()) //Title button toolbar.titleBarButton.title = titleText if #available(iOS 11, *) {} else { toolbar.titleBarButton.customView?.frame = CGRect.zero } items.append(toolbar.titleBarButton) //Flexible space items.append(UIView.flexibleBarButtonItem()) } if let rightConfig = rightBarButtonConfiguration { var done = toolbar.doneBarButton if rightConfig.barButtonSystemItem == nil, !done.isSystemItem { done.title = rightConfig.title done.accessibilityLabel = rightConfig.accessibilityLabel done.image = rightConfig.image done.target = target done.action = rightConfig.action } else { if let systemItem = rightConfig.barButtonSystemItem { done = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: rightConfig.action) done.isSystemItem = true } else if let image = rightConfig.image { done = IQBarButtonItem(image: image, style: .plain, target: target, action: rightConfig.action) } else { done = IQBarButtonItem(title: rightConfig.title, style: .plain, target: target, action: rightConfig.action) } done.invocation = toolbar.doneBarButton.invocation done.accessibilityLabel = rightConfig.accessibilityLabel done.isEnabled = toolbar.doneBarButton.isEnabled done.tag = toolbar.doneBarButton.tag toolbar.doneBarButton = done } items.append(done) } // Adding button to toolBar. toolbar.items = items if let textInput = self as? UITextInput { switch textInput.keyboardAppearance { case .dark?: toolbar.barStyle = .black default: toolbar.barStyle = .default } } // Setting toolbar to keyboard. if let textField = self as? UITextField { textField.inputAccessoryView = toolbar } else if let textView = self as? UITextView { textView.inputAccessoryView = toolbar } } } // MARK: Right func addDoneOnKeyboardWithTarget(_ target: AnyObject?, action: Selector, shouldShowPlaceholder: Bool = false) { addDoneOnKeyboardWithTarget(target, action: action, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) } func addDoneOnKeyboardWithTarget(_ target: AnyObject?, action: Selector, titleText: String?) { let rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: action) addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration) } func addRightButtonOnKeyboardWithImage(_ image: UIImage, target: AnyObject?, action: Selector, shouldShowPlaceholder: Bool = false) { addRightButtonOnKeyboardWithImage(image, target: target, action: action, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) } func addRightButtonOnKeyboardWithImage(_ image: UIImage, target: AnyObject?, action: Selector, titleText: String?) { let rightConfiguration = IQBarButtonItemConfiguration(image: image, action: action) addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration) } func addRightButtonOnKeyboardWithText(_ text: String, target: AnyObject?, action: Selector, shouldShowPlaceholder: Bool = false) { addRightButtonOnKeyboardWithText(text, target: target, action: action, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) } func addRightButtonOnKeyboardWithText(_ text: String, target: AnyObject?, action: Selector, titleText: String?) { let rightConfiguration = IQBarButtonItemConfiguration(title: text, action: action) addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration) } // MARK: Right/Left func addCancelDoneOnKeyboardWithTarget(_ target: AnyObject?, cancelAction: Selector, doneAction: Selector, shouldShowPlaceholder: Bool = false) { addCancelDoneOnKeyboardWithTarget(target, cancelAction: cancelAction, doneAction: doneAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) } func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonTitle: String, rightButtonTitle: String, leftButtonAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) { addRightLeftOnKeyboardWithTarget(target, leftButtonTitle: leftButtonTitle, rightButtonTitle: rightButtonTitle, leftButtonAction: leftButtonAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) } func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonImage: UIImage, rightButtonImage: UIImage, leftButtonAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) { addRightLeftOnKeyboardWithTarget(target, leftButtonImage: leftButtonImage, rightButtonImage: rightButtonImage, leftButtonAction: leftButtonAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) } func addCancelDoneOnKeyboardWithTarget(_ target: AnyObject?, cancelAction: Selector, doneAction: Selector, titleText: String?) { let leftConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .cancel, action: cancelAction) let rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: doneAction) addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: leftConfiguration) } func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonTitle: String, rightButtonTitle: String, leftButtonAction: Selector, rightButtonAction: Selector, titleText: String?) { let leftConfiguration = IQBarButtonItemConfiguration(title: leftButtonTitle, action: leftButtonAction) let rightConfiguration = IQBarButtonItemConfiguration(title: rightButtonTitle, action: rightButtonAction) addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: leftConfiguration) } func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonImage: UIImage, rightButtonImage: UIImage, leftButtonAction: Selector, rightButtonAction: Selector, titleText: String?) { let leftConfiguration = IQBarButtonItemConfiguration(image: leftButtonImage, action: leftButtonAction) let rightConfiguration = IQBarButtonItemConfiguration(image: rightButtonImage, action: rightButtonAction) addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: leftConfiguration) } // MARK: Previous/Next/Right func addPreviousNextDoneOnKeyboardWithTarget (_ target: AnyObject?, previousAction: Selector, nextAction: Selector, doneAction: Selector, shouldShowPlaceholder: Bool = false) { addPreviousNextDoneOnKeyboardWithTarget(target, previousAction: previousAction, nextAction: nextAction, doneAction: doneAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) } func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonImage: UIImage, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) { addPreviousNextRightOnKeyboardWithTarget(target, rightButtonImage: rightButtonImage, previousAction: previousAction, nextAction: nextAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) } func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonTitle: String, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) { addPreviousNextRightOnKeyboardWithTarget(target, rightButtonTitle: rightButtonTitle, previousAction: previousAction, nextAction: nextAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) } func addPreviousNextDoneOnKeyboardWithTarget (_ target: AnyObject?, previousAction: Selector, nextAction: Selector, doneAction: Selector, titleText: String?) { let rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: doneAction) let nextConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardNextImage() ?? UIImage(), action: nextAction) let prevConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardPreviousImage() ?? UIImage(), action: previousAction) addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration) } func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonImage: UIImage, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, titleText: String?) { let rightConfiguration = IQBarButtonItemConfiguration(image: rightButtonImage, action: rightButtonAction) let nextConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardNextImage() ?? UIImage(), action: nextAction) let prevConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardPreviousImage() ?? UIImage(), action: previousAction) addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration) } func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonTitle: String, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, titleText: String?) { let rightConfiguration = IQBarButtonItemConfiguration(title: rightButtonTitle, action: rightButtonAction) let nextConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardNextImage() ?? UIImage(), action: nextAction) let prevConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardPreviousImage() ?? UIImage(), action: previousAction) addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration) } } ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/LICENSE.md ================================================ MIT License Copyright (c) 2013-2017 Iftekhar Qurashi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift/README.md ================================================

Icon

IQKeyboardManager

GitHub license [![Build Status](https://travis-ci.org/hackiftekhar/IQKeyboardManager.svg)](https://travis-ci.org/hackiftekhar/IQKeyboardManager) While developing iOS apps, we often run into issues where the iPhone keyboard slides up and covers the `UITextField/UITextView`. `IQKeyboardManager` allows you to prevent this issue of keyboard sliding up and covering `UITextField/UITextView` without needing you to write any code or make any additional setup. To use `IQKeyboardManager` you simply need to add source files to your project. #### Key Features 1) `**CODELESS**, Zero Lines of Code` 2) `Works Automatically` 3) `No More UIScrollView` 4) `No More Subclasses` 5) `No More Manual Work` 6) `No More #imports` `IQKeyboardManager` works on all orientations, and with the toolbar. It also has nice optional features allowing you to customize the distance from the text field, behaviour of previous, next and done buttons in the keyboard toolbar, play sound when the user navigates through the form and more. ## Screenshot [![IQKeyboardManager](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManagerScreenshot.png)](http://youtu.be/6nhLw6hju2A) [![Settings](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManagerSettings.png)](http://youtu.be/6nhLw6hju2A) ## GIF animation [![IQKeyboardManager](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManager.gif)](http://youtu.be/6nhLw6hju2A) ## Video IQKeyboardManager Demo Video ## Tutorial video by @rebeloper ([#1135](https://github.com/hackiftekhar/IQKeyboardManager/issues/1135)) @rebeloper demonstrated two videos on how to implement **IQKeyboardManager** at it's core: Youtube Tutorial Playlist https://www.youtube.com/playlist?list=PL_csAAO9PQ8aTL87XnueOXi3RpWE2m_8v ## Warning - **If you're planning to build SDK/library/framework and want to handle UITextField/UITextView with IQKeyboardManager then you're totally going the wrong way.** I would never suggest to add **IQKeyboardManager** as **dependency/adding/shipping** with any third-party library. Instead of adding **IQKeyboardManager** you should implement your own solution to achieve same kind of results. **IQKeyboardManager** is totally designed for projects to help developers for their convenience, it's not designed for **adding/dependency/shipping** with any **third-party library**, because **doing this could block adoption by other developers for their projects as well (who are not using IQKeyboardManager and have implemented their custom solution to handle UITextField/UITextView in the project).** - If **IQKeyboardManager** conflicts with other **third-party library**, then it's **developer responsibility** to **enable/disable IQKeyboardManager** when **presenting/dismissing** third-party library UI. Third-party libraries are not responsible to handle IQKeyboardManager. ## Requirements [![Platform iOS](https://img.shields.io/badge/Platform-iOS-blue.svg?style=fla)]() | | Language | Minimum iOS Target | Minimum Xcode Version | |------------------------|----------|--------------------|-----------------------| | IQKeyboardManager | Obj-C | iOS 8.0 | Xcode 9 | | IQKeyboardManagerSwift | Swift | iOS 8.0 | Xcode 9 | | Demo Project | | | Xcode 11 | #### Swift versions support | Swift | Xcode | IQKeyboardManagerSwift | |-------------------|-------|------------------------| | 5.1, 5.0, 4.2, 4.0, 3.2, 3.0| 11 | >= 6.5.0 | | 5.0,4.2, 4.0, 3.2, 3.0| 10.2 | >= 6.2.1 | | 4.2, 4.0, 3.2, 3.0| 10.0 | >= 6.0.4 | | 4.0, 3.2, 3.0 | 9.0 | 5.0.0 | Installation ========================== #### Installation with CocoaPods [![CocoaPods](https://img.shields.io/cocoapods/v/IQKeyboardManager.svg)](http://cocoadocs.org/docsets/IQKeyboardManager) ***IQKeyboardManager (Objective-C):*** IQKeyboardManager is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile: ([#9](https://github.com/hackiftekhar/IQKeyboardManager/issues/9)) ```ruby pod 'IQKeyboardManager' #iOS8 and later ``` ***IQKeyboardManager (Swift):*** IQKeyboardManagerSwift is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile: ([#236](https://github.com/hackiftekhar/IQKeyboardManager/issues/236)) *Swift 5.1, 5.0, 4.2, 4.0, 3.2, 3.0 (Xcode 11)* ```ruby pod 'IQKeyboardManagerSwift' ``` *Or you can choose the version you need based on Swift support table from [Requirements](README.md#requirements)* ```ruby pod 'IQKeyboardManagerSwift', '6.3.0' ``` In AppDelegate.swift, just import IQKeyboardManagerSwift framework and enable IQKeyboardManager. ```swift import IQKeyboardManagerSwift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { IQKeyboardManager.shared.enable = true return true } } ``` #### Installation with Carthage [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. You can install Carthage with [Homebrew](http://brew.sh/) using the following command: ```bash $ brew update $ brew install carthage ``` To integrate `IQKeyboardManger` or `IQKeyboardManagerSwift` into your Xcode project using Carthage, add the following line to your `Cartfile`: ```ogdl github "hackiftekhar/IQKeyboardManager" ``` Run `carthage` to build the frameworks and drag the appropriate framework (`IQKeyboardManager.framework` or `IQKeyboardManagerSwift.framework`) into your Xcode project based on your need. Make sure to add only one framework and not both. #### Installation with Source Code [![Github tag](https://img.shields.io/github/tag/hackiftekhar/iqkeyboardmanager.svg)]() ***IQKeyboardManager (Objective-C):*** Just ***drag and drop*** `IQKeyboardManager` directory from demo project to your project. That's it. ***IQKeyboardManager (Swift):*** ***Drag and drop*** `IQKeyboardManagerSwift` directory from demo project to your project In AppDelegate.swift, just enable IQKeyboardManager. ```swift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { IQKeyboardManager.shared.enable = true return true } } ``` #### Installation with Swift Package Manager [Swift Package Manager(SPM)](https://swift.org/package-manager/) is Apple's dependency manager tool. It is now supported in Xcode 11. So it can be used in all appleOS types of projects. It can be used alongside other tools like CocoaPods and Carthage as well. To install IQKeyboardManager package into your packages, add a reference to IQKeyboardManager and a targeting release version in the dependencies section in `Package.swift` file: ```swift import PackageDescription let package = Package( name: "YOUR_PROJECT_NAME", products: [], dependencies: [ .package(url: "https://github.com/hackiftekhar/IQKeyboardManager.git", from: "6.5.0") ] ) ``` To install IQKeyboardManager package via Xcode * Go to File -> Swift Packages -> Add Package Dependency... * Then search for https://github.com/hackiftekhar/IQKeyboardManager.git * And choose the version you want Migration Guide ========================== - [IQKeyboardManager 6.0.0 Migration Guide](https://github.com/hackiftekhar/IQKeyboardManager/wiki/IQKeyboardManager-6.0.0-Migration-Guide) Other Links ========================== - [Known Issues](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Known-Issues) - [Manual Management Tweaks](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Manual-Management) - [Properties and functions usage](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Properties-&-Functions) ## Flow Diagram [![IQKeyboardManager CFD](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Screenshot/IQKeyboardManagerFlowDiagram.jpg)](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Screenshot/IQKeyboardManagerFlowDiagram.jpg) If you would like to see detailed Flow diagram then check [Detailed Flow Diagram](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManagerCFD.jpg). LICENSE --- Distributed under the MIT License. Contributions --- Any contribution is more than welcome! You can contribute through pull requests and issues on GitHub. Author --- If you wish to contact me, email at: hack.iftekhar@gmail.com ================================================ FILE: JetChat/Pods/IQKeyboardManagerSwift.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 09F78371F70C68C71B694C323479D046 /* IQUIView+IQKeyboardToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10A6BBAC5E85397B6190ED993912E11F /* IQUIView+IQKeyboardToolbar.swift */; }; 0DC3549480F18768F124FA46A82DF1FA /* IQToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76C2C0372BCB3128DF6CA0C53A9E451A /* IQToolbar.swift */; }; 261F5BD0E587B7A6729F0C70C66D914B /* IQKeyboardManagerConstantsInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92C832DB42077D84C205249F9E997905 /* IQKeyboardManagerConstantsInternal.swift */; }; 2678EF9A64B7AC17B365DE0F0C4FA336 /* IQUIViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA60EFCD6DA12DDF7AE96003860DDC9F /* IQUIViewController+Additions.swift */; }; 2D3468D1970863E4C141E7648F07DE07 /* IQKeyboardManager+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2353E46FF36871C1A382E58209C2512F /* IQKeyboardManager+Internal.swift */; }; 379ED1A166A1F1232501538158C3574B /* IQPreviousNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1FB9DC92897C5D1421E3C934A93822 /* IQPreviousNextView.swift */; }; 37B97479813D5149C1057A437E60E585 /* IQBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E03920B0B02D80596B3F67A97082AEF4 /* IQBarButtonItem.swift */; }; 40C78E974028D3242DC56B6E1EFA93F0 /* IQNSArray+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661B78E184D4847D2EF59219CC3F5A9B /* IQNSArray+Sort.swift */; }; 47346AB31C29A468EA5D403432B8D3A6 /* IQTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0CA644EB9E5EC44C32C872B27BF5496 /* IQTextView.swift */; }; 51B4862EAB5231B79D95334697D349A9 /* IQUIScrollView+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10459AAF85BA8BD05D7A6DDBF42B5437 /* IQUIScrollView+Additions.swift */; }; 5E41FE2B5A6ECD2EDE8552238451631D /* IQUIView+Hierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D576980A1AC7CDCB8A72F07285D564 /* IQUIView+Hierarchy.swift */; }; 617358488245BCAAD55499E9116418FD /* IQInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEB8AF5FBFCC6770E99358B4B672112 /* IQInvocation.swift */; }; 66A05923F602C527222EE0C304C2E850 /* IQUITextFieldView+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B914646B657819A80DE94151ED66AD98 /* IQUITextFieldView+Additions.swift */; }; 6F42942749B76F3D22B4D090BC5C2B91 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 946A9EDB57AE4614BB8937C72844BFE6 /* QuartzCore.framework */; }; 7EA80FFAA03DDE0AFCF4C6585D704928 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D680AA427A6A61493387E0A6E4E5029 /* UIKit.framework */; }; 8B4DE7CA92B312ABD57FAEC4A236B908 /* IQTitleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99089A30A3E333438B4B2466B97FF114 /* IQTitleBarButtonItem.swift */; }; 8EF2D582CF5564471E18B0A5FB92701D /* IQKeyboardManager+Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCA4FD17F57944E5BEEBA0015885799 /* IQKeyboardManager+Position.swift */; }; 932EA9A4118FCB6C9AD7CB92452A7074 /* IQKeyboardManager+UIKeyboardNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = F682DA676AD168D3091C5B133DA42D9F /* IQKeyboardManager+UIKeyboardNotification.swift */; }; 9701F2C0D0EB05C8F878BB29DA410C12 /* IQKeyboardManager+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88BEDD53D2B5D575373CB71E58A8AB12 /* IQKeyboardManager+Toolbar.swift */; }; 9D06E20FD926D170B925C0B5EE2BDCAD /* IQKeyboardManagerConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A9007D57C0B709E3AEE1DFE50874F32 /* IQKeyboardManagerConstants.swift */; }; A2731AD5D558174D7B7FC76367CC65B7 /* IQKeyboardManager+OrientationNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A349561195FB92F21A65DB78F57AB13 /* IQKeyboardManager+OrientationNotification.swift */; }; C250B91B22DD89802F9E23A910E052C5 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0FA6DAC4FFD3027DA42EAE16FB5F47D /* CoreGraphics.framework */; }; C3AB4C7196DF999E30027F5873C91611 /* IQKeyboardManager+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFD5ABB7FA590430552E190D99A750AB /* IQKeyboardManager+Debug.swift */; }; D76E1452CEE7FB84E648900E86967C95 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E85534CACDFD728F3C94A3009624954A /* Foundation.framework */; }; D96A987E1282E35ED8EB60C0E76F439A /* IQKeyboardReturnKeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AD77D1185EB62775EE3960141FCE996 /* IQKeyboardReturnKeyHandler.swift */; }; EB7FB2AB0DD7EE8D7485DE2DA8DD4131 /* IQKeyboardManagerSwift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = ACD7538D01886F5C6399954BF036AED7 /* IQKeyboardManagerSwift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; EBC0A418E7F2FAB2EF764B95A1B3843A /* IQKeyboardManagerSwift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B74594226A518BED96E389F0B28C6E3 /* IQKeyboardManagerSwift-dummy.m */; }; F7F36074E1C10D486E34F4501E214BF0 /* IQKeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8B098F97A55365762D8D2B7FA4E857 /* IQKeyboardManager.swift */; }; F9079CF07AE7C8110F5E5AE403C1F0CD /* IQKeyboardManager+UITextFieldViewNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CB5D81ABA6E4FBD1CF6B65226B391B /* IQKeyboardManager+UITextFieldViewNotification.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0AD77D1185EB62775EE3960141FCE996 /* IQKeyboardReturnKeyHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardReturnKeyHandler.swift; path = IQKeyboardManagerSwift/IQKeyboardReturnKeyHandler.swift; sourceTree = ""; }; 0D680AA427A6A61493387E0A6E4E5029 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 10459AAF85BA8BD05D7A6DDBF42B5437 /* IQUIScrollView+Additions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIScrollView+Additions.swift"; path = "IQKeyboardManagerSwift/Categories/IQUIScrollView+Additions.swift"; sourceTree = ""; }; 10A6BBAC5E85397B6190ED993912E11F /* IQUIView+IQKeyboardToolbar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIView+IQKeyboardToolbar.swift"; path = "IQKeyboardManagerSwift/IQToolbar/IQUIView+IQKeyboardToolbar.swift"; sourceTree = ""; }; 1A349561195FB92F21A65DB78F57AB13 /* IQKeyboardManager+OrientationNotification.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+OrientationNotification.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+OrientationNotification.swift"; sourceTree = ""; }; 2353E46FF36871C1A382E58209C2512F /* IQKeyboardManager+Internal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Internal.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Internal.swift"; sourceTree = ""; }; 28D576980A1AC7CDCB8A72F07285D564 /* IQUIView+Hierarchy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIView+Hierarchy.swift"; path = "IQKeyboardManagerSwift/Categories/IQUIView+Hierarchy.swift"; sourceTree = ""; }; 2D7BA32644F54A1127B420AA6F6F2096 /* IQKeyboardManagerSwift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = IQKeyboardManagerSwift.modulemap; sourceTree = ""; }; 2E8B098F97A55365762D8D2B7FA4E857 /* IQKeyboardManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardManager.swift; path = IQKeyboardManagerSwift/IQKeyboardManager.swift; sourceTree = ""; }; 2FCA4FD17F57944E5BEEBA0015885799 /* IQKeyboardManager+Position.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Position.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Position.swift"; sourceTree = ""; }; 3A1FB9DC92897C5D1421E3C934A93822 /* IQPreviousNextView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQPreviousNextView.swift; path = IQKeyboardManagerSwift/IQToolbar/IQPreviousNextView.swift; sourceTree = ""; }; 3A2EA8E6127AE7662760044E338E4C84 /* IQKeyboardManagerSwift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "IQKeyboardManagerSwift-Info.plist"; sourceTree = ""; }; 4A9007D57C0B709E3AEE1DFE50874F32 /* IQKeyboardManagerConstants.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardManagerConstants.swift; path = IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstants.swift; sourceTree = ""; }; 4B74594226A518BED96E389F0B28C6E3 /* IQKeyboardManagerSwift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "IQKeyboardManagerSwift-dummy.m"; sourceTree = ""; }; 4C008775202466506BDDE46A61D8BF41 /* IQKeyboardManagerSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IQKeyboardManagerSwift.debug.xcconfig; sourceTree = ""; }; 55CB5D81ABA6E4FBD1CF6B65226B391B /* IQKeyboardManager+UITextFieldViewNotification.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+UITextFieldViewNotification.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+UITextFieldViewNotification.swift"; sourceTree = ""; }; 66191495FD45426E541C9C6A1C138E25 /* IQKeyboardManagerSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IQKeyboardManagerSwift.release.xcconfig; sourceTree = ""; }; 661B78E184D4847D2EF59219CC3F5A9B /* IQNSArray+Sort.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQNSArray+Sort.swift"; path = "IQKeyboardManagerSwift/Categories/IQNSArray+Sort.swift"; sourceTree = ""; }; 76C2C0372BCB3128DF6CA0C53A9E451A /* IQToolbar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQToolbar.swift; path = IQKeyboardManagerSwift/IQToolbar/IQToolbar.swift; sourceTree = ""; }; 7A053AAF86A5B414C2A43A01BD0934FB /* IQKeyboardManagerSwift */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = IQKeyboardManagerSwift; path = IQKeyboardManagerSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 88BEDD53D2B5D575373CB71E58A8AB12 /* IQKeyboardManager+Toolbar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Toolbar.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Toolbar.swift"; sourceTree = ""; }; 92C832DB42077D84C205249F9E997905 /* IQKeyboardManagerConstantsInternal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardManagerConstantsInternal.swift; path = IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstantsInternal.swift; sourceTree = ""; }; 946A9EDB57AE4614BB8937C72844BFE6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; 99089A30A3E333438B4B2466B97FF114 /* IQTitleBarButtonItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQTitleBarButtonItem.swift; path = IQKeyboardManagerSwift/IQToolbar/IQTitleBarButtonItem.swift; sourceTree = ""; }; 9AF38034AC895F027F0F8AB8D608FD3D /* IQKeyboardManagerSwift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IQKeyboardManagerSwift-prefix.pch"; sourceTree = ""; }; ACD7538D01886F5C6399954BF036AED7 /* IQKeyboardManagerSwift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IQKeyboardManagerSwift-umbrella.h"; sourceTree = ""; }; B914646B657819A80DE94151ED66AD98 /* IQUITextFieldView+Additions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUITextFieldView+Additions.swift"; path = "IQKeyboardManagerSwift/Categories/IQUITextFieldView+Additions.swift"; sourceTree = ""; }; C0FA6DAC4FFD3027DA42EAE16FB5F47D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; CA60EFCD6DA12DDF7AE96003860DDC9F /* IQUIViewController+Additions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIViewController+Additions.swift"; path = "IQKeyboardManagerSwift/Categories/IQUIViewController+Additions.swift"; sourceTree = ""; }; CEEB8AF5FBFCC6770E99358B4B672112 /* IQInvocation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQInvocation.swift; path = IQKeyboardManagerSwift/IQToolbar/IQInvocation.swift; sourceTree = ""; }; CFD5ABB7FA590430552E190D99A750AB /* IQKeyboardManager+Debug.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Debug.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Debug.swift"; sourceTree = ""; }; E03920B0B02D80596B3F67A97082AEF4 /* IQBarButtonItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQBarButtonItem.swift; path = IQKeyboardManagerSwift/IQToolbar/IQBarButtonItem.swift; sourceTree = ""; }; E85534CACDFD728F3C94A3009624954A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; F0CA644EB9E5EC44C32C872B27BF5496 /* IQTextView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQTextView.swift; path = IQKeyboardManagerSwift/IQTextView/IQTextView.swift; sourceTree = ""; }; F682DA676AD168D3091C5B133DA42D9F /* IQKeyboardManager+UIKeyboardNotification.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+UIKeyboardNotification.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+UIKeyboardNotification.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ EE9C193F8B9319E1878C2598247EC06D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C250B91B22DD89802F9E23A910E052C5 /* CoreGraphics.framework in Frameworks */, D76E1452CEE7FB84E648900E86967C95 /* Foundation.framework in Frameworks */, 6F42942749B76F3D22B4D090BC5C2B91 /* QuartzCore.framework in Frameworks */, 7EA80FFAA03DDE0AFCF4C6585D704928 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0C62A8006B186CB47F899BB62E8BDF89 /* Products */ = { isa = PBXGroup; children = ( 7A053AAF86A5B414C2A43A01BD0934FB /* IQKeyboardManagerSwift */, ); name = Products; sourceTree = ""; }; 385CD504322E21A8AB187D1715528B45 /* Frameworks */ = { isa = PBXGroup; children = ( 9500A2775E8786D82A24E7A7AF32C5E0 /* iOS */, ); name = Frameworks; sourceTree = ""; }; 705E4DC4E9C90DEA5E4C3C6F868F6023 = { isa = PBXGroup; children = ( 385CD504322E21A8AB187D1715528B45 /* Frameworks */, BF14D39036C089F7E1B3F629DA08CE5B /* IQKeyboardManagerSwift */, 0C62A8006B186CB47F899BB62E8BDF89 /* Products */, ); sourceTree = ""; }; 9500A2775E8786D82A24E7A7AF32C5E0 /* iOS */ = { isa = PBXGroup; children = ( C0FA6DAC4FFD3027DA42EAE16FB5F47D /* CoreGraphics.framework */, E85534CACDFD728F3C94A3009624954A /* Foundation.framework */, 946A9EDB57AE4614BB8937C72844BFE6 /* QuartzCore.framework */, 0D680AA427A6A61493387E0A6E4E5029 /* UIKit.framework */, ); name = iOS; sourceTree = ""; }; BF14D39036C089F7E1B3F629DA08CE5B /* IQKeyboardManagerSwift */ = { isa = PBXGroup; children = ( E03920B0B02D80596B3F67A97082AEF4 /* IQBarButtonItem.swift */, CEEB8AF5FBFCC6770E99358B4B672112 /* IQInvocation.swift */, 2E8B098F97A55365762D8D2B7FA4E857 /* IQKeyboardManager.swift */, CFD5ABB7FA590430552E190D99A750AB /* IQKeyboardManager+Debug.swift */, 2353E46FF36871C1A382E58209C2512F /* IQKeyboardManager+Internal.swift */, 1A349561195FB92F21A65DB78F57AB13 /* IQKeyboardManager+OrientationNotification.swift */, 2FCA4FD17F57944E5BEEBA0015885799 /* IQKeyboardManager+Position.swift */, 88BEDD53D2B5D575373CB71E58A8AB12 /* IQKeyboardManager+Toolbar.swift */, F682DA676AD168D3091C5B133DA42D9F /* IQKeyboardManager+UIKeyboardNotification.swift */, 55CB5D81ABA6E4FBD1CF6B65226B391B /* IQKeyboardManager+UITextFieldViewNotification.swift */, 4A9007D57C0B709E3AEE1DFE50874F32 /* IQKeyboardManagerConstants.swift */, 92C832DB42077D84C205249F9E997905 /* IQKeyboardManagerConstantsInternal.swift */, 0AD77D1185EB62775EE3960141FCE996 /* IQKeyboardReturnKeyHandler.swift */, 661B78E184D4847D2EF59219CC3F5A9B /* IQNSArray+Sort.swift */, 3A1FB9DC92897C5D1421E3C934A93822 /* IQPreviousNextView.swift */, F0CA644EB9E5EC44C32C872B27BF5496 /* IQTextView.swift */, 99089A30A3E333438B4B2466B97FF114 /* IQTitleBarButtonItem.swift */, 76C2C0372BCB3128DF6CA0C53A9E451A /* IQToolbar.swift */, 10459AAF85BA8BD05D7A6DDBF42B5437 /* IQUIScrollView+Additions.swift */, B914646B657819A80DE94151ED66AD98 /* IQUITextFieldView+Additions.swift */, 28D576980A1AC7CDCB8A72F07285D564 /* IQUIView+Hierarchy.swift */, 10A6BBAC5E85397B6190ED993912E11F /* IQUIView+IQKeyboardToolbar.swift */, CA60EFCD6DA12DDF7AE96003860DDC9F /* IQUIViewController+Additions.swift */, F9B87AFBA515A94071D76CADE8FF7150 /* Support Files */, ); name = IQKeyboardManagerSwift; path = IQKeyboardManagerSwift; sourceTree = ""; }; F9B87AFBA515A94071D76CADE8FF7150 /* Support Files */ = { isa = PBXGroup; children = ( 2D7BA32644F54A1127B420AA6F6F2096 /* IQKeyboardManagerSwift.modulemap */, 4B74594226A518BED96E389F0B28C6E3 /* IQKeyboardManagerSwift-dummy.m */, 3A2EA8E6127AE7662760044E338E4C84 /* IQKeyboardManagerSwift-Info.plist */, 9AF38034AC895F027F0F8AB8D608FD3D /* IQKeyboardManagerSwift-prefix.pch */, ACD7538D01886F5C6399954BF036AED7 /* IQKeyboardManagerSwift-umbrella.h */, 4C008775202466506BDDE46A61D8BF41 /* IQKeyboardManagerSwift.debug.xcconfig */, 66191495FD45426E541C9C6A1C138E25 /* IQKeyboardManagerSwift.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/IQKeyboardManagerSwift"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 59655A0626809782FAC9937796962C1C /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( EB7FB2AB0DD7EE8D7485DE2DA8DD4131 /* IQKeyboardManagerSwift-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 29F73A40F1F145F65BBA049AC76AB585 /* IQKeyboardManagerSwift */ = { isa = PBXNativeTarget; buildConfigurationList = 7E4427D52DC0676CBE97AF632BC16C36 /* Build configuration list for PBXNativeTarget "IQKeyboardManagerSwift" */; buildPhases = ( 59655A0626809782FAC9937796962C1C /* Headers */, 142F12A286285C7BC5D9F52175676DBC /* Sources */, EE9C193F8B9319E1878C2598247EC06D /* Frameworks */, 5CF04424C320B7C8EB6665875DD40074 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = IQKeyboardManagerSwift; productName = IQKeyboardManagerSwift; productReference = 7A053AAF86A5B414C2A43A01BD0934FB /* IQKeyboardManagerSwift */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 620DDB51B2348FFA878131EB4C01AF83 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = B229E972215028783E93F53D1B10FF8F /* Build configuration list for PBXProject "IQKeyboardManagerSwift" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 705E4DC4E9C90DEA5E4C3C6F868F6023; productRefGroup = 0C62A8006B186CB47F899BB62E8BDF89 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 29F73A40F1F145F65BBA049AC76AB585 /* IQKeyboardManagerSwift */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 5CF04424C320B7C8EB6665875DD40074 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 142F12A286285C7BC5D9F52175676DBC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 37B97479813D5149C1057A437E60E585 /* IQBarButtonItem.swift in Sources */, 617358488245BCAAD55499E9116418FD /* IQInvocation.swift in Sources */, F7F36074E1C10D486E34F4501E214BF0 /* IQKeyboardManager.swift in Sources */, C3AB4C7196DF999E30027F5873C91611 /* IQKeyboardManager+Debug.swift in Sources */, 2D3468D1970863E4C141E7648F07DE07 /* IQKeyboardManager+Internal.swift in Sources */, A2731AD5D558174D7B7FC76367CC65B7 /* IQKeyboardManager+OrientationNotification.swift in Sources */, 8EF2D582CF5564471E18B0A5FB92701D /* IQKeyboardManager+Position.swift in Sources */, 9701F2C0D0EB05C8F878BB29DA410C12 /* IQKeyboardManager+Toolbar.swift in Sources */, 932EA9A4118FCB6C9AD7CB92452A7074 /* IQKeyboardManager+UIKeyboardNotification.swift in Sources */, F9079CF07AE7C8110F5E5AE403C1F0CD /* IQKeyboardManager+UITextFieldViewNotification.swift in Sources */, 9D06E20FD926D170B925C0B5EE2BDCAD /* IQKeyboardManagerConstants.swift in Sources */, 261F5BD0E587B7A6729F0C70C66D914B /* IQKeyboardManagerConstantsInternal.swift in Sources */, EBC0A418E7F2FAB2EF764B95A1B3843A /* IQKeyboardManagerSwift-dummy.m in Sources */, D96A987E1282E35ED8EB60C0E76F439A /* IQKeyboardReturnKeyHandler.swift in Sources */, 40C78E974028D3242DC56B6E1EFA93F0 /* IQNSArray+Sort.swift in Sources */, 379ED1A166A1F1232501538158C3574B /* IQPreviousNextView.swift in Sources */, 47346AB31C29A468EA5D403432B8D3A6 /* IQTextView.swift in Sources */, 8B4DE7CA92B312ABD57FAEC4A236B908 /* IQTitleBarButtonItem.swift in Sources */, 0DC3549480F18768F124FA46A82DF1FA /* IQToolbar.swift in Sources */, 51B4862EAB5231B79D95334697D349A9 /* IQUIScrollView+Additions.swift in Sources */, 66A05923F602C527222EE0C304C2E850 /* IQUITextFieldView+Additions.swift in Sources */, 5E41FE2B5A6ECD2EDE8552238451631D /* IQUIView+Hierarchy.swift in Sources */, 09F78371F70C68C71B694C323479D046 /* IQUIView+IQKeyboardToolbar.swift in Sources */, 2678EF9A64B7AC17B365DE0F0C4FA336 /* IQUIViewController+Additions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 55BDEF7D4E4129339FE32D2AE727C48D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 66191495FD45426E541C9C6A1C138E25 /* IQKeyboardManagerSwift.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-prefix.pch"; INFOPLIST_FILE = "Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift.modulemap"; PRODUCT_MODULE_NAME = IQKeyboardManagerSwift; PRODUCT_NAME = IQKeyboardManagerSwift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.5; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 62806AFACBEC989EBB15092ACFD3D342 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4C008775202466506BDDE46A61D8BF41 /* IQKeyboardManagerSwift.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-prefix.pch"; INFOPLIST_FILE = "Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift.modulemap"; PRODUCT_MODULE_NAME = IQKeyboardManagerSwift; PRODUCT_NAME = IQKeyboardManagerSwift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.5; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 9BA584B11DDA731685E34764506D9981 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; F574F7E06A687B8215386CE16451C6D1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 7E4427D52DC0676CBE97AF632BC16C36 /* Build configuration list for PBXNativeTarget "IQKeyboardManagerSwift" */ = { isa = XCConfigurationList; buildConfigurations = ( 62806AFACBEC989EBB15092ACFD3D342 /* Debug */, 55BDEF7D4E4129339FE32D2AE727C48D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B229E972215028783E93F53D1B10FF8F /* Build configuration list for PBXProject "IQKeyboardManagerSwift" */ = { isa = XCConfigurationList; buildConfigurations = ( 9BA584B11DDA731685E34764506D9981 /* Debug */, F574F7E06A687B8215386CE16451C6D1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 620DDB51B2348FFA878131EB4C01AF83 /* Project object */; } ================================================ FILE: JetChat/Pods/Kingfisher/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2019 Wei Wang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/Kingfisher/README.md ================================================

Kingfisher


Kingfisher is a powerful, pure-Swift library for downloading and caching images from the web. It provides you a chance to use a pure-Swift way to work with remote images in your next app. ## Features - [x] Asynchronous image downloading and caching. - [x] Loading image from either `URLSession`-based networking or local provided data. - [x] Useful image processors and filters provided. - [x] Multiple-layer hybrid cache for both memory and disk. - [x] Fine control on cache behavior. Customizable expiration date and size limit. - [x] Cancelable downloading and auto-reusing previous downloaded content to improve performance. - [x] Independent components. Use the downloader, caching system, and image processors separately as you need. - [x] Prefetching images and showing them from the cache to boost your app. - [x] View extensions for `UIImageView`, `NSImageView`, `NSButton` and `UIButton` to directly set an image from a URL. - [x] Built-in transition animation when setting images. - [x] Customizable placeholder and indicator while loading images. - [x] Extensible image processing and image format easily. - [x] Low Data Mode support. - [x] SwiftUI support. ### Kingfisher 101 The simplest use-case is setting an image to an image view with the `UIImageView` extension: ```swift import Kingfisher let url = URL(string: "https://example.com/image.png") imageView.kf.setImage(with: url) ``` Kingfisher will download the image from `url`, send it to both memory cache and disk cache, and display it in `imageView`. When you set with the same URL later, the image will be retrieved from the cache and shown immediately. It also works if you use SwiftUI: ```swift var body: some View { KFImage(URL(string: "https://example.com/image.png")!) } ``` ### A More Advanced Example With the powerful options, you can do hard tasks with Kingfisher in a simple way. For example, the code below: 1. Downloads a high-resolution image. 2. Downsamples it to match the image view size. 3. Makes it round cornered with a given radius. 4. Shows a system indicator and a placeholder image while downloading. 5. When prepared, it animates the small thumbnail image with a "fade in" effect. 6. The original large image is also cached to disk for later use, to get rid of downloading it again in a detail view. 7. A console log is printed when the task finishes, either for success or failure. ```swift let url = URL(string: "https://example.com/high_resolution_image.png") let processor = DownsamplingImageProcessor(size: imageView.bounds.size) |> RoundCornerImageProcessor(cornerRadius: 20) imageView.kf.indicatorType = .activity imageView.kf.setImage( with: url, placeholder: UIImage(named: "placeholderImage"), options: [ .processor(processor), .scaleFactor(UIScreen.main.scale), .transition(.fade(1)), .cacheOriginalImage ]) { result in switch result { case .success(let value): print("Task done for: \(value.source.url?.absoluteString ?? "")") case .failure(let error): print("Job failed: \(error.localizedDescription)") } } ``` It is a common situation I can meet in my daily work. Think about how many lines you need to write without Kingfisher! ### Method Chaining If you are not a fan of the `kf` extension, you can also prefer to use the `KF` builder and chained the method invocations. The code below is doing the same thing: ```swift // Use `kf` extension imageView.kf.setImage( with: url, placeholder: placeholderImage, options: [ .processor(processor), .loadDiskFileSynchronously, .cacheOriginalImage, .transition(.fade(0.25)), .lowDataMode(.network(lowResolutionURL)) ], progressBlock: { receivedSize, totalSize in // Progress updated }, completionHandler: { result in // Done } ) // Use `KF` builder KF.url(url) .placeholder(placeholderImage) .setProcessor(processor) .loadDiskFileSynchronously() .cacheMemoryOnly() .fade(duration: 0.25) .lowDataModeSource(.network(lowResolutionURL)) .onProgress { receivedSize, totalSize in } .onSuccess { result in } .onFailure { error in } .set(to: imageView) ``` And even better, if later you want to switch to SwiftUI, just make some trivial changes and you've done. ```swift struct ContentView: View { var body: some View { KFImage.url(url) .placeholder(placeholderImage) .setProcessor(processor) .loadDiskFileSynchronously() .cacheMemoryOnly() .fade(duration: 0.25) .lowDataModeSource(.network(lowResolutionURL)) .onProgress { receivedSize, totalSize in } .onSuccess { result in } .onFailure { error in } } } ``` ### Learn More To learn the use of Kingfisher by more examples, take a look at the well-prepared [Cheat Sheet](https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet). T here we summarized the most common tasks in Kingfisher, you can get a better idea of what this framework can do. There are also some performance tips, remember to check them too. ## Requirements - iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+ - Swift 4.0+ ### Installation A detailed guide for installation can be found in [Installation Guide](https://github.com/onevcat/Kingfisher/wiki/Installation-Guide). #### Swift Package Manager - File > Swift Packages > Add Package Dependency - Add `https://github.com/onevcat/Kingfisher.git` - Select "Up to Next Major" with "6.0.0" #### CocoaPods ```ruby source 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.0' use_frameworks! target 'MyApp' do pod 'Kingfisher', '~> 6.0' end ``` #### Carthage ``` github "onevcat/Kingfisher" ~> 6.0 ``` ### Migrating [Kingfisher 6.0 Migration](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-6.0-Migration-Guide) - Kingfisher 6.x is NOT fully compatible with the previous version. However, the migration is not difficult. Depending on your use cases, it may take no effect or several minutes to modify your existing code for the new version. Please follow the [migration guide](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-6.0-Migration-Guide) when you prepare to upgrade Kingfisher in your project. If you are using an even earlier version, see the guides below to know the steps for migrating. > - [Kingfisher 5.0 Migration](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-5.0-Migration-Guide) - If you are upgrading to Kingfisher 5.x from 4.x, please read this for more information. > - Kingfisher 4.0 Migration - Kingfisher 3.x should be source compatible to Kingfisher 4. The reason for a major update is that we need to specify the Swift version explicitly for Xcode. All deprecated methods in Kingfisher 3 were removed, so please ensure you have no warning left before you migrate from Kingfisher 3 to Kingfisher 4. If you have any trouble when migrating, please open an issue to discuss. > - [Kingfisher 3.0 Migration](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-3.0-Migration-Guide) - If you are upgrading to Kingfisher 3.x from an earlier version, please read this for more information. ## Next Steps We prepared a [wiki page](https://github.com/onevcat/Kingfisher/wiki). You can find tons of useful things there. * [Installation Guide](https://github.com/onevcat/Kingfisher/wiki/Installation-Guide) - Follow it to integrate Kingfisher into your project. * [Cheat Sheet](https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet)- Curious about what Kingfisher could do and how would it look like when used in your project? See this page for useful code snippets. If you are already familiar with Kingfisher, you could also learn new tricks to improve the way you use Kingfisher! * [API Reference](https://kingfisher.onevcat.com/) - Lastly, please remember to read the full whenever you may need a more detailed reference. ## Other ### Future of Kingfisher I want to keep Kingfisher lightweight. This framework focuses on providing a simple solution for downloading and caching images. This doesn’t mean the framework can’t be improved. Kingfisher is far from perfect, so necessary and useful updates will be made to make it better. ### Developments and Tests Any contributing and pull requests are warmly welcome. However, before you plan to implement some features or try to fix an uncertain issue, it is recommended to open a discussion first. It would be appreciated if your pull requests could build and with all tests green. :) ### About the logo The logo of Kingfisher is inspired by [Tangram (七巧板)](http://en.wikipedia.org/wiki/Tangram), a dissection puzzle consisting of seven flat shapes from China. I believe she's a kingfisher bird instead of a swift, but someone insists that she is a pigeon. I guess I should give her a name. Hi, guys, do you have any suggestions? ### Contact Follow and contact me on [Twitter](http://twitter.com/onevcat) or [Sina Weibo](http://weibo.com/onevcat). If you find an issue, [open a ticket](https://github.com/onevcat/Kingfisher/issues/new). Pull requests are warmly welcome as well. ## Backers & Sponsors Open-source projects cannot live long without your help. If you find Kingfisher is useful, please consider supporting this project by becoming a sponsor. Your user icon or company logo shows up [on my blog](https://onevcat.com/tabs/about/) with a link to your home page. Become a sponsor through [GitHub Sponsors](https://github.com/sponsors/onevcat) or [Open Collective](https://opencollective.com/kingfisher#sponsor). :heart: ### License Kingfisher is released under the MIT license. See LICENSE for details. ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Cache/CacheSerializer.swift ================================================ // // CacheSerializer.swift // Kingfisher // // Created by Wei Wang on 2016/09/02. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation import CoreGraphics /// An `CacheSerializer` is used to convert some data to an image object after /// retrieving it from disk storage, and vice versa, to convert an image to data object /// for storing to the disk storage. public protocol CacheSerializer { /// Gets the serialized data from a provided image /// and optional original data for caching to disk. /// /// - Parameters: /// - image: The image needed to be serialized. /// - original: The original data which is just downloaded. /// If the image is retrieved from cache instead of /// downloaded, it will be `nil`. /// - Returns: The data object for storing to disk, or `nil` when no valid /// data could be serialized. func data(with image: KFCrossPlatformImage, original: Data?) -> Data? /// Gets an image from provided serialized data. /// /// - Parameters: /// - data: The data from which an image should be deserialized. /// - options: The parsed options for deserialization. /// - Returns: An image deserialized or `nil` when no valid image /// could be deserialized. func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? } /// Represents a basic and default `CacheSerializer` used in Kingfisher disk cache system. /// It could serialize and deserialize images in PNG, JPEG and GIF format. For /// image other than these formats, a normalized `pngRepresentation` will be used. public struct DefaultCacheSerializer: CacheSerializer { /// The default general cache serializer used across Kingfisher's cache. public static let `default` = DefaultCacheSerializer() /// The compression quality when converting image to a lossy format data. Default is 1.0. public var compressionQuality: CGFloat = 1.0 /// Whether the original data should be preferred when serializing the image. /// If `true`, the input original data will be checked first and used unless the data is `nil`. /// In that case, the serialization will fall back to creating data from image. public var preferCacheOriginalData: Bool = false /// Creates a cache serializer that serialize and deserialize images in PNG, JPEG and GIF format. /// /// - Note: /// Use `DefaultCacheSerializer.default` unless you need to specify your own properties. /// public init() { } /// - Parameters: /// - image: The image needed to be serialized. /// - original: The original data which is just downloaded. /// If the image is retrieved from cache instead of /// downloaded, it will be `nil`. /// - Returns: The data object for storing to disk, or `nil` when no valid /// data could be serialized. /// /// - Note: /// Only when `original` contains valid PNG, JPEG and GIF format data, the `image` will be /// converted to the corresponding data type. Otherwise, if the `original` is provided but it is not /// If `original` is `nil`, the input `image` will be encoded as PNG data. public func data(with image: KFCrossPlatformImage, original: Data?) -> Data? { if preferCacheOriginalData { return original ?? image.kf.data( format: original?.kf.imageFormat ?? .unknown, compressionQuality: compressionQuality ) } else { return image.kf.data( format: original?.kf.imageFormat ?? .unknown, compressionQuality: compressionQuality ) } } /// Gets an image deserialized from provided data. /// /// - Parameters: /// - data: The data from which an image should be deserialized. /// - options: Options for deserialization. /// - Returns: An image deserialized or `nil` when no valid image /// could be deserialized. public func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Cache/DiskStorage.swift ================================================ // // DiskStorage.swift // Kingfisher // // Created by Wei Wang on 2018/10/15. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents a set of conception related to storage which stores a certain type of value in disk. /// This is a namespace for the disk storage types. A `Backend` with a certain `Config` will be used to describe the /// storage. See these composed types for more information. public enum DiskStorage { /// Represents a storage back-end for the `DiskStorage`. The value is serialized to data /// and stored as file in the file system under a specified location. /// /// You can config a `DiskStorage.Backend` in its initializer by passing a `DiskStorage.Config` value. /// or modifying the `config` property after it being created. `DiskStorage` will use file's attributes to keep /// track of a file for its expiration or size limitation. public class Backend { /// The config used for this disk storage. public var config: Config // The final storage URL on disk, with `name` and `cachePathBlock` considered. public let directoryURL: URL let metaChangingQueue: DispatchQueue var maybeCached : Set? let maybeCachedCheckingQueue = DispatchQueue(label: "com.onevcat.Kingfisher.maybeCachedCheckingQueue") // `false` if the storage initialized with an error. This prevents unexpected forcibly crash when creating // storage in the default cache. private var storageReady: Bool = true /// Creates a disk storage with the given `DiskStorage.Config`. /// /// - Parameter config: The config used for this disk storage. /// - Throws: An error if the folder for storage cannot be got or created. public convenience init(config: Config) throws { self.init(noThrowConfig: config, creatingDirectory: false) try prepareDirectory() } // If `creatingDirectory` is `false`, the directory preparation will be skipped. // We need to call `prepareDirectory` manually after this returns. init(noThrowConfig config: Config, creatingDirectory: Bool) { var config = config let creation = Creation(config) self.directoryURL = creation.directoryURL // Break any possible retain cycle set by outside. config.cachePathBlock = nil self.config = config metaChangingQueue = DispatchQueue(label: creation.cacheName) setupCacheChecking() if creatingDirectory { try? prepareDirectory() } } private func setupCacheChecking() { maybeCachedCheckingQueue.async { do { self.maybeCached = Set() try self.config.fileManager.contentsOfDirectory(atPath: self.directoryURL.path).forEach { fileName in self.maybeCached?.insert(fileName) } } catch { // Just disable the functionality if we fail to initialize it properly. This will just revert to // the behavior which is to check file existence on disk directly. self.maybeCached = nil } } } // Creates the storage folder. private func prepareDirectory() throws { let fileManager = config.fileManager let path = directoryURL.path guard !fileManager.fileExists(atPath: path) else { return } do { try fileManager.createDirectory( atPath: path, withIntermediateDirectories: true, attributes: nil) } catch { self.storageReady = false throw KingfisherError.cacheError(reason: .cannotCreateDirectory(path: path, error: error)) } } /// Stores a value to the storage under the specified key and expiration policy. /// - Parameters: /// - value: The value to be stored. /// - key: The key to which the `value` will be stored. If there is already a value under the key, /// the old value will be overwritten by `value`. /// - expiration: The expiration policy used by this store action. /// - Throws: An error during converting the value to a data format or during writing it to disk. public func store( value: T, forKey key: String, expiration: StorageExpiration? = nil) throws { guard storageReady else { throw KingfisherError.cacheError(reason: .diskStorageIsNotReady(cacheURL: directoryURL)) } let expiration = expiration ?? config.expiration // The expiration indicates that already expired, no need to store. guard !expiration.isExpired else { return } let data: Data do { data = try value.toData() } catch { throw KingfisherError.cacheError(reason: .cannotConvertToData(object: value, error: error)) } let fileURL = cacheFileURL(forKey: key) do { try data.write(to: fileURL) } catch { throw KingfisherError.cacheError( reason: .cannotCreateCacheFile(fileURL: fileURL, key: key, data: data, error: error) ) } let now = Date() let attributes: [FileAttributeKey : Any] = [ // The last access date. .creationDate: now.fileAttributeDate, // The estimated expiration date. .modificationDate: expiration.estimatedExpirationSinceNow.fileAttributeDate ] do { try config.fileManager.setAttributes(attributes, ofItemAtPath: fileURL.path) } catch { try? config.fileManager.removeItem(at: fileURL) throw KingfisherError.cacheError( reason: .cannotSetCacheFileAttribute( filePath: fileURL.path, attributes: attributes, error: error ) ) } maybeCachedCheckingQueue.async { self.maybeCached?.insert(fileURL.lastPathComponent) } } /// Gets a value from the storage. /// - Parameters: /// - key: The cache key of value. /// - extendingExpiration: The expiration policy used by this getting action. /// - Throws: An error during converting the data to a value or during operation of disk files. /// - Returns: The value under `key` if it is valid and found in the storage. Otherwise, `nil`. public func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) throws -> T? { return try value(forKey: key, referenceDate: Date(), actuallyLoad: true, extendingExpiration: extendingExpiration) } func value( forKey key: String, referenceDate: Date, actuallyLoad: Bool, extendingExpiration: ExpirationExtending) throws -> T? { guard storageReady else { throw KingfisherError.cacheError(reason: .diskStorageIsNotReady(cacheURL: directoryURL)) } let fileManager = config.fileManager let fileURL = cacheFileURL(forKey: key) let filePath = fileURL.path let fileMaybeCached = maybeCachedCheckingQueue.sync { return maybeCached?.contains(fileURL.lastPathComponent) ?? true } guard fileMaybeCached else { return nil } guard fileManager.fileExists(atPath: filePath) else { return nil } let meta: FileMeta do { let resourceKeys: Set = [.contentModificationDateKey, .creationDateKey] meta = try FileMeta(fileURL: fileURL, resourceKeys: resourceKeys) } catch { throw KingfisherError.cacheError( reason: .invalidURLResource(error: error, key: key, url: fileURL)) } if meta.expired(referenceDate: referenceDate) { return nil } if !actuallyLoad { return T.empty } do { let data = try Data(contentsOf: fileURL) let obj = try T.fromData(data) metaChangingQueue.async { meta.extendExpiration(with: fileManager, extendingExpiration: extendingExpiration) } return obj } catch { throw KingfisherError.cacheError(reason: .cannotLoadDataFromDisk(url: fileURL, error: error)) } } /// Whether there is valid cached data under a given key. /// - Parameter key: The cache key of value. /// - Returns: If there is valid data under the key, `true`. Otherwise, `false`. /// /// - Note: /// This method does not actually load the data from disk, so it is faster than directly loading the cached value /// by checking the nullability of `value(forKey:extendingExpiration:)` method. /// public func isCached(forKey key: String) -> Bool { return isCached(forKey: key, referenceDate: Date()) } /// Whether there is valid cached data under a given key and a reference date. /// - Parameters: /// - key: The cache key of value. /// - referenceDate: A reference date to check whether the cache is still valid. /// - Returns: If there is valid data under the key, `true`. Otherwise, `false`. /// /// - Note: /// If you pass `Date()` to `referenceDate`, this method is identical to `isCached(forKey:)`. Use the /// `referenceDate` to determine whether the cache is still valid for a future date. public func isCached(forKey key: String, referenceDate: Date) -> Bool { do { let result = try value( forKey: key, referenceDate: referenceDate, actuallyLoad: false, extendingExpiration: .none ) return result != nil } catch { return false } } /// Removes a value from a specified key. /// - Parameter key: The cache key of value. /// - Throws: An error during removing the value. public func remove(forKey key: String) throws { let fileURL = cacheFileURL(forKey: key) try removeFile(at: fileURL) } func removeFile(at url: URL) throws { try config.fileManager.removeItem(at: url) } /// Removes all values in this storage. /// - Throws: An error during removing the values. public func removeAll() throws { try removeAll(skipCreatingDirectory: false) } func removeAll(skipCreatingDirectory: Bool) throws { try config.fileManager.removeItem(at: directoryURL) if !skipCreatingDirectory { try prepareDirectory() } } /// The URL of the cached file with a given computed `key`. /// /// - Parameter key: The final computed key used when caching the image. Please note that usually this is not /// the `cacheKey` of an image `Source`. It is the computed key with processor identifier considered. /// /// - Note: /// This method does not guarantee there is an image already cached in the returned URL. It just gives your /// the URL that the image should be if it exists in disk storage, with the give key. /// public func cacheFileURL(forKey key: String) -> URL { let fileName = cacheFileName(forKey: key) return directoryURL.appendingPathComponent(fileName, isDirectory: false) } func cacheFileName(forKey key: String) -> String { if config.usesHashedFileName { let hashedKey = key.kf.md5 if let ext = config.pathExtension { return "\(hashedKey).\(ext)" } else if config.autoExtAfterHashedFileName, let ext = key.kf.ext { return "\(hashedKey).\(ext)" } return hashedKey } else { if let ext = config.pathExtension { return "\(key).\(ext)" } return key } } func allFileURLs(for propertyKeys: [URLResourceKey]) throws -> [URL] { let fileManager = config.fileManager guard let directoryEnumerator = fileManager.enumerator( at: directoryURL, includingPropertiesForKeys: propertyKeys, options: .skipsHiddenFiles) else { throw KingfisherError.cacheError(reason: .fileEnumeratorCreationFailed(url: directoryURL)) } guard let urls = directoryEnumerator.allObjects as? [URL] else { throw KingfisherError.cacheError(reason: .invalidFileEnumeratorContent(url: directoryURL)) } return urls } /// Removes all expired values from this storage. /// - Throws: A file manager error during removing the file. /// - Returns: The URLs for removed files. public func removeExpiredValues() throws -> [URL] { return try removeExpiredValues(referenceDate: Date()) } func removeExpiredValues(referenceDate: Date) throws -> [URL] { let propertyKeys: [URLResourceKey] = [ .isDirectoryKey, .contentModificationDateKey ] let urls = try allFileURLs(for: propertyKeys) let keys = Set(propertyKeys) let expiredFiles = urls.filter { fileURL in do { let meta = try FileMeta(fileURL: fileURL, resourceKeys: keys) if meta.isDirectory { return false } return meta.expired(referenceDate: referenceDate) } catch { return true } } try expiredFiles.forEach { url in try removeFile(at: url) } return expiredFiles } /// Removes all size exceeded values from this storage. /// - Throws: A file manager error during removing the file. /// - Returns: The URLs for removed files. /// /// - Note: This method checks `config.sizeLimit` and remove cached files in an LRU (Least Recently Used) way. func removeSizeExceededValues() throws -> [URL] { if config.sizeLimit == 0 { return [] } // Back compatible. 0 means no limit. var size = try totalSize() if size < config.sizeLimit { return [] } let propertyKeys: [URLResourceKey] = [ .isDirectoryKey, .creationDateKey, .fileSizeKey ] let keys = Set(propertyKeys) let urls = try allFileURLs(for: propertyKeys) var pendings: [FileMeta] = urls.compactMap { fileURL in guard let meta = try? FileMeta(fileURL: fileURL, resourceKeys: keys) else { return nil } return meta } // Sort by last access date. Most recent file first. pendings.sort(by: FileMeta.lastAccessDate) var removed: [URL] = [] let target = config.sizeLimit / 2 while size > target, let meta = pendings.popLast() { size -= UInt(meta.fileSize) try removeFile(at: meta.url) removed.append(meta.url) } return removed } /// Gets the total file size of the folder in bytes. public func totalSize() throws -> UInt { let propertyKeys: [URLResourceKey] = [.fileSizeKey] let urls = try allFileURLs(for: propertyKeys) let keys = Set(propertyKeys) let totalSize: UInt = urls.reduce(0) { size, fileURL in do { let meta = try FileMeta(fileURL: fileURL, resourceKeys: keys) return size + UInt(meta.fileSize) } catch { return size } } return totalSize } } } extension DiskStorage { /// Represents the config used in a `DiskStorage`. public struct Config { /// The file size limit on disk of the storage in bytes. 0 means no limit. public var sizeLimit: UInt /// The `StorageExpiration` used in this disk storage. Default is `.days(7)`, /// means that the disk cache would expire in one week. public var expiration: StorageExpiration = .days(7) /// The preferred extension of cache item. It will be appended to the file name as its extension. /// Default is `nil`, means that the cache file does not contain a file extension. public var pathExtension: String? = nil /// Default is `true`, means that the cache file name will be hashed before storing. public var usesHashedFileName = true /// Default is `false` /// If set to `true`, image extension will be extracted from original file name and append to /// the hased file name and used as the cache key on disk. public var autoExtAfterHashedFileName = false let name: String let fileManager: FileManager let directory: URL? var cachePathBlock: ((_ directory: URL, _ cacheName: String) -> URL)! = { (directory, cacheName) in return directory.appendingPathComponent(cacheName, isDirectory: true) } /// Creates a config value based on given parameters. /// /// - Parameters: /// - name: The name of cache. It is used as a part of storage folder. It is used to identify the disk /// storage. Two storages with the same `name` would share the same folder in disk, and it should /// be prevented. /// - sizeLimit: The size limit in bytes for all existing files in the disk storage. /// - fileManager: The `FileManager` used to manipulate files on disk. Default is `FileManager.default`. /// - directory: The URL where the disk storage should live. The storage will use this as the root folder, /// and append a path which is constructed by input `name`. Default is `nil`, indicates that /// the cache directory under user domain mask will be used. public init( name: String, sizeLimit: UInt, fileManager: FileManager = .default, directory: URL? = nil) { self.name = name self.fileManager = fileManager self.directory = directory self.sizeLimit = sizeLimit } } } extension DiskStorage { struct FileMeta { let url: URL let lastAccessDate: Date? let estimatedExpirationDate: Date? let isDirectory: Bool let fileSize: Int static func lastAccessDate(lhs: FileMeta, rhs: FileMeta) -> Bool { return lhs.lastAccessDate ?? .distantPast > rhs.lastAccessDate ?? .distantPast } init(fileURL: URL, resourceKeys: Set) throws { let meta = try fileURL.resourceValues(forKeys: resourceKeys) self.init( fileURL: fileURL, lastAccessDate: meta.creationDate, estimatedExpirationDate: meta.contentModificationDate, isDirectory: meta.isDirectory ?? false, fileSize: meta.fileSize ?? 0) } init( fileURL: URL, lastAccessDate: Date?, estimatedExpirationDate: Date?, isDirectory: Bool, fileSize: Int) { self.url = fileURL self.lastAccessDate = lastAccessDate self.estimatedExpirationDate = estimatedExpirationDate self.isDirectory = isDirectory self.fileSize = fileSize } func expired(referenceDate: Date) -> Bool { return estimatedExpirationDate?.isPast(referenceDate: referenceDate) ?? true } func extendExpiration(with fileManager: FileManager, extendingExpiration: ExpirationExtending) { guard let lastAccessDate = lastAccessDate, let lastEstimatedExpiration = estimatedExpirationDate else { return } let attributes: [FileAttributeKey : Any] switch extendingExpiration { case .none: // not extending expiration time here return case .cacheTime: let originalExpiration: StorageExpiration = .seconds(lastEstimatedExpiration.timeIntervalSince(lastAccessDate)) attributes = [ .creationDate: Date().fileAttributeDate, .modificationDate: originalExpiration.estimatedExpirationSinceNow.fileAttributeDate ] case .expirationTime(let expirationTime): attributes = [ .creationDate: Date().fileAttributeDate, .modificationDate: expirationTime.estimatedExpirationSinceNow.fileAttributeDate ] } try? fileManager.setAttributes(attributes, ofItemAtPath: url.path) } } } extension DiskStorage { struct Creation { let directoryURL: URL let cacheName: String init(_ config: Config) { let url: URL if let directory = config.directory { url = directory } else { url = config.fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0] } cacheName = "com.onevcat.Kingfisher.ImageCache.\(config.name)" directoryURL = config.cachePathBlock(url, cacheName) } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Cache/FormatIndicatedCacheSerializer.swift ================================================ // // RequestModifier.swift // Kingfisher // // Created by Junyu Kuang on 5/28/17. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation import CoreGraphics /// `FormatIndicatedCacheSerializer` lets you indicate an image format for serialized caches. /// /// It could serialize and deserialize PNG, JPEG and GIF images. For /// image other than these formats, a normalized `pngRepresentation` will be used. /// /// Example: /// ```` /// let profileImageSize = CGSize(width: 44, height: 44) /// /// // A round corner image. /// let imageProcessor = RoundCornerImageProcessor( /// cornerRadius: profileImageSize.width / 2, targetSize: profileImageSize) /// /// let optionsInfo: KingfisherOptionsInfo = [ /// .cacheSerializer(FormatIndicatedCacheSerializer.png), /// .processor(imageProcessor)] /// /// A URL pointing to a JPEG image. /// let url = URL(string: "https://example.com/image.jpg")! /// /// // Image will be always cached as PNG format to preserve alpha channel for round rectangle. /// // So when you load it from cache again later, it will be still round cornered. /// // Otherwise, the corner part would be filled by white color (since JPEG does not contain an alpha channel). /// imageView.kf.setImage(with: url, options: optionsInfo) /// ```` public struct FormatIndicatedCacheSerializer: CacheSerializer { /// A `FormatIndicatedCacheSerializer` which converts image from and to PNG format. If the image cannot be /// represented by PNG format, it will fallback to its real format which is determined by `original` data. public static let png = FormatIndicatedCacheSerializer(imageFormat: .PNG, jpegCompressionQuality: nil) /// A `FormatIndicatedCacheSerializer` which converts image from and to JPEG format. If the image cannot be /// represented by JPEG format, it will fallback to its real format which is determined by `original` data. /// The compression quality is 1.0 when using this serializer. If you need to set a customized compression quality, /// use `jpeg(compressionQuality:)`. public static let jpeg = FormatIndicatedCacheSerializer(imageFormat: .JPEG, jpegCompressionQuality: 1.0) /// A `FormatIndicatedCacheSerializer` which converts image from and to JPEG format with a settable compression /// quality. If the image cannot be represented by JPEG format, it will fallback to its real format which is /// determined by `original` data. /// - Parameter compressionQuality: The compression quality when converting image to JPEG data. public static func jpeg(compressionQuality: CGFloat) -> FormatIndicatedCacheSerializer { return FormatIndicatedCacheSerializer(imageFormat: .JPEG, jpegCompressionQuality: compressionQuality) } /// A `FormatIndicatedCacheSerializer` which converts image from and to GIF format. If the image cannot be /// represented by GIF format, it will fallback to its real format which is determined by `original` data. public static let gif = FormatIndicatedCacheSerializer(imageFormat: .GIF, jpegCompressionQuality: nil) /// The indicated image format. private let imageFormat: ImageFormat /// The compression quality used for loss image format (like JPEG). private let jpegCompressionQuality: CGFloat? /// Creates data which represents the given `image` under a format. public func data(with image: KFCrossPlatformImage, original: Data?) -> Data? { func imageData(withFormat imageFormat: ImageFormat) -> Data? { return autoreleasepool { () -> Data? in switch imageFormat { case .PNG: return image.kf.pngRepresentation() case .JPEG: return image.kf.jpegRepresentation(compressionQuality: jpegCompressionQuality ?? 1.0) case .GIF: return image.kf.gifRepresentation() case .unknown: return nil } } } // generate data with indicated image format if let data = imageData(withFormat: imageFormat) { return data } let originalFormat = original?.kf.imageFormat ?? .unknown // generate data with original image's format if originalFormat != imageFormat, let data = imageData(withFormat: originalFormat) { return data } return original ?? image.kf.normalized.kf.pngRepresentation() } /// Same implementation as `DefaultCacheSerializer`. public func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Cache/ImageCache.swift ================================================ // // ImageCache.swift // Kingfisher // // Created by Wei Wang on 15/4/6. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if os(macOS) import AppKit #else import UIKit #endif extension Notification.Name { /// This notification will be sent when the disk cache got cleaned either there are cached files expired or the /// total size exceeding the max allowed size. The manually invoking of `clearDiskCache` method will not trigger /// this notification. /// /// The `object` of this notification is the `ImageCache` object which sends the notification. /// A list of removed hashes (files) could be retrieved by accessing the array under /// `KingfisherDiskCacheCleanedHashKey` key in `userInfo` of the notification object you received. /// By checking the array, you could know the hash codes of files are removed. public static let KingfisherDidCleanDiskCache = Notification.Name("com.onevcat.Kingfisher.KingfisherDidCleanDiskCache") } /// Key for array of cleaned hashes in `userInfo` of `KingfisherDidCleanDiskCacheNotification`. public let KingfisherDiskCacheCleanedHashKey = "com.onevcat.Kingfisher.cleanedHash" /// Cache type of a cached image. /// - none: The image is not cached yet when retrieving it. /// - memory: The image is cached in memory. /// - disk: The image is cached in disk. public enum CacheType { /// The image is not cached yet when retrieving it. case none /// The image is cached in memory. case memory /// The image is cached in disk. case disk /// Whether the cache type represents the image is already cached or not. public var cached: Bool { switch self { case .memory, .disk: return true case .none: return false } } } /// Represents the caching operation result. public struct CacheStoreResult { /// The cache result for memory cache. Caching an image to memory will never fail. public let memoryCacheResult: Result<(), Never> /// The cache result for disk cache. If an error happens during caching operation, /// you can get it from `.failure` case of this `diskCacheResult`. public let diskCacheResult: Result<(), KingfisherError> } extension KFCrossPlatformImage: CacheCostCalculable { /// Cost of an image public var cacheCost: Int { return kf.cost } } extension Data: DataTransformable { public func toData() throws -> Data { return self } public static func fromData(_ data: Data) throws -> Data { return data } public static let empty = Data() } /// Represents the getting image operation from the cache. /// /// - disk: The image can be retrieved from disk cache. /// - memory: The image can be retrieved memory cache. /// - none: The image does not exist in the cache. public enum ImageCacheResult { /// The image can be retrieved from disk cache. case disk(KFCrossPlatformImage) /// The image can be retrieved memory cache. case memory(KFCrossPlatformImage) /// The image does not exist in the cache. case none /// Extracts the image from cache result. It returns the associated `Image` value for /// `.disk` and `.memory` case. For `.none` case, `nil` is returned. public var image: KFCrossPlatformImage? { switch self { case .disk(let image): return image case .memory(let image): return image case .none: return nil } } /// Returns the corresponding `CacheType` value based on the result type of `self`. public var cacheType: CacheType { switch self { case .disk: return .disk case .memory: return .memory case .none: return .none } } } /// Represents a hybrid caching system which is composed by a `MemoryStorage.Backend` and a `DiskStorage.Backend`. /// `ImageCache` is a high level abstract for storing an image as well as its data to disk memory and disk, and /// retrieving them back. /// /// While a default image cache object will be used if you prefer the extension methods of Kingfisher, you can create /// your own cache object and configure its storages as your need. This class also provide an interface for you to set /// the memory and disk storage config. open class ImageCache { // MARK: Singleton /// The default `ImageCache` object. Kingfisher will use this cache for its related methods if there is no /// other cache specified. The `name` of this default cache is "default", and you should not use this name /// for any of your customize cache. public static let `default` = ImageCache(name: "default") // MARK: Public Properties /// The `MemoryStorage.Backend` object used in this cache. This storage holds loaded images in memory with a /// reasonable expire duration and a maximum memory usage. To modify the configuration of a storage, just set /// the storage `config` and its properties. public let memoryStorage: MemoryStorage.Backend /// The `DiskStorage.Backend` object used in this cache. This storage stores loaded images in disk with a /// reasonable expire duration and a maximum disk usage. To modify the configuration of a storage, just set /// the storage `config` and its properties. public let diskStorage: DiskStorage.Backend private let ioQueue: DispatchQueue /// Closure that defines the disk cache path from a given path and cacheName. public typealias DiskCachePathClosure = (URL, String) -> URL // MARK: Initializers /// Creates an `ImageCache` from a customized `MemoryStorage` and `DiskStorage`. /// /// - Parameters: /// - memoryStorage: The `MemoryStorage.Backend` object to use in the image cache. /// - diskStorage: The `DiskStorage.Backend` object to use in the image cache. public init( memoryStorage: MemoryStorage.Backend, diskStorage: DiskStorage.Backend) { self.memoryStorage = memoryStorage self.diskStorage = diskStorage let ioQueueName = "com.onevcat.Kingfisher.ImageCache.ioQueue.\(UUID().uuidString)" ioQueue = DispatchQueue(label: ioQueueName) let notifications: [(Notification.Name, Selector)] #if !os(macOS) && !os(watchOS) notifications = [ (UIApplication.didReceiveMemoryWarningNotification, #selector(clearMemoryCache)), (UIApplication.willTerminateNotification, #selector(cleanExpiredDiskCache)), (UIApplication.didEnterBackgroundNotification, #selector(backgroundCleanExpiredDiskCache)) ] #elseif os(macOS) notifications = [ (NSApplication.willResignActiveNotification, #selector(cleanExpiredDiskCache)), ] #else notifications = [] #endif notifications.forEach { NotificationCenter.default.addObserver(self, selector: $0.1, name: $0.0, object: nil) } } /// Creates an `ImageCache` with a given `name`. Both `MemoryStorage` and `DiskStorage` will be created /// with a default config based on the `name`. /// /// - Parameter name: The name of cache object. It is used to setup disk cache directories and IO queue. /// You should not use the same `name` for different caches, otherwise, the disk storage would /// be conflicting to each other. The `name` should not be an empty string. public convenience init(name: String) { self.init(noThrowName: name, cacheDirectoryURL: nil, diskCachePathClosure: nil) } /// Creates an `ImageCache` with a given `name`, cache directory `path` /// and a closure to modify the cache directory. /// /// - Parameters: /// - name: The name of cache object. It is used to setup disk cache directories and IO queue. /// You should not use the same `name` for different caches, otherwise, the disk storage would /// be conflicting to each other. /// - cacheDirectoryURL: Location of cache directory URL on disk. It will be internally pass to the /// initializer of `DiskStorage` as the disk cache directory. If `nil`, the cache /// directory under user domain mask will be used. /// - diskCachePathClosure: Closure that takes in an optional initial path string and generates /// the final disk cache path. You could use it to fully customize your cache path. /// - Throws: An error that happens during image cache creating, such as unable to create a directory at the given /// path. public convenience init( name: String, cacheDirectoryURL: URL?, diskCachePathClosure: DiskCachePathClosure? = nil ) throws { if name.isEmpty { fatalError("[Kingfisher] You should specify a name for the cache. A cache with empty name is not permitted.") } let memoryStorage = ImageCache.createMemoryStorage() let config = ImageCache.createConfig( name: name, cacheDirectoryURL: cacheDirectoryURL, diskCachePathClosure: diskCachePathClosure ) let diskStorage = try DiskStorage.Backend(config: config) self.init(memoryStorage: memoryStorage, diskStorage: diskStorage) } convenience init( noThrowName name: String, cacheDirectoryURL: URL?, diskCachePathClosure: DiskCachePathClosure? ) { if name.isEmpty { fatalError("[Kingfisher] You should specify a name for the cache. A cache with empty name is not permitted.") } let memoryStorage = ImageCache.createMemoryStorage() let config = ImageCache.createConfig( name: name, cacheDirectoryURL: cacheDirectoryURL, diskCachePathClosure: diskCachePathClosure ) let diskStorage = DiskStorage.Backend(noThrowConfig: config, creatingDirectory: true) self.init(memoryStorage: memoryStorage, diskStorage: diskStorage) } private static func createMemoryStorage() -> MemoryStorage.Backend { let totalMemory = ProcessInfo.processInfo.physicalMemory let costLimit = totalMemory / 4 let memoryStorage = MemoryStorage.Backend(config: .init(totalCostLimit: (costLimit > Int.max) ? Int.max : Int(costLimit))) return memoryStorage } private static func createConfig( name: String, cacheDirectoryURL: URL?, diskCachePathClosure: DiskCachePathClosure? = nil ) -> DiskStorage.Config { var diskConfig = DiskStorage.Config( name: name, sizeLimit: 0, directory: cacheDirectoryURL ) if let closure = diskCachePathClosure { diskConfig.cachePathBlock = closure } return diskConfig } deinit { NotificationCenter.default.removeObserver(self) } // MARK: Storing Images open func store(_ image: KFCrossPlatformImage, original: Data? = nil, forKey key: String, options: KingfisherParsedOptionsInfo, toDisk: Bool = true, completionHandler: ((CacheStoreResult) -> Void)? = nil) { let identifier = options.processor.identifier let callbackQueue = options.callbackQueue let computedKey = key.computedKey(with: identifier) // Memory storage should not throw. memoryStorage.storeNoThrow(value: image, forKey: computedKey, expiration: options.memoryCacheExpiration) guard toDisk else { if let completionHandler = completionHandler { let result = CacheStoreResult(memoryCacheResult: .success(()), diskCacheResult: .success(())) callbackQueue.execute { completionHandler(result) } } return } ioQueue.async { let serializer = options.cacheSerializer if let data = serializer.data(with: image, original: original) { self.syncStoreToDisk( data, forKey: key, processorIdentifier: identifier, callbackQueue: callbackQueue, expiration: options.diskCacheExpiration, completionHandler: completionHandler) } else { guard let completionHandler = completionHandler else { return } let diskError = KingfisherError.cacheError( reason: .cannotSerializeImage(image: image, original: original, serializer: serializer)) let result = CacheStoreResult( memoryCacheResult: .success(()), diskCacheResult: .failure(diskError)) callbackQueue.execute { completionHandler(result) } } } } /// Stores an image to the cache. /// /// - Parameters: /// - image: The image to be stored. /// - original: The original data of the image. This value will be forwarded to the provided `serializer` for /// further use. By default, Kingfisher uses a `DefaultCacheSerializer` to serialize the image to /// data for caching in disk, it checks the image format based on `original` data to determine in /// which image format should be used. For other types of `serializer`, it depends on their /// implementation detail on how to use this original data. /// - key: The key used for caching the image. /// - identifier: The identifier of processor being used for caching. If you are using a processor for the /// image, pass the identifier of processor to this parameter. /// - serializer: The `CacheSerializer` /// - toDisk: Whether this image should be cached to disk or not. If `false`, the image is only cached in memory. /// Otherwise, it is cached in both memory storage and disk storage. Default is `true`. /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.untouch`. For case /// that `toDisk` is `false`, a `.untouch` queue means `callbackQueue` will be invoked from the /// caller queue of this method. If `toDisk` is `true`, the `completionHandler` will be called /// from an internal file IO queue. To change this behavior, specify another `CallbackQueue` /// value. /// - completionHandler: A closure which is invoked when the cache operation finishes. open func store(_ image: KFCrossPlatformImage, original: Data? = nil, forKey key: String, processorIdentifier identifier: String = "", cacheSerializer serializer: CacheSerializer = DefaultCacheSerializer.default, toDisk: Bool = true, callbackQueue: CallbackQueue = .untouch, completionHandler: ((CacheStoreResult) -> Void)? = nil) { struct TempProcessor: ImageProcessor { let identifier: String func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { return nil } } let options = KingfisherParsedOptionsInfo([ .processor(TempProcessor(identifier: identifier)), .cacheSerializer(serializer), .callbackQueue(callbackQueue) ]) store(image, original: original, forKey: key, options: options, toDisk: toDisk, completionHandler: completionHandler) } open func storeToDisk( _ data: Data, forKey key: String, processorIdentifier identifier: String = "", expiration: StorageExpiration? = nil, callbackQueue: CallbackQueue = .untouch, completionHandler: ((CacheStoreResult) -> Void)? = nil) { ioQueue.async { self.syncStoreToDisk( data, forKey: key, processorIdentifier: identifier, callbackQueue: callbackQueue, expiration: expiration, completionHandler: completionHandler) } } private func syncStoreToDisk( _ data: Data, forKey key: String, processorIdentifier identifier: String = "", callbackQueue: CallbackQueue = .untouch, expiration: StorageExpiration? = nil, completionHandler: ((CacheStoreResult) -> Void)? = nil) { let computedKey = key.computedKey(with: identifier) let result: CacheStoreResult do { try self.diskStorage.store(value: data, forKey: computedKey, expiration: expiration) result = CacheStoreResult(memoryCacheResult: .success(()), diskCacheResult: .success(())) } catch { let diskError: KingfisherError if let error = error as? KingfisherError { diskError = error } else { diskError = .cacheError(reason: .cannotConvertToData(object: data, error: error)) } result = CacheStoreResult( memoryCacheResult: .success(()), diskCacheResult: .failure(diskError) ) } if let completionHandler = completionHandler { callbackQueue.execute { completionHandler(result) } } } // MARK: Removing Images /// Removes the image for the given key from the cache. /// /// - Parameters: /// - key: The key used for caching the image. /// - identifier: The identifier of processor being used for caching. If you are using a processor for the /// image, pass the identifier of processor to this parameter. /// - fromMemory: Whether this image should be removed from memory storage or not. /// If `false`, the image won't be removed from the memory storage. Default is `true`. /// - fromDisk: Whether this image should be removed from disk storage or not. /// If `false`, the image won't be removed from the disk storage. Default is `true`. /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.untouch`. /// - completionHandler: A closure which is invoked when the cache removing operation finishes. open func removeImage(forKey key: String, processorIdentifier identifier: String = "", fromMemory: Bool = true, fromDisk: Bool = true, callbackQueue: CallbackQueue = .untouch, completionHandler: (() -> Void)? = nil) { let computedKey = key.computedKey(with: identifier) if fromMemory { memoryStorage.remove(forKey: computedKey) } if fromDisk { ioQueue.async{ try? self.diskStorage.remove(forKey: computedKey) if let completionHandler = completionHandler { callbackQueue.execute { completionHandler() } } } } else { if let completionHandler = completionHandler { callbackQueue.execute { completionHandler() } } } } func retrieveImage(forKey key: String, options: KingfisherParsedOptionsInfo, callbackQueue: CallbackQueue = .mainCurrentOrAsync, completionHandler: ((Result) -> Void)?) { // No completion handler. No need to start working and early return. guard let completionHandler = completionHandler else { return } // Try to check the image from memory cache first. if let image = retrieveImageInMemoryCache(forKey: key, options: options) { callbackQueue.execute { completionHandler(.success(.memory(image))) } } else if options.fromMemoryCacheOrRefresh { callbackQueue.execute { completionHandler(.success(.none)) } } else { // Begin to disk search. self.retrieveImageInDiskCache(forKey: key, options: options, callbackQueue: callbackQueue) { result in switch result { case .success(let image): guard let image = image else { // No image found in disk storage. callbackQueue.execute { completionHandler(.success(.none)) } return } // Cache the disk image to memory. // We are passing `false` to `toDisk`, the memory cache does not change // callback queue, we can call `completionHandler` without another dispatch. var cacheOptions = options cacheOptions.callbackQueue = .untouch self.store( image, forKey: key, options: cacheOptions, toDisk: false) { _ in callbackQueue.execute { completionHandler(.success(.disk(image))) } } case .failure(let error): callbackQueue.execute { completionHandler(.failure(error)) } } } } } // MARK: Getting Images /// Gets an image for a given key from the cache, either from memory storage or disk storage. /// /// - Parameters: /// - key: The key used for caching the image. /// - options: The `KingfisherOptionsInfo` options setting used for retrieving the image. /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.mainCurrentOrAsync`. /// - completionHandler: A closure which is invoked when the image getting operation finishes. If the /// image retrieving operation finishes without problem, an `ImageCacheResult` value /// will be sent to this closure as result. Otherwise, a `KingfisherError` result /// with detail failing reason will be sent. open func retrieveImage(forKey key: String, options: KingfisherOptionsInfo? = nil, callbackQueue: CallbackQueue = .mainCurrentOrAsync, completionHandler: ((Result) -> Void)?) { retrieveImage( forKey: key, options: KingfisherParsedOptionsInfo(options), callbackQueue: callbackQueue, completionHandler: completionHandler) } func retrieveImageInMemoryCache( forKey key: String, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { let computedKey = key.computedKey(with: options.processor.identifier) return memoryStorage.value(forKey: computedKey, extendingExpiration: options.memoryCacheAccessExtendingExpiration) } /// Gets an image for a given key from the memory storage. /// /// - Parameters: /// - key: The key used for caching the image. /// - options: The `KingfisherOptionsInfo` options setting used for retrieving the image. /// - Returns: The image stored in memory cache, if exists and valid. Otherwise, if the image does not exist or /// has already expired, `nil` is returned. open func retrieveImageInMemoryCache( forKey key: String, options: KingfisherOptionsInfo? = nil) -> KFCrossPlatformImage? { return retrieveImageInMemoryCache(forKey: key, options: KingfisherParsedOptionsInfo(options)) } func retrieveImageInDiskCache( forKey key: String, options: KingfisherParsedOptionsInfo, callbackQueue: CallbackQueue = .untouch, completionHandler: @escaping (Result) -> Void) { let computedKey = key.computedKey(with: options.processor.identifier) let loadingQueue: CallbackQueue = options.loadDiskFileSynchronously ? .untouch : .dispatch(ioQueue) loadingQueue.execute { do { var image: KFCrossPlatformImage? = nil if let data = try self.diskStorage.value(forKey: computedKey, extendingExpiration: options.diskCacheAccessExtendingExpiration) { image = options.cacheSerializer.image(with: data, options: options) } callbackQueue.execute { completionHandler(.success(image)) } } catch { if let error = error as? KingfisherError { callbackQueue.execute { completionHandler(.failure(error)) } } else { assertionFailure("The internal thrown error should be a `KingfisherError`.") } } } } /// Gets an image for a given key from the disk storage. /// /// - Parameters: /// - key: The key used for caching the image. /// - options: The `KingfisherOptionsInfo` options setting used for retrieving the image. /// - callbackQueue: The callback queue on which `completionHandler` is invoked. Default is `.untouch`. /// - completionHandler: A closure which is invoked when the operation finishes. open func retrieveImageInDiskCache( forKey key: String, options: KingfisherOptionsInfo? = nil, callbackQueue: CallbackQueue = .untouch, completionHandler: @escaping (Result) -> Void) { retrieveImageInDiskCache( forKey: key, options: KingfisherParsedOptionsInfo(options), callbackQueue: callbackQueue, completionHandler: completionHandler) } // MARK: Cleaning /// Clears the memory & disk storage of this cache. This is an async operation. /// /// - Parameter handler: A closure which is invoked when the cache clearing operation finishes. /// This `handler` will be called from the main queue. public func clearCache(completion handler: (() -> Void)? = nil) { clearMemoryCache() clearDiskCache(completion: handler) } /// Clears the memory storage of this cache. @objc public func clearMemoryCache() { memoryStorage.removeAll() } /// Clears the disk storage of this cache. This is an async operation. /// /// - Parameter handler: A closure which is invoked when the cache clearing operation finishes. /// This `handler` will be called from the main queue. open func clearDiskCache(completion handler: (() -> Void)? = nil) { ioQueue.async { do { try self.diskStorage.removeAll() } catch _ { } if let handler = handler { DispatchQueue.main.async { handler() } } } } /// Clears the expired images from memory & disk storage. This is an async operation. open func cleanExpiredCache(completion handler: (() -> Void)? = nil) { cleanExpiredMemoryCache() cleanExpiredDiskCache(completion: handler) } /// Clears the expired images from disk storage. open func cleanExpiredMemoryCache() { memoryStorage.removeExpired() } /// Clears the expired images from disk storage. This is an async operation. @objc func cleanExpiredDiskCache() { cleanExpiredDiskCache(completion: nil) } /// Clears the expired images from disk storage. This is an async operation. /// /// - Parameter handler: A closure which is invoked when the cache clearing operation finishes. /// This `handler` will be called from the main queue. open func cleanExpiredDiskCache(completion handler: (() -> Void)? = nil) { ioQueue.async { do { var removed: [URL] = [] let removedExpired = try self.diskStorage.removeExpiredValues() removed.append(contentsOf: removedExpired) let removedSizeExceeded = try self.diskStorage.removeSizeExceededValues() removed.append(contentsOf: removedSizeExceeded) if !removed.isEmpty { DispatchQueue.main.async { let cleanedHashes = removed.map { $0.lastPathComponent } NotificationCenter.default.post( name: .KingfisherDidCleanDiskCache, object: self, userInfo: [KingfisherDiskCacheCleanedHashKey: cleanedHashes]) } } if let handler = handler { DispatchQueue.main.async { handler() } } } catch {} } } #if !os(macOS) && !os(watchOS) /// Clears the expired images from disk storage when app is in background. This is an async operation. /// In most cases, you should not call this method explicitly. /// It will be called automatically when `UIApplicationDidEnterBackgroundNotification` received. @objc public func backgroundCleanExpiredDiskCache() { // if 'sharedApplication()' is unavailable, then return guard let sharedApplication = KingfisherWrapper.shared else { return } func endBackgroundTask(_ task: inout UIBackgroundTaskIdentifier) { sharedApplication.endBackgroundTask(task) task = UIBackgroundTaskIdentifier.invalid } var backgroundTask: UIBackgroundTaskIdentifier! backgroundTask = sharedApplication.beginBackgroundTask { endBackgroundTask(&backgroundTask!) } cleanExpiredDiskCache { endBackgroundTask(&backgroundTask!) } } #endif // MARK: Image Cache State /// Returns the cache type for a given `key` and `identifier` combination. /// This method is used for checking whether an image is cached in current cache. /// It also provides information on which kind of cache can it be found in the return value. /// /// - Parameters: /// - key: The key used for caching the image. /// - identifier: Processor identifier which used for this image. Default is the `identifier` of /// `DefaultImageProcessor.default`. /// - Returns: A `CacheType` instance which indicates the cache status. /// `.none` means the image is not in cache or it is already expired. open func imageCachedType( forKey key: String, processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> CacheType { let computedKey = key.computedKey(with: identifier) if memoryStorage.isCached(forKey: computedKey) { return .memory } if diskStorage.isCached(forKey: computedKey) { return .disk } return .none } /// Returns whether the file exists in cache for a given `key` and `identifier` combination. /// /// - Parameters: /// - key: The key used for caching the image. /// - identifier: Processor identifier which used for this image. Default is the `identifier` of /// `DefaultImageProcessor.default`. /// - Returns: A `Bool` which indicates whether a cache could match the given `key` and `identifier` combination. /// /// - Note: /// The return value does not contain information about from which kind of storage the cache matches. /// To get the information about cache type according `CacheType`, /// use `imageCachedType(forKey:processorIdentifier:)` instead. public func isCached( forKey key: String, processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> Bool { return imageCachedType(forKey: key, processorIdentifier: identifier).cached } /// Gets the hash used as cache file name for the key. /// /// - Parameters: /// - key: The key used for caching the image. /// - identifier: Processor identifier which used for this image. Default is the `identifier` of /// `DefaultImageProcessor.default`. /// - Returns: The hash which is used as the cache file name. /// /// - Note: /// By default, for a given combination of `key` and `identifier`, `ImageCache` will use the value /// returned by this method as the cache file name. You can use this value to check and match cache file /// if you need. open func hash( forKey key: String, processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> String { let computedKey = key.computedKey(with: identifier) return diskStorage.cacheFileName(forKey: computedKey) } /// Calculates the size taken by the disk storage. /// It is the total file size of all cached files in the `diskStorage` on disk in bytes. /// /// - Parameter handler: Called with the size calculating finishes. This closure is invoked from the main queue. open func calculateDiskStorageSize(completion handler: @escaping ((Result) -> Void)) { ioQueue.async { do { let size = try self.diskStorage.totalSize() DispatchQueue.main.async { handler(.success(size)) } } catch { if let error = error as? KingfisherError { DispatchQueue.main.async { handler(.failure(error)) } } else { assertionFailure("The internal thrown error should be a `KingfisherError`.") } } } } /// Gets the cache path for the key. /// It is useful for projects with web view or anyone that needs access to the local file path. /// /// i.e. Replacing the `` tag in your HTML. /// /// - Parameters: /// - key: The key used for caching the image. /// - identifier: Processor identifier which used for this image. Default is the `identifier` of /// `DefaultImageProcessor.default`. /// - Returns: The disk path of cached image under the given `key` and `identifier`. /// /// - Note: /// This method does not guarantee there is an image already cached in the returned path. It just gives your /// the path that the image should be, if it exists in disk storage. /// /// You could use `isCached(forKey:)` method to check whether the image is cached under that key in disk. open func cachePath( forKey key: String, processorIdentifier identifier: String = DefaultImageProcessor.default.identifier) -> String { let computedKey = key.computedKey(with: identifier) return diskStorage.cacheFileURL(forKey: computedKey).path } } extension Dictionary { func keysSortedByValue(_ isOrderedBefore: (Value, Value) -> Bool) -> [Key] { return Array(self).sorted{ isOrderedBefore($0.1, $1.1) }.map{ $0.0 } } } #if !os(macOS) && !os(watchOS) // MARK: - For App Extensions extension UIApplication: KingfisherCompatible { } extension KingfisherWrapper where Base: UIApplication { public static var shared: UIApplication? { let selector = NSSelectorFromString("sharedApplication") guard Base.responds(to: selector) else { return nil } return Base.perform(selector).takeUnretainedValue() as? UIApplication } } #endif extension String { func computedKey(with identifier: String) -> String { if identifier.isEmpty { return self } else { return appending("@\(identifier)") } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Cache/MemoryStorage.swift ================================================ // // MemoryStorage.swift // Kingfisher // // Created by Wei Wang on 2018/10/15. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents a set of conception related to storage which stores a certain type of value in memory. /// This is a namespace for the memory storage types. A `Backend` with a certain `Config` will be used to describe the /// storage. See these composed types for more information. public enum MemoryStorage { /// Represents a storage which stores a certain type of value in memory. It provides fast access, /// but limited storing size. The stored value type needs to conform to `CacheCostCalculable`, /// and its `cacheCost` will be used to determine the cost of size for the cache item. /// /// You can config a `MemoryStorage.Backend` in its initializer by passing a `MemoryStorage.Config` value. /// or modifying the `config` property after it being created. The backend of `MemoryStorage` has /// upper limitation on cost size in memory and item count. All items in the storage has an expiration /// date. When retrieved, if the target item is already expired, it will be recognized as it does not /// exist in the storage. The `MemoryStorage` also contains a scheduled self clean task, to evict expired /// items from memory. public class Backend { let storage = NSCache>() // Keys trackes the objects once inside the storage. For object removing triggered by user, the corresponding // key would be also removed. However, for the object removing triggered by cache rule/policy of system, the // key will be remained there until next `removeExpired` happens. // // Breaking the strict tracking could save additional locking behaviors. // See https://github.com/onevcat/Kingfisher/issues/1233 var keys = Set() private var cleanTimer: Timer? = nil private let lock = NSLock() /// The config used in this storage. It is a value you can set and /// use to config the storage in air. public var config: Config { didSet { storage.totalCostLimit = config.totalCostLimit storage.countLimit = config.countLimit } } /// Creates a `MemoryStorage` with a given `config`. /// /// - Parameter config: The config used to create the storage. It determines the max size limitation, /// default expiration setting and more. public init(config: Config) { self.config = config storage.totalCostLimit = config.totalCostLimit storage.countLimit = config.countLimit cleanTimer = .scheduledTimer(withTimeInterval: config.cleanInterval, repeats: true) { [weak self] _ in guard let self = self else { return } self.removeExpired() } } /// Removes the expired values from the storage. public func removeExpired() { lock.lock() defer { lock.unlock() } for key in keys { let nsKey = key as NSString guard let object = storage.object(forKey: nsKey) else { // This could happen if the object is moved by cache `totalCostLimit` or `countLimit` rule. // We didn't remove the key yet until now, since we do not want to introduce additional lock. // See https://github.com/onevcat/Kingfisher/issues/1233 keys.remove(key) continue } if object.estimatedExpiration.isPast { storage.removeObject(forKey: nsKey) keys.remove(key) } } } /// Stores a value to the storage under the specified key and expiration policy. /// - Parameters: /// - value: The value to be stored. /// - key: The key to which the `value` will be stored. /// - expiration: The expiration policy used by this store action. /// - Throws: No error will public func store( value: T, forKey key: String, expiration: StorageExpiration? = nil) { storeNoThrow(value: value, forKey: key, expiration: expiration) } // The no throw version for storing value in cache. Kingfisher knows the detail so it // could use this version to make syntax simpler internally. func storeNoThrow( value: T, forKey key: String, expiration: StorageExpiration? = nil) { lock.lock() defer { lock.unlock() } let expiration = expiration ?? config.expiration // The expiration indicates that already expired, no need to store. guard !expiration.isExpired else { return } let object = StorageObject(value, key: key, expiration: expiration) storage.setObject(object, forKey: key as NSString, cost: value.cacheCost) keys.insert(key) } /// Gets a value from the storage. /// /// - Parameters: /// - key: The cache key of value. /// - extendingExpiration: The expiration policy used by this getting action. /// - Returns: The value under `key` if it is valid and found in the storage. Otherwise, `nil`. public func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) -> T? { guard let object = storage.object(forKey: key as NSString) else { return nil } if object.expired { return nil } object.extendExpiration(extendingExpiration) return object.value } /// Whether there is valid cached data under a given key. /// - Parameter key: The cache key of value. /// - Returns: If there is valid data under the key, `true`. Otherwise, `false`. public func isCached(forKey key: String) -> Bool { guard let _ = value(forKey: key, extendingExpiration: .none) else { return false } return true } /// Removes a value from a specified key. /// - Parameter key: The cache key of value. public func remove(forKey key: String) { lock.lock() defer { lock.unlock() } storage.removeObject(forKey: key as NSString) keys.remove(key) } /// Removes all values in this storage. public func removeAll() { lock.lock() defer { lock.unlock() } storage.removeAllObjects() keys.removeAll() } } } extension MemoryStorage { /// Represents the config used in a `MemoryStorage`. public struct Config { /// Total cost limit of the storage in bytes. public var totalCostLimit: Int /// The item count limit of the memory storage. public var countLimit: Int = .max /// The `StorageExpiration` used in this memory storage. Default is `.seconds(300)`, /// means that the memory cache would expire in 5 minutes. public var expiration: StorageExpiration = .seconds(300) /// The time interval between the storage do clean work for swiping expired items. public let cleanInterval: TimeInterval /// Creates a config from a given `totalCostLimit` value. /// /// - Parameters: /// - totalCostLimit: Total cost limit of the storage in bytes. /// - cleanInterval: The time interval between the storage do clean work for swiping expired items. /// Default is 120, means the auto eviction happens once per two minutes. /// /// - Note: /// Other members of `MemoryStorage.Config` will use their default values when created. public init(totalCostLimit: Int, cleanInterval: TimeInterval = 120) { self.totalCostLimit = totalCostLimit self.cleanInterval = cleanInterval } } } extension MemoryStorage { class StorageObject { let value: T let expiration: StorageExpiration let key: String private(set) var estimatedExpiration: Date init(_ value: T, key: String, expiration: StorageExpiration) { self.value = value self.key = key self.expiration = expiration self.estimatedExpiration = expiration.estimatedExpirationSinceNow } func extendExpiration(_ extendingExpiration: ExpirationExtending = .cacheTime) { switch extendingExpiration { case .none: return case .cacheTime: self.estimatedExpiration = expiration.estimatedExpirationSinceNow case .expirationTime(let expirationTime): self.estimatedExpiration = expirationTime.estimatedExpirationSinceNow } } var expired: Bool { return estimatedExpiration.isPast } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Cache/Storage.swift ================================================ // // Storage.swift // Kingfisher // // Created by Wei Wang on 2018/10/15. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Constants for some time intervals struct TimeConstants { static let secondsInOneMinute = 60 static let minutesInOneHour = 60 static let hoursInOneDay = 24 static let secondsInOneDay = 86_400 } /// Represents the expiration strategy used in storage. /// /// - never: The item never expires. /// - seconds: The item expires after a time duration of given seconds from now. /// - days: The item expires after a time duration of given days from now. /// - date: The item expires after a given date. public enum StorageExpiration { /// The item never expires. case never /// The item expires after a time duration of given seconds from now. case seconds(TimeInterval) /// The item expires after a time duration of given days from now. case days(Int) /// The item expires after a given date. case date(Date) /// Indicates the item is already expired. Use this to skip cache. case expired func estimatedExpirationSince(_ date: Date) -> Date { switch self { case .never: return .distantFuture case .seconds(let seconds): return date.addingTimeInterval(seconds) case .days(let days): let duration: TimeInterval = TimeInterval(TimeConstants.secondsInOneDay) * TimeInterval(days) return date.addingTimeInterval(duration) case .date(let ref): return ref case .expired: return .distantPast } } var estimatedExpirationSinceNow: Date { return estimatedExpirationSince(Date()) } var isExpired: Bool { return timeInterval <= 0 } var timeInterval: TimeInterval { switch self { case .never: return .infinity case .seconds(let seconds): return seconds case .days(let days): return TimeInterval(TimeConstants.secondsInOneDay) * TimeInterval(days) case .date(let ref): return ref.timeIntervalSinceNow case .expired: return -(.infinity) } } } /// Represents the expiration extending strategy used in storage to after access. /// /// - none: The item expires after the original time, without extending after access. /// - cacheTime: The item expiration extends by the original cache time after each access. /// - expirationTime: The item expiration extends by the provided time after each access. public enum ExpirationExtending { /// The item expires after the original time, without extending after access. case none /// The item expiration extends by the original cache time after each access. case cacheTime /// The item expiration extends by the provided time after each access. case expirationTime(_ expiration: StorageExpiration) } /// Represents types which cost in memory can be calculated. public protocol CacheCostCalculable { var cacheCost: Int { get } } /// Represents types which can be converted to and from data. public protocol DataTransformable { func toData() throws -> Data static func fromData(_ data: Data) throws -> Self static var empty: Self { get } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Extensions/ImageView+Kingfisher.swift ================================================ // // ImageView+Kingfisher.swift // Kingfisher // // Created by Wei Wang on 15/4/6. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if !os(watchOS) #if os(macOS) import AppKit #else import UIKit #endif extension KingfisherWrapper where Base: KFCrossPlatformImageView { // MARK: Setting Image /// Sets an image to the image view with a `Source`. /// /// - Parameters: /// - source: The `Source` object defines data information from network or a data provider. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// This is the easiest way to use Kingfisher to boost the image setting process from a source. Since all parameters /// have a default value except the `source`, you can set an image from a certain URL to an image view like this: /// /// ``` /// // Set image from a network source. /// let url = URL(string: "https://example.com/image.png")! /// imageView.kf.setImage(with: .network(url)) /// /// // Or set image from a data provider. /// let provider = LocalFileImageDataProvider(fileURL: fileURL) /// imageView.kf.setImage(with: .provider(provider)) /// ``` /// /// For both `.network` and `.provider` source, there are corresponding view extension methods. So the code /// above is equivalent to: /// /// ``` /// imageView.kf.setImage(with: url) /// imageView.kf.setImage(with: provider) /// ``` /// /// Internally, this method will use `KingfisherManager` to get the source. /// Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with source: Source?, placeholder: Placeholder? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { let options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) return setImage(with: source, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: completionHandler) } /// Sets an image to the image view with a `Source`. /// /// - Parameters: /// - source: The `Source` object defines data information from network or a data provider. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// This is the easiest way to use Kingfisher to boost the image setting process from a source. Since all parameters /// have a default value except the `source`, you can set an image from a certain URL to an image view like this: /// /// ``` /// // Set image from a network source. /// let url = URL(string: "https://example.com/image.png")! /// imageView.kf.setImage(with: .network(url)) /// /// // Or set image from a data provider. /// let provider = LocalFileImageDataProvider(fileURL: fileURL) /// imageView.kf.setImage(with: .provider(provider)) /// ``` /// /// For both `.network` and `.provider` source, there are corresponding view extension methods. So the code /// above is equivalent to: /// /// ``` /// imageView.kf.setImage(with: url) /// imageView.kf.setImage(with: provider) /// ``` /// /// Internally, this method will use `KingfisherManager` to get the source. /// Since this method will perform UI changes, you must call it from the main thread. /// The `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with source: Source?, placeholder: Placeholder? = nil, options: KingfisherOptionsInfo? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setImage( with: source, placeholder: placeholder, options: options, progressBlock: nil, completionHandler: completionHandler ) } /// Sets an image to the image view with a requested resource. /// /// - Parameters: /// - resource: The `Resource` object contains information about the resource. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// This is the easiest way to use Kingfisher to boost the image setting process from network. Since all parameters /// have a default value except the `resource`, you can set an image from a certain URL to an image view like this: /// /// ``` /// let url = URL(string: "https://example.com/image.png")! /// imageView.kf.setImage(with: url) /// ``` /// /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache /// or network. Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with resource: Resource?, placeholder: Placeholder? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setImage( with: resource?.convertToSource(), placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } /// Sets an image to the image view with a requested resource. /// /// - Parameters: /// - resource: The `Resource` object contains information about the resource. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// This is the easiest way to use Kingfisher to boost the image setting process from network. Since all parameters /// have a default value except the `resource`, you can set an image from a certain URL to an image view like this: /// /// ``` /// let url = URL(string: "https://example.com/image.png")! /// imageView.kf.setImage(with: url) /// ``` /// /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache /// or network. Since this method will perform UI changes, you must call it from the main thread. /// The `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with resource: Resource?, placeholder: Placeholder? = nil, options: KingfisherOptionsInfo? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setImage( with: resource, placeholder: placeholder, options: options, progressBlock: nil, completionHandler: completionHandler ) } /// Sets an image to the image view with a data provider. /// /// - Parameters: /// - provider: The `ImageDataProvider` object contains information about the data. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// Internally, this method will use `KingfisherManager` to get the image data, from either cache /// or the data provider. Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with provider: ImageDataProvider?, placeholder: Placeholder? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setImage( with: provider.map { .provider($0) }, placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } /// Sets an image to the image view with a data provider. /// /// - Parameters: /// - provider: The `ImageDataProvider` object contains information about the data. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// Internally, this method will use `KingfisherManager` to get the image data, from either cache /// or the data provider. Since this method will perform UI changes, you must call it from the main thread. /// The `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with provider: ImageDataProvider?, placeholder: Placeholder? = nil, options: KingfisherOptionsInfo? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setImage( with: provider, placeholder: placeholder, options: options, progressBlock: nil, completionHandler: completionHandler ) } func setImage( with source: Source?, placeholder: Placeholder? = nil, parsedOptions: KingfisherParsedOptionsInfo, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { var mutatingSelf = self guard let source = source else { mutatingSelf.placeholder = placeholder mutatingSelf.taskIdentifier = nil completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) return nil } var options = parsedOptions let isEmptyImage = base.image == nil && self.placeholder == nil if !options.keepCurrentImageWhileLoading || isEmptyImage { // Always set placeholder while there is no image/placeholder yet. mutatingSelf.placeholder = placeholder } let maybeIndicator = indicator maybeIndicator?.startAnimatingView() let issuedIdentifier = Source.Identifier.next() mutatingSelf.taskIdentifier = issuedIdentifier if base.shouldPreloadAllAnimation() { options.preloadAllAnimationData = true } if let block = progressBlock { options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] } if let provider = ImageProgressiveProvider(options, refresh: { image in self.base.image = image }) { options.onDataReceived = (options.onDataReceived ?? []) + [provider] } options.onDataReceived?.forEach { $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } } let task = KingfisherManager.shared.retrieveImage( with: source, options: options, downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, completionHandler: { result in CallbackQueue.mainCurrentOrAsync.execute { maybeIndicator?.stopAnimatingView() guard issuedIdentifier == self.taskIdentifier else { let reason: KingfisherError.ImageSettingErrorReason do { let value = try result.get() reason = .notCurrentSourceTask(result: value, error: nil, source: source) } catch { reason = .notCurrentSourceTask(result: nil, error: error, source: source) } let error = KingfisherError.imageSettingError(reason: reason) completionHandler?(.failure(error)) return } mutatingSelf.imageTask = nil mutatingSelf.taskIdentifier = nil switch result { case .success(let value): guard self.needsTransition(options: options, cacheType: value.cacheType) else { mutatingSelf.placeholder = nil self.base.image = value.image completionHandler?(result) return } self.makeTransition(image: value.image, transition: options.transition) { completionHandler?(result) } case .failure: if let image = options.onFailureImage { self.base.image = image } completionHandler?(result) } } } ) mutatingSelf.imageTask = task return task } // MARK: Cancelling Downloading Task /// Cancels the image download task of the image view if it is running. /// Nothing will happen if the downloading has already finished. public func cancelDownloadTask() { imageTask?.cancel() } private func needsTransition(options: KingfisherParsedOptionsInfo, cacheType: CacheType) -> Bool { switch options.transition { case .none: return false #if os(macOS) case .fade: // Fade is only a placeholder for SwiftUI on macOS. return false #else default: if options.forceTransition { return true } if cacheType == .none { return true } return false #endif } } private func makeTransition(image: KFCrossPlatformImage, transition: ImageTransition, done: @escaping () -> Void) { #if !os(macOS) // Force hiding the indicator without transition first. UIView.transition( with: self.base, duration: 0.0, options: [], animations: { self.indicator?.stopAnimatingView() }, completion: { _ in var mutatingSelf = self mutatingSelf.placeholder = nil UIView.transition( with: self.base, duration: transition.duration, options: [transition.animationOptions, .allowUserInteraction], animations: { transition.animations?(self.base, image) }, completion: { finished in transition.completion?(finished) done() } ) } ) #else done() #endif } } // MARK: - Associated Object private var taskIdentifierKey: Void? private var indicatorKey: Void? private var indicatorTypeKey: Void? private var placeholderKey: Void? private var imageTaskKey: Void? extension KingfisherWrapper where Base: KFCrossPlatformImageView { // MARK: Properties public private(set) var taskIdentifier: Source.Identifier.Value? { get { let box: Box? = getAssociatedObject(base, &taskIdentifierKey) return box?.value } set { let box = newValue.map { Box($0) } setRetainedAssociatedObject(base, &taskIdentifierKey, box) } } /// Holds which indicator type is going to be used. /// Default is `.none`, means no indicator will be shown while downloading. public var indicatorType: IndicatorType { get { return getAssociatedObject(base, &indicatorTypeKey) ?? .none } set { switch newValue { case .none: indicator = nil case .activity: indicator = ActivityIndicator() case .image(let data): indicator = ImageIndicator(imageData: data) case .custom(let anIndicator): indicator = anIndicator } setRetainedAssociatedObject(base, &indicatorTypeKey, newValue) } } /// Holds any type that conforms to the protocol `Indicator`. /// The protocol `Indicator` has a `view` property that will be shown when loading an image. /// It will be `nil` if `indicatorType` is `.none`. public private(set) var indicator: Indicator? { get { let box: Box? = getAssociatedObject(base, &indicatorKey) return box?.value } set { // Remove previous if let previousIndicator = indicator { previousIndicator.view.removeFromSuperview() } // Add new if let newIndicator = newValue { // Set default indicator layout let view = newIndicator.view base.addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false view.centerXAnchor.constraint( equalTo: base.centerXAnchor, constant: newIndicator.centerOffset.x).isActive = true view.centerYAnchor.constraint( equalTo: base.centerYAnchor, constant: newIndicator.centerOffset.y).isActive = true switch newIndicator.sizeStrategy(in: base) { case .intrinsicSize: break case .full: view.heightAnchor.constraint(equalTo: base.heightAnchor, constant: 0).isActive = true view.widthAnchor.constraint(equalTo: base.widthAnchor, constant: 0).isActive = true case .size(let size): view.heightAnchor.constraint(equalToConstant: size.height).isActive = true view.widthAnchor.constraint(equalToConstant: size.width).isActive = true } newIndicator.view.isHidden = true } // Save in associated object // Wrap newValue with Box to workaround an issue that Swift does not recognize // and casting protocol for associate object correctly. https://github.com/onevcat/Kingfisher/issues/872 setRetainedAssociatedObject(base, &indicatorKey, newValue.map(Box.init)) } } private var imageTask: DownloadTask? { get { return getAssociatedObject(base, &imageTaskKey) } set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} } /// Represents the `Placeholder` used for this image view. A `Placeholder` will be shown in the view while /// it is downloading an image. public private(set) var placeholder: Placeholder? { get { return getAssociatedObject(base, &placeholderKey) } set { if let previousPlaceholder = placeholder { previousPlaceholder.remove(from: base) } if let newPlaceholder = newValue { newPlaceholder.add(to: base) } else { base.image = nil } setRetainedAssociatedObject(base, &placeholderKey, newValue) } } } extension KFCrossPlatformImageView { @objc func shouldPreloadAllAnimation() -> Bool { return true } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Extensions/NSButton+Kingfisher.swift ================================================ // // NSButton+Kingfisher.swift // Kingfisher // // Created by Jie Zhang on 14/04/2016. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit extension KingfisherWrapper where Base: NSButton { // MARK: Setting Image /// Sets an image to the button with a source. /// /// - Parameters: /// - source: The `Source` object contains information about how to get the image. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// Internally, this method will use `KingfisherManager` to get the requested source. /// Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with source: Source?, placeholder: KFCrossPlatformImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { let options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) return setImage( with: source, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: completionHandler ) } /// Sets an image to the button with a requested resource. /// /// - Parameters: /// - resource: The `Resource` object contains information about the resource. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache /// or network. Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with resource: Resource?, placeholder: KFCrossPlatformImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setImage( with: resource?.convertToSource(), placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } func setImage( with source: Source?, placeholder: KFCrossPlatformImage? = nil, parsedOptions: KingfisherParsedOptionsInfo, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { var mutatingSelf = self guard let source = source else { base.image = placeholder mutatingSelf.taskIdentifier = nil completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) return nil } var options = parsedOptions if !options.keepCurrentImageWhileLoading { base.image = placeholder } let issuedIdentifier = Source.Identifier.next() mutatingSelf.taskIdentifier = issuedIdentifier if let block = progressBlock { options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] } if let provider = ImageProgressiveProvider(options, refresh: { image in self.base.image = image }) { options.onDataReceived = (options.onDataReceived ?? []) + [provider] } options.onDataReceived?.forEach { $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } } let task = KingfisherManager.shared.retrieveImage( with: source, options: options, downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, completionHandler: { result in CallbackQueue.mainCurrentOrAsync.execute { guard issuedIdentifier == self.taskIdentifier else { let reason: KingfisherError.ImageSettingErrorReason do { let value = try result.get() reason = .notCurrentSourceTask(result: value, error: nil, source: source) } catch { reason = .notCurrentSourceTask(result: nil, error: error, source: source) } let error = KingfisherError.imageSettingError(reason: reason) completionHandler?(.failure(error)) return } mutatingSelf.imageTask = nil mutatingSelf.taskIdentifier = nil switch result { case .success(let value): self.base.image = value.image completionHandler?(result) case .failure: if let image = options.onFailureImage { self.base.image = image } completionHandler?(result) } } } ) mutatingSelf.imageTask = task return task } // MARK: Cancelling Downloading Task /// Cancels the image download task of the button if it is running. /// Nothing will happen if the downloading has already finished. public func cancelImageDownloadTask() { imageTask?.cancel() } // MARK: Setting Alternate Image @discardableResult public func setAlternateImage( with source: Source?, placeholder: KFCrossPlatformImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { let options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) return setAlternateImage( with: source, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: completionHandler ) } /// Sets an alternate image to the button with a requested resource. /// /// - Parameters: /// - resource: The `Resource` object contains information about the resource. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache /// or network. Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setAlternateImage( with resource: Resource?, placeholder: KFCrossPlatformImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setAlternateImage( with: resource?.convertToSource(), placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } func setAlternateImage( with source: Source?, placeholder: KFCrossPlatformImage? = nil, parsedOptions: KingfisherParsedOptionsInfo, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { var mutatingSelf = self guard let source = source else { base.alternateImage = placeholder mutatingSelf.alternateTaskIdentifier = nil completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) return nil } var options = parsedOptions if !options.keepCurrentImageWhileLoading { base.alternateImage = placeholder } let issuedIdentifier = Source.Identifier.next() mutatingSelf.alternateTaskIdentifier = issuedIdentifier if let block = progressBlock { options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] } if let provider = ImageProgressiveProvider(options, refresh: { image in self.base.alternateImage = image }) { options.onDataReceived = (options.onDataReceived ?? []) + [provider] } options.onDataReceived?.forEach { $0.onShouldApply = { issuedIdentifier == self.alternateTaskIdentifier } } let task = KingfisherManager.shared.retrieveImage( with: source, options: options, downloadTaskUpdated: { mutatingSelf.alternateImageTask = $0 }, completionHandler: { result in CallbackQueue.mainCurrentOrAsync.execute { guard issuedIdentifier == self.alternateTaskIdentifier else { let reason: KingfisherError.ImageSettingErrorReason do { let value = try result.get() reason = .notCurrentSourceTask(result: value, error: nil, source: source) } catch { reason = .notCurrentSourceTask(result: nil, error: error, source: source) } let error = KingfisherError.imageSettingError(reason: reason) completionHandler?(.failure(error)) return } mutatingSelf.alternateImageTask = nil mutatingSelf.alternateTaskIdentifier = nil switch result { case .success(let value): self.base.alternateImage = value.image completionHandler?(result) case .failure: if let image = options.onFailureImage { self.base.alternateImage = image } completionHandler?(result) } } } ) mutatingSelf.alternateImageTask = task return task } // MARK: Cancelling Alternate Image Downloading Task /// Cancels the alternate image download task of the button if it is running. /// Nothing will happen if the downloading has already finished. public func cancelAlternateImageDownloadTask() { alternateImageTask?.cancel() } } // MARK: - Associated Object private var taskIdentifierKey: Void? private var imageTaskKey: Void? private var alternateTaskIdentifierKey: Void? private var alternateImageTaskKey: Void? extension KingfisherWrapper where Base: NSButton { // MARK: Properties public private(set) var taskIdentifier: Source.Identifier.Value? { get { let box: Box? = getAssociatedObject(base, &taskIdentifierKey) return box?.value } set { let box = newValue.map { Box($0) } setRetainedAssociatedObject(base, &taskIdentifierKey, box) } } private var imageTask: DownloadTask? { get { return getAssociatedObject(base, &imageTaskKey) } set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} } public private(set) var alternateTaskIdentifier: Source.Identifier.Value? { get { let box: Box? = getAssociatedObject(base, &alternateTaskIdentifierKey) return box?.value } set { let box = newValue.map { Box($0) } setRetainedAssociatedObject(base, &alternateTaskIdentifierKey, box) } } private var alternateImageTask: DownloadTask? { get { return getAssociatedObject(base, &alternateImageTaskKey) } set { setRetainedAssociatedObject(base, &alternateImageTaskKey, newValue)} } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Extensions/NSTextAttachment+Kingfisher.swift ================================================ // // NSTextAttachment+Kingfisher.swift // Kingfisher // // Created by Benjamin Briggs on 22/07/2019. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if !os(watchOS) #if os(macOS) import AppKit #else import UIKit #endif extension KingfisherWrapper where Base: NSTextAttachment { // MARK: Setting Image /// Sets an image to the text attachment with a source. /// /// - Parameters: /// - source: The `Source` object defines data information from network or a data provider. /// - attributedView: The owner of the attributed string which this `NSTextAttachment` is added. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// /// Internally, this method will use `KingfisherManager` to get the requested source /// Since this method will perform UI changes, you must call it from the main thread. /// /// The retrieved image will be set to `NSTextAttachment.image` property. Because it is not an image view based /// rendering, options related to view, such as `.transition`, are not supported. /// /// Kingfisher will call `setNeedsDisplay` on the `attributedView` when the image task done. It gives the view a /// chance to render the attributed string again for displaying the downloaded image. For example, if you set an /// attributed with this `NSTextAttachment` to a `UILabel` object, pass it as the `attributedView` parameter. /// /// Here is a typical use case: /// /// ```swift /// let attributedText = NSMutableAttributedString(string: "Hello World") /// let textAttachment = NSTextAttachment() /// /// textAttachment.kf.setImage( /// with: URL(string: "https://onevcat.com/assets/images/avatar.jpg")!, /// attributedView: label, /// options: [ /// .processor( /// ResizingImageProcessor(referenceSize: .init(width: 30, height: 30)) /// |> RoundCornerImageProcessor(cornerRadius: 15)) /// ] /// ) /// attributedText.replaceCharacters(in: NSRange(), with: NSAttributedString(attachment: textAttachment)) /// label.attributedText = attributedText /// ``` /// @discardableResult public func setImage( with source: Source?, attributedView: KFCrossPlatformView, placeholder: KFCrossPlatformImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { let options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) return setImage( with: source, attributedView: attributedView, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: completionHandler ) } /// Sets an image to the text attachment with a source. /// /// - Parameters: /// - resource: The `Resource` object contains information about the resource. /// - attributedView: The owner of the attributed string which this `NSTextAttachment` is added. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// /// Internally, this method will use `KingfisherManager` to get the requested source /// Since this method will perform UI changes, you must call it from the main thread. /// /// The retrieved image will be set to `NSTextAttachment.image` property. Because it is not an image view based /// rendering, options related to view, such as `.transition`, are not supported. /// /// Kingfisher will call `setNeedsDisplay` on the `attributedView` when the image task done. It gives the view a /// chance to render the attributed string again for displaying the downloaded image. For example, if you set an /// attributed with this `NSTextAttachment` to a `UILabel` object, pass it as the `attributedView` parameter. /// /// Here is a typical use case: /// /// ```swift /// let attributedText = NSMutableAttributedString(string: "Hello World") /// let textAttachment = NSTextAttachment() /// /// textAttachment.kf.setImage( /// with: URL(string: "https://onevcat.com/assets/images/avatar.jpg")!, /// attributedView: label, /// options: [ /// .processor( /// ResizingImageProcessor(referenceSize: .init(width: 30, height: 30)) /// |> RoundCornerImageProcessor(cornerRadius: 15)) /// ] /// ) /// attributedText.replaceCharacters(in: NSRange(), with: NSAttributedString(attachment: textAttachment)) /// label.attributedText = attributedText /// ``` /// @discardableResult public func setImage( with resource: Resource?, attributedView: KFCrossPlatformView, placeholder: KFCrossPlatformImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setImage( with: resource.map { .network($0) }, attributedView: attributedView, placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } func setImage( with source: Source?, attributedView: KFCrossPlatformView, placeholder: KFCrossPlatformImage? = nil, parsedOptions: KingfisherParsedOptionsInfo, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { var mutatingSelf = self guard let source = source else { base.image = placeholder mutatingSelf.taskIdentifier = nil completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) return nil } var options = parsedOptions if !options.keepCurrentImageWhileLoading { base.image = placeholder } let issuedIdentifier = Source.Identifier.next() mutatingSelf.taskIdentifier = issuedIdentifier if let block = progressBlock { options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] } if let provider = ImageProgressiveProvider(options, refresh: { image in self.base.image = image }) { options.onDataReceived = (options.onDataReceived ?? []) + [provider] } options.onDataReceived?.forEach { $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } } let task = KingfisherManager.shared.retrieveImage( with: source, options: options, completionHandler: { result in CallbackQueue.mainCurrentOrAsync.execute { guard issuedIdentifier == self.taskIdentifier else { let reason: KingfisherError.ImageSettingErrorReason do { let value = try result.get() reason = .notCurrentSourceTask(result: value, error: nil, source: source) } catch { reason = .notCurrentSourceTask(result: nil, error: error, source: source) } let error = KingfisherError.imageSettingError(reason: reason) completionHandler?(.failure(error)) return } mutatingSelf.imageTask = nil mutatingSelf.taskIdentifier = nil switch result { case .success(let value): self.base.image = value.image #if canImport(UIKit) attributedView.setNeedsDisplay() #else attributedView.setNeedsDisplay(attributedView.bounds) #endif case .failure: if let image = options.onFailureImage { self.base.image = image } } completionHandler?(result) } } ) mutatingSelf.imageTask = task return task } // MARK: Cancelling Image /// Cancel the image download task bounded to the text attachment if it is running. /// Nothing will happen if the downloading has already finished. public func cancelDownloadTask() { imageTask?.cancel() } } private var taskIdentifierKey: Void? private var imageTaskKey: Void? // MARK: Properties extension KingfisherWrapper where Base: NSTextAttachment { public private(set) var taskIdentifier: Source.Identifier.Value? { get { let box: Box? = getAssociatedObject(base, &taskIdentifierKey) return box?.value } set { let box = newValue.map { Box($0) } setRetainedAssociatedObject(base, &taskIdentifierKey, box) } } private var imageTask: DownloadTask? { get { return getAssociatedObject(base, &imageTaskKey) } set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Extensions/TVMonogramView+Kingfisher.swift ================================================ // // TVMonogramView+Kingfisher.swift // Kingfisher // // Created by Marvin Nazari on 2020-12-07. // // Copyright (c) 2020 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation #if canImport(TVUIKit) import TVUIKit @available(tvOS 12.0, *) extension KingfisherWrapper where Base: TVMonogramView { // MARK: Setting Image /// Sets an image to the image view with a source. /// /// - Parameters: /// - source: The `Source` object contains information about the image. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// /// Internally, this method will use `KingfisherManager` to get the requested source /// Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with source: Source?, placeholder: KFCrossPlatformImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { let options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) return setImage( with: source, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: completionHandler ) } func setImage( with source: Source?, placeholder: KFCrossPlatformImage? = nil, parsedOptions: KingfisherParsedOptionsInfo, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { var mutatingSelf = self guard let source = source else { base.image = placeholder mutatingSelf.taskIdentifier = nil completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) return nil } var options = parsedOptions if !options.keepCurrentImageWhileLoading { base.image = placeholder } let issuedIdentifier = Source.Identifier.next() mutatingSelf.taskIdentifier = issuedIdentifier if let block = progressBlock { options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] } if let provider = ImageProgressiveProvider(options, refresh: { image in self.base.image = image }) { options.onDataReceived = (options.onDataReceived ?? []) + [provider] } options.onDataReceived?.forEach { $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } } let task = KingfisherManager.shared.retrieveImage( with: source, options: options, downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, completionHandler: { result in CallbackQueue.mainCurrentOrAsync.execute { guard issuedIdentifier == self.taskIdentifier else { let reason: KingfisherError.ImageSettingErrorReason do { let value = try result.get() reason = .notCurrentSourceTask(result: value, error: nil, source: source) } catch { reason = .notCurrentSourceTask(result: nil, error: error, source: source) } let error = KingfisherError.imageSettingError(reason: reason) completionHandler?(.failure(error)) return } mutatingSelf.imageTask = nil mutatingSelf.taskIdentifier = nil switch result { case .success(let value): self.base.image = value.image completionHandler?(result) case .failure: if let image = options.onFailureImage { self.base.image = image } completionHandler?(result) } } } ) mutatingSelf.imageTask = task return task } /// Sets an image to the image view with a requested resource. /// /// - Parameters: /// - resource: The `Resource` object contains information about the image. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache /// or network. Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with resource: Resource?, placeholder: KFCrossPlatformImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setImage( with: resource?.convertToSource(), placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } // MARK: Cancelling Image /// Cancel the image download task bounded to the image view if it is running. /// Nothing will happen if the downloading has already finished. public func cancelDownloadTask() { imageTask?.cancel() } } private var taskIdentifierKey: Void? private var imageTaskKey: Void? // MARK: Properties @available(tvOS 12.0, *) extension KingfisherWrapper where Base: TVMonogramView { public private(set) var taskIdentifier: Source.Identifier.Value? { get { let box: Box? = getAssociatedObject(base, &taskIdentifierKey) return box?.value } set { let box = newValue.map { Box($0) } setRetainedAssociatedObject(base, &taskIdentifierKey, box) } } private var imageTask: DownloadTask? { get { return getAssociatedObject(base, &imageTaskKey) } set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Extensions/UIButton+Kingfisher.swift ================================================ // // UIButton+Kingfisher.swift // Kingfisher // // Created by Wei Wang on 15/4/13. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if !os(watchOS) #if canImport(UIKit) import UIKit extension KingfisherWrapper where Base: UIButton { // MARK: Setting Image /// Sets an image to the button for a specified state with a source. /// /// - Parameters: /// - source: The `Source` object contains information about the image. /// - state: The button state to which the image should be set. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// Internally, this method will use `KingfisherManager` to get the requested source, from either cache /// or network. Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with source: Source?, for state: UIControl.State, placeholder: UIImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { let options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) return setImage( with: source, for: state, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: completionHandler ) } /// Sets an image to the button for a specified state with a requested resource. /// /// - Parameters: /// - resource: The `Resource` object contains information about the resource. /// - state: The button state to which the image should be set. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache /// or network. Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with resource: Resource?, for state: UIControl.State, placeholder: UIImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setImage( with: resource?.convertToSource(), for: state, placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } @discardableResult public func setImage( with source: Source?, for state: UIControl.State, placeholder: UIImage? = nil, parsedOptions: KingfisherParsedOptionsInfo, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { guard let source = source else { base.setImage(placeholder, for: state) setTaskIdentifier(nil, for: state) completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) return nil } var options = parsedOptions if !options.keepCurrentImageWhileLoading { base.setImage(placeholder, for: state) } var mutatingSelf = self let issuedIdentifier = Source.Identifier.next() setTaskIdentifier(issuedIdentifier, for: state) if let block = progressBlock { options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] } if let provider = ImageProgressiveProvider(options, refresh: { image in self.base.setImage(image, for: state) }) { options.onDataReceived = (options.onDataReceived ?? []) + [provider] } options.onDataReceived?.forEach { $0.onShouldApply = { issuedIdentifier == self.taskIdentifier(for: state) } } let task = KingfisherManager.shared.retrieveImage( with: source, options: options, downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, completionHandler: { result in CallbackQueue.mainCurrentOrAsync.execute { guard issuedIdentifier == self.taskIdentifier(for: state) else { let reason: KingfisherError.ImageSettingErrorReason do { let value = try result.get() reason = .notCurrentSourceTask(result: value, error: nil, source: source) } catch { reason = .notCurrentSourceTask(result: nil, error: error, source: source) } let error = KingfisherError.imageSettingError(reason: reason) completionHandler?(.failure(error)) return } mutatingSelf.imageTask = nil mutatingSelf.setTaskIdentifier(nil, for: state) switch result { case .success(let value): self.base.setImage(value.image, for: state) completionHandler?(result) case .failure: if let image = options.onFailureImage { self.base.setImage(image, for: state) } completionHandler?(result) } } } ) mutatingSelf.imageTask = task return task } // MARK: Cancelling Downloading Task /// Cancels the image download task of the button if it is running. /// Nothing will happen if the downloading has already finished. public func cancelImageDownloadTask() { imageTask?.cancel() } // MARK: Setting Background Image /// Sets a background image to the button for a specified state with a source. /// /// - Parameters: /// - source: The `Source` object contains information about the image. /// - state: The button state to which the image should be set. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// Internally, this method will use `KingfisherManager` to get the requested source /// Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setBackgroundImage( with source: Source?, for state: UIControl.State, placeholder: UIImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { let options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) return setBackgroundImage( with: source, for: state, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: completionHandler ) } /// Sets a background image to the button for a specified state with a requested resource. /// /// - Parameters: /// - resource: The `Resource` object contains information about the resource. /// - state: The button state to which the image should be set. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache /// or network. Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setBackgroundImage( with resource: Resource?, for state: UIControl.State, placeholder: UIImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setBackgroundImage( with: resource?.convertToSource(), for: state, placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } func setBackgroundImage( with source: Source?, for state: UIControl.State, placeholder: UIImage? = nil, parsedOptions: KingfisherParsedOptionsInfo, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { guard let source = source else { base.setBackgroundImage(placeholder, for: state) setBackgroundTaskIdentifier(nil, for: state) completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) return nil } var options = parsedOptions if !options.keepCurrentImageWhileLoading { base.setBackgroundImage(placeholder, for: state) } var mutatingSelf = self let issuedIdentifier = Source.Identifier.next() setBackgroundTaskIdentifier(issuedIdentifier, for: state) if let block = progressBlock { options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] } if let provider = ImageProgressiveProvider(options, refresh: { image in self.base.setBackgroundImage(image, for: state) }) { options.onDataReceived = (options.onDataReceived ?? []) + [provider] } options.onDataReceived?.forEach { $0.onShouldApply = { issuedIdentifier == self.backgroundTaskIdentifier(for: state) } } let task = KingfisherManager.shared.retrieveImage( with: source, options: options, downloadTaskUpdated: { mutatingSelf.backgroundImageTask = $0 }, completionHandler: { result in CallbackQueue.mainCurrentOrAsync.execute { guard issuedIdentifier == self.backgroundTaskIdentifier(for: state) else { let reason: KingfisherError.ImageSettingErrorReason do { let value = try result.get() reason = .notCurrentSourceTask(result: value, error: nil, source: source) } catch { reason = .notCurrentSourceTask(result: nil, error: error, source: source) } let error = KingfisherError.imageSettingError(reason: reason) completionHandler?(.failure(error)) return } mutatingSelf.backgroundImageTask = nil mutatingSelf.setBackgroundTaskIdentifier(nil, for: state) switch result { case .success(let value): self.base.setBackgroundImage(value.image, for: state) completionHandler?(result) case .failure: if let image = options.onFailureImage { self.base.setBackgroundImage(image, for: state) } completionHandler?(result) } } } ) mutatingSelf.backgroundImageTask = task return task } // MARK: Cancelling Background Downloading Task /// Cancels the background image download task of the button if it is running. /// Nothing will happen if the downloading has already finished. public func cancelBackgroundImageDownloadTask() { backgroundImageTask?.cancel() } } // MARK: - Associated Object private var taskIdentifierKey: Void? private var imageTaskKey: Void? // MARK: Properties extension KingfisherWrapper where Base: UIButton { private typealias TaskIdentifier = Box<[UInt: Source.Identifier.Value]> public func taskIdentifier(for state: UIControl.State) -> Source.Identifier.Value? { return taskIdentifierInfo.value[state.rawValue] } private func setTaskIdentifier(_ identifier: Source.Identifier.Value?, for state: UIControl.State) { taskIdentifierInfo.value[state.rawValue] = identifier } private var taskIdentifierInfo: TaskIdentifier { return getAssociatedObject(base, &taskIdentifierKey) ?? { setRetainedAssociatedObject(base, &taskIdentifierKey, $0) return $0 } (TaskIdentifier([:])) } private var imageTask: DownloadTask? { get { return getAssociatedObject(base, &imageTaskKey) } set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} } } private var backgroundTaskIdentifierKey: Void? private var backgroundImageTaskKey: Void? // MARK: Background Properties extension KingfisherWrapper where Base: UIButton { public func backgroundTaskIdentifier(for state: UIControl.State) -> Source.Identifier.Value? { return backgroundTaskIdentifierInfo.value[state.rawValue] } private func setBackgroundTaskIdentifier(_ identifier: Source.Identifier.Value?, for state: UIControl.State) { backgroundTaskIdentifierInfo.value[state.rawValue] = identifier } private var backgroundTaskIdentifierInfo: TaskIdentifier { return getAssociatedObject(base, &backgroundTaskIdentifierKey) ?? { setRetainedAssociatedObject(base, &backgroundTaskIdentifierKey, $0) return $0 } (TaskIdentifier([:])) } private var backgroundImageTask: DownloadTask? { get { return getAssociatedObject(base, &backgroundImageTaskKey) } mutating set { setRetainedAssociatedObject(base, &backgroundImageTaskKey, newValue) } } } #endif #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Extensions/WKInterfaceImage+Kingfisher.swift ================================================ // // WKInterfaceImage+Kingfisher.swift // Kingfisher // // Created by Rodrigo Borges Soares on 04/05/18. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if canImport(WatchKit) import WatchKit extension KingfisherWrapper where Base: WKInterfaceImage { // MARK: Setting Image /// Sets an image to the image view with a source. /// /// - Parameters: /// - source: The `Source` object contains information about the image. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// /// Internally, this method will use `KingfisherManager` to get the requested source /// Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with source: Source?, placeholder: KFCrossPlatformImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { let options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty)) return setImage( with: source, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: completionHandler ) } /// Sets an image to the image view with a requested resource. /// /// - Parameters: /// - resource: The `Resource` object contains information about the image. /// - placeholder: A placeholder to show while retrieving the image from the given `resource`. /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. /// - completionHandler: Called when the image retrieved and set finished. /// - Returns: A task represents the image downloading. /// /// - Note: /// /// Internally, this method will use `KingfisherManager` to get the requested resource, from either cache /// or network. Since this method will perform UI changes, you must call it from the main thread. /// Both `progressBlock` and `completionHandler` will be also executed in the main thread. /// @discardableResult public func setImage( with resource: Resource?, placeholder: KFCrossPlatformImage? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { return setImage( with: resource?.convertToSource(), placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } func setImage( with source: Source?, placeholder: KFCrossPlatformImage? = nil, parsedOptions: KingfisherParsedOptionsInfo, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { var mutatingSelf = self guard let source = source else { base.setImage(placeholder) mutatingSelf.taskIdentifier = nil completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource))) return nil } var options = parsedOptions if !options.keepCurrentImageWhileLoading { base.setImage(placeholder) } let issuedIdentifier = Source.Identifier.next() mutatingSelf.taskIdentifier = issuedIdentifier if let block = progressBlock { options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] } if let provider = ImageProgressiveProvider(options, refresh: { image in self.base.setImage(image) }) { options.onDataReceived = (options.onDataReceived ?? []) + [provider] } options.onDataReceived?.forEach { $0.onShouldApply = { issuedIdentifier == self.taskIdentifier } } let task = KingfisherManager.shared.retrieveImage( with: source, options: options, downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, completionHandler: { result in CallbackQueue.mainCurrentOrAsync.execute { guard issuedIdentifier == self.taskIdentifier else { let reason: KingfisherError.ImageSettingErrorReason do { let value = try result.get() reason = .notCurrentSourceTask(result: value, error: nil, source: source) } catch { reason = .notCurrentSourceTask(result: nil, error: error, source: source) } let error = KingfisherError.imageSettingError(reason: reason) completionHandler?(.failure(error)) return } mutatingSelf.imageTask = nil mutatingSelf.taskIdentifier = nil switch result { case .success(let value): self.base.setImage(value.image) completionHandler?(result) case .failure: if let image = options.onFailureImage { self.base.setImage(image) } completionHandler?(result) } } } ) mutatingSelf.imageTask = task return task } // MARK: Cancelling Image /// Cancel the image download task bounded to the image view if it is running. /// Nothing will happen if the downloading has already finished. public func cancelDownloadTask() { imageTask?.cancel() } } private var taskIdentifierKey: Void? private var imageTaskKey: Void? // MARK: Properties extension KingfisherWrapper where Base: WKInterfaceImage { public private(set) var taskIdentifier: Source.Identifier.Value? { get { let box: Box? = getAssociatedObject(base, &taskIdentifierKey) return box?.value } set { let box = newValue.map { Box($0) } setRetainedAssociatedObject(base, &taskIdentifierKey, box) } } private var imageTask: DownloadTask? { get { return getAssociatedObject(base, &imageTaskKey) } set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)} } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/General/ImageSource/AVAssetImageDataProvider.swift ================================================ // // AVAssetImageDataProvider.swift // Kingfisher // // Created by onevcat on 2020/08/09. // // Copyright (c) 2020 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if !os(watchOS) import Foundation import AVKit #if canImport(MobileCoreServices) import MobileCoreServices #else import CoreServices #endif /// A data provider to provide thumbnail data from a given AVKit asset. public struct AVAssetImageDataProvider: ImageDataProvider { /// The possible error might be caused by the `AVAssetImageDataProvider`. /// - userCancelled: The data provider process is cancelled. /// - invalidImage: The retrieved image is invalid. public enum AVAssetImageDataProviderError: Error { case userCancelled case invalidImage(_ image: CGImage?) } /// The asset image generator bound to `self`. public let assetImageGenerator: AVAssetImageGenerator /// The time at which the image should be generate in the asset. public let time: CMTime private var internalKey: String { return (assetImageGenerator.asset as? AVURLAsset)?.url.absoluteString ?? UUID().uuidString } /// The cache key used by `self`. public var cacheKey: String { return "\(internalKey)_\(time.seconds)" } /// Creates an asset image data provider. /// - Parameters: /// - assetImageGenerator: The asset image generator controls data providing behaviors. /// - time: At which time in the asset the image should be generated. public init(assetImageGenerator: AVAssetImageGenerator, time: CMTime) { self.assetImageGenerator = assetImageGenerator self.time = time } /// Creates an asset image data provider. /// - Parameters: /// - assetURL: The URL of asset for providing image data. /// - time: At which time in the asset the image should be generated. /// /// This method uses `assetURL` to create an `AVAssetImageGenerator` object and calls /// the `init(assetImageGenerator:time:)` initializer. /// public init(assetURL: URL, time: CMTime) { let asset = AVAsset(url: assetURL) let generator = AVAssetImageGenerator(asset: asset) self.init(assetImageGenerator: generator, time: time) } /// Creates an asset image data provider. /// /// - Parameters: /// - assetURL: The URL of asset for providing image data. /// - seconds: At which time in seconds in the asset the image should be generated. /// /// This method uses `assetURL` to create an `AVAssetImageGenerator` object, uses `seconds` to create a `CMTime`, /// and calls the `init(assetImageGenerator:time:)` initializer. /// public init(assetURL: URL, seconds: TimeInterval) { let time = CMTime(seconds: seconds, preferredTimescale: 600) self.init(assetURL: assetURL, time: time) } public func data(handler: @escaping (Result) -> Void) { assetImageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: time)]) { (requestedTime, image, imageTime, result, error) in if let error = error { handler(.failure(error)) return } if result == .cancelled { handler(.failure(AVAssetImageDataProviderError.userCancelled)) return } guard let cgImage = image, let data = cgImage.jpegData else { handler(.failure(AVAssetImageDataProviderError.invalidImage(image))) return } handler(.success(data)) } } } extension CGImage { var jpegData: Data? { guard let mutableData = CFDataCreateMutable(nil, 0), let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) else { return nil } CGImageDestinationAddImage(destination, self, nil) guard CGImageDestinationFinalize(destination) else { return nil } return mutableData as Data } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/General/ImageSource/ImageDataProvider.swift ================================================ // // ImageDataProvider.swift // Kingfisher // // Created by onevcat on 2018/11/13. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents a data provider to provide image data to Kingfisher when setting with /// `Source.provider` source. Compared to `Source.network` member, it gives a chance /// to load some image data in your own way, as long as you can provide the data /// representation for the image. public protocol ImageDataProvider { /// The key used in cache. var cacheKey: String { get } /// Provides the data which represents image. Kingfisher uses the data you pass in the /// handler to process images and caches it for later use. /// /// - Parameter handler: The handler you should call when you prepared your data. /// If the data is loaded successfully, call the handler with /// a `.success` with the data associated. Otherwise, call it /// with a `.failure` and pass the error. /// /// - Note: /// If the `handler` is called with a `.failure` with error, a `dataProviderError` of /// `ImageSettingErrorReason` will be finally thrown out to you as the `KingfisherError` /// from the framework. func data(handler: @escaping (Result) -> Void) /// The content URL represents this provider, if exists. var contentURL: URL? { get } } public extension ImageDataProvider { var contentURL: URL? { return nil } func convertToSource() -> Source { .provider(self) } } /// Represents an image data provider for loading from a local file URL on disk. /// Uses this type for adding a disk image to Kingfisher. Compared to loading it /// directly, you can get benefit of using Kingfisher's extension methods, as well /// as applying `ImageProcessor`s and storing the image to `ImageCache` of Kingfisher. public struct LocalFileImageDataProvider: ImageDataProvider { // MARK: Public Properties /// The file URL from which the image be loaded. public let fileURL: URL // MARK: Initializers /// Creates an image data provider by supplying the target local file URL. /// /// - Parameters: /// - fileURL: The file URL from which the image be loaded. /// - cacheKey: The key is used for caching the image data. By default, /// the `absoluteString` of `fileURL` is used. public init(fileURL: URL, cacheKey: String? = nil) { self.fileURL = fileURL self.cacheKey = cacheKey ?? fileURL.absoluteString } // MARK: Protocol Conforming /// The key used in cache. public var cacheKey: String public func data(handler: (Result) -> Void) { handler(Result(catching: { try Data(contentsOf: fileURL) })) } /// The URL of the local file on the disk. public var contentURL: URL? { return fileURL } } /// Represents an image data provider for loading image from a given Base64 encoded string. public struct Base64ImageDataProvider: ImageDataProvider { // MARK: Public Properties /// The encoded Base64 string for the image. public let base64String: String // MARK: Initializers /// Creates an image data provider by supplying the Base64 encoded string. /// /// - Parameters: /// - base64String: The Base64 encoded string for an image. /// - cacheKey: The key is used for caching the image data. You need a different key for any different image. public init(base64String: String, cacheKey: String) { self.base64String = base64String self.cacheKey = cacheKey } // MARK: Protocol Conforming /// The key used in cache. public var cacheKey: String public func data(handler: (Result) -> Void) { let data = Data(base64Encoded: base64String)! handler(.success(data)) } } /// Represents an image data provider for a raw data object. public struct RawImageDataProvider: ImageDataProvider { // MARK: Public Properties /// The raw data object to provide to Kingfisher image loader. public let data: Data // MARK: Initializers /// Creates an image data provider by the given raw `data` value and a `cacheKey` be used in Kingfisher cache. /// /// - Parameters: /// - data: The raw data reprensents an image. /// - cacheKey: The key is used for caching the image data. You need a different key for any different image. public init(data: Data, cacheKey: String) { self.data = data self.cacheKey = cacheKey } // MARK: Protocol Conforming /// The key used in cache. public var cacheKey: String public func data(handler: @escaping (Result) -> Void) { handler(.success(data)) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/General/ImageSource/Resource.swift ================================================ // // Resource.swift // Kingfisher // // Created by Wei Wang on 15/4/6. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents an image resource at a certain url and a given cache key. /// Kingfisher will use a `Resource` to download a resource from network and cache it with the cache key when /// using `Source.network` as its image setting source. public protocol Resource { /// The key used in cache. var cacheKey: String { get } /// The target image URL. var downloadURL: URL { get } } extension Resource { /// Converts `self` to a valid `Source` based on its `downloadURL` scheme. A `.provider` with /// `LocalFileImageDataProvider` associated will be returned if the URL points to a local file. Otherwise, /// `.network` is returned. public func convertToSource(overrideCacheKey: String? = nil) -> Source { return downloadURL.isFileURL ? .provider(LocalFileImageDataProvider(fileURL: downloadURL, cacheKey: overrideCacheKey ?? cacheKey)) : .network(ImageResource(downloadURL: downloadURL, cacheKey: overrideCacheKey ?? cacheKey)) } } /// ImageResource is a simple combination of `downloadURL` and `cacheKey`. /// When passed to image view set methods, Kingfisher will try to download the target /// image from the `downloadURL`, and then store it with the `cacheKey` as the key in cache. public struct ImageResource: Resource { // MARK: - Initializers /// Creates an image resource. /// /// - Parameters: /// - downloadURL: The target image URL from where the image can be downloaded. /// - cacheKey: The cache key. If `nil`, Kingfisher will use the `absoluteString` of `downloadURL` as the key. /// Default is `nil`. public init(downloadURL: URL, cacheKey: String? = nil) { self.downloadURL = downloadURL self.cacheKey = cacheKey ?? downloadURL.absoluteString } // MARK: Protocol Conforming /// The key used in cache. public let cacheKey: String /// The target image URL. public let downloadURL: URL } /// URL conforms to `Resource` in Kingfisher. /// The `absoluteString` of this URL is used as `cacheKey`. And the URL itself will be used as `downloadURL`. /// If you need customize the url and/or cache key, use `ImageResource` instead. extension URL: Resource { public var cacheKey: String { return absoluteString } public var downloadURL: URL { return self } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/General/ImageSource/Source.swift ================================================ // // Source.swift // Kingfisher // // Created by onevcat on 2018/11/17. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents an image setting source for Kingfisher methods. /// /// A `Source` value indicates the way how the target image can be retrieved and cached. /// /// - network: The target image should be got from network remotely. The associated `Resource` /// value defines detail information like image URL and cache key. /// - provider: The target image should be provided in a data format. Normally, it can be an image /// from local storage or in any other encoding format (like Base64). public enum Source { /// Represents the source task identifier when setting an image to a view with extension methods. public enum Identifier { /// The underlying value type of source identifier. public typealias Value = UInt static var current: Value = 0 static func next() -> Value { current += 1 return current } } // MARK: Member Cases /// The target image should be got from network remotely. The associated `Resource` /// value defines detail information like image URL and cache key. case network(Resource) /// The target image should be provided in a data format. Normally, it can be an image /// from local storage or in any other encoding format (like Base64). case provider(ImageDataProvider) // MARK: Getting Properties /// The cache key defined for this source value. public var cacheKey: String { switch self { case .network(let resource): return resource.cacheKey case .provider(let provider): return provider.cacheKey } } /// The URL defined for this source value. /// /// For a `.network` source, it is the `downloadURL` of associated `Resource` instance. /// For a `.provider` value, it is always `nil`. public var url: URL? { switch self { case .network(let resource): return resource.downloadURL case .provider(let provider): return provider.contentURL } } } extension Source: Hashable { public static func == (lhs: Source, rhs: Source) -> Bool { switch (lhs, rhs) { case (.network(let r1), .network(let r2)): return r1.cacheKey == r2.cacheKey && r1.downloadURL == r2.downloadURL case (.provider(let p1), .provider(let p2)): return p1.cacheKey == p2.cacheKey && p1.contentURL == p2.contentURL case (.provider(_), .network(_)): return false case (.network(_), .provider(_)): return false } } public func hash(into hasher: inout Hasher) { switch self { case .network(let r): hasher.combine(r.cacheKey) hasher.combine(r.downloadURL) case .provider(let p): hasher.combine(p.cacheKey) hasher.combine(p.contentURL) } } } extension Source { var asResource: Resource? { guard case .network(let resource) = self else { return nil } return resource } var asProvider: ImageDataProvider? { guard case .provider(let provider) = self else { return nil } return provider } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/General/KF.swift ================================================ // // KF.swift // Kingfisher // // Created by onevcat on 2020/09/21. // // Copyright (c) 2020 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if canImport(UIKit) import UIKit #endif #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit #endif #if canImport(WatchKit) import WatchKit #endif #if canImport(TVUIKit) import TVUIKit #endif /// A helper type to create image setting tasks in a builder pattern. /// Use methods in this type to create a `KF.Builder` instance and configure image tasks there. public enum KF { /// Creates a builder for a given `Source`. /// - Parameter source: The `Source` object defines data information from network or a data provider. /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)` /// to start the image loading. public static func source(_ source: Source?) -> KF.Builder { Builder(source: source) } /// Creates a builder for a given `Resource`. /// - Parameter resource: The `Resource` object defines data information like key or URL. /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)` /// to start the image loading. public static func resource(_ resource: Resource?) -> KF.Builder { source(resource?.convertToSource()) } /// Creates a builder for a given `URL` and an optional cache key. /// - Parameters: /// - url: The URL where the image should be downloaded. /// - cacheKey: The key used to store the downloaded image in cache. /// If `nil`, the `absoluteString` of `url` is used as the cache key. /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)` /// to start the image loading. public static func url(_ url: URL?, cacheKey: String? = nil) -> KF.Builder { source(url?.convertToSource(overrideCacheKey: cacheKey)) } /// Creates a builder for a given `ImageDataProvider`. /// - Parameter provider: The `ImageDataProvider` object contains information about the data. /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)` /// to start the image loading. public static func dataProvider(_ provider: ImageDataProvider?) -> KF.Builder { source(provider?.convertToSource()) } /// Creates a builder for some given raw data and a cache key. /// - Parameters: /// - data: The data object from which the image should be created. /// - cacheKey: The key used to store the downloaded image in cache. /// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)` /// to start the image loading. public static func data(_ data: Data?, cacheKey: String) -> KF.Builder { if let data = data { return dataProvider(RawImageDataProvider(data: data, cacheKey: cacheKey)) } else { return dataProvider(nil) } } } extension KF { /// A builder class to configure an image retrieving task and set it to a holder view or component. public class Builder { private let source: Source? #if os(watchOS) private var placeholder: KFCrossPlatformImage? #else private var placeholder: Placeholder? #endif public var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions) public let onFailureDelegate = Delegate() public let onSuccessDelegate = Delegate() public let onProgressDelegate = Delegate<(Int64, Int64), Void>() init(source: Source?) { self.source = source } private var resultHandler: ((Result) -> Void)? { { switch $0 { case .success(let result): self.onSuccessDelegate(result) case .failure(let error): self.onFailureDelegate(error) } } } private var progressBlock: DownloadProgressBlock { { self.onProgressDelegate(($0, $1)) } } } } extension KF.Builder { #if !os(watchOS) /// Builds the image task request and sets it to an image view. /// - Parameter imageView: The image view which loads the task and should be set with the image. /// - Returns: A task represents the image downloading, if initialized. /// This value is `nil` if the image is being loaded from cache. @discardableResult public func set(to imageView: KFCrossPlatformImageView) -> DownloadTask? { imageView.kf.setImage( with: source, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: resultHandler ) } /// Builds the image task request and sets it to an `NSTextAttachment` object. /// - Parameters: /// - attachment: The text attachment object which loads the task and should be set with the image. /// - attributedView: The owner of the attributed string which this `NSTextAttachment` is added. /// - Returns: A task represents the image downloading, if initialized. /// This value is `nil` if the image is being loaded from cache. @discardableResult public func set(to attachment: NSTextAttachment, attributedView: KFCrossPlatformView) -> DownloadTask? { let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil return attachment.kf.setImage( with: source, attributedView: attributedView, placeholder: placeholderImage, parsedOptions: options, progressBlock: progressBlock, completionHandler: resultHandler ) } #if canImport(UIKit) /// Builds the image task request and sets it to a button. /// - Parameters: /// - button: The button which loads the task and should be set with the image. /// - state: The button state to which the image should be set. /// - Returns: A task represents the image downloading, if initialized. /// This value is `nil` if the image is being loaded from cache. @discardableResult public func set(to button: UIButton, for state: UIControl.State) -> DownloadTask? { let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil return button.kf.setImage( with: source, for: state, placeholder: placeholderImage, parsedOptions: options, progressBlock: progressBlock, completionHandler: resultHandler ) } /// Builds the image task request and sets it to the background image for a button. /// - Parameters: /// - button: The button which loads the task and should be set with the image. /// - state: The button state to which the image should be set. /// - Returns: A task represents the image downloading, if initialized. /// This value is `nil` if the image is being loaded from cache. @discardableResult public func setBackground(to button: UIButton, for state: UIControl.State) -> DownloadTask? { let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil return button.kf.setBackgroundImage( with: source, for: state, placeholder: placeholderImage, parsedOptions: options, progressBlock: progressBlock, completionHandler: resultHandler ) } #endif // end of canImport(UIKit) #if canImport(AppKit) && !targetEnvironment(macCatalyst) /// Builds the image task request and sets it to a button. /// - Parameter button: The button which loads the task and should be set with the image. /// - Returns: A task represents the image downloading, if initialized. /// This value is `nil` if the image is being loaded from cache. @discardableResult public func set(to button: NSButton) -> DownloadTask? { let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil return button.kf.setImage( with: source, placeholder: placeholderImage, parsedOptions: options, progressBlock: progressBlock, completionHandler: resultHandler ) } /// Builds the image task request and sets it to the alternative image for a button. /// - Parameter button: The button which loads the task and should be set with the image. /// - Returns: A task represents the image downloading, if initialized. /// This value is `nil` if the image is being loaded from cache. @discardableResult public func setAlternative(to button: NSButton) -> DownloadTask? { let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil return button.kf.setAlternateImage( with: source, placeholder: placeholderImage, parsedOptions: options, progressBlock: progressBlock, completionHandler: resultHandler ) } #endif // end of canImport(AppKit) #endif // end of !os(watchOS) #if canImport(WatchKit) /// Builds the image task request and sets it to a `WKInterfaceImage` object. /// - Parameter interfaceImage: The watch interface image which loads the task and should be set with the image. /// - Returns: A task represents the image downloading, if initialized. /// This value is `nil` if the image is being loaded from cache. @discardableResult public func set(to interfaceImage: WKInterfaceImage) -> DownloadTask? { return interfaceImage.kf.setImage( with: source, placeholder: placeholder, parsedOptions: options, progressBlock: progressBlock, completionHandler: resultHandler ) } #endif // end of canImport(WatchKit) #if canImport(TVUIKit) /// Builds the image task request and sets it to a TV monogram view. /// - Parameter monogramView: The monogram view which loads the task and should be set with the image. /// - Returns: A task represents the image downloading, if initialized. /// This value is `nil` if the image is being loaded from cache. @available(tvOS 12.0, *) @discardableResult public func set(to monogramView: TVMonogramView) -> DownloadTask? { let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil return monogramView.kf.setImage( with: source, placeholder: placeholderImage, parsedOptions: options, progressBlock: progressBlock, completionHandler: resultHandler ) } #endif // end of canImport(TVUIKit) } #if !os(watchOS) extension KF.Builder { #if os(iOS) || os(tvOS) /// Sets a placeholder which is used while retrieving the image. /// - Parameter placeholder: A placeholder to show while retrieving the image from its source. /// - Returns: A `KF.Builder` with changes applied. public func placeholder(_ placeholder: Placeholder?) -> Self { self.placeholder = placeholder return self } #endif /// Sets a placeholder image which is used while retrieving the image. /// - Parameter placeholder: An image to show while retrieving the image from its source. /// - Returns: A `KF.Builder` with changes applied. public func placeholder(_ image: KFCrossPlatformImage?) -> Self { self.placeholder = image return self } } #endif extension KF.Builder { #if os(iOS) || os(tvOS) /// Sets the transition for the image task. /// - Parameter transition: The desired transition effect when setting the image to image view. /// - Returns: A `KF.Builder` with changes applied. /// /// Kingfisher will use the `transition` to animate the image in if it is downloaded from web. /// The transition will not happen when the /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when /// the image being retrieved from cache, also call `forceRefresh()` on the returned `KF.Builder`. public func transition(_ transition: ImageTransition) -> Self { options.transition = transition return self } /// Sets a fade transition for the image task. /// - Parameter duration: The duration of the fade transition. /// - Returns: A `KF.Builder` with changes applied. /// /// Kingfisher will use the fade transition to animate the image in if it is downloaded from web. /// The transition will not happen when the /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when /// the image being retrieved from cache, also call `forceRefresh()` on the returned `KF.Builder`. public func fade(duration: TimeInterval) -> Self { options.transition = .fade(duration) return self } #endif /// Sets whether keeping the existing image of image view while setting another image to it. /// - Parameter enabled: Whether the existing image should be kept. /// - Returns: A `KF.Builder` with changes applied. /// /// By setting this option, the placeholder image parameter of image view extension method /// will be ignored and the current image will be kept while loading or downloading the new image. /// public func keepCurrentImageWhileLoading(_ enabled: Bool = true) -> Self { options.keepCurrentImageWhileLoading = enabled return self } /// Sets whether only the first frame from an animated image file should be loaded as a single image. /// - Parameter enabled: Whether the only the first frame should be loaded. /// - Returns: A `KF.Builder` with changes applied. /// /// Loading an animated images may take too much memory. It will be useful when you want to display a /// static preview of the first frame from an animated image. /// /// This option will be ignored if the target image is not animated image data. /// public func onlyLoadFirstFrame(_ enabled: Bool = true) -> Self { options.onlyLoadFirstFrame = enabled return self } /// Sets the image that will be used if an image retrieving task fails. /// - Parameter image: The image that will be used when something goes wrong. /// - Returns: A `KF.Builder` with changes applied. /// /// If set and an image retrieving error occurred Kingfisher will set provided image (or empty) /// in place of requested one. It's useful when you don't want to show placeholder /// during loading time but wants to use some default image when requests will be failed. /// public func onFailureImage(_ image: KFCrossPlatformImage?) -> Self { options.onFailureImage = .some(image) return self } /// Enables progressive image loading with a specified `ImageProgressive` setting to process the /// progressive JPEG data and display it in a progressive way. /// - Parameter progressive: The progressive settings which is used while loading. /// - Returns: A `KF.Builder` with changes applied. public func progressiveJPEG(_ progressive: ImageProgressive? = .default) -> Self { options.progressiveJPEG = progressive return self } } // MARK: - Redirect Handler extension KF { /// Represents the detail information when a task redirect happens. It is wrapping necessary information for a /// `ImageDownloadRedirectHandler`. See that protocol for more information. public struct RedirectPayload { /// The related session data task when the redirect happens. It is /// the current `SessionDataTask` which triggers this redirect. public let task: SessionDataTask /// The response received during redirection. public let response: HTTPURLResponse /// The request for redirection which can be modified. public let newRequest: URLRequest /// A closure for being called with modified request. public let completionHandler: (URLRequest?) -> Void } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/General/KFOptionsSetter.swift ================================================ // // KFOptionsSetter.swift // Kingfisher // // Created by onevcat on 2020/12/22. // // Copyright (c) 2020 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation import CoreGraphics public protocol KFOptionSetter { var options: KingfisherParsedOptionsInfo { get nonmutating set } var onFailureDelegate: Delegate { get } var onSuccessDelegate: Delegate { get } var onProgressDelegate: Delegate<(Int64, Int64), Void> { get } var delegateObserver: AnyObject { get } } extension KF.Builder: KFOptionSetter { public var delegateObserver: AnyObject { self } } #if canImport(SwiftUI) && canImport(Combine) @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension KFImage: KFOptionSetter { public var options: KingfisherParsedOptionsInfo { get { context.binder.options } nonmutating set { context.binder.options = newValue } } public var onFailureDelegate: Delegate { context.binder.onFailureDelegate } public var onSuccessDelegate: Delegate { context.binder.onSuccessDelegate } public var onProgressDelegate: Delegate<(Int64, Int64), Void> { context.binder.onProgressDelegate } public var delegateObserver: AnyObject { context.binder } } #endif // MARK: - Life cycles extension KFOptionSetter { /// Sets the progress block to current builder. /// - Parameter block: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. If `block` is `nil`, the callback /// will be reset. /// - Returns: A `Self` value with changes applied. public func onProgress(_ block: DownloadProgressBlock?) -> Self { onProgressDelegate.delegate(on: delegateObserver) { (observer, result) in block?(result.0, result.1) } return self } /// Sets the the done block to current builder. /// - Parameter block: Called when the image task successfully completes and the the image set is done. If `block` /// is `nil`, the callback will be reset. /// - Returns: A `KF.Builder` with changes applied. public func onSuccess(_ block: ((RetrieveImageResult) -> Void)?) -> Self { onSuccessDelegate.delegate(on: delegateObserver) { (observer, result) in block?(result) } return self } /// Sets the catch block to current builder. /// - Parameter block: Called when an error happens during the image task. If `block` /// is `nil`, the callback will be reset. /// - Returns: A `KF.Builder` with changes applied. public func onFailure(_ block: ((KingfisherError) -> Void)?) -> Self { onFailureDelegate.delegate(on: delegateObserver) { (observer, error) in block?(error) } return self } } // MARK: - Basic options settings. extension KFOptionSetter { /// Sets the target image cache for this task. /// - Parameter cache: The target cache is about to be used for the task. /// - Returns: A `Self` value with changes applied. /// /// Kingfisher will use the associated `ImageCache` object when handling related operations, /// including trying to retrieve the cached images and store the downloaded image to it. /// public func targetCache(_ cache: ImageCache) -> Self { options.targetCache = cache return self } /// Sets the target image cache to store the original downloaded image for this task. /// - Parameter cache: The target cache is about to be used for storing the original downloaded image from the task. /// - Returns: A `Self` value with changes applied. /// /// The `ImageCache` for storing and retrieving original images. If `originalCache` is /// contained in the options, it will be preferred for storing and retrieving original images. /// If there is no `.originalCache` in the options, `.targetCache` will be used to store original images. /// /// When using KingfisherManager to download and store an image, if `cacheOriginalImage` is /// applied in the option, the original image will be stored to this `originalCache`. At the /// same time, if a requested final image (with processor applied) cannot be found in `targetCache`, /// Kingfisher will try to search the original image to check whether it is already there. If found, /// it will be used and applied with the given processor. It is an optimization for not downloading /// the same image for multiple times. /// public func originalCache(_ cache: ImageCache) -> Self { options.originalCache = cache return self } /// Sets the downloader used to perform the image download task. /// - Parameter downloader: The downloader which is about to be used for downloading. /// - Returns: A `Self` value with changes applied. /// /// Kingfisher will use the set `ImageDownloader` object to download the requested images. public func downloader(_ downloader: ImageDownloader) -> Self { options.downloader = downloader return self } /// Sets the download priority for the image task. /// - Parameter priority: The download priority of image download task. /// - Returns: A `Self` value with changes applied. /// /// The `priority` value will be set as the priority of the image download task. The value for it should be /// between 0.0~1.0. You can choose a value between `URLSessionTask.defaultPriority`, `URLSessionTask.lowPriority` /// or `URLSessionTask.highPriority`. If this option not set, the default value (`URLSessionTask.defaultPriority`) /// will be used. public func downloadPriority(_ priority: Float) -> Self { options.downloadPriority = priority return self } /// Sets whether Kingfisher should ignore the cache and try to start a download task for the image source. /// - Parameter enabled: Enable the force refresh or not. /// - Returns: A `Self` value with changes applied. public func forceRefresh(_ enabled: Bool = true) -> Self { options.forceRefresh = enabled return self } /// Sets whether Kingfisher should try to retrieve the image from memory cache first. If not found, it ignores the /// disk cache and starts a download task for the image source. /// - Parameter enabled: Enable the memory-only cache searching or not. /// - Returns: A `Self` value with changes applied. /// /// This is useful when you want to display a changeable image behind the same url at the same app session, while /// avoiding download it for multiple times. public func fromMemoryCacheOrRefresh(_ enabled: Bool = true) -> Self { options.fromMemoryCacheOrRefresh = enabled return self } /// Sets whether the image should only be cached in memory but not in disk. /// - Parameter enabled: Whether the image should be only cache in memory or not. /// - Returns: A `Self` value with changes applied. public func cacheMemoryOnly(_ enabled: Bool = true) -> Self { options.cacheMemoryOnly = enabled return self } /// Sets whether Kingfisher should wait for caching operation to be completed before calling the /// `onSuccess` or `onFailure` block. /// - Parameter enabled: Whether Kingfisher should wait for caching operation. /// - Returns: A `Self` value with changes applied. public func waitForCache(_ enabled: Bool = true) -> Self { options.waitForCache = enabled return self } /// Sets whether Kingfisher should only try to retrieve the image from cache, but not from network. /// - Parameter enabled: Whether Kingfisher should only try to retrieve the image from cache. /// - Returns: A `Self` value with changes applied. /// /// If the image is not in cache, the image retrieving will fail with the /// `KingfisherError.cacheError` with `.imageNotExisting` as its reason. public func onlyFromCache(_ enabled: Bool = true) -> Self { options.onlyFromCache = enabled return self } /// Sets whether the image should be decoded in a background thread before using. /// - Parameter enabled: Whether the image should be decoded in a background thread before using. /// - Returns: A `Self` value with changes applied. /// /// Setting to `true` will decode the downloaded image data and do a off-screen rendering to extract pixel /// information in background. This can speed up display, but will cost more time and memory to prepare the image /// for using. public func backgroundDecode(_ enabled: Bool = true) -> Self { options.backgroundDecode = enabled return self } /// Sets the callback queue which is used as the target queue of dispatch callbacks when retrieving images from /// cache. If not set, Kingfisher will use main queue for callbacks. /// - Parameter queue: The target queue which the cache retrieving callback will be invoked on. /// - Returns: A `Self` value with changes applied. /// /// - Note: /// This option does not affect the callbacks for UI related extension methods or `KFImage` result handlers. /// You will always get the callbacks called from main queue. public func callbackQueue(_ queue: CallbackQueue) -> Self { options.callbackQueue = queue return self } /// Sets the scale factor value when converting retrieved data to an image. /// - Parameter factor: The scale factor value. /// - Returns: A `Self` value with changes applied. /// /// Specify the image scale, instead of your screen scale. You may need to set the correct scale when you dealing /// with 2x or 3x retina images. Otherwise, Kingfisher will convert the data to image object at `scale` 1.0. /// public func scaleFactor(_ factor: CGFloat) -> Self { options.scaleFactor = factor return self } /// Sets whether the original image should be cached even when the original image has been processed by any other /// `ImageProcessor`s. /// - Parameter enabled: Whether the original image should be cached. /// - Returns: A `Self` value with changes applied. /// /// If set and an `ImageProcessor` is used, Kingfisher will try to cache both the final result and original /// image. Kingfisher will have a chance to use the original image when another processor is applied to the same /// resource, instead of downloading it again. You can use `.originalCache` to specify a cache or the original /// images if necessary. /// /// The original image will be only cached to disk storage. /// public func cacheOriginalImage(_ enabled: Bool = true) -> Self { options.cacheOriginalImage = enabled return self } /// Sets whether the disk storage loading should happen in the same calling queue. /// - Parameter enabled: Whether the disk storage loading should happen in the same calling queue. /// - Returns: A `Self` value with changes applied. /// /// By default, disk storage file loading /// happens in its own queue with an asynchronous dispatch behavior. Although it provides better non-blocking disk /// loading performance, it also causes a flickering when you reload an image from disk, if the image view already /// has an image set. /// /// Set this options will stop that flickering by keeping all loading in the same queue (typically the UI queue /// if you are using Kingfisher's extension methods to set an image), with a tradeoff of loading performance. /// public func loadDiskFileSynchronously(_ enabled: Bool = true) -> Self { options.loadDiskFileSynchronously = enabled return self } /// Sets a queue on which the image processing should happen. /// - Parameter queue: The queue on which the image processing should happen. /// - Returns: A `Self` value with changes applied. /// /// By default, Kingfisher uses a pre-defined serial /// queue to process images. Use this option to change this behavior. For example, specify a `.mainCurrentOrAsync` /// to let the image be processed in main queue to prevent a possible flickering (but with a possibility of /// blocking the UI, especially if the processor needs a lot of time to run). public func processingQueue(_ queue: CallbackQueue?) -> Self { options.processingQueue = queue return self } /// Sets the alternative sources that will be used when loading of the original input `Source` fails. /// - Parameter sources: The alternative sources will be used. /// - Returns: A `Self` value with changes applied. /// /// Values of the `sources` array will be used to start a new image loading task if the previous task /// fails due to an error. The image source loading process will stop as soon as a source is loaded successfully. /// If all `sources` are used but the loading is still failing, an `imageSettingError` with /// `alternativeSourcesExhausted` as its reason will be given out in the `catch` block. /// /// This is useful if you want to implement a fallback solution for setting image. /// /// User cancellation will not trigger the alternative source loading. public func alternativeSources(_ sources: [Source]?) -> Self { options.alternativeSources = sources return self } /// Sets a retry strategy that will be used when something gets wrong during the image retrieving. /// - Parameter strategy: The provided strategy to define how the retrying should happen. /// - Returns: A `Self` value with changes applied. public func retry(_ strategy: RetryStrategy) -> Self { options.retryStrategy = strategy return self } /// Sets a retry strategy with a max retry count and retrying interval. /// - Parameters: /// - maxCount: The maximum count before the retry stops. /// - interval: The time interval between each retry attempt. /// - Returns: A `Self` value with changes applied. /// /// This defines the simplest retry strategy, which retry a failing request for several times, with some certain /// interval between each time. For example, `.retry(maxCount: 3, interval: .second(3))` means attempt for at most /// three times, and wait for 3 seconds if a previous retry attempt fails, then start a new attempt. public func retry(maxCount: Int, interval: DelayRetryStrategy.Interval = .seconds(3)) -> Self { let strategy = DelayRetryStrategy(maxRetryCount: maxCount, retryInterval: interval) options.retryStrategy = strategy return self } /// Sets the `Source` should be loaded when user enables Low Data Mode and the original source fails with an /// `NSURLErrorNetworkUnavailableReason.constrained` error. /// - Parameter source: The `Source` will be loaded under low data mode. /// - Returns: A `Self` value with changes applied. /// /// When this option is set, the /// `allowsConstrainedNetworkAccess` property of the request for the original source will be set to `false` and the /// `Source` in associated value will be used to retrieve the image for low data mode. Usually, you can provide a /// low-resolution version of your image or a local image provider to display a placeholder. /// /// If not set or the `source` is `nil`, the device Low Data Mode will be ignored and the original source will /// be loaded following the system default behavior, in a normal way. public func lowDataModeSource(_ source: Source?) -> Self { options.lowDataModeSource = source return self } /// Sets whether the image setting for an image view should happen with transition even when retrieved from cache. /// - Parameter enabled: Enable the force transition or not. /// - Returns: A `KF.Builder` with changes applied. public func forceTransition(_ enabled: Bool = true) -> Self { options.forceTransition = enabled return self } } // MARK: - Request Modifier extension KFOptionSetter { /// Sets an `ImageDownloadRequestModifier` to change the image download request before it being sent. /// - Parameter modifier: The modifier will be used to change the request before it being sent. /// - Returns: A `Self` value with changes applied. /// /// This is the last chance you can modify the image download request. You can modify the request for some /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping. /// public func requestModifier(_ modifier: ImageDownloadRequestModifier) -> Self { options.requestModifier = modifier return self } /// Sets a block to change the image download request before it being sent. /// - Parameter modifyBlock: The modifying block will be called to change the request before it being sent. /// - Returns: A `Self` value with changes applied. /// /// This is the last chance you can modify the image download request. You can modify the request for some /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping. /// public func requestModifier(_ modifyBlock: @escaping (inout URLRequest) -> Void) -> Self { options.requestModifier = AnyModifier { r -> URLRequest? in var request = r modifyBlock(&request) return request } return self } } // MARK: - Redirect Handler extension KFOptionSetter { /// The `ImageDownloadRedirectHandler` argument will be used to change the request before redirection. /// This is the possibility you can modify the image download request during redirect. You can modify the request for /// some customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url /// mapping. /// The original redirection request will be sent without any modification by default. /// - Parameter handler: The handler will be used for redirection. /// - Returns: A `Self` value with changes applied. public func redirectHandler(_ handler: ImageDownloadRedirectHandler) -> Self { options.redirectHandler = handler return self } /// The `block` will be used to change the request before redirection. /// This is the possibility you can modify the image download request during redirect. You can modify the request for /// some customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url /// mapping. /// The original redirection request will be sent without any modification by default. /// - Parameter block: The block will be used for redirection. /// - Returns: A `Self` value with changes applied. public func redirectHandler(_ block: @escaping (KF.RedirectPayload) -> Void) -> Self { let redirectHandler = AnyRedirectHandler { (task, response, request, handler) in let payload = KF.RedirectPayload( task: task, response: response, newRequest: request, completionHandler: handler ) block(payload) } options.redirectHandler = redirectHandler return self } } // MARK: - Processor extension KFOptionSetter { /// Sets an image processor for the image task. It replaces the current image processor settings. /// /// - Parameter processor: The processor you want to use to process the image after it is downloaded. /// - Returns: A `Self` value with changes applied. /// /// - Note: /// To append a processor to current ones instead of replacing them all, use `appendProcessor(_:)`. public func setProcessor(_ processor: ImageProcessor) -> Self { options.processor = processor return self } /// Sets an array of image processors for the image task. It replaces the current image processor settings. /// - Parameter processors: An array of processors. The processors inside this array will be concatenated one by one /// to form a processor pipeline. /// - Returns: A `Self` value with changes applied. /// /// - Note: /// To append processors to current ones instead of replacing them all, concatenate them by `|>`, then use /// `appendProcessor(_:)`. public func setProcessors(_ processors: [ImageProcessor]) -> Self { switch processors.count { case 0: options.processor = DefaultImageProcessor.default case 1...: options.processor = processors.dropFirst().reduce(processors[0]) { $0 |> $1 } default: assertionFailure("Never happen") } return self } /// Appends a processor to the current set processors. /// - Parameter processor: The processor which will be appended to current processor settings. /// - Returns: A `Self` value with changes applied. public func appendProcessor(_ processor: ImageProcessor) -> Self { options.processor = options.processor |> processor return self } /// Appends a `RoundCornerImageProcessor` to current processors. /// - Parameters: /// - radius: The radius will be applied in processing. Specify a certain point value with `.point`, or a fraction /// of the target image with `.widthFraction`. or `.heightFraction`. For example, given a square image /// with width and height equals, `.widthFraction(0.5)` means use half of the length of size and makes /// the final image a round one. /// - targetSize: Target size of output image should be. If `nil`, the image will keep its original size after processing. /// - corners: The target corners which will be applied rounding. /// - backgroundColor: Background color of the output image. If `nil`, it will use a transparent background. /// - Returns: A `Self` value with changes applied. public func roundCorner( radius: RoundCornerImageProcessor.Radius, targetSize: CGSize? = nil, roundingCorners corners: RectCorner = .all, backgroundColor: KFCrossPlatformColor? = nil ) -> Self { let processor = RoundCornerImageProcessor( radius: radius, targetSize: targetSize, roundingCorners: corners, backgroundColor: backgroundColor ) return appendProcessor(processor) } /// Appends a `BlurImageProcessor` to current processors. /// - Parameter radius: Blur radius for the simulated Gaussian blur. /// - Returns: A `Self` value with changes applied. public func blur(radius: CGFloat) -> Self { appendProcessor( BlurImageProcessor(blurRadius: radius) ) } /// Appends a `OverlayImageProcessor` to current processors. /// - Parameters: /// - color: Overlay color will be used to overlay the input image. /// - fraction: Fraction will be used when overlay the color to image. /// - Returns: A `Self` value with changes applied. public func overlay(color: KFCrossPlatformColor, fraction: CGFloat = 0.5) -> Self { appendProcessor( OverlayImageProcessor(overlay: color, fraction: fraction) ) } /// Appends a `TintImageProcessor` to current processors. /// - Parameter color: Tint color will be used to tint the input image. /// - Returns: A `Self` value with changes applied. public func tint(color: KFCrossPlatformColor) -> Self { appendProcessor( TintImageProcessor(tint: color) ) } /// Appends a `BlackWhiteProcessor` to current processors. /// - Returns: A `Self` value with changes applied. public func blackWhite() -> Self { appendProcessor( BlackWhiteProcessor() ) } /// Appends a `CroppingImageProcessor` to current processors. /// - Parameters: /// - size: Target size of output image should be. /// - anchor: Anchor point from which the output size should be calculate. The anchor point is consisted by two /// values between 0.0 and 1.0. It indicates a related point in current image. /// See `CroppingImageProcessor.init(size:anchor:)` for more. /// - Returns: A `Self` value with changes applied. public func cropping(size: CGSize, anchor: CGPoint = .init(x: 0.5, y: 0.5)) -> Self { appendProcessor( CroppingImageProcessor(size: size, anchor: anchor) ) } /// Appends a `DownsamplingImageProcessor` to current processors. /// /// Compared to `ResizingImageProcessor`, the `DownsamplingImageProcessor` does not render the original images and /// then resize it. Instead, it downsamples the input data directly to a thumbnail image. So it is a more efficient /// than `ResizingImageProcessor`. Prefer to use `DownsamplingImageProcessor` as possible /// as you can than the `ResizingImageProcessor`. /// /// Only CG-based images are supported. Animated images (like GIF) is not supported. /// /// - Parameter size: Target size of output image should be. It should be smaller than the size of input image. /// If it is larger, the result image will be the same size of input data without downsampling. /// - Returns: A `Self` value with changes applied. public func downsampling(size: CGSize) -> Self { let processor = DownsamplingImageProcessor(size: size) if options.processor == DefaultImageProcessor.default { return setProcessor(processor) } else { return appendProcessor(processor) } } /// Appends a `ResizingImageProcessor` to current processors. /// /// If you need to resize a data represented image to a smaller size, use `DownsamplingImageProcessor` /// instead, which is more efficient and uses less memory. /// /// - Parameters: /// - referenceSize: The reference size for resizing operation in point. /// - mode: Target content mode of output image should be. Default is `.none`. /// - Returns: A `Self` value with changes applied. public func resizing(referenceSize: CGSize, mode: ContentMode = .none) -> Self { appendProcessor( ResizingImageProcessor(referenceSize: referenceSize, mode: mode) ) } } // MARK: - Cache Serializer extension KFOptionSetter { /// Uses a given `CacheSerializer` to convert some data to an image object for retrieving from disk cache or vice /// versa for storing to disk cache. /// - Parameter cacheSerializer: The `CacheSerializer` which will be used. /// - Returns: A `Self` value with changes applied. public func serialize(by cacheSerializer: CacheSerializer) -> Self { options.cacheSerializer = cacheSerializer return self } /// Uses a given format to serializer the image data to disk. It converts the image object to the give data format. /// - Parameters: /// - format: The desired data encoding format when store the image on disk. /// - jpegCompressionQuality: If the format is `.JPEG`, it specify the compression quality when converting the /// image to a JPEG data. Otherwise, it is ignored. /// - Returns: A `Self` value with changes applied. public func serialize(as format: ImageFormat, jpegCompressionQuality: CGFloat? = nil) -> Self { let cacheSerializer: FormatIndicatedCacheSerializer switch format { case .JPEG: cacheSerializer = .jpeg(compressionQuality: jpegCompressionQuality ?? 1.0) case .PNG: cacheSerializer = .png case .GIF: cacheSerializer = .gif case .unknown: cacheSerializer = .png } options.cacheSerializer = cacheSerializer return self } } // MARK: - Image Modifier extension KFOptionSetter { /// Sets an `ImageModifier` to the image task. Use this to modify the fetched image object properties if needed. /// /// If the image was fetched directly from the downloader, the modifier will run directly after the /// `ImageProcessor`. If the image is being fetched from a cache, the modifier will run after the `CacheSerializer`. /// - Parameter modifier: The `ImageModifier` which will be used to modify the image object. /// - Returns: A `Self` value with changes applied. public func imageModifier(_ modifier: ImageModifier?) -> Self { options.imageModifier = modifier return self } /// Sets a block to modify the image object. Use this to modify the fetched image object properties if needed. /// /// If the image was fetched directly from the downloader, the modifier block will run directly after the /// `ImageProcessor`. If the image is being fetched from a cache, the modifier will run after the `CacheSerializer`. /// /// - Parameter block: The block which is used to modify the image object. /// - Returns: A `Self` value with changes applied. public func imageModifier(_ block: @escaping (inout KFCrossPlatformImage) throws -> Void) -> Self { let modifier = AnyImageModifier { image -> KFCrossPlatformImage in var image = image try block(&image) return image } options.imageModifier = modifier return self } } // MARK: - Cache Expiration extension KFOptionSetter { /// Sets the expiration setting for memory cache of this image task. /// /// By default, the underlying `MemoryStorage.Backend` uses the /// expiration in its config for all items. If set, the `MemoryStorage.Backend` will use this value to overwrite /// the config setting for this caching item. /// /// - Parameter expiration: The expiration setting used in cache storage. /// - Returns: A `Self` value with changes applied. public func memoryCacheExpiration(_ expiration: StorageExpiration?) -> Self { options.memoryCacheExpiration = expiration return self } /// Sets the expiration extending setting for memory cache. The item expiration time will be incremented by this /// value after access. /// /// By default, the underlying `MemoryStorage.Backend` uses the initial cache expiration as extending /// value: .cacheTime. /// /// To disable extending option at all, sets `.none` to it. /// /// - Parameter extending: The expiration extending setting used in cache storage. /// - Returns: A `Self` value with changes applied. public func memoryCacheAccessExtending(_ extending: ExpirationExtending) -> Self { options.memoryCacheAccessExtendingExpiration = extending return self } /// Sets the expiration setting for disk cache of this image task. /// /// By default, the underlying `DiskStorage.Backend` uses the expiration in its config for all items. If set, /// the `DiskStorage.Backend` will use this value to overwrite the config setting for this caching item. /// /// - Parameter expiration: The expiration setting used in cache storage. /// - Returns: A `Self` value with changes applied. public func diskCacheExpiration(_ expiration: StorageExpiration?) -> Self { options.diskCacheExpiration = expiration return self } /// Sets the expiration extending setting for disk cache. The item expiration time will be incremented by this /// value after access. /// /// By default, the underlying `DiskStorage.Backend` uses the initial cache expiration as extending /// value: .cacheTime. /// /// To disable extending option at all, sets `.none` to it. /// /// - Parameter extending: The expiration extending setting used in cache storage. /// - Returns: A `Self` value with changes applied. public func diskCacheAccessExtending(_ extending: ExpirationExtending) -> Self { options.diskCacheAccessExtendingExpiration = extending return self } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/General/Kingfisher.swift ================================================ // // Kingfisher.swift // Kingfisher // // Created by Wei Wang on 16/9/14. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation import ImageIO #if os(macOS) import AppKit public typealias KFCrossPlatformImage = NSImage public typealias KFCrossPlatformView = NSView public typealias KFCrossPlatformColor = NSColor public typealias KFCrossPlatformImageView = NSImageView public typealias KFCrossPlatformButton = NSButton #else import UIKit public typealias KFCrossPlatformImage = UIImage public typealias KFCrossPlatformColor = UIColor #if !os(watchOS) public typealias KFCrossPlatformImageView = UIImageView public typealias KFCrossPlatformView = UIView public typealias KFCrossPlatformButton = UIButton #if canImport(TVUIKit) import TVUIKit #endif #else import WatchKit #endif #endif /// Wrapper for Kingfisher compatible types. This type provides an extension point for /// connivence methods in Kingfisher. public struct KingfisherWrapper { public let base: Base public init(_ base: Base) { self.base = base } } /// Represents an object type that is compatible with Kingfisher. You can use `kf` property to get a /// value in the namespace of Kingfisher. public protocol KingfisherCompatible: AnyObject { } /// Represents a value type that is compatible with Kingfisher. You can use `kf` property to get a /// value in the namespace of Kingfisher. public protocol KingfisherCompatibleValue {} extension KingfisherCompatible { /// Gets a namespace holder for Kingfisher compatible types. public var kf: KingfisherWrapper { get { return KingfisherWrapper(self) } set { } } } extension KingfisherCompatibleValue { /// Gets a namespace holder for Kingfisher compatible types. public var kf: KingfisherWrapper { get { return KingfisherWrapper(self) } set { } } } extension KFCrossPlatformImage: KingfisherCompatible { } #if !os(watchOS) extension KFCrossPlatformImageView: KingfisherCompatible { } extension KFCrossPlatformButton: KingfisherCompatible { } extension NSTextAttachment: KingfisherCompatible { } #else extension WKInterfaceImage: KingfisherCompatible { } #endif #if os(tvOS) && canImport(TVUIKit) @available(tvOS 12.0, *) extension TVMonogramView: KingfisherCompatible { } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/General/KingfisherError.swift ================================================ // // KingfisherError.swift // Kingfisher // // Created by onevcat on 2018/09/26. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation extension Never {} /// Represents all the errors which can happen in Kingfisher framework. /// Kingfisher related methods always throw a `KingfisherError` or invoke the callback with `KingfisherError` /// as its error type. To handle errors from Kingfisher, you switch over the error to get a reason catalog, /// then switch over the reason to know error detail. public enum KingfisherError: Error { // MARK: Error Reason Types /// Represents the error reason during networking request phase. /// /// - emptyRequest: The request is empty. Code 1001. /// - invalidURL: The URL of request is invalid. Code 1002. /// - taskCancelled: The downloading task is cancelled by user. Code 1003. public enum RequestErrorReason { /// The request is empty. Code 1001. case emptyRequest /// The URL of request is invalid. Code 1002. /// - request: The request is tend to be sent but its URL is invalid. case invalidURL(request: URLRequest) /// The downloading task is cancelled by user. Code 1003. /// - task: The session data task which is cancelled. /// - token: The cancel token which is used for cancelling the task. case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken) } /// Represents the error reason during networking response phase. /// /// - invalidURLResponse: The response is not a valid URL response. Code 2001. /// - invalidHTTPStatusCode: The response contains an invalid HTTP status code. Code 2002. /// - URLSessionError: An error happens in the system URL session. Code 2003. /// - dataModifyingFailed: Data modifying fails on returning a valid data. Code 2004. /// - noURLResponse: The task is done but no URL response found. Code 2005. public enum ResponseErrorReason { /// The response is not a valid URL response. Code 2001. /// - response: The received invalid URL response. /// The response is expected to be an HTTP response, but it is not. case invalidURLResponse(response: URLResponse) /// The response contains an invalid HTTP status code. Code 2002. /// - Note: /// By default, status code 200..<400 is recognized as valid. You can override /// this behavior by conforming to the `ImageDownloaderDelegate`. /// - response: The received response. case invalidHTTPStatusCode(response: HTTPURLResponse) /// An error happens in the system URL session. Code 2003. /// - error: The underlying URLSession error object. case URLSessionError(error: Error) /// Data modifying fails on returning a valid data. Code 2004. /// - task: The failed task. case dataModifyingFailed(task: SessionDataTask) /// The task is done but no URL response found. Code 2005. /// - task: The failed task. case noURLResponse(task: SessionDataTask) } /// Represents the error reason during Kingfisher caching system. /// /// - fileEnumeratorCreationFailed: Cannot create a file enumerator for a certain disk URL. Code 3001. /// - invalidFileEnumeratorContent: Cannot get correct file contents from a file enumerator. Code 3002. /// - invalidURLResource: The file at target URL exists, but its URL resource is unavailable. Code 3003. /// - cannotLoadDataFromDisk: The file at target URL exists, but the data cannot be loaded from it. Code 3004. /// - cannotCreateDirectory: Cannot create a folder at a given path. Code 3005. /// - imageNotExisting: The requested image does not exist in cache. Code 3006. /// - cannotConvertToData: Cannot convert an object to data for storing. Code 3007. /// - cannotSerializeImage: Cannot serialize an image to data for storing. Code 3008. /// - cannotCreateCacheFile: Cannot create the cache file at a certain fileURL under a key. Code 3009. /// - cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010. public enum CacheErrorReason { /// Cannot create a file enumerator for a certain disk URL. Code 3001. /// - url: The target disk URL from which the file enumerator should be created. case fileEnumeratorCreationFailed(url: URL) /// Cannot get correct file contents from a file enumerator. Code 3002. /// - url: The target disk URL from which the content of a file enumerator should be got. case invalidFileEnumeratorContent(url: URL) /// The file at target URL exists, but its URL resource is unavailable. Code 3003. /// - error: The underlying error thrown by file manager. /// - key: The key used to getting the resource from cache. /// - url: The disk URL where the target cached file exists. case invalidURLResource(error: Error, key: String, url: URL) /// The file at target URL exists, but the data cannot be loaded from it. Code 3004. /// - url: The disk URL where the target cached file exists. /// - error: The underlying error which describes why this error happens. case cannotLoadDataFromDisk(url: URL, error: Error) /// Cannot create a folder at a given path. Code 3005. /// - path: The disk path where the directory creating operation fails. /// - error: The underlying error which describes why this error happens. case cannotCreateDirectory(path: String, error: Error) /// The requested image does not exist in cache. Code 3006. /// - key: Key of the requested image in cache. case imageNotExisting(key: String) /// Cannot convert an object to data for storing. Code 3007. /// - object: The object which needs be convert to data. case cannotConvertToData(object: Any, error: Error) /// Cannot serialize an image to data for storing. Code 3008. /// - image: The input image needs to be serialized to cache. /// - original: The original image data, if exists. /// - serializer: The `CacheSerializer` used for the image serializing. case cannotSerializeImage(image: KFCrossPlatformImage?, original: Data?, serializer: CacheSerializer) /// Cannot create the cache file at a certain fileURL under a key. Code 3009. /// - fileURL: The url where the cache file should be created. /// - key: The cache key used for the cache. When caching a file through `KingfisherManager` and Kingfisher's /// extension method, it is the resolved cache key based on your input `Source` and the image processors. /// - data: The data to be cached. /// - error: The underlying error originally thrown by Foundation when writing the `data` to the disk file at /// `fileURL`. case cannotCreateCacheFile(fileURL: URL, key: String, data: Data, error: Error) /// Cannot set file attributes to a cached file. Code 3010. /// - filePath: The path of target cache file. /// - attributes: The file attribute to be set to the target file. /// - error: The underlying error originally thrown by Foundation when setting the `attributes` to the disk /// file at `filePath`. case cannotSetCacheFileAttribute(filePath: String, attributes: [FileAttributeKey : Any], error: Error) /// The disk storage of cache is not ready. Code 3011. /// /// This is usually due to extremely lack of space on disk storage, and /// Kingfisher failed even when creating the cache folder. The disk storage will be in unusable state. Normally, /// ask user to free some spaces and restart the app to make the disk storage work again. /// - cacheURL: The intended URL which should be the storage folder. case diskStorageIsNotReady(cacheURL: URL) } /// Represents the error reason during image processing phase. /// /// - processingFailed: Image processing fails. There is no valid output image from the processor. Code 4001. public enum ProcessorErrorReason { /// Image processing fails. There is no valid output image from the processor. Code 4001. /// - processor: The `ImageProcessor` used to process the image or its data in `item`. /// - item: The image or its data content. case processingFailed(processor: ImageProcessor, item: ImageProcessItem) } /// Represents the error reason during image setting in a view related class. /// /// - emptySource: The input resource is empty or `nil`. Code 5001. /// - notCurrentSourceTask: The source task is finished, but it is not the one expected now. Code 5002. /// - dataProviderError: An error happens during getting data from an `ImageDataProvider`. Code 5003. public enum ImageSettingErrorReason { /// The input resource is empty or `nil`. Code 5001. case emptySource /// The resource task is finished, but it is not the one expected now. This usually happens when you set another /// resource on the view without cancelling the current on-going one. The previous setting task will fail with /// this `.notCurrentSourceTask` error when a result got, regardless of it being successful or not for that task. /// The result of this original task is contained in the associated value. /// Code 5002. /// - result: The `RetrieveImageResult` if the source task is finished without problem. `nil` if an error /// happens. /// - error: The `Error` if an issue happens during image setting task. `nil` if the task finishes without /// problem. /// - source: The original source value of the task. case notCurrentSourceTask(result: RetrieveImageResult?, error: Error?, source: Source) /// An error happens during getting data from an `ImageDataProvider`. Code 5003. case dataProviderError(provider: ImageDataProvider, error: Error) /// No more alternative `Source` can be used in current loading process. It means that the /// `.alternativeSources` are used and Kingfisher tried to recovery from the original error, but still /// fails for all the given alternative sources. The associated value holds all the errors encountered during /// the load process, including the original source loading error and all the alternative sources errors. /// Code 5004. case alternativeSourcesExhausted([PropagationError]) } // MARK: Member Cases /// Represents the error reason during networking request phase. case requestError(reason: RequestErrorReason) /// Represents the error reason during networking response phase. case responseError(reason: ResponseErrorReason) /// Represents the error reason during Kingfisher caching system. case cacheError(reason: CacheErrorReason) /// Represents the error reason during image processing phase. case processorError(reason: ProcessorErrorReason) /// Represents the error reason during image setting in a view related class. case imageSettingError(reason: ImageSettingErrorReason) // MARK: Helper Properties & Methods /// Helper property to check whether this error is a `RequestErrorReason.taskCancelled` or not. public var isTaskCancelled: Bool { if case .requestError(reason: .taskCancelled) = self { return true } return false } /// Helper method to check whether this error is a `ResponseErrorReason.invalidHTTPStatusCode` and the /// associated value is a given status code. /// /// - Parameter code: The given status code. /// - Returns: If `self` is a `ResponseErrorReason.invalidHTTPStatusCode` error /// and its status code equals to `code`, `true` is returned. Otherwise, `false`. public func isInvalidResponseStatusCode(_ code: Int) -> Bool { if case .responseError(reason: .invalidHTTPStatusCode(let response)) = self { return response.statusCode == code } return false } public var isInvalidResponseStatusCode: Bool { if case .responseError(reason: .invalidHTTPStatusCode) = self { return true } return false } /// Helper property to check whether this error is a `ImageSettingErrorReason.notCurrentSourceTask` or not. /// When a new image setting task starts while the old one is still running, the new task identifier will be /// set and the old one is overwritten. A `.notCurrentSourceTask` error will be raised when the old task finishes /// to let you know the setting process finishes with a certain result, but the image view or button is not set. public var isNotCurrentTask: Bool { if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self { return true } return false } var isLowDataModeConstrained: Bool { if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *), case .responseError(reason: .URLSessionError(let sessionError)) = self, let urlError = sessionError as? URLError, urlError.networkUnavailableReason == .constrained { return true } return false } } // MARK: - LocalizedError Conforming extension KingfisherError: LocalizedError { /// A localized message describing what error occurred. public var errorDescription: String? { switch self { case .requestError(let reason): return reason.errorDescription case .responseError(let reason): return reason.errorDescription case .cacheError(let reason): return reason.errorDescription case .processorError(let reason): return reason.errorDescription case .imageSettingError(let reason): return reason.errorDescription } } } // MARK: - CustomNSError Conforming extension KingfisherError: CustomNSError { /// The error domain of `KingfisherError`. All errors from Kingfisher is under this domain. public static let domain = "com.onevcat.Kingfisher.Error" /// The error code within the given domain. public var errorCode: Int { switch self { case .requestError(let reason): return reason.errorCode case .responseError(let reason): return reason.errorCode case .cacheError(let reason): return reason.errorCode case .processorError(let reason): return reason.errorCode case .imageSettingError(let reason): return reason.errorCode } } } extension KingfisherError.RequestErrorReason { var errorDescription: String? { switch self { case .emptyRequest: return "The request is empty or `nil`." case .invalidURL(let request): return "The request contains an invalid or empty URL. Request: \(request)." case .taskCancelled(let task, let token): return "The session task was cancelled. Task: \(task), cancel token: \(token)." } } var errorCode: Int { switch self { case .emptyRequest: return 1001 case .invalidURL: return 1002 case .taskCancelled: return 1003 } } } extension KingfisherError.ResponseErrorReason { var errorDescription: String? { switch self { case .invalidURLResponse(let response): return "The URL response is invalid: \(response)" case .invalidHTTPStatusCode(let response): return "The HTTP status code in response is invalid. Code: \(response.statusCode), response: \(response)." case .URLSessionError(let error): return "A URL session error happened. The underlying error: \(error)" case .dataModifyingFailed(let task): return "The data modifying delegate returned `nil` for the downloaded data. Task: \(task)." case .noURLResponse(let task): return "No URL response received. Task: \(task)," } } var errorCode: Int { switch self { case .invalidURLResponse: return 2001 case .invalidHTTPStatusCode: return 2002 case .URLSessionError: return 2003 case .dataModifyingFailed: return 2004 case .noURLResponse: return 2005 } } } extension KingfisherError.CacheErrorReason { var errorDescription: String? { switch self { case .fileEnumeratorCreationFailed(let url): return "Cannot create file enumerator for URL: \(url)." case .invalidFileEnumeratorContent(let url): return "Cannot get contents from the file enumerator at URL: \(url)." case .invalidURLResource(let error, let key, let url): return "Cannot get URL resource values or data for the given URL: \(url). " + "Cache key: \(key). Underlying error: \(error)" case .cannotLoadDataFromDisk(let url, let error): return "Cannot load data from disk at URL: \(url). Underlying error: \(error)" case .cannotCreateDirectory(let path, let error): return "Cannot create directory at given path: Path: \(path). Underlying error: \(error)" case .imageNotExisting(let key): return "The image is not in cache, but you requires it should only be " + "from cache by enabling the `.onlyFromCache` option. Key: \(key)." case .cannotConvertToData(let object, let error): return "Cannot convert the input object to a `Data` object when storing it to disk cache. " + "Object: \(object). Underlying error: \(error)" case .cannotSerializeImage(let image, let originalData, let serializer): return "Cannot serialize an image due to the cache serializer returning `nil`. " + "Image: \(String(describing:image)), original data: \(String(describing: originalData)), " + "serializer: \(serializer)." case .cannotCreateCacheFile(let fileURL, let key, let data, let error): return "Cannot create cache file at url: \(fileURL), key: \(key), data length: \(data.count). " + "Underlying foundation error: \(error)." case .cannotSetCacheFileAttribute(let filePath, let attributes, let error): return "Cannot set file attribute for the cache file at path: \(filePath), attributes: \(attributes)." + "Underlying foundation error: \(error)." case .diskStorageIsNotReady(let cacheURL): return "The disk storage is not ready to use yet at URL: '\(cacheURL)'. " + "This is usually caused by extremely lack of disk space. Ask users to free up some space and restart the app." } } var errorCode: Int { switch self { case .fileEnumeratorCreationFailed: return 3001 case .invalidFileEnumeratorContent: return 3002 case .invalidURLResource: return 3003 case .cannotLoadDataFromDisk: return 3004 case .cannotCreateDirectory: return 3005 case .imageNotExisting: return 3006 case .cannotConvertToData: return 3007 case .cannotSerializeImage: return 3008 case .cannotCreateCacheFile: return 3009 case .cannotSetCacheFileAttribute: return 3010 case .diskStorageIsNotReady: return 3011 } } } extension KingfisherError.ProcessorErrorReason { var errorDescription: String? { switch self { case .processingFailed(let processor, let item): return "Processing image failed. Processor: \(processor). Processing item: \(item)." } } var errorCode: Int { switch self { case .processingFailed: return 4001 } } } extension KingfisherError.ImageSettingErrorReason { var errorDescription: String? { switch self { case .emptySource: return "The input resource is empty." case .notCurrentSourceTask(let result, let error, let resource): if let result = result { return "Retrieving resource succeeded, but this source is " + "not the one currently expected. Result: \(result). Resource: \(resource)." } else if let error = error { return "Retrieving resource failed, and this resource is " + "not the one currently expected. Error: \(error). Resource: \(resource)." } else { return nil } case .dataProviderError(let provider, let error): return "Image data provider fails to provide data. Provider: \(provider), error: \(error)" case .alternativeSourcesExhausted(let errors): return "Image setting from alternaive sources failed: \(errors)" } } var errorCode: Int { switch self { case .emptySource: return 5001 case .notCurrentSourceTask: return 5002 case .dataProviderError: return 5003 case .alternativeSourcesExhausted: return 5004 } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/General/KingfisherManager.swift ================================================ // // KingfisherManager.swift // Kingfisher // // Created by Wei Wang on 15/4/6. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// The downloading progress block type. /// The parameter value is the `receivedSize` of current response. /// The second parameter is the total expected data length from response's "Content-Length" header. /// If the expected length is not available, this block will not be called. public typealias DownloadProgressBlock = ((_ receivedSize: Int64, _ totalSize: Int64) -> Void) /// Represents the result of a Kingfisher retrieving image task. public struct RetrieveImageResult { /// Gets the image object of this result. public let image: KFCrossPlatformImage /// Gets the cache source of the image. It indicates from which layer of cache this image is retrieved. /// If the image is just downloaded from network, `.none` will be returned. public let cacheType: CacheType /// The `Source` which this result is related to. This indicated where the `image` of `self` is referring. public let source: Source /// The original `Source` from which the retrieve task begins. It can be different from the `source` property. /// When an alternative source loading happened, the `source` will be the replacing loading target, while the /// `originalSource` will be kept as the initial `source` which issued the image loading process. public let originalSource: Source } /// A struct that stores some related information of an `KingfisherError`. It provides some context information for /// a pure error so you can identify the error easier. public struct PropagationError { /// The `Source` to which current `error` is bound. public let source: Source /// The actual error happens in framework. public let error: KingfisherError } /// The downloading task updated block type. The parameter `newTask` is the updated new task of image setting process. /// It is a `nil` if the image loading does not require an image downloading process. If an image downloading is issued, /// this value will contain the actual `DownloadTask` for you to keep and cancel it later if you need. public typealias DownloadTaskUpdatedBlock = ((_ newTask: DownloadTask?) -> Void) /// Main manager class of Kingfisher. It connects Kingfisher downloader and cache, /// to provide a set of convenience methods to use Kingfisher for tasks. /// You can use this class to retrieve an image via a specified URL from web or cache. public class KingfisherManager { /// Represents a shared manager used across Kingfisher. /// Use this instance for getting or storing images with Kingfisher. public static let shared = KingfisherManager() // Mark: Public Properties /// The `ImageCache` used by this manager. It is `ImageCache.default` by default. /// If a cache is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be /// used instead. public var cache: ImageCache /// The `ImageDownloader` used by this manager. It is `ImageDownloader.default` by default. /// If a downloader is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be /// used instead. public var downloader: ImageDownloader /// Default options used by the manager. This option will be used in /// Kingfisher manager related methods, as well as all view extension methods. /// You can also passing other options for each image task by sending an `options` parameter /// to Kingfisher's APIs. The per image options will overwrite the default ones, /// if the option exists in both. public var defaultOptions = KingfisherOptionsInfo.empty // Use `defaultOptions` to overwrite the `downloader` and `cache`. private var currentDefaultOptions: KingfisherOptionsInfo { return [.downloader(downloader), .targetCache(cache)] + defaultOptions } private let processingQueue: CallbackQueue private convenience init() { self.init(downloader: .default, cache: .default) } /// Creates an image setting manager with specified downloader and cache. /// /// - Parameters: /// - downloader: The image downloader used to download images. /// - cache: The image cache which stores memory and disk images. public init(downloader: ImageDownloader, cache: ImageCache) { self.downloader = downloader self.cache = cache let processQueueName = "com.onevcat.Kingfisher.KingfisherManager.processQueue.\(UUID().uuidString)" processingQueue = .dispatch(DispatchQueue(label: processQueueName)) } // MARK: - Getting Images /// Gets an image from a given resource. /// - Parameters: /// - resource: The `Resource` object defines data information like key or URL. /// - options: Options to use when creating the image. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. `progressBlock` is always called in /// main queue. /// - downloadTaskUpdated: Called when a new image downloading task is created for current image retrieving. This /// usually happens when an alternative source is used to replace the original (failed) /// task. You can update your reference of `DownloadTask` if you want to manually `cancel` /// the new task. /// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked /// from the `options.callbackQueue`. If not specified, the main queue will be used. /// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource, /// the started `DownloadTask` is returned. Otherwise, `nil` is returned. /// /// - Note: /// This method will first check whether the requested `resource` is already in cache or not. If cached, /// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it /// will download the `resource`, store it in cache, then call `completionHandler`. @discardableResult public func retrieveImage( with resource: Resource, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil, completionHandler: ((Result) -> Void)?) -> DownloadTask? { return retrieveImage( with: resource.convertToSource(), options: options, progressBlock: progressBlock, downloadTaskUpdated: downloadTaskUpdated, completionHandler: completionHandler ) } /// Gets an image from a given resource. /// /// - Parameters: /// - source: The `Source` object defines data information from network or a data provider. /// - options: Options to use when creating the image. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an /// `expectedContentLength`, this block will not be called. `progressBlock` is always called in /// main queue. /// - downloadTaskUpdated: Called when a new image downloading task is created for current image retrieving. This /// usually happens when an alternative source is used to replace the original (failed) /// task. You can update your reference of `DownloadTask` if you want to manually `cancel` /// the new task. /// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked /// from the `options.callbackQueue`. If not specified, the main queue will be used. /// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource, /// the started `DownloadTask` is returned. Otherwise, `nil` is returned. /// /// - Note: /// This method will first check whether the requested `source` is already in cache or not. If cached, /// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it /// will try to load the `source`, store it in cache, then call `completionHandler`. /// public func retrieveImage( with source: Source, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil, completionHandler: ((Result) -> Void)?) -> DownloadTask? { let options = currentDefaultOptions + (options ?? .empty) let info = KingfisherParsedOptionsInfo(options) return retrieveImage( with: source, options: info, progressBlock: progressBlock, downloadTaskUpdated: downloadTaskUpdated, completionHandler: completionHandler) } func retrieveImage( with source: Source, options: KingfisherParsedOptionsInfo, progressBlock: DownloadProgressBlock? = nil, downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil, completionHandler: ((Result) -> Void)?) -> DownloadTask? { var info = options if let block = progressBlock { info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] } return retrieveImage( with: source, options: info, downloadTaskUpdated: downloadTaskUpdated, completionHandler: completionHandler) } func retrieveImage( with source: Source, options: KingfisherParsedOptionsInfo, downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil, completionHandler: ((Result) -> Void)?) -> DownloadTask? { let retrievingContext = RetrievingContext(options: options, originalSource: source) var retryContext: RetryContext? func startNewRetrieveTask( with source: Source, downloadTaskUpdated: DownloadTaskUpdatedBlock? ) { let newTask = self.retrieveImage(with: source, context: retrievingContext) { result in handler(currentSource: source, result: result) } downloadTaskUpdated?(newTask) } func failCurrentSource(_ source: Source, with error: KingfisherError) { // Skip alternative sources if the user cancelled it. guard !error.isTaskCancelled else { completionHandler?(.failure(error)) return } // When low data mode constrained error, retry with the low data mode source instead of use alternative on fly. guard !error.isLowDataModeConstrained else { if let source = retrievingContext.options.lowDataModeSource { retrievingContext.options.lowDataModeSource = nil startNewRetrieveTask(with: source, downloadTaskUpdated: downloadTaskUpdated) } else { // This should not happen. completionHandler?(.failure(error)) } return } if let nextSource = retrievingContext.popAlternativeSource() { retrievingContext.appendError(error, to: source) startNewRetrieveTask(with: nextSource, downloadTaskUpdated: downloadTaskUpdated) } else { // No other alternative source. Finish with error. if retrievingContext.propagationErrors.isEmpty { completionHandler?(.failure(error)) } else { retrievingContext.appendError(error, to: source) let finalError = KingfisherError.imageSettingError( reason: .alternativeSourcesExhausted(retrievingContext.propagationErrors) ) completionHandler?(.failure(finalError)) } } } func handler(currentSource: Source, result: (Result)) -> Void { switch result { case .success: completionHandler?(result) case .failure(let error): if let retryStrategy = options.retryStrategy { let context = retryContext?.increaseRetryCount() ?? RetryContext(source: source, error: error) retryContext = context retryStrategy.retry(context: context) { decision in switch decision { case .retry(let userInfo): retryContext?.userInfo = userInfo startNewRetrieveTask(with: source, downloadTaskUpdated: downloadTaskUpdated) case .stop: failCurrentSource(currentSource, with: error) } } } else { failCurrentSource(currentSource, with: error) } } } return retrieveImage( with: source, context: retrievingContext) { result in handler(currentSource: source, result: result) } } private func retrieveImage( with source: Source, context: RetrievingContext, completionHandler: ((Result) -> Void)?) -> DownloadTask? { let options = context.options if options.forceRefresh { return loadAndCacheImage( source: source, context: context, completionHandler: completionHandler)?.value } else { let loadedFromCache = retrieveImageFromCache( source: source, context: context, completionHandler: completionHandler) if loadedFromCache { return nil } if options.onlyFromCache { let error = KingfisherError.cacheError(reason: .imageNotExisting(key: source.cacheKey)) completionHandler?(.failure(error)) return nil } return loadAndCacheImage( source: source, context: context, completionHandler: completionHandler)?.value } } func provideImage( provider: ImageDataProvider, options: KingfisherParsedOptionsInfo, completionHandler: ((Result) -> Void)?) { guard let completionHandler = completionHandler else { return } provider.data { result in switch result { case .success(let data): (options.processingQueue ?? self.processingQueue).execute { let processor = options.processor let processingItem = ImageProcessItem.data(data) guard let image = processor.process(item: processingItem, options: options) else { options.callbackQueue.execute { let error = KingfisherError.processorError( reason: .processingFailed(processor: processor, item: processingItem)) completionHandler(.failure(error)) } return } options.callbackQueue.execute { let result = ImageLoadingResult(image: image, url: nil, originalData: data) completionHandler(.success(result)) } } case .failure(let error): options.callbackQueue.execute { let error = KingfisherError.imageSettingError( reason: .dataProviderError(provider: provider, error: error)) completionHandler(.failure(error)) } } } } private func cacheImage( source: Source, options: KingfisherParsedOptionsInfo, context: RetrievingContext, result: Result, completionHandler: ((Result) -> Void)? ) { switch result { case .success(let value): let needToCacheOriginalImage = options.cacheOriginalImage && options.processor != DefaultImageProcessor.default let coordinator = CacheCallbackCoordinator( shouldWaitForCache: options.waitForCache, shouldCacheOriginal: needToCacheOriginalImage) let result = RetrieveImageResult( image: options.imageModifier?.modify(value.image) ?? value.image, cacheType: .none, source: source, originalSource: context.originalSource ) // Add image to cache. let targetCache = options.targetCache ?? self.cache targetCache.store( value.image, original: value.originalData, forKey: source.cacheKey, options: options, toDisk: !options.cacheMemoryOnly) { _ in coordinator.apply(.cachingImage) { completionHandler?(.success(result)) } } // Add original image to cache if necessary. if needToCacheOriginalImage { let originalCache = options.originalCache ?? targetCache originalCache.storeToDisk( value.originalData, forKey: source.cacheKey, processorIdentifier: DefaultImageProcessor.default.identifier, expiration: options.diskCacheExpiration) { _ in coordinator.apply(.cachingOriginalImage) { completionHandler?(.success(result)) } } } coordinator.apply(.cacheInitiated) { completionHandler?(.success(result)) } case .failure(let error): completionHandler?(.failure(error)) } } @discardableResult func loadAndCacheImage( source: Source, context: RetrievingContext, completionHandler: ((Result) -> Void)?) -> DownloadTask.WrappedTask? { let options = context.options func _cacheImage(_ result: Result) { cacheImage( source: source, options: options, context: context, result: result, completionHandler: completionHandler ) } switch source { case .network(let resource): let downloader = options.downloader ?? self.downloader let task = downloader.downloadImage( with: resource.downloadURL, options: options, completionHandler: _cacheImage ) // The code below is neat, but it fails the Swift 5.2 compiler with a runtime crash when // `BUILD_LIBRARY_FOR_DISTRIBUTION` is turned on. I believe it is a bug in the compiler. // Let's fallback to a traditional style before it can be fixed in Swift. // // https://github.com/onevcat/Kingfisher/issues/1436 // // return task.map(DownloadTask.WrappedTask.download) if let task = task { return .download(task) } else { return nil } case .provider(let provider): provideImage(provider: provider, options: options, completionHandler: _cacheImage) return .dataProviding } } /// Retrieves image from memory or disk cache. /// /// - Parameters: /// - source: The target source from which to get image. /// - key: The key to use when caching the image. /// - url: Image request URL. This is not used when retrieving image from cache. It is just used for /// `RetrieveImageResult` callback compatibility. /// - options: Options on how to get the image from image cache. /// - completionHandler: Called when the image retrieving finishes, either with succeeded /// `RetrieveImageResult` or an error. /// - Returns: `true` if the requested image or the original image before being processed is existing in cache. /// Otherwise, this method returns `false`. /// /// - Note: /// The image retrieving could happen in either memory cache or disk cache. The `.processor` option in /// `options` will be considered when searching in the cache. If no processed image is found, Kingfisher /// will try to check whether an original version of that image is existing or not. If there is already an /// original, Kingfisher retrieves it from cache and processes it. Then, the processed image will be store /// back to cache for later use. func retrieveImageFromCache( source: Source, context: RetrievingContext, completionHandler: ((Result) -> Void)?) -> Bool { let options = context.options // 1. Check whether the image was already in target cache. If so, just get it. let targetCache = options.targetCache ?? cache let key = source.cacheKey let targetImageCached = targetCache.imageCachedType( forKey: key, processorIdentifier: options.processor.identifier) let validCache = targetImageCached.cached && (options.fromMemoryCacheOrRefresh == false || targetImageCached == .memory) if validCache { targetCache.retrieveImage(forKey: key, options: options) { result in guard let completionHandler = completionHandler else { return } options.callbackQueue.execute { result.match( onSuccess: { cacheResult in let value: Result if let image = cacheResult.image { value = result.map { RetrieveImageResult( image: options.imageModifier?.modify(image) ?? image, cacheType: $0.cacheType, source: source, originalSource: context.originalSource ) } } else { value = .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))) } completionHandler(value) }, onFailure: { _ in completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))) } ) } } return true } // 2. Check whether the original image exists. If so, get it, process it, save to storage and return. let originalCache = options.originalCache ?? targetCache // No need to store the same file in the same cache again. if originalCache === targetCache && options.processor == DefaultImageProcessor.default { return false } // Check whether the unprocessed image existing or not. let originalImageCacheType = originalCache.imageCachedType( forKey: key, processorIdentifier: DefaultImageProcessor.default.identifier) let canAcceptDiskCache = !options.fromMemoryCacheOrRefresh let canUseOriginalImageCache = (canAcceptDiskCache && originalImageCacheType.cached) || (!canAcceptDiskCache && originalImageCacheType == .memory) if canUseOriginalImageCache { // Now we are ready to get found the original image from cache. We need the unprocessed image, so remove // any processor from options first. var optionsWithoutProcessor = options optionsWithoutProcessor.processor = DefaultImageProcessor.default originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { result in result.match( onSuccess: { cacheResult in guard let image = cacheResult.image else { assertionFailure("The image (under key: \(key) should be existing in the original cache.") return } let processor = options.processor (options.processingQueue ?? self.processingQueue).execute { let item = ImageProcessItem.image(image) guard let processedImage = processor.process(item: item, options: options) else { let error = KingfisherError.processorError( reason: .processingFailed(processor: processor, item: item)) options.callbackQueue.execute { completionHandler?(.failure(error)) } return } var cacheOptions = options cacheOptions.callbackQueue = .untouch let coordinator = CacheCallbackCoordinator( shouldWaitForCache: options.waitForCache, shouldCacheOriginal: false) let result = RetrieveImageResult( image: options.imageModifier?.modify(processedImage) ?? processedImage, cacheType: .none, source: source, originalSource: context.originalSource ) targetCache.store( processedImage, forKey: key, options: cacheOptions, toDisk: !options.cacheMemoryOnly) { _ in coordinator.apply(.cachingImage) { options.callbackQueue.execute { completionHandler?(.success(result)) } } } coordinator.apply(.cacheInitiated) { options.callbackQueue.execute { completionHandler?(.success(result)) } } } }, onFailure: { _ in // This should not happen actually, since we already confirmed `originalImageCached` is `true`. // Just in case... options.callbackQueue.execute { completionHandler?( .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))) ) } } ) } return true } return false } } class RetrievingContext { var options: KingfisherParsedOptionsInfo let originalSource: Source var propagationErrors: [PropagationError] = [] init(options: KingfisherParsedOptionsInfo, originalSource: Source) { self.originalSource = originalSource self.options = options } func popAlternativeSource() -> Source? { guard var alternativeSources = options.alternativeSources, !alternativeSources.isEmpty else { return nil } let nextSource = alternativeSources.removeFirst() options.alternativeSources = alternativeSources return nextSource } @discardableResult func appendError(_ error: KingfisherError, to source: Source) -> [PropagationError] { let item = PropagationError(source: source, error: error) propagationErrors.append(item) return propagationErrors } } class CacheCallbackCoordinator { enum State { case idle case imageCached case originalImageCached case done } enum Action { case cacheInitiated case cachingImage case cachingOriginalImage } private let shouldWaitForCache: Bool private let shouldCacheOriginal: Bool private let stateQueue: DispatchQueue private var threadSafeState: State = .idle private (set) var state: State { set { stateQueue.sync { threadSafeState = newValue } } get { stateQueue.sync { threadSafeState } } } init(shouldWaitForCache: Bool, shouldCacheOriginal: Bool) { self.shouldWaitForCache = shouldWaitForCache self.shouldCacheOriginal = shouldCacheOriginal let stateQueueName = "com.onevcat.Kingfisher.CacheCallbackCoordinator.stateQueue.\(UUID().uuidString)" self.stateQueue = DispatchQueue(label: stateQueueName) } func apply(_ action: Action, trigger: () -> Void) { switch (state, action) { case (.done, _): break // From .idle case (.idle, .cacheInitiated): if !shouldWaitForCache { state = .done trigger() } case (.idle, .cachingImage): if shouldCacheOriginal { state = .imageCached } else { state = .done trigger() } case (.idle, .cachingOriginalImage): state = .originalImageCached // From .imageCached case (.imageCached, .cachingOriginalImage): state = .done trigger() // From .originalImageCached case (.originalImageCached, .cachingImage): state = .done trigger() default: assertionFailure("This case should not happen in CacheCallbackCoordinator: \(state) - \(action)") } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/General/KingfisherOptionsInfo.swift ================================================ // // KingfisherOptionsInfo.swift // Kingfisher // // Created by Wei Wang on 15/4/23. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if os(macOS) import AppKit #else import UIKit #endif /// KingfisherOptionsInfo is a typealias for [KingfisherOptionsInfoItem]. /// You can use the enum of option item with value to control some behaviors of Kingfisher. public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem] extension Array where Element == KingfisherOptionsInfoItem { static let empty: KingfisherOptionsInfo = [] } /// Represents the available option items could be used in `KingfisherOptionsInfo`. public enum KingfisherOptionsInfoItem { /// Kingfisher will use the associated `ImageCache` object when handling related operations, /// including trying to retrieve the cached images and store the downloaded image to it. case targetCache(ImageCache) /// The `ImageCache` for storing and retrieving original images. If `originalCache` is /// contained in the options, it will be preferred for storing and retrieving original images. /// If there is no `.originalCache` in the options, `.targetCache` will be used to store original images. /// /// When using KingfisherManager to download and store an image, if `cacheOriginalImage` is /// applied in the option, the original image will be stored to this `originalCache`. At the /// same time, if a requested final image (with processor applied) cannot be found in `targetCache`, /// Kingfisher will try to search the original image to check whether it is already there. If found, /// it will be used and applied with the given processor. It is an optimization for not downloading /// the same image for multiple times. case originalCache(ImageCache) /// Kingfisher will use the associated `ImageDownloader` object to download the requested images. case downloader(ImageDownloader) /// Member for animation transition when using `UIImageView`. Kingfisher will use the `ImageTransition` of /// this enum to animate the image in if it is downloaded from web. The transition will not happen when the /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when /// the image being retrieved from cache, set `.forceRefresh` as well. case transition(ImageTransition) /// Associated `Float` value will be set as the priority of image download task. The value for it should be /// between 0.0~1.0. If this option not set, the default value (`URLSessionTask.defaultPriority`) will be used. case downloadPriority(Float) /// If set, Kingfisher will ignore the cache and try to start a download task for the image source. case forceRefresh /// If set, Kingfisher will try to retrieve the image from memory cache first. If the image is not in memory /// cache, then it will ignore the disk cache but download the image again from network. This is useful when /// you want to display a changeable image behind the same url at the same app session, while avoiding download /// it for multiple times. case fromMemoryCacheOrRefresh /// If set, setting the image to an image view will happen with transition even when retrieved from cache. /// See `.transition` option for more. case forceTransition /// If set, Kingfisher will only cache the value in memory but not in disk. case cacheMemoryOnly /// If set, Kingfisher will wait for caching operation to be completed before calling the completion block. case waitForCache /// If set, Kingfisher will only try to retrieve the image from cache, but not from network. If the image is not in /// cache, the image retrieving will fail with the `KingfisherError.cacheError` with `.imageNotExisting` as its /// reason. case onlyFromCache /// Decode the image in background thread before using. It will decode the downloaded image data and do a off-screen /// rendering to extract pixel information in background. This can speed up display, but will cost more time to /// prepare the image for using. case backgroundDecode /// The associated value will be used as the target queue of dispatch callbacks when retrieving images from /// cache. If not set, Kingfisher will use `.mainCurrentOrAsync` for callbacks. /// /// - Note: /// This option does not affect the callbacks for UI related extension methods. You will always get the /// callbacks called from main queue. case callbackQueue(CallbackQueue) /// The associated value will be used as the scale factor when converting retrieved data to an image. /// Specify the image scale, instead of your screen scale. You may need to set the correct scale when you dealing /// with 2x or 3x retina images. Otherwise, Kingfisher will convert the data to image object at `scale` 1.0. case scaleFactor(CGFloat) /// Whether all the animated image data should be preloaded. Default is `false`, which means only following frames /// will be loaded on need. If `true`, all the animated image data will be loaded and decoded into memory. /// /// This option is mainly used for back compatibility internally. You should not set it directly. Instead, /// you should choose the image view class to control the GIF data loading. There are two classes in Kingfisher /// support to display a GIF image. `AnimatedImageView` does not preload all data, it takes much less memory, but /// uses more CPU when display. While a normal image view (`UIImageView` or `NSImageView`) loads all data at once, /// which uses more memory but only decode image frames once. case preloadAllAnimationData /// The `ImageDownloadRequestModifier` contained will be used to change the request before it being sent. /// This is the last chance you can modify the image download request. You can modify the request for some /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping. /// The original request will be sent without any modification by default. case requestModifier(AsyncImageDownloadRequestModifier) /// The `ImageDownloadRedirectHandler` contained will be used to change the request before redirection. /// This is the possibility you can modify the image download request during redirect. You can modify the request for /// some customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url /// mapping. /// The original redirection request will be sent without any modification by default. case redirectHandler(ImageDownloadRedirectHandler) /// Processor for processing when the downloading finishes, a processor will convert the downloaded data to an image /// and/or apply some filter on it. If a cache is connected to the downloader (it happens when you are using /// KingfisherManager or any of the view extension methods), the converted image will also be sent to cache as well. /// If not set, the `DefaultImageProcessor.default` will be used. case processor(ImageProcessor) /// Provides a `CacheSerializer` to convert some data to an image object for /// retrieving from disk cache or vice versa for storing to disk cache. /// If not set, the `DefaultCacheSerializer.default` will be used. case cacheSerializer(CacheSerializer) /// An `ImageModifier` is for modifying an image as needed right before it is used. If the image was fetched /// directly from the downloader, the modifier will run directly after the `ImageProcessor`. If the image is being /// fetched from a cache, the modifier will run after the `CacheSerializer`. /// /// Use `ImageModifier` when you need to set properties that do not persist when caching the image on a concrete /// type of `Image`, such as the `renderingMode` or the `alignmentInsets` of `UIImage`. case imageModifier(ImageModifier) /// Keep the existing image of image view while setting another image to it. /// By setting this option, the placeholder image parameter of image view extension method /// will be ignored and the current image will be kept while loading or downloading the new image. case keepCurrentImageWhileLoading /// If set, Kingfisher will only load the first frame from an animated image file as a single image. /// Loading an animated images may take too much memory. It will be useful when you want to display a /// static preview of the first frame from an animated image. /// /// This option will be ignored if the target image is not animated image data. case onlyLoadFirstFrame /// If set and an `ImageProcessor` is used, Kingfisher will try to cache both the final result and original /// image. Kingfisher will have a chance to use the original image when another processor is applied to the same /// resource, instead of downloading it again. You can use `.originalCache` to specify a cache or the original /// images if necessary. /// /// The original image will be only cached to disk storage. case cacheOriginalImage /// If set and an image retrieving error occurred Kingfisher will set provided image (or empty) /// in place of requested one. It's useful when you don't want to show placeholder /// during loading time but wants to use some default image when requests will be failed. case onFailureImage(KFCrossPlatformImage?) /// If set and used in `ImagePrefetcher`, the prefetching operation will load the images into memory storage /// aggressively. By default this is not contained in the options, that means if the requested image is already /// in disk cache, Kingfisher will not try to load it to memory. case alsoPrefetchToMemory /// If set, the disk storage loading will happen in the same calling queue. By default, disk storage file loading /// happens in its own queue with an asynchronous dispatch behavior. Although it provides better non-blocking disk /// loading performance, it also causes a flickering when you reload an image from disk, if the image view already /// has an image set. /// /// Set this options will stop that flickering by keeping all loading in the same queue (typically the UI queue /// if you are using Kingfisher's extension methods to set an image), with a tradeoff of loading performance. case loadDiskFileSynchronously /// The expiration setting for memory cache. By default, the underlying `MemoryStorage.Backend` uses the /// expiration in its config for all items. If set, the `MemoryStorage.Backend` will use this associated /// value to overwrite the config setting for this caching item. case memoryCacheExpiration(StorageExpiration) /// The expiration extending setting for memory cache. The item expiration time will be incremented by this /// value after access. /// By default, the underlying `MemoryStorage.Backend` uses the initial cache expiration as extending /// value: .cacheTime. /// /// To disable extending option at all add memoryCacheAccessExtendingExpiration(.none) to options. case memoryCacheAccessExtendingExpiration(ExpirationExtending) /// The expiration setting for disk cache. By default, the underlying `DiskStorage.Backend` uses the /// expiration in its config for all items. If set, the `DiskStorage.Backend` will use this associated /// value to overwrite the config setting for this caching item. case diskCacheExpiration(StorageExpiration) /// The expiration extending setting for disk cache. The item expiration time will be incremented by this value after access. /// By default, the underlying `DiskStorage.Backend` uses the initial cache expiration as extending value: .cacheTime. /// To disable extending option at all add diskCacheAccessExtendingExpiration(.none) to options. case diskCacheAccessExtendingExpiration(ExpirationExtending) /// Decides on which queue the image processing should happen. By default, Kingfisher uses a pre-defined serial /// queue to process images. Use this option to change this behavior. For example, specify a `.mainCurrentOrAsync` /// to let the image be processed in main queue to prevent a possible flickering (but with a possibility of /// blocking the UI, especially if the processor needs a lot of time to run). case processingQueue(CallbackQueue) /// Enable progressive image loading, Kingfisher will use the associated `ImageProgressive` value to process the /// progressive JPEG data and display it in a progressive way. case progressiveJPEG(ImageProgressive) /// The alternative sources will be used when the original input `Source` fails. The `Source`s in the associated /// array will be used to start a new image loading task if the previous task fails due to an error. The image /// source loading process will stop as soon as a source is loaded successfully. If all `[Source]`s are used but /// the loading is still failing, an `imageSettingError` with `alternativeSourcesExhausted` as its reason will be /// thrown out. /// /// This option is useful if you want to implement a fallback solution for setting image. /// /// User cancellation will not trigger the alternative source loading. case alternativeSources([Source]) /// Provide a retry strategy which will be used when something gets wrong during the image retrieving process from /// `KingfisherManager`. You can define a strategy by create a type conforming to the `RetryStrategy` protocol. /// /// - Note: /// /// All extension methods of Kingfisher (`kf` extensions on `UIImageView` or `UIButton`) retrieve images through /// `KingfisherManager`, so the retry strategy also applies when using them. However, this option does not apply /// when pass to an `ImageDownloader` or `ImageCache`. /// case retryStrategy(RetryStrategy) /// The `Source` should be loaded when user enables Low Data Mode and the original source fails with an /// `NSURLErrorNetworkUnavailableReason.constrained` error. When this option is set, the /// `allowsConstrainedNetworkAccess` property of the request for the original source will be set to `false` and the /// `Source` in associated value will be used to retrieve the image for low data mode. Usually, you can provide a /// low-resolution version of your image or a local image provider to display a placeholder. /// /// If not set or the `source` is `nil`, the device Low Data Mode will be ignored and the original source will /// be loaded following the system default behavior, in a normal way. case lowDataMode(Source?) } // Improve performance by parsing the input `KingfisherOptionsInfo` (self) first. // So we can prevent the iterating over the options array again and again. /// The parsed options info used across Kingfisher methods. Each property in this type corresponds a case member /// in `KingfisherOptionsInfoItem`. When a `KingfisherOptionsInfo` sent to Kingfisher related methods, it will be /// parsed and converted to a `KingfisherParsedOptionsInfo` first, and pass through the internal methods. public struct KingfisherParsedOptionsInfo { public var targetCache: ImageCache? = nil public var originalCache: ImageCache? = nil public var downloader: ImageDownloader? = nil public var transition: ImageTransition = .none public var downloadPriority: Float = URLSessionTask.defaultPriority public var forceRefresh = false public var fromMemoryCacheOrRefresh = false public var forceTransition = false public var cacheMemoryOnly = false public var waitForCache = false public var onlyFromCache = false public var backgroundDecode = false public var preloadAllAnimationData = false public var callbackQueue: CallbackQueue = .mainCurrentOrAsync public var scaleFactor: CGFloat = 1.0 public var requestModifier: AsyncImageDownloadRequestModifier? = nil public var redirectHandler: ImageDownloadRedirectHandler? = nil public var processor: ImageProcessor = DefaultImageProcessor.default public var imageModifier: ImageModifier? = nil public var cacheSerializer: CacheSerializer = DefaultCacheSerializer.default public var keepCurrentImageWhileLoading = false public var onlyLoadFirstFrame = false public var cacheOriginalImage = false public var onFailureImage: Optional = .none public var alsoPrefetchToMemory = false public var loadDiskFileSynchronously = false public var memoryCacheExpiration: StorageExpiration? = nil public var memoryCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime public var diskCacheExpiration: StorageExpiration? = nil public var diskCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime public var processingQueue: CallbackQueue? = nil public var progressiveJPEG: ImageProgressive? = nil public var alternativeSources: [Source]? = nil public var retryStrategy: RetryStrategy? = nil public var lowDataModeSource: Source? = nil var onDataReceived: [DataReceivingSideEffect]? = nil public init(_ info: KingfisherOptionsInfo?) { guard let info = info else { return } for option in info { switch option { case .targetCache(let value): targetCache = value case .originalCache(let value): originalCache = value case .downloader(let value): downloader = value case .transition(let value): transition = value case .downloadPriority(let value): downloadPriority = value case .forceRefresh: forceRefresh = true case .fromMemoryCacheOrRefresh: fromMemoryCacheOrRefresh = true case .forceTransition: forceTransition = true case .cacheMemoryOnly: cacheMemoryOnly = true case .waitForCache: waitForCache = true case .onlyFromCache: onlyFromCache = true case .backgroundDecode: backgroundDecode = true case .preloadAllAnimationData: preloadAllAnimationData = true case .callbackQueue(let value): callbackQueue = value case .scaleFactor(let value): scaleFactor = value case .requestModifier(let value): requestModifier = value case .redirectHandler(let value): redirectHandler = value case .processor(let value): processor = value case .imageModifier(let value): imageModifier = value case .cacheSerializer(let value): cacheSerializer = value case .keepCurrentImageWhileLoading: keepCurrentImageWhileLoading = true case .onlyLoadFirstFrame: onlyLoadFirstFrame = true case .cacheOriginalImage: cacheOriginalImage = true case .onFailureImage(let value): onFailureImage = .some(value) case .alsoPrefetchToMemory: alsoPrefetchToMemory = true case .loadDiskFileSynchronously: loadDiskFileSynchronously = true case .memoryCacheExpiration(let expiration): memoryCacheExpiration = expiration case .memoryCacheAccessExtendingExpiration(let expirationExtending): memoryCacheAccessExtendingExpiration = expirationExtending case .diskCacheExpiration(let expiration): diskCacheExpiration = expiration case .diskCacheAccessExtendingExpiration(let expirationExtending): diskCacheAccessExtendingExpiration = expirationExtending case .processingQueue(let queue): processingQueue = queue case .progressiveJPEG(let value): progressiveJPEG = value case .alternativeSources(let sources): alternativeSources = sources case .retryStrategy(let strategy): retryStrategy = strategy case .lowDataMode(let source): lowDataModeSource = source } } if originalCache == nil { originalCache = targetCache } } } extension KingfisherParsedOptionsInfo { var imageCreatingOptions: ImageCreatingOptions { return ImageCreatingOptions( scale: scaleFactor, duration: 0.0, preloadAll: preloadAllAnimationData, onlyFirstFrame: onlyLoadFirstFrame) } } protocol DataReceivingSideEffect: AnyObject { var onShouldApply: () -> Bool { get set } func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) } class ImageLoadingProgressSideEffect: DataReceivingSideEffect { var onShouldApply: () -> Bool = { return true } let block: DownloadProgressBlock init(_ block: @escaping DownloadProgressBlock) { self.block = block } func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) { guard self.onShouldApply() else { return } guard let expectedContentLength = task.task.response?.expectedContentLength, expectedContentLength != -1 else { return } let dataLength = Int64(task.mutableData.count) DispatchQueue.main.async { self.block(dataLength, expectedContentLength) } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Image/Filter.swift ================================================ // // Filter.swift // Kingfisher // // Created by Wei Wang on 2016/08/31. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if !os(watchOS) import CoreImage // Reuse the same CI Context for all CI drawing. private let ciContext = CIContext(options: nil) /// Represents the type of transformer method, which will be used in to provide a `Filter`. public typealias Transformer = (CIImage) -> CIImage? /// Represents a processor based on a `CIImage` `Filter`. /// It requires a filter to create an `ImageProcessor`. public protocol CIImageProcessor: ImageProcessor { var filter: Filter { get } } extension CIImageProcessor { /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): return image.kf.apply(filter) case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) } } } /// A wrapper struct for a `Transformer` of CIImage filters. A `Filter` /// value could be used to create a `CIImage` processor. public struct Filter { let transform: Transformer public init(transform: @escaping Transformer) { self.transform = transform } /// Tint filter which will apply a tint color to images. public static var tint: (KFCrossPlatformColor) -> Filter = { color in Filter { input in let colorFilter = CIFilter(name: "CIConstantColorGenerator")! colorFilter.setValue(CIColor(color: color), forKey: kCIInputColorKey) let filter = CIFilter(name: "CISourceOverCompositing")! let colorImage = colorFilter.outputImage filter.setValue(colorImage, forKey: kCIInputImageKey) filter.setValue(input, forKey: kCIInputBackgroundImageKey) return filter.outputImage?.cropped(to: input.extent) } } /// Represents color control elements. It is a tuple of /// `(brightness, contrast, saturation, inputEV)` public typealias ColorElement = (CGFloat, CGFloat, CGFloat, CGFloat) /// Color control filter which will apply color control change to images. public static var colorControl: (ColorElement) -> Filter = { arg -> Filter in let (brightness, contrast, saturation, inputEV) = arg return Filter { input in let paramsColor = [kCIInputBrightnessKey: brightness, kCIInputContrastKey: contrast, kCIInputSaturationKey: saturation] let blackAndWhite = input.applyingFilter("CIColorControls", parameters: paramsColor) let paramsExposure = [kCIInputEVKey: inputEV] return blackAndWhite.applyingFilter("CIExposureAdjust", parameters: paramsExposure) } } } extension KingfisherWrapper where Base: KFCrossPlatformImage { /// Applies a `Filter` containing `CIImage` transformer to `self`. /// /// - Parameter filter: The filter used to transform `self`. /// - Returns: A transformed image by input `Filter`. /// /// - Note: /// Only CG-based images are supported. If any error happens /// during transforming, `self` will be returned. public func apply(_ filter: Filter) -> KFCrossPlatformImage { guard let cgImage = cgImage else { assertionFailure("[Kingfisher] Tint image only works for CG-based image.") return base } let inputImage = CIImage(cgImage: cgImage) guard let outputImage = filter.transform(inputImage) else { return base } guard let result = ciContext.createCGImage(outputImage, from: outputImage.extent) else { assertionFailure("[Kingfisher] Can not make an tint image within context.") return base } #if os(macOS) return fixedForRetinaPixel(cgImage: result, to: size) #else return KFCrossPlatformImage(cgImage: result, scale: base.scale, orientation: base.imageOrientation) #endif } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Image/GIFAnimatedImage.swift ================================================ // // AnimatedImage.swift // Kingfisher // // Created by onevcat on 2018/09/26. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation import ImageIO /// Represents a set of image creating options used in Kingfisher. public struct ImageCreatingOptions { /// The target scale of image needs to be created. public let scale: CGFloat /// The expected animation duration if an animated image being created. public let duration: TimeInterval /// For an animated image, whether or not all frames should be loaded before displaying. public let preloadAll: Bool /// For an animated image, whether or not only the first image should be /// loaded as a static image. It is useful for preview purpose of an animated image. public let onlyFirstFrame: Bool /// Creates an `ImageCreatingOptions` object. /// /// - Parameters: /// - scale: The target scale of image needs to be created. Default is `1.0`. /// - duration: The expected animation duration if an animated image being created. /// A value less or equal to `0.0` means the animated image duration will /// be determined by the frame data. Default is `0.0`. /// - preloadAll: For an animated image, whether or not all frames should be loaded before displaying. /// Default is `false`. /// - onlyFirstFrame: For an animated image, whether or not only the first image should be /// loaded as a static image. It is useful for preview purpose of an animated image. /// Default is `false`. public init( scale: CGFloat = 1.0, duration: TimeInterval = 0.0, preloadAll: Bool = false, onlyFirstFrame: Bool = false) { self.scale = scale self.duration = duration self.preloadAll = preloadAll self.onlyFirstFrame = onlyFirstFrame } } // Represents the decoding for a GIF image. This class extracts frames from an `imageSource`, then // hold the images for later use. class GIFAnimatedImage { let images: [KFCrossPlatformImage] let duration: TimeInterval init?(from imageSource: CGImageSource, for info: [String: Any], options: ImageCreatingOptions) { let frameCount = CGImageSourceGetCount(imageSource) var images = [KFCrossPlatformImage]() var gifDuration = 0.0 for i in 0 ..< frameCount { guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, info as CFDictionary) else { return nil } if frameCount == 1 { gifDuration = .infinity } else { // Get current animated GIF frame duration gifDuration += GIFAnimatedImage.getFrameDuration(from: imageSource, at: i) } images.append(KingfisherWrapper.image(cgImage: imageRef, scale: options.scale, refImage: nil)) if options.onlyFirstFrame { break } } self.images = images self.duration = gifDuration } // Calculates frame duration for a gif frame out of the kCGImagePropertyGIFDictionary dictionary. static func getFrameDuration(from gifInfo: [String: Any]?) -> TimeInterval { let defaultFrameDuration = 0.1 guard let gifInfo = gifInfo else { return defaultFrameDuration } let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber let duration = unclampedDelayTime ?? delayTime guard let frameDuration = duration else { return defaultFrameDuration } return frameDuration.doubleValue > 0.011 ? frameDuration.doubleValue : defaultFrameDuration } // Calculates frame duration at a specific index for a gif from an `imageSource`. static func getFrameDuration(from imageSource: CGImageSource, at index: Int) -> TimeInterval { guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, index, nil) as? [String: Any] else { return 0.0 } let gifInfo = properties[kCGImagePropertyGIFDictionary as String] as? [String: Any] return getFrameDuration(from: gifInfo) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Image/GraphicsContext.swift ================================================ // // GraphicsContext.swift // Kingfisher // // Created by taras on 19/04/2021. // // Copyright (c) 2021 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit #endif #if canImport(UIKit) import UIKit #endif enum GraphicsContext { static func begin(size: CGSize, scale: CGFloat) { #if os(macOS) NSGraphicsContext.saveGraphicsState() #else UIGraphicsBeginImageContextWithOptions(size, false, scale) #endif } static func current(size: CGSize, scale: CGFloat, inverting: Bool, cgImage: CGImage?) -> CGContext? { #if os(macOS) guard let rep = NSBitmapImageRep( bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height), bitsPerSample: cgImage?.bitsPerComponent ?? 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0) else { assertionFailure("[Kingfisher] Image representation cannot be created.") return nil } rep.size = size guard let context = NSGraphicsContext(bitmapImageRep: rep) else { assertionFailure("[Kingfisher] Image context cannot be created.") return nil } NSGraphicsContext.current = context return context.cgContext #else guard let context = UIGraphicsGetCurrentContext() else { return nil } if inverting { // If drawing a CGImage, we need to make context flipped. context.scaleBy(x: 1.0, y: -1.0) context.translateBy(x: 0, y: -size.height) } return context #endif } static func end() { #if os(macOS) NSGraphicsContext.restoreGraphicsState() #else UIGraphicsEndImageContext() #endif } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Image/Image.swift ================================================ // // Image.swift // Kingfisher // // Created by Wei Wang on 16/1/6. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if os(macOS) import AppKit private var imagesKey: Void? private var durationKey: Void? #else import UIKit import MobileCoreServices private var imageSourceKey: Void? #endif #if !os(watchOS) import CoreImage #endif import CoreGraphics import ImageIO private var animatedImageDataKey: Void? private var imageFrameCountKey: Void? // MARK: - Image Properties extension KingfisherWrapper where Base: KFCrossPlatformImage { private(set) var animatedImageData: Data? { get { return getAssociatedObject(base, &animatedImageDataKey) } set { setRetainedAssociatedObject(base, &animatedImageDataKey, newValue) } } public var imageFrameCount: Int? { get { return getAssociatedObject(base, &imageFrameCountKey) } set { setRetainedAssociatedObject(base, &imageFrameCountKey, newValue) } } #if os(macOS) var cgImage: CGImage? { return base.cgImage(forProposedRect: nil, context: nil, hints: nil) } var scale: CGFloat { return 1.0 } private(set) var images: [KFCrossPlatformImage]? { get { return getAssociatedObject(base, &imagesKey) } set { setRetainedAssociatedObject(base, &imagesKey, newValue) } } private(set) var duration: TimeInterval { get { return getAssociatedObject(base, &durationKey) ?? 0.0 } set { setRetainedAssociatedObject(base, &durationKey, newValue) } } var size: CGSize { return base.representations.reduce(.zero) { size, rep in let width = max(size.width, CGFloat(rep.pixelsWide)) let height = max(size.height, CGFloat(rep.pixelsHigh)) return CGSize(width: width, height: height) } } #else var cgImage: CGImage? { return base.cgImage } var scale: CGFloat { return base.scale } var images: [KFCrossPlatformImage]? { return base.images } var duration: TimeInterval { return base.duration } var size: CGSize { return base.size } private(set) var imageSource: CGImageSource? { get { return getAssociatedObject(base, &imageSourceKey) } set { setRetainedAssociatedObject(base, &imageSourceKey, newValue) } } #endif // Bitmap memory cost with bytes. var cost: Int { let pixel = Int(size.width * size.height * scale * scale) guard let cgImage = cgImage else { return pixel * 4 } return pixel * cgImage.bitsPerPixel / 8 } } // MARK: - Image Conversion extension KingfisherWrapper where Base: KFCrossPlatformImage { #if os(macOS) static func image(cgImage: CGImage, scale: CGFloat, refImage: KFCrossPlatformImage?) -> KFCrossPlatformImage { return KFCrossPlatformImage(cgImage: cgImage, size: .zero) } /// Normalize the image. This getter does nothing on macOS but return the image itself. public var normalized: KFCrossPlatformImage { return base } #else /// Creating an image from a give `CGImage` at scale and orientation for refImage. The method signature is for /// compatibility of macOS version. static func image(cgImage: CGImage, scale: CGFloat, refImage: KFCrossPlatformImage?) -> KFCrossPlatformImage { return KFCrossPlatformImage(cgImage: cgImage, scale: scale, orientation: refImage?.imageOrientation ?? .up) } /// Returns normalized image for current `base` image. /// This method will try to redraw an image with orientation and scale considered. public var normalized: KFCrossPlatformImage { // prevent animated image (GIF) lose it's images guard images == nil else { return base.copy() as! KFCrossPlatformImage } // No need to do anything if already up guard base.imageOrientation != .up else { return base.copy() as! KFCrossPlatformImage } return draw(to: size, inverting: true, refImage: KFCrossPlatformImage()) { fixOrientation(in: $0) return true } } func fixOrientation(in context: CGContext) { var transform = CGAffineTransform.identity let orientation = base.imageOrientation switch orientation { case .down, .downMirrored: transform = transform.translatedBy(x: size.width, y: size.height) transform = transform.rotated(by: .pi) case .left, .leftMirrored: transform = transform.translatedBy(x: size.width, y: 0) transform = transform.rotated(by: .pi / 2.0) case .right, .rightMirrored: transform = transform.translatedBy(x: 0, y: size.height) transform = transform.rotated(by: .pi / -2.0) case .up, .upMirrored: break #if compiler(>=5) @unknown default: break #endif } //Flip image one more time if needed to, this is to prevent flipped image switch orientation { case .upMirrored, .downMirrored: transform = transform.translatedBy(x: size.width, y: 0) transform = transform.scaledBy(x: -1, y: 1) case .leftMirrored, .rightMirrored: transform = transform.translatedBy(x: size.height, y: 0) transform = transform.scaledBy(x: -1, y: 1) case .up, .down, .left, .right: break #if compiler(>=5) @unknown default: break #endif } context.concatenate(transform) switch orientation { case .left, .leftMirrored, .right, .rightMirrored: context.draw(cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width)) default: context.draw(cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) } } #endif } // MARK: - Image Representation extension KingfisherWrapper where Base: KFCrossPlatformImage { /// Returns PNG representation of `base` image. /// /// - Returns: PNG data of image. public func pngRepresentation() -> Data? { #if os(macOS) guard let cgImage = cgImage else { return nil } let rep = NSBitmapImageRep(cgImage: cgImage) return rep.representation(using: .png, properties: [:]) #else return base.pngData() #endif } /// Returns JPEG representation of `base` image. /// /// - Parameter compressionQuality: The compression quality when converting image to JPEG data. /// - Returns: JPEG data of image. public func jpegRepresentation(compressionQuality: CGFloat) -> Data? { #if os(macOS) guard let cgImage = cgImage else { return nil } let rep = NSBitmapImageRep(cgImage: cgImage) return rep.representation(using:.jpeg, properties: [.compressionFactor: compressionQuality]) #else return base.jpegData(compressionQuality: compressionQuality) #endif } /// Returns GIF representation of `base` image. /// /// - Returns: Original GIF data of image. public func gifRepresentation() -> Data? { return animatedImageData } /// Returns a data representation for `base` image, with the `format` as the format indicator. /// /// - Parameter format: The format in which the output data should be. If `unknown`, the `base` image will be /// converted in the PNG representation. /// /// - Returns: The output data representing. /// Returns a data representation for `base` image, with the `format` as the format indicator. /// - Parameters: /// - format: The format in which the output data should be. If `unknown`, the `base` image will be /// converted in the PNG representation. /// - compressionQuality: The compression quality when converting image to a lossy format data. public func data(format: ImageFormat, compressionQuality: CGFloat = 1.0) -> Data? { return autoreleasepool { () -> Data? in let data: Data? switch format { case .PNG: data = pngRepresentation() case .JPEG: data = jpegRepresentation(compressionQuality: compressionQuality) case .GIF: data = gifRepresentation() case .unknown: data = normalized.kf.pngRepresentation() } return data } } } // MARK: - Creating Images extension KingfisherWrapper where Base: KFCrossPlatformImage { /// Creates an animated image from a given data and options. Currently only GIF data is supported. /// /// - Parameters: /// - data: The animated image data. /// - options: Options to use when creating the animated image. /// - Returns: An `Image` object represents the animated image. It is in form of an array of image frames with a /// certain duration. `nil` if anything wrong when creating animated image. public static func animatedImage(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage? { let info: [String: Any] = [ kCGImageSourceShouldCache as String: true, kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF ] guard let imageSource = CGImageSourceCreateWithData(data as CFData, info as CFDictionary) else { return nil } #if os(macOS) guard let animatedImage = GIFAnimatedImage(from: imageSource, for: info, options: options) else { return nil } var image: KFCrossPlatformImage? if options.onlyFirstFrame { image = animatedImage.images.first } else { image = KFCrossPlatformImage(data: data) var kf = image?.kf kf?.images = animatedImage.images kf?.duration = animatedImage.duration } image?.kf.animatedImageData = data image?.kf.imageFrameCount = Int(CGImageSourceGetCount(imageSource)) return image #else var image: KFCrossPlatformImage? if options.preloadAll || options.onlyFirstFrame { // Use `images` image if you want to preload all animated data guard let animatedImage = GIFAnimatedImage(from: imageSource, for: info, options: options) else { return nil } if options.onlyFirstFrame { image = animatedImage.images.first } else { let duration = options.duration <= 0.0 ? animatedImage.duration : options.duration image = .animatedImage(with: animatedImage.images, duration: duration) } image?.kf.animatedImageData = data } else { image = KFCrossPlatformImage(data: data, scale: options.scale) var kf = image?.kf kf?.imageSource = imageSource kf?.animatedImageData = data } image?.kf.imageFrameCount = Int(CGImageSourceGetCount(imageSource)) return image #endif } /// Creates an image from a given data and options. `.JPEG`, `.PNG` or `.GIF` is supported. For other /// image format, image initializer from system will be used. If no image object could be created from /// the given `data`, `nil` will be returned. /// /// - Parameters: /// - data: The image data representation. /// - options: Options to use when creating the image. /// - Returns: An `Image` object represents the image if created. If the `data` is invalid or not supported, `nil` /// will be returned. public static func image(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage? { var image: KFCrossPlatformImage? switch data.kf.imageFormat { case .JPEG: image = KFCrossPlatformImage(data: data, scale: options.scale) case .PNG: image = KFCrossPlatformImage(data: data, scale: options.scale) case .GIF: image = KingfisherWrapper.animatedImage(data: data, options: options) case .unknown: image = KFCrossPlatformImage(data: data, scale: options.scale) } return image } /// Creates a downsampled image from given data to a certain size and scale. /// /// - Parameters: /// - data: The image data contains a JPEG or PNG image. /// - pointSize: The target size in point to which the image should be downsampled. /// - scale: The scale of result image. /// - Returns: A downsampled `Image` object following the input conditions. /// /// - Note: /// Different from image `resize` methods, downsampling will not render the original /// input image in pixel format. It does downsampling from the image data, so it is much /// more memory efficient and friendly. Choose to use downsampling as possible as you can. /// /// The input size should be smaller than the size of input image. If it is larger than the /// original image size, the result image will be the same size of input without downsampling. public static func downsampledImage(data: Data, to pointSize: CGSize, scale: CGFloat) -> KFCrossPlatformImage? { let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else { return nil } let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale let downsampleOptions = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil } return KingfisherWrapper.image(cgImage: downsampledImage, scale: scale, refImage: nil) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Image/ImageDrawing.swift ================================================ // // ImageDrawing.swift // Kingfisher // // Created by onevcat on 2018/09/28. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Accelerate #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit #endif #if canImport(UIKit) import UIKit #endif // MARK: - Image Transforming extension KingfisherWrapper where Base: KFCrossPlatformImage { // MARK: Blend Mode /// Create image from `base` image and apply blend mode. /// /// - parameter blendMode: The blend mode of creating image. /// - parameter alpha: The alpha should be used for image. /// - parameter backgroundColor: The background color for the output image. /// /// - returns: An image with blend mode applied. /// /// - Note: This method only works for CG-based image. #if !os(macOS) public func image(withBlendMode blendMode: CGBlendMode, alpha: CGFloat = 1.0, backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage { guard let _ = cgImage else { assertionFailure("[Kingfisher] Blend mode image only works for CG-based image.") return base } let rect = CGRect(origin: .zero, size: size) return draw(to: rect.size) { _ in if let backgroundColor = backgroundColor { backgroundColor.setFill() UIRectFill(rect) } base.draw(in: rect, blendMode: blendMode, alpha: alpha) return false } } #endif #if os(macOS) // MARK: Compositing /// Creates image from `base` image and apply compositing operation. /// /// - Parameters: /// - compositingOperation: The compositing operation of creating image. /// - alpha: The alpha should be used for image. /// - backgroundColor: The background color for the output image. /// - Returns: An image with compositing operation applied. /// /// - Note: This method only works for CG-based image. For any non-CG-based image, `base` itself is returned. public func image(withCompositingOperation compositingOperation: NSCompositingOperation, alpha: CGFloat = 1.0, backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage { guard let _ = cgImage else { assertionFailure("[Kingfisher] Compositing Operation image only works for CG-based image.") return base } let rect = CGRect(origin: .zero, size: size) return draw(to: rect.size) { _ in if let backgroundColor = backgroundColor { backgroundColor.setFill() rect.fill() } base.draw(in: rect, from: .zero, operation: compositingOperation, fraction: alpha) return false } } #endif // MARK: Round Corner /// Creates a round corner image from on `base` image. /// /// - Parameters: /// - radius: The round corner radius of creating image. /// - size: The target size of creating image. /// - corners: The target corners which will be applied rounding. /// - backgroundColor: The background color for the output image /// - Returns: An image with round corner of `self`. /// /// - Note: This method only works for CG-based image. The current image scale is kept. /// For any non-CG-based image, `base` itself is returned. public func image(withRoundRadius radius: CGFloat, fit size: CGSize, roundingCorners corners: RectCorner = .all, backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage { guard let _ = cgImage else { assertionFailure("[Kingfisher] Round corner image only works for CG-based image.") return base } let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) return draw(to: size) { _ in #if os(macOS) if let backgroundColor = backgroundColor { let rectPath = NSBezierPath(rect: rect) backgroundColor.setFill() rectPath.fill() } let path = NSBezierPath(roundedRect: rect, byRoundingCorners: corners, radius: radius) path.windingRule = .evenOdd path.addClip() base.draw(in: rect) #else guard let context = UIGraphicsGetCurrentContext() else { assertionFailure("[Kingfisher] Failed to create CG context for image.") return false } if let backgroundColor = backgroundColor { let rectPath = UIBezierPath(rect: rect) backgroundColor.setFill() rectPath.fill() } let path = UIBezierPath( roundedRect: rect, byRoundingCorners: corners.uiRectCorner, cornerRadii: CGSize(width: radius, height: radius) ) context.addPath(path.cgPath) context.clip() base.draw(in: rect) #endif return false } } #if os(iOS) || os(tvOS) func resize(to size: CGSize, for contentMode: UIView.ContentMode) -> KFCrossPlatformImage { switch contentMode { case .scaleAspectFit: return resize(to: size, for: .aspectFit) case .scaleAspectFill: return resize(to: size, for: .aspectFill) default: return resize(to: size) } } #endif // MARK: Resizing /// Resizes `base` image to an image with new size. /// /// - Parameter size: The target size in point. /// - Returns: An image with new size. /// - Note: This method only works for CG-based image. The current image scale is kept. /// For any non-CG-based image, `base` itself is returned. public func resize(to size: CGSize) -> KFCrossPlatformImage { guard let _ = cgImage else { assertionFailure("[Kingfisher] Resize only works for CG-based image.") return base } let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) return draw(to: size) { _ in #if os(macOS) base.draw(in: rect, from: .zero, operation: .copy, fraction: 1.0) #else base.draw(in: rect) #endif return false } } /// Resizes `base` image to an image of new size, respecting the given content mode. /// /// - Parameters: /// - targetSize: The target size in point. /// - contentMode: Content mode of output image should be. /// - Returns: An image with new size. /// /// - Note: This method only works for CG-based image. The current image scale is kept. /// For any non-CG-based image, `base` itself is returned. public func resize(to targetSize: CGSize, for contentMode: ContentMode) -> KFCrossPlatformImage { let newSize = size.kf.resize(to: targetSize, for: contentMode) return resize(to: newSize) } // MARK: Cropping /// Crops `base` image to a new size with a given anchor. /// /// - Parameters: /// - size: The target size. /// - anchor: The anchor point from which the size should be calculated. /// - Returns: An image with new size. /// /// - Note: This method only works for CG-based image. The current image scale is kept. /// For any non-CG-based image, `base` itself is returned. public func crop(to size: CGSize, anchorOn anchor: CGPoint) -> KFCrossPlatformImage { guard let cgImage = cgImage else { assertionFailure("[Kingfisher] Crop only works for CG-based image.") return base } let rect = self.size.kf.constrainedRect(for: size, anchor: anchor) guard let image = cgImage.cropping(to: rect.scaled(scale)) else { assertionFailure("[Kingfisher] Cropping image failed.") return base } return KingfisherWrapper.image(cgImage: image, scale: scale, refImage: base) } // MARK: Blur /// Creates an image with blur effect based on `base` image. /// /// - Parameter radius: The blur radius should be used when creating blur effect. /// - Returns: An image with blur effect applied. /// /// - Note: This method only works for CG-based image. The current image scale is kept. /// For any non-CG-based image, `base` itself is returned. public func blurred(withRadius radius: CGFloat) -> KFCrossPlatformImage { guard let cgImage = cgImage else { assertionFailure("[Kingfisher] Blur only works for CG-based image.") return base } // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) // if d is odd, use three box-blurs of size 'd', centered on the output pixel. let s = max(radius, 2.0) // We will do blur on a resized image (*0.5), so the blur radius could be half as well. // Fix the slow compiling time for Swift 3. // See https://github.com/onevcat/Kingfisher/issues/611 let pi2 = 2 * CGFloat.pi let sqrtPi2 = sqrt(pi2) var targetRadius = floor(s * 3.0 * sqrtPi2 / 4.0 + 0.5) if targetRadius.isEven { targetRadius += 1 } // Determine necessary iteration count by blur radius. let iterations: Int if radius < 0.5 { iterations = 1 } else if radius < 1.5 { iterations = 2 } else { iterations = 3 } let w = Int(size.width) let h = Int(size.height) func createEffectBuffer(_ context: CGContext) -> vImage_Buffer { let data = context.data let width = vImagePixelCount(context.width) let height = vImagePixelCount(context.height) let rowBytes = context.bytesPerRow return vImage_Buffer(data: data, height: height, width: width, rowBytes: rowBytes) } GraphicsContext.begin(size: size, scale: scale) guard let context = GraphicsContext.current(size: size, scale: scale, inverting: true, cgImage: cgImage) else { assertionFailure("[Kingfisher] Failed to create CG context for blurring image.") return base } context.draw(cgImage, in: CGRect(x: 0, y: 0, width: w, height: h)) GraphicsContext.end() var inBuffer = createEffectBuffer(context) GraphicsContext.begin(size: size, scale: scale) guard let outContext = GraphicsContext.current(size: size, scale: scale, inverting: true, cgImage: cgImage) else { assertionFailure("[Kingfisher] Failed to create CG context for blurring image.") return base } defer { GraphicsContext.end() } var outBuffer = createEffectBuffer(outContext) for _ in 0 ..< iterations { let flag = vImage_Flags(kvImageEdgeExtend) vImageBoxConvolve_ARGB8888( &inBuffer, &outBuffer, nil, 0, 0, UInt32(targetRadius), UInt32(targetRadius), nil, flag) // Next inBuffer should be the outButter of current iteration (inBuffer, outBuffer) = (outBuffer, inBuffer) } #if os(macOS) let result = outContext.makeImage().flatMap { fixedForRetinaPixel(cgImage: $0, to: size) } #else let result = outContext.makeImage().flatMap { KFCrossPlatformImage(cgImage: $0, scale: base.scale, orientation: base.imageOrientation) } #endif guard let blurredImage = result else { assertionFailure("[Kingfisher] Can not make an blurred image within this context.") return base } return blurredImage } // MARK: Overlay /// Creates an image from `base` image with a color overlay layer. /// /// - Parameters: /// - color: The color should be use to overlay. /// - fraction: Fraction of input color. From 0.0 to 1.0. 0.0 means solid color, /// 1.0 means transparent overlay. /// - Returns: An image with a color overlay applied. /// /// - Note: This method only works for CG-based image. The current image scale is kept. /// For any non-CG-based image, `base` itself is returned. public func overlaying(with color: KFCrossPlatformColor, fraction: CGFloat) -> KFCrossPlatformImage { guard let _ = cgImage else { assertionFailure("[Kingfisher] Overlaying only works for CG-based image.") return base } let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) return draw(to: rect.size) { context in #if os(macOS) base.draw(in: rect) if fraction > 0 { color.withAlphaComponent(1 - fraction).set() rect.fill(using: .sourceAtop) } #else color.set() UIRectFill(rect) base.draw(in: rect, blendMode: .destinationIn, alpha: 1.0) if fraction > 0 { base.draw(in: rect, blendMode: .sourceAtop, alpha: fraction) } #endif return false } } // MARK: Tint /// Creates an image from `base` image with a color tint. /// /// - Parameter color: The color should be used to tint `base` /// - Returns: An image with a color tint applied. public func tinted(with color: KFCrossPlatformColor) -> KFCrossPlatformImage { #if os(watchOS) return base #else return apply(.tint(color)) #endif } // MARK: Color Control /// Create an image from `self` with color control. /// /// - Parameters: /// - brightness: Brightness changing to image. /// - contrast: Contrast changing to image. /// - saturation: Saturation changing to image. /// - inputEV: InputEV changing to image. /// - Returns: An image with color control applied. public func adjusted(brightness: CGFloat, contrast: CGFloat, saturation: CGFloat, inputEV: CGFloat) -> KFCrossPlatformImage { #if os(watchOS) return base #else return apply(.colorControl((brightness, contrast, saturation, inputEV))) #endif } /// Return an image with given scale. /// /// - Parameter scale: Target scale factor the new image should have. /// - Returns: The image with target scale. If the base image is already in the scale, `base` will be returned. public func scaled(to scale: CGFloat) -> KFCrossPlatformImage { guard scale != self.scale else { return base } guard let cgImage = cgImage else { assertionFailure("[Kingfisher] Scaling only works for CG-based image.") return base } return KingfisherWrapper.image(cgImage: cgImage, scale: scale, refImage: base) } } // MARK: - Decoding Image extension KingfisherWrapper where Base: KFCrossPlatformImage { /// Returns the decoded image of the `base` image. It will draw the image in a plain context and return the data /// from it. This could improve the drawing performance when an image is just created from data but not yet /// displayed for the first time. /// /// - Note: This method only works for CG-based image. The current image scale is kept. /// For any non-CG-based image or animated image, `base` itself is returned. public var decoded: KFCrossPlatformImage { return decoded(scale: scale) } /// Returns decoded image of the `base` image at a given scale. It will draw the image in a plain context and /// return the data from it. This could improve the drawing performance when an image is just created from /// data but not yet displayed for the first time. /// /// - Parameter scale: The given scale of target image should be. /// - Returns: The decoded image ready to be displayed. /// /// - Note: This method only works for CG-based image. The current image scale is kept. /// For any non-CG-based image or animated image, `base` itself is returned. public func decoded(scale: CGFloat) -> KFCrossPlatformImage { // Prevent animated image (GIF) losing it's images #if os(iOS) if imageSource != nil { return base } #else if images != nil { return base } #endif guard let imageRef = cgImage else { assertionFailure("[Kingfisher] Decoding only works for CG-based image.") return base } let size = CGSize(width: CGFloat(imageRef.width) / scale, height: CGFloat(imageRef.height) / scale) return draw(to: size, inverting: true, scale: scale) { context in context.draw(imageRef, in: CGRect(origin: .zero, size: size)) return true } } /// Returns decoded image of the `base` image at a given scale. It will draw the image in a plain context and /// return the data from it. This could improve the drawing performance when an image is just created from /// data but not yet displayed for the first time. /// /// - Parameter context: The context for drawing. /// - Returns: The decoded image ready to be displayed. /// /// - Note: This method only works for CG-based image. The current image scale is kept. /// For any non-CG-based image or animated image, `base` itself is returned. public func decoded(on context: CGContext) -> KFCrossPlatformImage { // Prevent animated image (GIF) losing it's images #if os(iOS) if imageSource != nil { return base } #else if images != nil { return base } #endif guard let refImage = cgImage else { assertionFailure("[Kingfisher] Decoding only works for CG-based image.") return base } let size = CGSize(width: CGFloat(refImage.width) / scale, height: CGFloat(refImage.height) / scale) context.draw(refImage, in: CGRect(origin: .zero, size: size)) guard let cgImage = context.makeImage() else { return base } return KingfisherWrapper.image(cgImage: cgImage, scale: scale, refImage: base) } } extension KingfisherWrapper where Base: KFCrossPlatformImage { func draw( to size: CGSize, inverting: Bool = false, scale: CGFloat? = nil, refImage: KFCrossPlatformImage? = nil, draw: (CGContext) -> Bool // Whether use the refImage (`true`) or ignore image orientation (`false`) ) -> KFCrossPlatformImage { let targetScale = scale ?? self.scale GraphicsContext.begin(size: size, scale: targetScale) guard let context = GraphicsContext.current(size: size, scale: targetScale, inverting: inverting, cgImage: cgImage) else { assertionFailure("[Kingfisher] Failed to create CG context for blurring image.") return base } defer { GraphicsContext.end() } let useRefImage = draw(context) guard let cgImage = context.makeImage() else { return base } let ref = useRefImage ? (refImage ?? base) : nil return KingfisherWrapper.image(cgImage: cgImage, scale: targetScale, refImage: ref) } #if os(macOS) func fixedForRetinaPixel(cgImage: CGImage, to size: CGSize) -> KFCrossPlatformImage { let image = KFCrossPlatformImage(cgImage: cgImage, size: base.size) let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) return draw(to: self.size) { context in image.draw(in: rect, from: .zero, operation: .copy, fraction: 1.0) return false } } #endif } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Image/ImageFormat.swift ================================================ // // ImageFormat.swift // Kingfisher // // Created by onevcat on 2018/09/28. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents image format. /// /// - unknown: The format cannot be recognized or not supported yet. /// - PNG: PNG image format. /// - JPEG: JPEG image format. /// - GIF: GIF image format. public enum ImageFormat { /// The format cannot be recognized or not supported yet. case unknown /// PNG image format. case PNG /// JPEG image format. case JPEG /// GIF image format. case GIF struct HeaderData { static var PNG: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] static var JPEG_SOI: [UInt8] = [0xFF, 0xD8] static var JPEG_IF: [UInt8] = [0xFF] static var GIF: [UInt8] = [0x47, 0x49, 0x46] } /// https://en.wikipedia.org/wiki/JPEG public enum JPEGMarker { case SOF0 //baseline case SOF2 //progressive case DHT //Huffman Table case DQT //Quantization Table case DRI //Restart Interval case SOS //Start Of Scan case RSTn(UInt8) //Restart case APPn //Application-specific case COM //Comment case EOI //End Of Image var bytes: [UInt8] { switch self { case .SOF0: return [0xFF, 0xC0] case .SOF2: return [0xFF, 0xC2] case .DHT: return [0xFF, 0xC4] case .DQT: return [0xFF, 0xDB] case .DRI: return [0xFF, 0xDD] case .SOS: return [0xFF, 0xDA] case .RSTn(let n): return [0xFF, 0xD0 + n] case .APPn: return [0xFF, 0xE0] case .COM: return [0xFF, 0xFE] case .EOI: return [0xFF, 0xD9] } } } } extension Data: KingfisherCompatibleValue {} // MARK: - Misc Helpers extension KingfisherWrapper where Base == Data { /// Gets the image format corresponding to the data. public var imageFormat: ImageFormat { guard base.count > 8 else { return .unknown } var buffer = [UInt8](repeating: 0, count: 8) base.copyBytes(to: &buffer, count: 8) if buffer == ImageFormat.HeaderData.PNG { return .PNG } else if buffer[0] == ImageFormat.HeaderData.JPEG_SOI[0], buffer[1] == ImageFormat.HeaderData.JPEG_SOI[1], buffer[2] == ImageFormat.HeaderData.JPEG_IF[0] { return .JPEG } else if buffer[0] == ImageFormat.HeaderData.GIF[0], buffer[1] == ImageFormat.HeaderData.GIF[1], buffer[2] == ImageFormat.HeaderData.GIF[2] { return .GIF } return .unknown } public func contains(jpeg marker: ImageFormat.JPEGMarker) -> Bool { guard imageFormat == .JPEG else { return false } var buffer = [UInt8](repeating: 0, count: base.count) base.copyBytes(to: &buffer, count: base.count) for (index, item) in buffer.enumerated() { guard item == marker.bytes.first, buffer.count > index + 1, buffer[index + 1] == marker.bytes[1] else { continue } return true } return false } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Image/ImageProcessor.swift ================================================ // // ImageProcessor.swift // Kingfisher // // Created by Wei Wang on 2016/08/26. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation import CoreGraphics #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit #endif /// Represents an item which could be processed by an `ImageProcessor`. /// /// - image: Input image. The processor should provide a way to apply /// processing on this `image` and return the result image. /// - data: Input data. The processor should provide a way to apply /// processing on this `image` and return the result image. public enum ImageProcessItem { /// Input image. The processor should provide a way to apply /// processing on this `image` and return the result image. case image(KFCrossPlatformImage) /// Input data. The processor should provide a way to apply /// processing on this `image` and return the result image. case data(Data) } /// An `ImageProcessor` would be used to convert some downloaded data to an image. public protocol ImageProcessor { /// Identifier of the processor. It will be used to identify the processor when /// caching and retrieving an image. You might want to make sure that processors with /// same properties/functionality have the same identifiers, so correct processed images /// could be retrieved with proper key. /// /// - Note: Do not supply an empty string for a customized processor, which is already reserved by /// the `DefaultImageProcessor`. It is recommended to use a reverse domain name notation string of /// your own for the identifier. var identifier: String { get } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: The parsed options when processing the item. /// - Returns: The processed image. /// /// - Note: The return value should be `nil` if processing failed while converting an input item to image. /// If `nil` received by the processing caller, an error will be reported and the process flow stops. /// If the processing flow is not critical for your flow, then when the input item is already an image /// (`.image` case) and there is any errors in the processing, you could return the input image itself /// to keep the processing pipeline continuing. /// - Note: Most processor only supports CG-based images. watchOS is not supported for processors containing /// a filter, the input image will be returned directly on watchOS. func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? } extension ImageProcessor { /// Appends an `ImageProcessor` to another. The identifier of the new `ImageProcessor` /// will be "\(self.identifier)|>\(another.identifier)". /// /// - Parameter another: An `ImageProcessor` you want to append to `self`. /// - Returns: The new `ImageProcessor` will process the image in the order /// of the two processors concatenated. public func append(another: ImageProcessor) -> ImageProcessor { let newIdentifier = identifier.appending("|>\(another.identifier)") return GeneralProcessor(identifier: newIdentifier) { item, options in if let image = self.process(item: item, options: options) { return another.process(item: .image(image), options: options) } else { return nil } } } } func ==(left: ImageProcessor, right: ImageProcessor) -> Bool { return left.identifier == right.identifier } func !=(left: ImageProcessor, right: ImageProcessor) -> Bool { return !(left == right) } typealias ProcessorImp = ((ImageProcessItem, KingfisherParsedOptionsInfo) -> KFCrossPlatformImage?) struct GeneralProcessor: ImageProcessor { let identifier: String let p: ProcessorImp func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { return p(item, options) } } /// The default processor. It converts the input data to a valid image. /// Images of .PNG, .JPEG and .GIF format are supported. /// If an image item is given as `.image` case, `DefaultImageProcessor` will /// do nothing on it and return the associated image. public struct DefaultImageProcessor: ImageProcessor { /// A default `DefaultImageProcessor` could be used across. public static let `default` = DefaultImageProcessor() /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier = "" /// Creates a `DefaultImageProcessor`. Use `DefaultImageProcessor.default` to get an instance, /// if you do not have a good reason to create your own `DefaultImageProcessor`. public init() {} /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): return image.kf.scaled(to: options.scaleFactor) case .data(let data): return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions) } } } /// Represents the rect corner setting when processing a round corner image. public struct RectCorner: OptionSet { /// Raw value of the rect corner. public let rawValue: Int /// Represents the top left corner. public static let topLeft = RectCorner(rawValue: 1 << 0) /// Represents the top right corner. public static let topRight = RectCorner(rawValue: 1 << 1) /// Represents the bottom left corner. public static let bottomLeft = RectCorner(rawValue: 1 << 2) /// Represents the bottom right corner. public static let bottomRight = RectCorner(rawValue: 1 << 3) /// Represents all corners. public static let all: RectCorner = [.topLeft, .topRight, .bottomLeft, .bottomRight] /// Creates a `RectCorner` option set with a given value. /// /// - Parameter rawValue: The value represents a certain corner option. public init(rawValue: Int) { self.rawValue = rawValue } var cornerIdentifier: String { if self == .all { return "" } return "_corner(\(rawValue))" } } #if !os(macOS) /// Processor for adding an blend mode to images. Only CG-based images are supported. public struct BlendImageProcessor: ImageProcessor { /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier: String /// Blend Mode will be used to blend the input image. public let blendMode: CGBlendMode /// Alpha will be used when blend image. public let alpha: CGFloat /// Background color of the output image. If `nil`, it will stay transparent. public let backgroundColor: KFCrossPlatformColor? /// Creates a `BlendImageProcessor`. /// /// - Parameters: /// - blendMode: Blend Mode will be used to blend the input image. /// - alpha: Alpha will be used when blend image. From 0.0 to 1.0. 1.0 means solid image, /// 0.0 means transparent image (not visible at all). Default is 1.0. /// - backgroundColor: Background color to apply for the output image. Default is `nil`. public init(blendMode: CGBlendMode, alpha: CGFloat = 1.0, backgroundColor: KFCrossPlatformColor? = nil) { self.blendMode = blendMode self.alpha = alpha self.backgroundColor = backgroundColor var identifier = "com.onevcat.Kingfisher.BlendImageProcessor(\(blendMode.rawValue),\(alpha))" if let color = backgroundColor { identifier.append("_\(color.hex)") } self.identifier = identifier } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): return image.kf.scaled(to: options.scaleFactor) .kf.image(withBlendMode: blendMode, alpha: alpha, backgroundColor: backgroundColor) case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) } } } #endif #if os(macOS) /// Processor for adding an compositing operation to images. Only CG-based images are supported in macOS. public struct CompositingImageProcessor: ImageProcessor { /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier: String /// Compositing operation will be used to the input image. public let compositingOperation: NSCompositingOperation /// Alpha will be used when compositing image. public let alpha: CGFloat /// Background color of the output image. If `nil`, it will stay transparent. public let backgroundColor: KFCrossPlatformColor? /// Creates a `CompositingImageProcessor` /// /// - Parameters: /// - compositingOperation: Compositing operation will be used to the input image. /// - alpha: Alpha will be used when compositing image. /// From 0.0 to 1.0. 1.0 means solid image, 0.0 means transparent image. /// Default is 1.0. /// - backgroundColor: Background color to apply for the output image. Default is `nil`. public init(compositingOperation: NSCompositingOperation, alpha: CGFloat = 1.0, backgroundColor: KFCrossPlatformColor? = nil) { self.compositingOperation = compositingOperation self.alpha = alpha self.backgroundColor = backgroundColor var identifier = "com.onevcat.Kingfisher.CompositingImageProcessor(\(compositingOperation.rawValue),\(alpha))" if let color = backgroundColor { identifier.append("_\(color.hex)") } self.identifier = identifier } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): return image.kf.scaled(to: options.scaleFactor) .kf.image( withCompositingOperation: compositingOperation, alpha: alpha, backgroundColor: backgroundColor) case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) } } } #endif /// Processor for making round corner images. Only CG-based images are supported in macOS, /// if a non-CG image passed in, the processor will do nothing. /// /// - Note: The input image will be rendered with round corner pixels removed. If the image itself does not contain /// alpha channel (for example, a JPEG image), the processed image will contain an alpha channel in memory in order /// to show correctly. However, when cached to disk, Kingfisher respects the original image format by default. That /// means the alpha channel will be removed for these images. When you load the processed image from cache again, you /// will lose transparent corner. /// /// You could use `FormatIndicatedCacheSerializer.png` to force Kingfisher to serialize the image to PNG format in this /// case. /// public struct RoundCornerImageProcessor: ImageProcessor { /// Represents a radius specified in a `RoundCornerImageProcessor`. public enum Radius { /// The radius should be calculated as a fraction of the image width. Typically the associated value should be /// between 0 and 0.5, where 0 represents no radius and 0.5 represents using half of the image width. case widthFraction(CGFloat) /// The radius should be calculated as a fraction of the image height. Typically the associated value should be /// between 0 and 0.5, where 0 represents no radius and 0.5 represents using half of the image height. case heightFraction(CGFloat) /// Use a fixed point value as the round corner radius. case point(CGFloat) var radiusIdentifier: String { switch self { case .widthFraction(let f): return "w_frac_\(f)" case .heightFraction(let f): return "h_frac_\(f)" case .point(let p): return p.description } } } /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier: String /// The radius will be applied in processing. Specify a certain point value with `.point`, or a fraction of the /// target image with `.widthFraction`. or `.heightFraction`. For example, given a square image with width and /// height equals, `.widthFraction(0.5)` means use half of the length of size and makes the final image a round one. public let radius: Radius /// The target corners which will be applied rounding. public let roundingCorners: RectCorner /// Target size of output image should be. If `nil`, the image will keep its original size after processing. public let targetSize: CGSize? /// Background color of the output image. If `nil`, it will use a transparent background. public let backgroundColor: KFCrossPlatformColor? /// Creates a `RoundCornerImageProcessor`. /// /// - Parameters: /// - cornerRadius: Corner radius in point will be applied in processing. /// - targetSize: Target size of output image should be. If `nil`, /// the image will keep its original size after processing. /// Default is `nil`. /// - corners: The target corners which will be applied rounding. Default is `.all`. /// - backgroundColor: Background color to apply for the output image. Default is `nil`. /// /// - Note: /// /// This initializer accepts a concrete point value for `cornerRadius`. If you do not know the image size, but still /// want to apply a full round-corner (making the final image a round one), or specify the corner radius as a /// fraction of one dimension of the target image, use the `Radius` version instead. /// public init( cornerRadius: CGFloat, targetSize: CGSize? = nil, roundingCorners corners: RectCorner = .all, backgroundColor: KFCrossPlatformColor? = nil ) { let radius = Radius.point(cornerRadius) self.init(radius: radius, targetSize: targetSize, roundingCorners: corners, backgroundColor: backgroundColor) } /// Creates a `RoundCornerImageProcessor`. /// /// - Parameters: /// - radius: The radius will be applied in processing. /// - targetSize: Target size of output image should be. If `nil`, /// the image will keep its original size after processing. /// Default is `nil`. /// - corners: The target corners which will be applied rounding. Default is `.all`. /// - backgroundColor: Background color to apply for the output image. Default is `nil`. public init( radius: Radius, targetSize: CGSize? = nil, roundingCorners corners: RectCorner = .all, backgroundColor: KFCrossPlatformColor? = nil ) { self.radius = radius self.targetSize = targetSize self.roundingCorners = corners self.backgroundColor = backgroundColor self.identifier = { var identifier = "" if let size = targetSize { identifier = "com.onevcat.Kingfisher.RoundCornerImageProcessor" + "(\(radius.radiusIdentifier)_\(size)\(corners.cornerIdentifier))" } else { identifier = "com.onevcat.Kingfisher.RoundCornerImageProcessor" + "(\(radius.radiusIdentifier)\(corners.cornerIdentifier))" } if let backgroundColor = backgroundColor { identifier += "_\(backgroundColor)" } return identifier }() } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): let size = targetSize ?? image.kf.size let cornerRadius: CGFloat switch radius { case .point(let point): cornerRadius = point case .widthFraction(let widthFraction): cornerRadius = size.width * widthFraction case .heightFraction(let heightFraction): cornerRadius = size.height * heightFraction } return image.kf.scaled(to: options.scaleFactor) .kf.image( withRoundRadius: cornerRadius, fit: size, roundingCorners: roundingCorners, backgroundColor: backgroundColor) case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) } } } /// Represents how a size adjusts itself to fit a target size. /// /// - none: Not scale the content. /// - aspectFit: Scales the content to fit the size of the view by maintaining the aspect ratio. /// - aspectFill: Scales the content to fill the size of the view. public enum ContentMode { /// Not scale the content. case none /// Scales the content to fit the size of the view by maintaining the aspect ratio. case aspectFit /// Scales the content to fill the size of the view. case aspectFill } /// Processor for resizing images. /// If you need to resize a data represented image to a smaller size, use `DownsamplingImageProcessor` /// instead, which is more efficient and uses less memory. public struct ResizingImageProcessor: ImageProcessor { /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier: String /// The reference size for resizing operation in point. public let referenceSize: CGSize /// Target content mode of output image should be. /// Default is `.none`. public let targetContentMode: ContentMode /// Creates a `ResizingImageProcessor`. /// /// - Parameters: /// - referenceSize: The reference size for resizing operation in point. /// - mode: Target content mode of output image should be. /// /// - Note: /// The instance of `ResizingImageProcessor` will follow its `mode` property /// and try to resizing the input images to fit or fill the `referenceSize`. /// That means if you are using a `mode` besides of `.none`, you may get an /// image with its size not be the same as the `referenceSize`. /// /// **Example**: With input image size: {100, 200}, /// `referenceSize`: {100, 100}, `mode`: `.aspectFit`, /// you will get an output image with size of {50, 100}, which "fit"s /// the `referenceSize`. /// /// If you need an output image exactly to be a specified size, append or use /// a `CroppingImageProcessor`. public init(referenceSize: CGSize, mode: ContentMode = .none) { self.referenceSize = referenceSize self.targetContentMode = mode if mode == .none { self.identifier = "com.onevcat.Kingfisher.ResizingImageProcessor(\(referenceSize))" } else { self.identifier = "com.onevcat.Kingfisher.ResizingImageProcessor(\(referenceSize), \(mode))" } } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): return image.kf.scaled(to: options.scaleFactor) .kf.resize(to: referenceSize, for: targetContentMode) case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) } } } /// Processor for adding blur effect to images. `Accelerate.framework` is used underhood for /// a better performance. A simulated Gaussian blur with specified blur radius will be applied. public struct BlurImageProcessor: ImageProcessor { /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier: String /// Blur radius for the simulated Gaussian blur. public let blurRadius: CGFloat /// Creates a `BlurImageProcessor` /// /// - parameter blurRadius: Blur radius for the simulated Gaussian blur. public init(blurRadius: CGFloat) { self.blurRadius = blurRadius self.identifier = "com.onevcat.Kingfisher.BlurImageProcessor(\(blurRadius))" } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): let radius = blurRadius * options.scaleFactor return image.kf.scaled(to: options.scaleFactor) .kf.blurred(withRadius: radius) case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) } } } /// Processor for adding an overlay to images. Only CG-based images are supported in macOS. public struct OverlayImageProcessor: ImageProcessor { /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier: String /// Overlay color will be used to overlay the input image. public let overlay: KFCrossPlatformColor /// Fraction will be used when overlay the color to image. public let fraction: CGFloat /// Creates an `OverlayImageProcessor` /// /// - parameter overlay: Overlay color will be used to overlay the input image. /// - parameter fraction: Fraction will be used when overlay the color to image. /// From 0.0 to 1.0. 0.0 means solid color, 1.0 means transparent overlay. public init(overlay: KFCrossPlatformColor, fraction: CGFloat = 0.5) { self.overlay = overlay self.fraction = fraction self.identifier = "com.onevcat.Kingfisher.OverlayImageProcessor(\(overlay.hex)_\(fraction))" } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): return image.kf.scaled(to: options.scaleFactor) .kf.overlaying(with: overlay, fraction: fraction) case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) } } } /// Processor for tint images with color. Only CG-based images are supported. public struct TintImageProcessor: ImageProcessor { /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier: String /// Tint color will be used to tint the input image. public let tint: KFCrossPlatformColor /// Creates a `TintImageProcessor` /// /// - parameter tint: Tint color will be used to tint the input image. public init(tint: KFCrossPlatformColor) { self.tint = tint self.identifier = "com.onevcat.Kingfisher.TintImageProcessor(\(tint.hex))" } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): return image.kf.scaled(to: options.scaleFactor) .kf.tinted(with: tint) case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) } } } /// Processor for applying some color control to images. Only CG-based images are supported. /// watchOS is not supported. public struct ColorControlsProcessor: ImageProcessor { /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier: String /// Brightness changing to image. public let brightness: CGFloat /// Contrast changing to image. public let contrast: CGFloat /// Saturation changing to image. public let saturation: CGFloat /// InputEV changing to image. public let inputEV: CGFloat /// Creates a `ColorControlsProcessor` /// /// - Parameters: /// - brightness: Brightness changing to image. /// - contrast: Contrast changing to image. /// - saturation: Saturation changing to image. /// - inputEV: InputEV changing to image. public init(brightness: CGFloat, contrast: CGFloat, saturation: CGFloat, inputEV: CGFloat) { self.brightness = brightness self.contrast = contrast self.saturation = saturation self.inputEV = inputEV self.identifier = "com.onevcat.Kingfisher.ColorControlsProcessor(\(brightness)_\(contrast)_\(saturation)_\(inputEV))" } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): return image.kf.scaled(to: options.scaleFactor) .kf.adjusted(brightness: brightness, contrast: contrast, saturation: saturation, inputEV: inputEV) case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) } } } /// Processor for applying black and white effect to images. Only CG-based images are supported. /// watchOS is not supported. public struct BlackWhiteProcessor: ImageProcessor { /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier = "com.onevcat.Kingfisher.BlackWhiteProcessor" /// Creates a `BlackWhiteProcessor` public init() {} /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { return ColorControlsProcessor(brightness: 0.0, contrast: 1.0, saturation: 0.0, inputEV: 0.7) .process(item: item, options: options) } } /// Processor for cropping an image. Only CG-based images are supported. /// watchOS is not supported. public struct CroppingImageProcessor: ImageProcessor { /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier: String /// Target size of output image should be. public let size: CGSize /// Anchor point from which the output size should be calculate. /// The anchor point is consisted by two values between 0.0 and 1.0. /// It indicates a related point in current image. /// See `CroppingImageProcessor.init(size:anchor:)` for more. public let anchor: CGPoint /// Creates a `CroppingImageProcessor`. /// /// - Parameters: /// - size: Target size of output image should be. /// - anchor: The anchor point from which the size should be calculated. /// Default is `CGPoint(x: 0.5, y: 0.5)`, which means the center of input image. /// - Note: /// The anchor point is consisted by two values between 0.0 and 1.0. /// It indicates a related point in current image, eg: (0.0, 0.0) for top-left /// corner, (0.5, 0.5) for center and (1.0, 1.0) for bottom-right corner. /// The `size` property of `CroppingImageProcessor` will be used along with /// `anchor` to calculate a target rectangle in the size of image. /// /// The target size will be automatically calculated with a reasonable behavior. /// For example, when you have an image size of `CGSize(width: 100, height: 100)`, /// and a target size of `CGSize(width: 20, height: 20)`: /// - with a (0.0, 0.0) anchor (top-left), the crop rect will be `{0, 0, 20, 20}`; /// - with a (0.5, 0.5) anchor (center), it will be `{40, 40, 20, 20}` /// - while with a (1.0, 1.0) anchor (bottom-right), it will be `{80, 80, 20, 20}` public init(size: CGSize, anchor: CGPoint = CGPoint(x: 0.5, y: 0.5)) { self.size = size self.anchor = anchor self.identifier = "com.onevcat.Kingfisher.CroppingImageProcessor(\(size)_\(anchor))" } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): return image.kf.scaled(to: options.scaleFactor) .kf.crop(to: size, anchorOn: anchor) case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options) } } } /// Processor for downsampling an image. Compared to `ResizingImageProcessor`, this processor /// does not render the images to resize. Instead, it downsamples the input data directly to an /// image. It is a more efficient than `ResizingImageProcessor`. Prefer to use `DownsamplingImageProcessor` as possible /// as you can than the `ResizingImageProcessor`. /// /// Only CG-based images are supported. Animated images (like GIF) is not supported. public struct DownsamplingImageProcessor: ImageProcessor { /// Target size of output image should be. It should be smaller than the size of /// input image. If it is larger, the result image will be the same size of input /// data without downsampling. public let size: CGSize /// Identifier of the processor. /// - Note: See documentation of `ImageProcessor` protocol for more. public let identifier: String /// Creates a `DownsamplingImageProcessor`. /// /// - Parameter size: The target size of the downsample operation. public init(size: CGSize) { self.size = size self.identifier = "com.onevcat.Kingfisher.DownsamplingImageProcessor(\(size))" } /// Processes the input `ImageProcessItem` with this processor. /// /// - Parameters: /// - item: Input item which will be processed by `self`. /// - options: Options when processing the item. /// - Returns: The processed image. /// /// - Note: See documentation of `ImageProcessor` protocol for more. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { switch item { case .image(let image): guard let data = image.kf.data(format: .unknown) else { return nil } return KingfisherWrapper.downsampledImage(data: data, to: size, scale: options.scaleFactor) case .data(let data): return KingfisherWrapper.downsampledImage(data: data, to: size, scale: options.scaleFactor) } } } infix operator |>: AdditionPrecedence public func |>(left: ImageProcessor, right: ImageProcessor) -> ImageProcessor { return left.append(another: right) } extension KFCrossPlatformColor { var hex: String { var r: CGFloat = 0 var g: CGFloat = 0 var b: CGFloat = 0 var a: CGFloat = 0 #if os(macOS) (usingColorSpace(.sRGB) ?? self).getRed(&r, green: &g, blue: &b, alpha: &a) #else getRed(&r, green: &g, blue: &b, alpha: &a) #endif let rInt = Int(r * 255) << 24 let gInt = Int(g * 255) << 16 let bInt = Int(b * 255) << 8 let aInt = Int(a * 255) let rgba = rInt | gInt | bInt | aInt return String(format:"#%08x", rgba) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Image/ImageProgressive.swift ================================================ // // ImageProgressive.swift // Kingfisher // // Created by lixiang on 2019/5/10. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation import CoreGraphics private let sharedProcessingQueue: CallbackQueue = .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process")) public struct ImageProgressive { /// A default `ImageProgressive` could be used across. It blurs the progressive loading with the fastest /// scan enabled and scan interval as 0. public static let `default` = ImageProgressive( isBlur: true, isFastestScan: true, scanInterval: 0 ) /// Whether to enable blur effect processing let isBlur: Bool /// Whether to enable the fastest scan let isFastestScan: Bool /// Minimum time interval for each scan let scanInterval: TimeInterval public init(isBlur: Bool, isFastestScan: Bool, scanInterval: TimeInterval ) { self.isBlur = isBlur self.isFastestScan = isFastestScan self.scanInterval = scanInterval } } protocol ImageSettable: AnyObject { var image: KFCrossPlatformImage? { get set } } final class ImageProgressiveProvider: DataReceivingSideEffect { var onShouldApply: () -> Bool = { return true } func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) { DispatchQueue.main.async { guard self.onShouldApply() else { return } self.update(data: task.mutableData, with: task.callbacks) } } private let option: ImageProgressive private let refresh: (KFCrossPlatformImage) -> Void private let decoder: ImageProgressiveDecoder private let queue = ImageProgressiveSerialQueue() init?(_ options: KingfisherParsedOptionsInfo, refresh: @escaping (KFCrossPlatformImage) -> Void) { guard let option = options.progressiveJPEG else { return nil } self.option = option self.refresh = refresh self.decoder = ImageProgressiveDecoder( option, processingQueue: options.processingQueue ?? sharedProcessingQueue, creatingOptions: options.imageCreatingOptions ) } func update(data: Data, with callbacks: [SessionDataTask.TaskCallback]) { guard !data.isEmpty else { return } queue.add(minimum: option.scanInterval) { completion in func decode(_ data: Data) { self.decoder.decode(data, with: callbacks) { image in defer { completion() } guard self.onShouldApply() else { return } guard let image = image else { return } self.refresh(image) } } let semaphore = DispatchSemaphore(value: 0) var onShouldApply: Bool = false CallbackQueue.mainAsync.execute { onShouldApply = self.onShouldApply() semaphore.signal() } semaphore.wait() guard onShouldApply else { self.queue.clean() completion() return } if self.option.isFastestScan { decode(self.decoder.scanning(data) ?? Data()) } else { self.decoder.scanning(data).forEach { decode($0) } } } } } private final class ImageProgressiveDecoder { private let option: ImageProgressive private let processingQueue: CallbackQueue private let creatingOptions: ImageCreatingOptions private(set) var scannedCount = 0 private(set) var scannedIndex = -1 init(_ option: ImageProgressive, processingQueue: CallbackQueue, creatingOptions: ImageCreatingOptions) { self.option = option self.processingQueue = processingQueue self.creatingOptions = creatingOptions } func scanning(_ data: Data) -> [Data] { guard data.kf.contains(jpeg: .SOF2) else { return [] } guard scannedIndex + 1 < data.count else { return [] } var datas: [Data] = [] var index = scannedIndex + 1 var count = scannedCount while index < data.count - 1 { scannedIndex = index // 0xFF, 0xDA - Start Of Scan let SOS = ImageFormat.JPEGMarker.SOS.bytes if data[index] == SOS[0], data[index + 1] == SOS[1] { if count > 0 { datas.append(data[0 ..< index]) } count += 1 } index += 1 } // Found more scans this the previous time guard count > scannedCount else { return [] } scannedCount = count // `> 1` checks that we've received a first scan (SOS) and then received // and also received a second scan (SOS). This way we know that we have // at least one full scan available. guard count > 1 else { return [] } return datas } func scanning(_ data: Data) -> Data? { guard data.kf.contains(jpeg: .SOF2) else { return nil } guard scannedIndex + 1 < data.count else { return nil } var index = scannedIndex + 1 var count = scannedCount var lastSOSIndex = 0 while index < data.count - 1 { scannedIndex = index // 0xFF, 0xDA - Start Of Scan let SOS = ImageFormat.JPEGMarker.SOS.bytes if data[index] == SOS[0], data[index + 1] == SOS[1] { lastSOSIndex = index count += 1 } index += 1 } // Found more scans this the previous time guard count > scannedCount else { return nil } scannedCount = count // `> 1` checks that we've received a first scan (SOS) and then received // and also received a second scan (SOS). This way we know that we have // at least one full scan available. guard count > 1 && lastSOSIndex > 0 else { return nil } return data[0 ..< lastSOSIndex] } func decode(_ data: Data, with callbacks: [SessionDataTask.TaskCallback], completion: @escaping (KFCrossPlatformImage?) -> Void) { guard data.kf.contains(jpeg: .SOF2) else { CallbackQueue.mainCurrentOrAsync.execute { completion(nil) } return } func processing(_ data: Data) { let processor = ImageDataProcessor( data: data, callbacks: callbacks, processingQueue: processingQueue ) processor.onImageProcessed.delegate(on: self) { (self, result) in guard let image = try? result.0.get() else { CallbackQueue.mainCurrentOrAsync.execute { completion(nil) } return } CallbackQueue.mainCurrentOrAsync.execute { completion(image) } } processor.process() } // Blur partial images. let count = scannedCount if option.isBlur, count < 6 { processingQueue.execute { // Progressively reduce blur as we load more scans. let image = KingfisherWrapper.image( data: data, options: self.creatingOptions ) let radius = max(2, 14 - count * 4) let temp = image?.kf.blurred(withRadius: CGFloat(radius)) processing(temp?.kf.data(format: .JPEG) ?? data) } } else { processing(data) } } } private final class ImageProgressiveSerialQueue { typealias ClosureCallback = ((@escaping () -> Void)) -> Void private let queue: DispatchQueue private var items: [DispatchWorkItem] = [] private var notify: (() -> Void)? private var lastTime: TimeInterval? var count: Int { return items.count } init() { self.queue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageProgressive.SerialQueue") } func add(minimum interval: TimeInterval, closure: @escaping ClosureCallback) { let completion = { [weak self] in guard let self = self else { return } self.queue.async { [weak self] in guard let self = self else { return } guard !self.items.isEmpty else { return } self.items.removeFirst() if let next = self.items.first { self.queue.asyncAfter( deadline: .now() + interval, execute: next ) } else { self.lastTime = Date().timeIntervalSince1970 self.notify?() self.notify = nil } } } queue.async { [weak self] in guard let self = self else { return } let item = DispatchWorkItem { closure(completion) } if self.items.isEmpty { let difference = Date().timeIntervalSince1970 - (self.lastTime ?? 0) let delay = difference < interval ? interval - difference : 0 self.queue.asyncAfter(deadline: .now() + delay, execute: item) } self.items.append(item) } } func notify(_ closure: @escaping () -> Void) { self.notify = closure } func clean() { queue.async { [weak self] in guard let self = self else { return } self.items.forEach { $0.cancel() } self.items.removeAll() } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Image/ImageTransition.swift ================================================ // // ImageTransition.swift // Kingfisher // // Created by Wei Wang on 15/9/18. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation #if os(iOS) || os(tvOS) import UIKit /// Transition effect which will be used when an image downloaded and set by `UIImageView` /// extension API in Kingfisher. You can assign an enum value with transition duration as /// an item in `KingfisherOptionsInfo` to enable the animation transition. /// /// Apple's UIViewAnimationOptions is used under the hood. /// For custom transition, you should specified your own transition options, animations and /// completion handler as well. /// /// - none: No animation transition. /// - fade: Fade in the loaded image in a given duration. /// - flipFromLeft: Flip from left transition. /// - flipFromRight: Flip from right transition. /// - flipFromTop: Flip from top transition. /// - flipFromBottom: Flip from bottom transition. /// - custom: Custom transition. public enum ImageTransition { /// No animation transition. case none /// Fade in the loaded image in a given duration. case fade(TimeInterval) /// Flip from left transition. case flipFromLeft(TimeInterval) /// Flip from right transition. case flipFromRight(TimeInterval) /// Flip from top transition. case flipFromTop(TimeInterval) /// Flip from bottom transition. case flipFromBottom(TimeInterval) /// Custom transition defined by a general animation block. /// - duration: The time duration of this custom transition. /// - options: `UIView.AnimationOptions` should be used in the transition. /// - animations: The animation block will be applied when setting image. /// - completion: A block called when the transition animation finishes. case custom(duration: TimeInterval, options: UIView.AnimationOptions, animations: ((UIImageView, UIImage) -> Void)?, completion: ((Bool) -> Void)?) var duration: TimeInterval { switch self { case .none: return 0 case .fade(let duration): return duration case .flipFromLeft(let duration): return duration case .flipFromRight(let duration): return duration case .flipFromTop(let duration): return duration case .flipFromBottom(let duration): return duration case .custom(let duration, _, _, _): return duration } } var animationOptions: UIView.AnimationOptions { switch self { case .none: return [] case .fade: return .transitionCrossDissolve case .flipFromLeft: return .transitionFlipFromLeft case .flipFromRight: return .transitionFlipFromRight case .flipFromTop: return .transitionFlipFromTop case .flipFromBottom: return .transitionFlipFromBottom case .custom(_, let options, _, _): return options } } var animations: ((UIImageView, UIImage) -> Void)? { switch self { case .custom(_, _, let animations, _): return animations default: return { $0.image = $1 } } } var completion: ((Bool) -> Void)? { switch self { case .custom(_, _, _, let completion): return completion default: return nil } } } #else // Just a placeholder for compiling on macOS. public enum ImageTransition { case none /// This is a placeholder on macOS now. It is for SwiftUI (KFImage) to identify the fade option only. case fade(TimeInterval) } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Image/Placeholder.swift ================================================ // // Placeholder.swift // Kingfisher // // Created by Tieme van Veen on 28/08/2017. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if !os(watchOS) #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit #endif #if canImport(UIKit) import UIKit #endif /// Represents a placeholder type which could be set while loading as well as /// loading finished without getting an image. public protocol Placeholder { /// How the placeholder should be added to a given image view. func add(to imageView: KFCrossPlatformImageView) /// How the placeholder should be removed from a given image view. func remove(from imageView: KFCrossPlatformImageView) } /// Default implementation of an image placeholder. The image will be set or /// reset directly for `image` property of the image view. extension KFCrossPlatformImage: Placeholder { /// How the placeholder should be added to a given image view. public func add(to imageView: KFCrossPlatformImageView) { imageView.image = self } /// How the placeholder should be removed from a given image view. public func remove(from imageView: KFCrossPlatformImageView) { imageView.image = nil } } /// Default implementation of an arbitrary view as placeholder. The view will be /// added as a subview when adding and be removed from its super view when removing. /// /// To use your customize View type as placeholder, simply let it conforming to /// `Placeholder` by `extension MyView: Placeholder {}`. extension Placeholder where Self: KFCrossPlatformView { /// How the placeholder should be added to a given image view. public func add(to imageView: KFCrossPlatformImageView) { imageView.addSubview(self) translatesAutoresizingMaskIntoConstraints = false centerXAnchor.constraint(equalTo: imageView.centerXAnchor).isActive = true centerYAnchor.constraint(equalTo: imageView.centerYAnchor).isActive = true heightAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true widthAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true } /// How the placeholder should be removed from a given image view. public func remove(from imageView: KFCrossPlatformImageView) { removeFromSuperview() } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/AuthenticationChallengeResponsable.swift ================================================ // // AuthenticationChallengeResponsable.swift // Kingfisher // // Created by Wei Wang on 2018/10/11. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Protocol indicates that an authentication challenge could be handled. public protocol AuthenticationChallengeResponsable: AnyObject { /// Called when a session level authentication challenge is received. /// This method provide a chance to handle and response to the authentication /// challenge before downloading could start. /// /// - Parameters: /// - downloader: The downloader which receives this challenge. /// - challenge: An object that contains the request for authentication. /// - completionHandler: A handler that your delegate method must call. /// /// - Note: This method is a forward from `URLSessionDelegate.urlSession(:didReceiveChallenge:completionHandler:)`. /// Please refer to the document of it in `URLSessionDelegate`. func downloader( _ downloader: ImageDownloader, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) /// Called when a task level authentication challenge is received. /// This method provide a chance to handle and response to the authentication /// challenge before downloading could start. /// /// - Parameters: /// - downloader: The downloader which receives this challenge. /// - task: The task whose request requires authentication. /// - challenge: An object that contains the request for authentication. /// - completionHandler: A handler that your delegate method must call. func downloader( _ downloader: ImageDownloader, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) } extension AuthenticationChallengeResponsable { public func downloader( _ downloader: ImageDownloader, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { if let trustedHosts = downloader.trustedHosts, trustedHosts.contains(challenge.protectionSpace.host) { let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) completionHandler(.useCredential, credential) return } } completionHandler(.performDefaultHandling, nil) } public func downloader( _ downloader: ImageDownloader, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { completionHandler(.performDefaultHandling, nil) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/ImageDataProcessor.swift ================================================ // // ImageDataProcessor.swift // Kingfisher // // Created by Wei Wang on 2018/10/11. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation private let sharedProcessingQueue: CallbackQueue = .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process")) // Handles image processing work on an own process queue. class ImageDataProcessor { let data: Data let callbacks: [SessionDataTask.TaskCallback] let queue: CallbackQueue // Note: We have an optimization choice there, to reduce queue dispatch by checking callback // queue settings in each option... let onImageProcessed = Delegate<(Result, SessionDataTask.TaskCallback), Void>() init(data: Data, callbacks: [SessionDataTask.TaskCallback], processingQueue: CallbackQueue?) { self.data = data self.callbacks = callbacks self.queue = processingQueue ?? sharedProcessingQueue } func process() { queue.execute(doProcess) } private func doProcess() { var processedImages = [String: KFCrossPlatformImage]() for callback in callbacks { let processor = callback.options.processor var image = processedImages[processor.identifier] if image == nil { image = processor.process(item: .data(data), options: callback.options) processedImages[processor.identifier] = image } let result: Result if let image = image { let finalImage = callback.options.backgroundDecode ? image.kf.decoded : image result = .success(finalImage) } else { let error = KingfisherError.processorError( reason: .processingFailed(processor: processor, item: .data(data))) result = .failure(error) } onImageProcessed.call((result, callback)) } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/ImageDownloader.swift ================================================ // // ImageDownloader.swift // Kingfisher // // Created by Wei Wang on 15/4/6. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if os(macOS) import AppKit #else import UIKit #endif typealias DownloadResult = Result /// Represents a success result of an image downloading progress. public struct ImageLoadingResult { /// The downloaded image. public let image: KFCrossPlatformImage /// Original URL of the image request. public let url: URL? /// The raw data received from downloader. public let originalData: Data } /// Represents a task of an image downloading process. public struct DownloadTask { /// The `SessionDataTask` object bounded to this download task. Multiple `DownloadTask`s could refer /// to a same `sessionTask`. This is an optimization in Kingfisher to prevent multiple downloading task /// for the same URL resource at the same time. /// /// When you `cancel` a `DownloadTask`, this `SessionDataTask` and its cancel token will be pass through. /// You can use them to identify the cancelled task. public let sessionTask: SessionDataTask /// The cancel token which is used to cancel the task. This is only for identify the task when it is cancelled. /// To cancel a `DownloadTask`, use `cancel` instead. public let cancelToken: SessionDataTask.CancelToken /// Cancel this task if it is running. It will do nothing if this task is not running. /// /// - Note: /// In Kingfisher, there is an optimization to prevent starting another download task if the target URL is being /// downloading. However, even when internally no new session task created, a `DownloadTask` will be still created /// and returned when you call related methods, but it will share the session downloading task with a previous task. /// In this case, if multiple `DownloadTask`s share a single session download task, cancelling a `DownloadTask` /// does not affect other `DownloadTask`s. /// /// If you need to cancel all `DownloadTask`s of a url, use `ImageDownloader.cancel(url:)`. If you need to cancel /// all downloading tasks of an `ImageDownloader`, use `ImageDownloader.cancelAll()`. public func cancel() { sessionTask.cancel(token: cancelToken) } } extension DownloadTask { enum WrappedTask { case download(DownloadTask) case dataProviding func cancel() { switch self { case .download(let task): task.cancel() case .dataProviding: break } } var value: DownloadTask? { switch self { case .download(let task): return task case .dataProviding: return nil } } } } /// Represents a downloading manager for requesting the image with a URL from server. open class ImageDownloader { // MARK: Singleton /// The default downloader. public static let `default` = ImageDownloader(name: "default") // MARK: Public Properties /// The duration before the downloading is timeout. Default is 15 seconds. open var downloadTimeout: TimeInterval = 15.0 /// A set of trusted hosts when receiving server trust challenges. A challenge with host name contained in this /// set will be ignored. You can use this set to specify the self-signed site. It only will be used if you don't /// specify the `authenticationChallengeResponder`. /// /// If `authenticationChallengeResponder` is set, this property will be ignored and the implementation of /// `authenticationChallengeResponder` will be used instead. open var trustedHosts: Set? /// Use this to set supply a configuration for the downloader. By default, /// NSURLSessionConfiguration.ephemeralSessionConfiguration() will be used. /// /// You could change the configuration before a downloading task starts. /// A configuration without persistent storage for caches is requested for downloader working correctly. open var sessionConfiguration = URLSessionConfiguration.ephemeral { didSet { session.invalidateAndCancel() session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) } } open var sessionDelegate: SessionDelegate { didSet { session.invalidateAndCancel() session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) setupSessionHandler() } } /// Whether the download requests should use pipeline or not. Default is false. open var requestsUsePipelining = false /// Delegate of this `ImageDownloader` object. See `ImageDownloaderDelegate` protocol for more. open weak var delegate: ImageDownloaderDelegate? /// A responder for authentication challenge. /// Downloader will forward the received authentication challenge for the downloading session to this responder. open weak var authenticationChallengeResponder: AuthenticationChallengeResponsable? private let name: String private var session: URLSession // MARK: Initializers /// Creates a downloader with name. /// /// - Parameter name: The name for the downloader. It should not be empty. public init(name: String) { if name.isEmpty { fatalError("[Kingfisher] You should specify a name for the downloader. " + "A downloader with empty name is not permitted.") } self.name = name sessionDelegate = SessionDelegate() session = URLSession( configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil) authenticationChallengeResponder = self setupSessionHandler() } deinit { session.invalidateAndCancel() } private func setupSessionHandler() { sessionDelegate.onReceiveSessionChallenge.delegate(on: self) { (self, invoke) in self.authenticationChallengeResponder?.downloader(self, didReceive: invoke.1, completionHandler: invoke.2) } sessionDelegate.onReceiveSessionTaskChallenge.delegate(on: self) { (self, invoke) in self.authenticationChallengeResponder?.downloader( self, task: invoke.1, didReceive: invoke.2, completionHandler: invoke.3) } sessionDelegate.onValidStatusCode.delegate(on: self) { (self, code) in return (self.delegate ?? self).isValidStatusCode(code, for: self) } sessionDelegate.onDownloadingFinished.delegate(on: self) { (self, value) in let (url, result) = value do { let value = try result.get() self.delegate?.imageDownloader(self, didFinishDownloadingImageForURL: url, with: value, error: nil) } catch { self.delegate?.imageDownloader(self, didFinishDownloadingImageForURL: url, with: nil, error: error) } } sessionDelegate.onDidDownloadData.delegate(on: self) { (self, task) in return (self.delegate ?? self).imageDownloader(self, didDownload: task.mutableData, with: task) } } // Wraps `completionHandler` to `onCompleted` respectively. private func createCompletionCallBack(_ completionHandler: ((DownloadResult) -> Void)?) -> Delegate? { return completionHandler.map { block -> Delegate in let delegate = Delegate, Void>() delegate.delegate(on: self) { (self, callback) in block(callback) } return delegate } } private func createTaskCallback( _ completionHandler: ((DownloadResult) -> Void)?, options: KingfisherParsedOptionsInfo ) -> SessionDataTask.TaskCallback { return SessionDataTask.TaskCallback( onCompleted: createCompletionCallBack(completionHandler), options: options ) } private func createDownloadContext( with url: URL, options: KingfisherParsedOptionsInfo, done: @escaping ((Result) -> Void) ) { func checkRequestAndDone(r: URLRequest) { // There is a possibility that request modifier changed the url to `nil` or empty. // In this case, throw an error. guard let url = r.url, !url.absoluteString.isEmpty else { done(.failure(KingfisherError.requestError(reason: .invalidURL(request: r)))) return } done(.success(DownloadingContext(url: url, request: r, options: options))) } // Creates default request. var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout) request.httpShouldUsePipelining = requestsUsePipelining if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) , options.lowDataModeSource != nil { request.allowsConstrainedNetworkAccess = false } if let requestModifier = options.requestModifier { // Modifies request before sending. requestModifier.modified(for: request) { result in guard let finalRequest = result else { done(.failure(KingfisherError.requestError(reason: .emptyRequest))) return } checkRequestAndDone(r: finalRequest) } } else { checkRequestAndDone(r: request) } } private func addDownloadTask( context: DownloadingContext, callback: SessionDataTask.TaskCallback ) -> DownloadTask { // Ready to start download. Add it to session task manager (`sessionHandler`) let downloadTask: DownloadTask if let existingTask = sessionDelegate.task(for: context.url) { downloadTask = sessionDelegate.append(existingTask, url: context.url, callback: callback) } else { let sessionDataTask = session.dataTask(with: context.request) sessionDataTask.priority = context.options.downloadPriority downloadTask = sessionDelegate.add(sessionDataTask, url: context.url, callback: callback) } return downloadTask } private func reportWillDownloadImage(url: URL, request: URLRequest) { delegate?.imageDownloader(self, willDownloadImageForURL: url, with: request) } private func reportDidDownloadImageData(result: Result<(Data, URLResponse?), KingfisherError>, url: URL) { var response: URLResponse? var err: Error? do { response = try result.get().1 } catch { err = error } self.delegate?.imageDownloader( self, didFinishDownloadingImageForURL: url, with: response, error: err ) } private func reportDidProcessImage( result: Result, url: URL, response: URLResponse? ) { if let image = try? result.get() { self.delegate?.imageDownloader(self, didDownload: image, for: url, with: response) } } private func startDownloadTask( context: DownloadingContext, callback: SessionDataTask.TaskCallback ) -> DownloadTask { let downloadTask = addDownloadTask(context: context, callback: callback) let sessionTask = downloadTask.sessionTask guard !sessionTask.started else { return downloadTask } sessionTask.onTaskDone.delegate(on: self) { (self, done) in // Underlying downloading finishes. // result: Result<(Data, URLResponse?)>, callbacks: [TaskCallback] let (result, callbacks) = done // Before processing the downloaded data. self.reportDidDownloadImageData(result: result, url: context.url) switch result { // Download finished. Now process the data to an image. case .success(let (data, response)): let processor = ImageDataProcessor( data: data, callbacks: callbacks, processingQueue: context.options.processingQueue ) processor.onImageProcessed.delegate(on: self) { (self, done) in // `onImageProcessed` will be called for `callbacks.count` times, with each // `SessionDataTask.TaskCallback` as the input parameter. // result: Result, callback: SessionDataTask.TaskCallback let (result, callback) = done self.reportDidProcessImage(result: result, url: context.url, response: response) let imageResult = result.map { ImageLoadingResult(image: $0, url: context.url, originalData: data) } let queue = callback.options.callbackQueue queue.execute { callback.onCompleted?.call(imageResult) } } processor.process() case .failure(let error): callbacks.forEach { callback in let queue = callback.options.callbackQueue queue.execute { callback.onCompleted?.call(.failure(error)) } } } } reportWillDownloadImage(url: context.url, request: context.request) sessionTask.resume() return downloadTask } // MARK: Downloading Task /// Downloads an image with a URL and option. Invoked internally by Kingfisher. Subclasses must invoke super. /// /// - Parameters: /// - url: Target URL. /// - options: The options could control download behavior. See `KingfisherOptionsInfo`. /// - completionHandler: Called when the download progress finishes. This block will be called in the queue /// defined in `.callbackQueue` in `options` parameter. /// - Returns: A downloading task. You could call `cancel` on it to stop the download task. @discardableResult open func downloadImage( with url: URL, options: KingfisherParsedOptionsInfo, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { var downloadTask: DownloadTask? createDownloadContext(with: url, options: options) { result in switch result { case .success(let context): // `downloadTask` will be set if the downloading started immediately. This is the case when no request // modifier or a sync modifier (`ImageDownloadRequestModifier`) is used. Otherwise, when an // `AsyncImageDownloadRequestModifier` is used the returned `downloadTask` of this method will be `nil` // and the actual "delayed" task is given in `AsyncImageDownloadRequestModifier.onDownloadTaskStarted` // callback. downloadTask = self.startDownloadTask( context: context, callback: self.createTaskCallback(completionHandler, options: options) ) if let modifier = options.requestModifier { modifier.onDownloadTaskStarted?(downloadTask) } case .failure(let error): options.callbackQueue.execute { completionHandler?(.failure(error)) } } } return downloadTask } /// Downloads an image with a URL and option. /// /// - Parameters: /// - url: Target URL. /// - options: The options could control download behavior. See `KingfisherOptionsInfo`. /// - progressBlock: Called when the download progress updated. This block will be always be called in main queue. /// - completionHandler: Called when the download progress finishes. This block will be called in the queue /// defined in `.callbackQueue` in `options` parameter. /// - Returns: A downloading task. You could call `cancel` on it to stop the download task. @discardableResult open func downloadImage( with url: URL, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { var info = KingfisherParsedOptionsInfo(options) if let block = progressBlock { info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)] } return downloadImage( with: url, options: info, completionHandler: completionHandler) } /// Downloads an image with a URL and option. /// /// - Parameters: /// - url: Target URL. /// - options: The options could control download behavior. See `KingfisherOptionsInfo`. /// - completionHandler: Called when the download progress finishes. This block will be called in the queue /// defined in `.callbackQueue` in `options` parameter. /// - Returns: A downloading task. You could call `cancel` on it to stop the download task. @discardableResult open func downloadImage( with url: URL, options: KingfisherOptionsInfo? = nil, completionHandler: ((Result) -> Void)? = nil) -> DownloadTask? { downloadImage( with: url, options: KingfisherParsedOptionsInfo(options), completionHandler: completionHandler ) } } // MARK: Cancelling Task extension ImageDownloader { /// Cancel all downloading tasks for this `ImageDownloader`. It will trigger the completion handlers /// for all not-yet-finished downloading tasks. /// /// If you need to only cancel a certain task, call `cancel()` on the `DownloadTask` /// returned by the downloading methods. If you need to cancel all `DownloadTask`s of a certain url, /// use `ImageDownloader.cancel(url:)`. public func cancelAll() { sessionDelegate.cancelAll() } /// Cancel all downloading tasks for a given URL. It will trigger the completion handlers for /// all not-yet-finished downloading tasks for the URL. /// /// - Parameter url: The URL which you want to cancel downloading. public func cancel(url: URL) { sessionDelegate.cancel(url: url) } } // Use the default implementation from extension of `AuthenticationChallengeResponsable`. extension ImageDownloader: AuthenticationChallengeResponsable {} // Use the default implementation from extension of `ImageDownloaderDelegate`. extension ImageDownloader: ImageDownloaderDelegate {} extension ImageDownloader { struct DownloadingContext { let url: URL let request: URLRequest let options: KingfisherParsedOptionsInfo } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/ImageDownloaderDelegate.swift ================================================ // // ImageDownloaderDelegate.swift // Kingfisher // // Created by Wei Wang on 2018/10/11. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Protocol of `ImageDownloader`. This protocol provides a set of methods which are related to image downloader /// working stages and rules. public protocol ImageDownloaderDelegate: AnyObject { /// Called when the `ImageDownloader` object will start downloading an image from a specified URL. /// /// - Parameters: /// - downloader: The `ImageDownloader` object which is used for the downloading operation. /// - url: URL of the starting request. /// - request: The request object for the download process. /// func imageDownloader(_ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?) /// Called when the `ImageDownloader` completes a downloading request with success or failure. /// /// - Parameters: /// - downloader: The `ImageDownloader` object which is used for the downloading operation. /// - url: URL of the original request URL. /// - response: The response object of the downloading process. /// - error: The error in case of failure. /// func imageDownloader( _ downloader: ImageDownloader, didFinishDownloadingImageForURL url: URL, with response: URLResponse?, error: Error?) /// Called when the `ImageDownloader` object successfully downloaded image data from specified URL. This is /// your last chance to verify or modify the downloaded data before Kingfisher tries to perform addition /// processing on the image data. /// /// - Parameters: /// - downloader: The `ImageDownloader` object which is used for the downloading operation. /// - data: The original downloaded data. /// - dataTask: The data task contains request and response information of the download. /// - Note: /// This can be used to pre-process raw image data before creation of `Image` instance (i.e. /// decrypting or verification). If `nil` returned, the processing is interrupted and a `KingfisherError` with /// `ResponseErrorReason.dataModifyingFailed` will be raised. You could use this fact to stop the image /// processing flow if you find the data is corrupted or malformed. /// /// If this method is implemented, `imageDownloader(_:didDownload:for:)` will not be called anymore. func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, with dataTask: SessionDataTask) -> Data? /// Called when the `ImageDownloader` object successfully downloaded image data from specified URL. This is /// your last chance to verify or modify the downloaded data before Kingfisher tries to perform addition /// processing on the image data. /// /// - Parameters: /// - downloader: The `ImageDownloader` object which is used for the downloading operation. /// - data: The original downloaded data. /// - url: The URL of the original request URL. /// - Returns: The data from which Kingfisher should use to create an image. You need to provide valid data /// which content is one of the supported image file format. Kingfisher will perform process on this /// data and try to convert it to an image object. /// - Note: /// This can be used to pre-process raw image data before creation of `Image` instance (i.e. /// decrypting or verification). If `nil` returned, the processing is interrupted and a `KingfisherError` with /// `ResponseErrorReason.dataModifyingFailed` will be raised. You could use this fact to stop the image /// processing flow if you find the data is corrupted or malformed. /// /// If `imageDownloader(_:didDownload:with:)` is implemented, this method will not be called anymore. func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data? /// Called when the `ImageDownloader` object successfully downloads and processes an image from specified URL. /// /// - Parameters: /// - downloader: The `ImageDownloader` object which is used for the downloading operation. /// - image: The downloaded and processed image. /// - url: URL of the original request URL. /// - response: The original response object of the downloading process. /// func imageDownloader( _ downloader: ImageDownloader, didDownload image: KFCrossPlatformImage, for url: URL, with response: URLResponse?) /// Checks if a received HTTP status code is valid or not. /// By default, a status code in range 200..<400 is considered as valid. /// If an invalid code is received, the downloader will raise an `KingfisherError` with /// `ResponseErrorReason.invalidHTTPStatusCode` as its reason. /// /// - Parameters: /// - code: The received HTTP status code. /// - downloader: The `ImageDownloader` object asks for validate status code. /// - Returns: Returns a value to indicate whether this HTTP status code is valid or not. /// - Note: If the default 200 to 400 valid code does not suit your need, /// you can implement this method to change that behavior. func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool } // Default implementation for `ImageDownloaderDelegate`. extension ImageDownloaderDelegate { public func imageDownloader( _ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?) {} public func imageDownloader( _ downloader: ImageDownloader, didFinishDownloadingImageForURL url: URL, with response: URLResponse?, error: Error?) {} public func imageDownloader( _ downloader: ImageDownloader, didDownload image: KFCrossPlatformImage, for url: URL, with response: URLResponse?) {} public func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool { return (200..<400).contains(code) } public func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, with task: SessionDataTask) -> Data? { guard let url = task.originalURL else { return data } return imageDownloader(downloader, didDownload: data, for: url) } public func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data? { return data } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/ImageModifier.swift ================================================ // // ImageModifier.swift // Kingfisher // // Created by Ethan Gill on 2017/11/28. // // Copyright (c) 2019 Ethan Gill // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// An `ImageModifier` can be used to change properties on an image between cache serialization and the actual use of /// the image. The `modify(_:)` method will be called after the image retrieved from its source and before it returned /// to the caller. This modified image is expected to be only used for rendering purpose, any changes applied by the /// `ImageModifier` will not be serialized or cached. public protocol ImageModifier { /// Modify an input `Image`. /// /// - parameter image: Image which will be modified by `self` /// /// - returns: The modified image. /// /// - Note: The return value will be unmodified if modifying is not possible on /// the current platform. /// - Note: Most modifiers support UIImage or NSImage, but not CGImage. func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage } /// A wrapper for creating an `ImageModifier` easier. /// This type conforms to `ImageModifier` and wraps an image modify block. /// If the `block` throws an error, the original image will be used. public struct AnyImageModifier: ImageModifier { /// A block which modifies images, or returns the original image /// if modification cannot be performed with an error. let block: (KFCrossPlatformImage) throws -> KFCrossPlatformImage /// Creates an `AnyImageModifier` with a given `modify` block. public init(modify: @escaping (KFCrossPlatformImage) throws -> KFCrossPlatformImage) { block = modify } /// Modify an input `Image`. See `ImageModifier` protocol for more. public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { return (try? block(image)) ?? image } } #if os(iOS) || os(tvOS) || os(watchOS) import UIKit /// Modifier for setting the rendering mode of images. public struct RenderingModeImageModifier: ImageModifier { /// The rendering mode to apply to the image. public let renderingMode: UIImage.RenderingMode /// Creates a `RenderingModeImageModifier`. /// /// - Parameter renderingMode: The rendering mode to apply to the image. Default is `.automatic`. public init(renderingMode: UIImage.RenderingMode = .automatic) { self.renderingMode = renderingMode } /// Modify an input `Image`. See `ImageModifier` protocol for more. public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { return image.withRenderingMode(renderingMode) } } /// Modifier for setting the `flipsForRightToLeftLayoutDirection` property of images. public struct FlipsForRightToLeftLayoutDirectionImageModifier: ImageModifier { /// Creates a `FlipsForRightToLeftLayoutDirectionImageModifier`. public init() {} /// Modify an input `Image`. See `ImageModifier` protocol for more. public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { return image.imageFlippedForRightToLeftLayoutDirection() } } /// Modifier for setting the `alignmentRectInsets` property of images. public struct AlignmentRectInsetsImageModifier: ImageModifier { /// The alignment insets to apply to the image public let alignmentInsets: UIEdgeInsets /// Creates an `AlignmentRectInsetsImageModifier`. public init(alignmentInsets: UIEdgeInsets) { self.alignmentInsets = alignmentInsets } /// Modify an input `Image`. See `ImageModifier` protocol for more. public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { return image.withAlignmentRectInsets(alignmentInsets) } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/ImagePrefetcher.swift ================================================ // // ImagePrefetcher.swift // Kingfisher // // Created by Claire Knight on 24/02/2016 // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if os(macOS) import AppKit #else import UIKit #endif /// Progress update block of prefetcher when initialized with a list of resources. /// /// - `skippedResources`: An array of resources that are already cached before the prefetching starting. /// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while /// downloading, encountered an error when downloading or the download not being started at all. /// - `completedResources`: An array of resources that are downloaded and cached successfully. public typealias PrefetcherProgressBlock = ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void) /// Progress update block of prefetcher when initialized with a list of resources. /// /// - `skippedSources`: An array of sources that are already cached before the prefetching starting. /// - `failedSources`: An array of sources that fail to be fetched. /// - `completedResources`: An array of sources that are fetched and cached successfully. public typealias PrefetcherSourceProgressBlock = ((_ skippedSources: [Source], _ failedSources: [Source], _ completedSources: [Source]) -> Void) /// Completion block of prefetcher when initialized with a list of sources. /// /// - `skippedResources`: An array of resources that are already cached before the prefetching starting. /// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while /// downloading, encountered an error when downloading or the download not being started at all. /// - `completedResources`: An array of resources that are downloaded and cached successfully. public typealias PrefetcherCompletionHandler = ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void) /// Completion block of prefetcher when initialized with a list of sources. /// /// - `skippedSources`: An array of sources that are already cached before the prefetching starting. /// - `failedSources`: An array of sources that fail to be fetched. /// - `completedSources`: An array of sources that are fetched and cached successfully. public typealias PrefetcherSourceCompletionHandler = ((_ skippedSources: [Source], _ failedSources: [Source], _ completedSources: [Source]) -> Void) /// `ImagePrefetcher` represents a downloading manager for requesting many images via URLs, then caching them. /// This is useful when you know a list of image resources and want to download them before showing. It also works with /// some Cocoa prefetching mechanism like table view or collection view `prefetchDataSource`, to start image downloading /// and caching before they display on screen. public class ImagePrefetcher: CustomStringConvertible { public var description: String { return "\(Unmanaged.passUnretained(self).toOpaque())" } /// The maximum concurrent downloads to use when prefetching images. Default is 5. public var maxConcurrentDownloads = 5 private let prefetchSources: [Source] private let optionsInfo: KingfisherParsedOptionsInfo private var progressBlock: PrefetcherProgressBlock? private var completionHandler: PrefetcherCompletionHandler? private var progressSourceBlock: PrefetcherSourceProgressBlock? private var completionSourceHandler: PrefetcherSourceCompletionHandler? private var tasks = [String: DownloadTask.WrappedTask]() private var pendingSources: ArraySlice private var skippedSources = [Source]() private var completedSources = [Source]() private var failedSources = [Source]() private var stopped = false // A manager used for prefetching. We will use the helper methods in manager. private let manager: KingfisherManager private let pretchQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImagePrefetcher.pretchQueue") private static let requestingQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImagePrefetcher.requestingQueue") private var finished: Bool { let totalFinished: Int = failedSources.count + skippedSources.count + completedSources.count return totalFinished == prefetchSources.count && tasks.isEmpty } /// Creates an image prefetcher with an array of URLs. /// /// The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable. /// After you get a valid `ImagePrefetcher` object, you call `start()` on it to begin the prefetching process. /// The images which are already cached will be skipped without downloading again. /// /// - Parameters: /// - urls: The URLs which should be prefetched. /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called every time an resource is downloaded, skipped or cancelled. /// - completionHandler: Called when the whole prefetching process finished. /// /// - Note: /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as /// the downloader and cache target respectively. You can specify another downloader or cache by using /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. public convenience init( urls: [URL], options: KingfisherOptionsInfo? = nil, progressBlock: PrefetcherProgressBlock? = nil, completionHandler: PrefetcherCompletionHandler? = nil) { let resources: [Resource] = urls.map { $0 } self.init( resources: resources, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } /// Creates an image prefetcher with an array of URLs. /// /// The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable. /// After you get a valid `ImagePrefetcher` object, you call `start()` on it to begin the prefetching process. /// The images which are already cached will be skipped without downloading again. /// /// - Parameters: /// - urls: The URLs which should be prefetched. /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. /// - completionHandler: Called when the whole prefetching process finished. /// /// - Note: /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as /// the downloader and cache target respectively. You can specify another downloader or cache by using /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. public convenience init( urls: [URL], options: KingfisherOptionsInfo? = nil, completionHandler: PrefetcherCompletionHandler? = nil) { let resources: [Resource] = urls.map { $0 } self.init( resources: resources, options: options, progressBlock: nil, completionHandler: completionHandler) } /// Creates an image prefetcher with an array of resources. /// /// - Parameters: /// - resources: The resources which should be prefetched. See `Resource` type for more. /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called every time an resource is downloaded, skipped or cancelled. /// - completionHandler: Called when the whole prefetching process finished. /// /// - Note: /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as /// the downloader and cache target respectively. You can specify another downloader or cache by using /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. public convenience init( resources: [Resource], options: KingfisherOptionsInfo? = nil, progressBlock: PrefetcherProgressBlock? = nil, completionHandler: PrefetcherCompletionHandler? = nil) { self.init(sources: resources.map { $0.convertToSource() }, options: options) self.progressBlock = progressBlock self.completionHandler = completionHandler } /// Creates an image prefetcher with an array of resources. /// /// - Parameters: /// - resources: The resources which should be prefetched. See `Resource` type for more. /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. /// - completionHandler: Called when the whole prefetching process finished. /// /// - Note: /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as /// the downloader and cache target respectively. You can specify another downloader or cache by using /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. public convenience init( resources: [Resource], options: KingfisherOptionsInfo? = nil, completionHandler: PrefetcherCompletionHandler? = nil) { self.init(sources: resources.map { $0.convertToSource() }, options: options) self.completionHandler = completionHandler } /// Creates an image prefetcher with an array of sources. /// /// - Parameters: /// - sources: The sources which should be prefetched. See `Source` type for more. /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. /// - progressBlock: Called every time an source fetching successes, fails, is skipped. /// - completionHandler: Called when the whole prefetching process finished. /// /// - Note: /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as /// the downloader and cache target respectively. You can specify another downloader or cache by using /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. public convenience init(sources: [Source], options: KingfisherOptionsInfo? = nil, progressBlock: PrefetcherSourceProgressBlock? = nil, completionHandler: PrefetcherSourceCompletionHandler? = nil) { self.init(sources: sources, options: options) self.progressSourceBlock = progressBlock self.completionSourceHandler = completionHandler } /// Creates an image prefetcher with an array of sources. /// /// - Parameters: /// - sources: The sources which should be prefetched. See `Source` type for more. /// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more. /// - completionHandler: Called when the whole prefetching process finished. /// /// - Note: /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as /// the downloader and cache target respectively. You can specify another downloader or cache by using /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method. public convenience init(sources: [Source], options: KingfisherOptionsInfo? = nil, completionHandler: PrefetcherSourceCompletionHandler? = nil) { self.init(sources: sources, options: options) self.completionSourceHandler = completionHandler } init(sources: [Source], options: KingfisherOptionsInfo?) { var options = KingfisherParsedOptionsInfo(options) prefetchSources = sources pendingSources = ArraySlice(sources) // We want all callbacks from our prefetch queue, so we should ignore the callback queue in options. // Add our own callback dispatch queue to make sure all internal callbacks are // coming back in our expected queue. options.callbackQueue = .dispatch(pretchQueue) optionsInfo = options let cache = optionsInfo.targetCache ?? .default let downloader = optionsInfo.downloader ?? .default manager = KingfisherManager(downloader: downloader, cache: cache) } /// Starts to download the resources and cache them. This can be useful for background downloading /// of assets that are required for later use in an app. This code will not try and update any UI /// with the results of the process. public func start() { pretchQueue.async { guard !self.stopped else { assertionFailure("You can not restart the same prefetcher. Try to create a new prefetcher.") self.handleComplete() return } guard self.maxConcurrentDownloads > 0 else { assertionFailure("There should be concurrent downloads value should be at least 1.") self.handleComplete() return } // Empty case. guard self.prefetchSources.count > 0 else { self.handleComplete() return } let initialConcurrentDownloads = min(self.prefetchSources.count, self.maxConcurrentDownloads) for _ in 0 ..< initialConcurrentDownloads { if let resource = self.pendingSources.popFirst() { self.startPrefetching(resource) } } } } /// Stops current downloading progress, and cancel any future prefetching activity that might be occuring. public func stop() { pretchQueue.async { if self.finished { return } self.stopped = true self.tasks.values.forEach { $0.cancel() } } } private func downloadAndCache(_ source: Source) { let downloadTaskCompletionHandler: ((Result) -> Void) = { result in self.tasks.removeValue(forKey: source.cacheKey) do { let _ = try result.get() self.completedSources.append(source) } catch { self.failedSources.append(source) } self.reportProgress() if self.stopped { if self.tasks.isEmpty { self.failedSources.append(contentsOf: self.pendingSources) self.handleComplete() } } else { self.reportCompletionOrStartNext() } } var downloadTask: DownloadTask.WrappedTask? ImagePrefetcher.requestingQueue.sync { let context = RetrievingContext( options: optionsInfo, originalSource: source ) downloadTask = manager.loadAndCacheImage( source: source, context: context, completionHandler: downloadTaskCompletionHandler) } if let downloadTask = downloadTask { tasks[source.cacheKey] = downloadTask } } private func append(cached source: Source) { skippedSources.append(source) reportProgress() reportCompletionOrStartNext() } private func startPrefetching(_ source: Source) { if optionsInfo.forceRefresh { downloadAndCache(source) return } let cacheType = manager.cache.imageCachedType( forKey: source.cacheKey, processorIdentifier: optionsInfo.processor.identifier) switch cacheType { case .memory: append(cached: source) case .disk: if optionsInfo.alsoPrefetchToMemory { let context = RetrievingContext(options: optionsInfo, originalSource: source) _ = manager.retrieveImageFromCache( source: source, context: context) { _ in self.append(cached: source) } } else { append(cached: source) } case .none: downloadAndCache(source) } } private func reportProgress() { if progressBlock == nil && progressSourceBlock == nil { return } let skipped = self.skippedSources let failed = self.failedSources let completed = self.completedSources CallbackQueue.mainCurrentOrAsync.execute { self.progressSourceBlock?(skipped, failed, completed) self.progressBlock?( skipped.compactMap { $0.asResource }, failed.compactMap { $0.asResource }, completed.compactMap { $0.asResource } ) } } private func reportCompletionOrStartNext() { if let resource = self.pendingSources.popFirst() { // Loose call stack for huge ammount of sources. pretchQueue.async { self.startPrefetching(resource) } } else { guard allFinished else { return } self.handleComplete() } } var allFinished: Bool { return skippedSources.count + failedSources.count + completedSources.count == prefetchSources.count } private func handleComplete() { if completionHandler == nil && completionSourceHandler == nil { return } // The completion handler should be called on the main thread CallbackQueue.mainCurrentOrAsync.execute { self.completionSourceHandler?(self.skippedSources, self.failedSources, self.completedSources) self.completionHandler?( self.skippedSources.compactMap { $0.asResource }, self.failedSources.compactMap { $0.asResource }, self.completedSources.compactMap { $0.asResource } ) self.completionHandler = nil self.progressBlock = nil } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/RedirectHandler.swift ================================================ // // RedirectHandler.swift // Kingfisher // // Created by Roman Maidanovych on 2018/12/10. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents and wraps a method for modifying request during an image download request redirection. public protocol ImageDownloadRedirectHandler { /// The `ImageDownloadRedirectHandler` contained will be used to change the request before redirection. /// This is the posibility you can modify the image download request during redirection. You can modify the /// request for some customizing purpose, such as adding auth token to the header, do basic HTTP auth or /// something like url mapping. /// /// Usually, you pass an `ImageDownloadRedirectHandler` as the associated value of /// `KingfisherOptionsInfoItem.redirectHandler` and use it as the `options` parameter in related methods. /// /// If you do nothing with the input `request` and return it as is, a downloading process will redirect with it. /// /// - Parameters: /// - task: The current `SessionDataTask` which triggers this redirect. /// - response: The response received during redirection. /// - newRequest: The request for redirection which can be modified. /// - completionHandler: A closure for being called with modified request. func handleHTTPRedirection( for task: SessionDataTask, response: HTTPURLResponse, newRequest: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) } /// A wrapper for creating an `ImageDownloadRedirectHandler` easier. /// This type conforms to `ImageDownloadRedirectHandler` and wraps a redirect request modify block. public struct AnyRedirectHandler: ImageDownloadRedirectHandler { let block: (SessionDataTask, HTTPURLResponse, URLRequest, @escaping (URLRequest?) -> Void) -> Void public func handleHTTPRedirection( for task: SessionDataTask, response: HTTPURLResponse, newRequest: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { block(task, response, newRequest, completionHandler) } /// Creates a value of `ImageDownloadRedirectHandler` which runs `modify` block. /// /// - Parameter modify: The request modifying block runs when a request modifying task comes. /// public init(handle: @escaping (SessionDataTask, HTTPURLResponse, URLRequest, @escaping (URLRequest?) -> Void) -> Void) { block = handle } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/RequestModifier.swift ================================================ // // RequestModifier.swift // Kingfisher // // Created by Wei Wang on 2016/09/05. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents and wraps a method for modifying request before an image download request starts in an asynchronous way. public protocol AsyncImageDownloadRequestModifier { /// This method will be called just before the `request` being sent. /// This is the last chance you can modify the image download request. You can modify the request for some /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping. /// When you have done with the modification, call the `reportModified` block with the modified request and the data /// download will happen with this request. /// /// Usually, you pass an `AsyncImageDownloadRequestModifier` as the associated value of /// `KingfisherOptionsInfoItem.requestModifier` and use it as the `options` parameter in related methods. /// /// If you do nothing with the input `request` and return it as is, a downloading process will start with it. /// /// - Parameters: /// - request: The input request contains necessary information like `url`. This request is generated /// according to your resource url as a GET request. /// - reportModified: The callback block you need to call after the asynchronous modifying done. /// func modified(for request: URLRequest, reportModified: @escaping (URLRequest?) -> Void) /// A block will be called when the download task started. /// /// If an `AsyncImageDownloadRequestModifier` and the asynchronous modification happens before the download, the /// related download method will not return a valid `DownloadTask` value. Instead, you can get one from this method. var onDownloadTaskStarted: ((DownloadTask?) -> Void)? { get } } /// Represents and wraps a method for modifying request before an image download request starts. public protocol ImageDownloadRequestModifier: AsyncImageDownloadRequestModifier { /// This method will be called just before the `request` being sent. /// This is the last chance you can modify the image download request. You can modify the request for some /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping. /// /// Usually, you pass an `ImageDownloadRequestModifier` as the associated value of /// `KingfisherOptionsInfoItem.requestModifier` and use it as the `options` parameter in related methods. /// /// If you do nothing with the input `request` and return it as is, a downloading process will start with it. /// /// - Parameter request: The input request contains necessary information like `url`. This request is generated /// according to your resource url as a GET request. /// - Returns: A modified version of request, which you wish to use for downloading an image. If `nil` returned, /// a `KingfisherError.requestError` with `.emptyRequest` as its reason will occur. /// func modified(for request: URLRequest) -> URLRequest? } extension ImageDownloadRequestModifier { public func modified(for request: URLRequest, reportModified: @escaping (URLRequest?) -> Void) { let request = modified(for: request) reportModified(request) } /// This is `nil` for a sync `ImageDownloadRequestModifier` by default. You can get the `DownloadTask` from the /// return value of downloader method. public var onDownloadTaskStarted: ((DownloadTask?) -> Void)? { return nil } } /// A wrapper for creating an `ImageDownloadRequestModifier` easier. /// This type conforms to `ImageDownloadRequestModifier` and wraps an image modify block. public struct AnyModifier: ImageDownloadRequestModifier { let block: (URLRequest) -> URLRequest? /// For `ImageDownloadRequestModifier` conformation. public func modified(for request: URLRequest) -> URLRequest? { return block(request) } /// Creates a value of `ImageDownloadRequestModifier` which runs `modify` block. /// /// - Parameter modify: The request modifying block runs when a request modifying task comes. /// The return `URLRequest?` value of this block will be used as the image download request. /// If `nil` returned, a `KingfisherError.requestError` with `.emptyRequest` as its /// reason will occur. public init(modify: @escaping (URLRequest) -> URLRequest?) { block = modify } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/RetryStrategy.swift ================================================ // // RetryStrategy.swift // Kingfisher // // Created by onevcat on 2020/05/04. // // Copyright (c) 2020 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents a retry context which could be used to determine the current retry status. public class RetryContext { /// The source from which the target image should be retrieved. public let source: Source /// The last error which caused current retry behavior. public let error: KingfisherError /// The retried count before current retry happens. This value is `0` if the current retry is for the first time. public var retriedCount: Int /// A user set value for passing any other information during the retry. If you choose to use `RetryDecision.retry` /// as the retry decision for `RetryStrategy.retry(context:retryHandler:)`, the associated value of /// `RetryDecision.retry` will be delivered to you in the next retry. public internal(set) var userInfo: Any? = nil init(source: Source, error: KingfisherError) { self.source = source self.error = error self.retriedCount = 0 } @discardableResult func increaseRetryCount() -> RetryContext { retriedCount += 1 return self } } /// Represents decision of behavior on the current retry. public enum RetryDecision { /// A retry should happen. The associated `userInfo` will be pass to the next retry in the `RetryContext` parameter. case retry(userInfo: Any?) /// There should be no more retry attempt. The image retrieving process will fail with an error. case stop } /// Defines a retry strategy can be applied to a `.retryStrategy` option. public protocol RetryStrategy { /// Kingfisher calls this method if an error happens during the image retrieving process from a `KingfisherManager`. /// You implement this method to provide necessary logic based on the `context` parameter. Then you need to call /// `retryHandler` to pass the retry decision back to Kingfisher. /// /// - Parameters: /// - context: The retry context containing information of current retry attempt. /// - retryHandler: A block you need to call with a decision of whether the retry should happen or not. func retry(context: RetryContext, retryHandler: @escaping (RetryDecision) -> Void) } /// A retry strategy that guides Kingfisher to retry when a `.responseError` happens, with a specified max retry count /// and a certain interval mechanism. public struct DelayRetryStrategy: RetryStrategy { /// Represents the interval mechanism which used in a `DelayRetryStrategy`. public enum Interval { /// The next retry attempt should happen in fixed seconds. For example, if the associated value is 3, the /// attempts happens after 3 seconds after the previous decision is made. case seconds(TimeInterval) /// The next retry attempt should happen in an accumulated duration. For example, if the associated value is 3, /// the attempts happens with interval of 3, 6, 9, 12, ... seconds. case accumulated(TimeInterval) /// Uses a block to determine the next interval. The current retry count is given as a parameter. case custom(block: (_ retriedCount: Int) -> TimeInterval) func timeInterval(for retriedCount: Int) -> TimeInterval { let retryAfter: TimeInterval switch self { case .seconds(let interval): retryAfter = interval case .accumulated(let interval): retryAfter = Double(retriedCount + 1) * interval case .custom(let block): retryAfter = block(retriedCount) } return retryAfter } } /// The max retry count defined for the retry strategy public let maxRetryCount: Int /// The retry interval mechanism defined for the retry strategy. public let retryInterval: Interval /// Creates a delay retry strategy. /// - Parameters: /// - maxRetryCount: The max retry count. /// - retryInterval: The retry interval mechanism. By default, `.seconds(3)` is used to provide a constant retry /// interval. public init(maxRetryCount: Int, retryInterval: Interval = .seconds(3)) { self.maxRetryCount = maxRetryCount self.retryInterval = retryInterval } public func retry(context: RetryContext, retryHandler: @escaping (RetryDecision) -> Void) { // Retry count exceeded. guard context.retriedCount < maxRetryCount else { retryHandler(.stop) return } // User cancel the task. No retry. guard !context.error.isTaskCancelled else { retryHandler(.stop) return } // Only retry for a response error. guard case KingfisherError.responseError = context.error else { retryHandler(.stop) return } let interval = retryInterval.timeInterval(for: context.retriedCount) if interval == 0 { retryHandler(.retry(userInfo: nil)) } else { DispatchQueue.main.asyncAfter(deadline: .now() + interval) { retryHandler(.retry(userInfo: nil)) } } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/SessionDataTask.swift ================================================ // // SessionDataTask.swift // Kingfisher // // Created by Wei Wang on 2018/11/1. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents a session data task in `ImageDownloader`. It consists of an underlying `URLSessionDataTask` and /// an array of `TaskCallback`. Multiple `TaskCallback`s could be added for a single downloading data task. public class SessionDataTask { /// Represents the type of token which used for cancelling a task. public typealias CancelToken = Int struct TaskCallback { let onCompleted: Delegate, Void>? let options: KingfisherParsedOptionsInfo } /// Downloaded raw data of current task. public private(set) var mutableData: Data // This is a copy of `task.originalRequest?.url`. It is for getting a race-safe behavior for a pitfall on iOS 13. // Ref: https://github.com/onevcat/Kingfisher/issues/1511 public let originalURL: URL? /// The underlying download task. It is only for debugging purpose when you encountered an error. You should not /// modify the content of this task or start it yourself. public let task: URLSessionDataTask private var callbacksStore = [CancelToken: TaskCallback]() var callbacks: [SessionDataTask.TaskCallback] { lock.lock() defer { lock.unlock() } return Array(callbacksStore.values) } private var currentToken = 0 private let lock = NSLock() let onTaskDone = Delegate<(Result<(Data, URLResponse?), KingfisherError>, [TaskCallback]), Void>() let onCallbackCancelled = Delegate<(CancelToken, TaskCallback), Void>() var started = false var containsCallbacks: Bool { // We should be able to use `task.state != .running` to check it. // However, in some rare cases, cancelling the task does not change // task state to `.cancelling` immediately, but still in `.running`. // So we need to check callbacks count to for sure that it is safe to remove the // task in delegate. return !callbacks.isEmpty } init(task: URLSessionDataTask) { self.task = task self.originalURL = task.originalRequest?.url mutableData = Data() } func addCallback(_ callback: TaskCallback) -> CancelToken { lock.lock() defer { lock.unlock() } callbacksStore[currentToken] = callback defer { currentToken += 1 } return currentToken } func removeCallback(_ token: CancelToken) -> TaskCallback? { lock.lock() defer { lock.unlock() } if let callback = callbacksStore[token] { callbacksStore[token] = nil return callback } return nil } func resume() { guard !started else { return } started = true task.resume() } func cancel(token: CancelToken) { guard let callback = removeCallback(token) else { return } onCallbackCancelled.call((token, callback)) } func forceCancel() { for token in callbacksStore.keys { cancel(token: token) } } func didReceiveData(_ data: Data) { mutableData.append(data) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Networking/SessionDelegate.swift ================================================ // // SessionDelegate.swift // Kingfisher // // Created by Wei Wang on 2018/11/1. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation // Represents the delegate object of downloader session. It also behave like a task manager for downloading. @objc(KFSessionDelegate) // Fix for ObjC header name conflicting. https://github.com/onevcat/Kingfisher/issues/1530 open class SessionDelegate: NSObject { typealias SessionChallengeFunc = ( URLSession, URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void ) typealias SessionTaskChallengeFunc = ( URLSession, URLSessionTask, URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void ) private var tasks: [URL: SessionDataTask] = [:] private let lock = NSLock() let onValidStatusCode = Delegate() let onDownloadingFinished = Delegate<(URL, Result), Void>() let onDidDownloadData = Delegate() let onReceiveSessionChallenge = Delegate() let onReceiveSessionTaskChallenge = Delegate() func add( _ dataTask: URLSessionDataTask, url: URL, callback: SessionDataTask.TaskCallback) -> DownloadTask { lock.lock() defer { lock.unlock() } // Create a new task if necessary. let task = SessionDataTask(task: dataTask) task.onCallbackCancelled.delegate(on: self) { [weak task] (self, value) in guard let task = task else { return } let (token, callback) = value let error = KingfisherError.requestError(reason: .taskCancelled(task: task, token: token)) task.onTaskDone.call((.failure(error), [callback])) // No other callbacks waiting, we can clear the task now. if !task.containsCallbacks { let dataTask = task.task self.cancelTask(dataTask) self.remove(task) } } let token = task.addCallback(callback) tasks[url] = task return DownloadTask(sessionTask: task, cancelToken: token) } private func cancelTask(_ dataTask: URLSessionDataTask) { lock.lock() defer { lock.unlock() } dataTask.cancel() } func append( _ task: SessionDataTask, url: URL, callback: SessionDataTask.TaskCallback) -> DownloadTask { let token = task.addCallback(callback) return DownloadTask(sessionTask: task, cancelToken: token) } private func remove(_ task: SessionDataTask) { lock.lock() defer { lock.unlock() } guard let url = task.originalURL else { return } tasks[url] = nil } private func task(for task: URLSessionTask) -> SessionDataTask? { lock.lock() defer { lock.unlock() } guard let url = task.originalRequest?.url else { return nil } guard let sessionTask = tasks[url] else { return nil } guard sessionTask.task.taskIdentifier == task.taskIdentifier else { return nil } return sessionTask } func task(for url: URL) -> SessionDataTask? { lock.lock() defer { lock.unlock() } return tasks[url] } func cancelAll() { lock.lock() let taskValues = tasks.values lock.unlock() for task in taskValues { task.forceCancel() } } func cancel(url: URL) { lock.lock() let task = tasks[url] lock.unlock() task?.forceCancel() } } extension SessionDelegate: URLSessionDataDelegate { open func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { guard let httpResponse = response as? HTTPURLResponse else { let error = KingfisherError.responseError(reason: .invalidURLResponse(response: response)) onCompleted(task: dataTask, result: .failure(error)) completionHandler(.cancel) return } let httpStatusCode = httpResponse.statusCode guard onValidStatusCode.call(httpStatusCode) == true else { let error = KingfisherError.responseError(reason: .invalidHTTPStatusCode(response: httpResponse)) onCompleted(task: dataTask, result: .failure(error)) completionHandler(.cancel) return } completionHandler(.allow) } open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { guard let task = self.task(for: dataTask) else { return } task.didReceiveData(data) task.callbacks.forEach { callback in callback.options.onDataReceived?.forEach { sideEffect in sideEffect.onDataReceived(session, task: task, data: data) } } } open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { guard let sessionTask = self.task(for: task) else { return } if let url = sessionTask.originalURL { let result: Result if let error = error { result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error))) } else if let response = task.response { result = .success(response) } else { result = .failure(KingfisherError.responseError(reason: .noURLResponse(task: sessionTask))) } onDownloadingFinished.call((url, result)) } let result: Result<(Data, URLResponse?), KingfisherError> if let error = error { result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error))) } else { if let data = onDidDownloadData.call(sessionTask) { result = .success((data, task.response)) } else { result = .failure(KingfisherError.responseError(reason: .dataModifyingFailed(task: sessionTask))) } } onCompleted(task: task, result: result) } open func urlSession( _ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { onReceiveSessionChallenge.call((session, challenge, completionHandler)) } open func urlSession( _ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { onReceiveSessionTaskChallenge.call((session, task, challenge, completionHandler)) } open func urlSession( _ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { guard let sessionDataTask = self.task(for: task), let redirectHandler = Array(sessionDataTask.callbacks).last?.options.redirectHandler else { completionHandler(request) return } redirectHandler.handleHTTPRedirection( for: sessionDataTask, response: response, newRequest: request, completionHandler: completionHandler) } private func onCompleted(task: URLSessionTask, result: Result<(Data, URLResponse?), KingfisherError>) { guard let sessionTask = self.task(for: task) else { return } remove(sessionTask) sessionTask.onTaskDone.call((result, sessionTask.callbacks)) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/SwiftUI/ImageBinder.swift ================================================ // // ImageBinder.swift // Kingfisher // // Created by onevcat on 2019/06/27. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if canImport(SwiftUI) && canImport(Combine) import Combine import SwiftUI @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension KFImage { /// Represents a binder for `KFImage`. It takes responsibility as an `ObjectBinding` and performs /// image downloading and progress reporting based on `KingfisherManager`. class ImageBinder: ObservableObject { let source: Source? var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions) var downloadTask: DownloadTask? var loadingOrSucceeded: Bool { return downloadTask != nil || loadedImage != nil } let onFailureDelegate = Delegate() let onSuccessDelegate = Delegate() let onProgressDelegate = Delegate<(Int64, Int64), Void>() var isLoaded: Binding var loaded = false { willSet { objectWillChange.send() } } var loadedImage: KFCrossPlatformImage? = nil { willSet { objectWillChange.send() } } @available(*, deprecated, message: "The `options` version is deprecated And will be removed soon.") init(source: Source?, options: KingfisherOptionsInfo? = nil, isLoaded: Binding) { self.source = source // The refreshing of `KFImage` would happen much more frequently then an `UIImageView`, even as a // "side-effect". To prevent unintended flickering, add `.loadDiskFileSynchronously` as a default. self.options = KingfisherParsedOptionsInfo( KingfisherManager.shared.defaultOptions + (options ?? []) + [.loadDiskFileSynchronously] ) self.isLoaded = isLoaded } init(source: Source?, isLoaded: Binding) { self.source = source // The refreshing of `KFImage` would happen much more frequently then an `UIImageView`, even as a // "side-effect". To prevent unintended flickering, add `.loadDiskFileSynchronously` as a default. self.options = KingfisherParsedOptionsInfo( KingfisherManager.shared.defaultOptions + [.loadDiskFileSynchronously] ) self.isLoaded = isLoaded } func start() { guard !loadingOrSucceeded else { return } guard let source = source else { CallbackQueue.mainCurrentOrAsync.execute { self.onFailureDelegate.call(KingfisherError.imageSettingError(reason: .emptySource)) } return } downloadTask = KingfisherManager.shared .retrieveImage( with: source, options: options, progressBlock: { size, total in self.onProgressDelegate.call((size, total)) }, completionHandler: { [weak self] result in guard let self = self else { return } self.downloadTask = nil switch result { case .success(let value): CallbackQueue.mainCurrentOrAsync.execute { self.loadedImage = value.image self.isLoaded.wrappedValue = true let animation = self.fadeTransitionDuration(cacheType: value.cacheType) .map { duration in Animation.linear(duration: duration) } withAnimation(animation) { self.loaded = true } } CallbackQueue.mainAsync.execute { self.onSuccessDelegate.call(value) } case .failure(let error): CallbackQueue.mainAsync.execute { self.onFailureDelegate.call(error) } } }) } /// Cancels the download task if it is in progress. func cancel() { downloadTask?.cancel() downloadTask = nil } private func shouldApplyFade(cacheType: CacheType) -> Bool { options.forceTransition || cacheType == .none } private func fadeTransitionDuration(cacheType: CacheType) -> TimeInterval? { shouldApplyFade(cacheType: cacheType) ? options.transition.fadeDuration : nil } } } @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension KFImage.ImageBinder: Hashable { static func == (lhs: KFImage.ImageBinder, rhs: KFImage.ImageBinder) -> Bool { lhs.source == rhs.source && lhs.options.processor.identifier == rhs.options.processor.identifier } func hash(into hasher: inout Hasher) { hasher.combine(source) hasher.combine(options.processor.identifier) } } extension ImageTransition { // Only for fade effect in SwiftUI. fileprivate var fadeDuration: TimeInterval? { switch self { case .fade(let duration): return duration default: return nil } } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/SwiftUI/KFImage.swift ================================================ // // KFImage.swift // Kingfisher // // Created by onevcat on 2019/06/26. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if canImport(SwiftUI) && canImport(Combine) import Combine import SwiftUI @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension Image { // Creates an Image with either UIImage or NSImage. init(crossPlatformImage: KFCrossPlatformImage) { #if canImport(UIKit) self.init(uiImage: crossPlatformImage) #elseif canImport(AppKit) self.init(nsImage: crossPlatformImage) #endif } } @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) public struct KFImage: View { var context: Context /// Creates a Kingfisher compatible image view to load image from the given `Source`. /// - Parameter source: The image `Source` defining where to load the target image. /// - Parameter options: The options should be applied when loading the image. /// Some UIKit related options (such as `ImageTransition.flip`) are not supported. /// - Parameter isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the /// wrapped value from outside. /// - Deprecated: Some options are not available in SwiftUI yet. Use `KFImage(source:isLoaded:)` to create a /// `KFImage` and configure the options through modifier instead. See methods of `KFOptionSetter` /// for more. @available(*, deprecated, message: "Some options are not available in SwiftUI yet. Use `KFImage(source:isLoaded:)` to create a `KFImage` and configure the options through modifier instead.") public init(source: Source?, options: KingfisherOptionsInfo? = nil, isLoaded: Binding = .constant(false)) { let binder = KFImage.ImageBinder(source: source, options: options, isLoaded: isLoaded) self.init(binder: binder) } /// Creates a Kingfisher compatible image view to load image from the given `URL`. /// - Parameter url: The image URL from where to load the target image. /// - Parameter options: The options should be applied when loading the image. /// Some UIKit related options (such as `ImageTransition.flip`) are not supported. /// - Parameter isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the /// wrapped value from outside. /// - Deprecated: Some options are not available in SwiftUI yet. Use `KFImage(_:isLoaded:)` to create a /// `KFImage` and configure the options through modifier instead. See methods of `KFOptionSetter` /// for more. @available(*, deprecated, message: "Some options are not available in SwiftUI yet. Use `KFImage(_:isLoaded:)` to create a `KFImage` and configure the options through modifier instead.") init(_ url: URL?, options: KingfisherOptionsInfo? = nil, isLoaded: Binding = .constant(false)) { self.init(source: url?.convertToSource(), options: options, isLoaded: isLoaded) } /// Creates a Kingfisher compatible image view to load image from the given `Source`. /// - Parameters: /// - source: The image `Source` defining where to load the target image. /// - isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the /// wrapped value from outside. public init(source: Source?, isLoaded: Binding = .constant(false)) { let binder = ImageBinder(source: source, isLoaded: isLoaded) self.init(binder: binder) } /// Creates a Kingfisher compatible image view to load image from the given `URL`. /// - Parameters: /// - source: The image `Source` defining where to load the target image. /// - isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the /// wrapped value from outside. public init(_ url: URL?, isLoaded: Binding = .constant(false)) { self.init(source: url?.convertToSource(), isLoaded: isLoaded) } init(binder: ImageBinder) { self.context = Context(binder: binder) } public var body: some View { KFImageRenderer(context) .id(context.binder) } /// Starts the loading process of `self` immediately. /// /// By default, a `KFImage` will not load its source until the `onAppear` is called. This is a lazily loading /// behavior and provides better performance. However, when you refresh the view, the lazy loading also causes a /// flickering since the loading does not happen immediately. Call this method if you want to start the load at once /// could help avoiding the flickering, with some performance trade-off. /// /// - Returns: The `Self` value with changes applied. public func loadImmediately(_ start: Bool = true) -> KFImage { if start { context.binder.start() } return self } } @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension KFImage { struct Context { var binder: ImageBinder var configurations: [(Image) -> Image] = [] var cancelOnDisappear: Bool = false var placeholder: AnyView? = nil init(binder: ImageBinder) { self.binder = binder } } } /// A Kingfisher compatible SwiftUI `View` to load an image from a `Source`. /// Declaring a `KFImage` in a `View`'s body to trigger loading from the given `Source`. @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) struct KFImageRenderer: View { /// An image binder that manages loading and cancelling image related task. @ObservedObject var binder: KFImage.ImageBinder // Acts as a placeholder when loading an image. var placeholder: AnyView? // Whether the download task should be cancelled when the view disappears. let cancelOnDisappear: Bool // Configurations should be performed on the image. let configurations: [(Image) -> Image] init(_ context: KFImage.Context) { self.binder = context.binder self.configurations = context.configurations self.placeholder = context.placeholder self.cancelOnDisappear = context.cancelOnDisappear } /// Declares the content and behavior of this view. @ViewBuilder var body: some View { if let image = binder.loadedImage { configurations .reduce(imageFromResult(image)) { current, config in config(current) } .opacity(binder.loaded ? 1.0 : 0.0) } else { Group { if placeholder != nil { placeholder } else { Color.clear } } .onAppear { [weak binder = self.binder] in guard let binder = binder else { return } if !binder.loadingOrSucceeded { binder.start() } } .onDisappear { [weak binder = self.binder] in guard let binder = binder else { return } if self.cancelOnDisappear { binder.cancel() } } } } private func imageFromResult(_ resultImage: KFCrossPlatformImage) -> Image { if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { return Image(crossPlatformImage: resultImage) } else { #if canImport(UIKit) // The CG image is used to solve #1395 // It should be not necessary if SwiftUI.Image can handle resizing correctly when created // by `Image.init(uiImage:)`. (The orientation information should be already contained in // a `UIImage`) // https://github.com/onevcat/Kingfisher/issues/1395 // // This issue happens on iOS 13 and was fixed by Apple from iOS 14. if let cgImage = resultImage.cgImage { return Image(decorative: cgImage, scale: resultImage.scale, orientation: resultImage.imageOrientation.toSwiftUI()) } else { return Image(crossPlatformImage: resultImage) } #else return Image(crossPlatformImage: resultImage) #endif } } } #if canImport(UIKit) @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension UIImage.Orientation { func toSwiftUI() -> Image.Orientation { switch self { case .down: return .down case .up: return .up case .left: return .left case .right: return .right case .upMirrored: return .upMirrored case .downMirrored: return .downMirrored case .leftMirrored: return .leftMirrored case .rightMirrored: return .rightMirrored @unknown default: return .up } } } #endif // MARK: - Image compatibility. @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension KFImage { /// Configures current image with a `block`. This block will be lazily applied when creating the final `Image`. /// - Parameter block: The block applies to loaded image. /// - Returns: A `KFImage` view that configures internal `Image` with `block`. public func configure(_ block: @escaping (Image) -> Image) -> KFImage { var result = self result.context.configurations.append(block) return result } public func resizable( capInsets: EdgeInsets = EdgeInsets(), resizingMode: Image.ResizingMode = .stretch) -> KFImage { configure { $0.resizable(capInsets: capInsets, resizingMode: resizingMode) } } public func renderingMode(_ renderingMode: Image.TemplateRenderingMode?) -> KFImage { configure { $0.renderingMode(renderingMode) } } public func interpolation(_ interpolation: Image.Interpolation) -> KFImage { configure { $0.interpolation(interpolation) } } public func antialiased(_ isAntialiased: Bool) -> KFImage { configure { $0.antialiased(isAntialiased) } } } #if DEBUG @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) struct KFImage_Previews : PreviewProvider { static var previews: some View { Group { KFImage(source: .network(URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/logo.png")!)) .onSuccess { r in print(r) } .resizable() .aspectRatio(contentMode: .fit) .padding() } } } #endif #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/SwiftUI/KFImageOptions.swift ================================================ // // KFImageOptions.swift // Kingfisher // // Created by onevcat on 2020/12/20. // // Copyright (c) 2020 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if canImport(SwiftUI) && canImport(Combine) import SwiftUI // MARK: - KFImage creating. @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension KFImage { /// Creates a `KFImage` for a given `Source`. /// - Parameters: /// - source: The `Source` object defines data information from network or a data provider. /// - isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the /// wrapped value from outside. /// - Returns: A `KFImage` for future configuration or embedding to a `SwiftUI.View`. public static func source( _ source: Source?, isLoaded: Binding = .constant(false) ) -> KFImage { KFImage(source: source, isLoaded: isLoaded) } /// Creates a `KFImage` for a given `Resource`. /// - Parameters: /// - source: The `Resource` object defines data information like key or URL. /// - isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the /// wrapped value from outside. /// - Returns: A `KFImage` for future configuration or embedding to a `SwiftUI.View`. public static func resource( _ resource: Resource?, isLoaded: Binding = .constant(false) ) -> KFImage { source(resource?.convertToSource(), isLoaded: isLoaded) } /// Creates a `KFImage` for a given `URL`. /// - Parameters: /// - url: The URL where the image should be downloaded. /// - cacheKey: The key used to store the downloaded image in cache. /// If `nil`, the `absoluteString` of `url` is used as the cache key. /// - isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the /// wrapped value from outside. /// - Returns: A `KFImage` for future configuration or embedding to a `SwiftUI.View`. public static func url( _ url: URL?, cacheKey: String? = nil, isLoaded: Binding = .constant(false) ) -> KFImage { source(url?.convertToSource(overrideCacheKey: cacheKey), isLoaded: isLoaded) } /// Creates a `KFImage` for a given `ImageDataProvider`. /// - Parameters: /// - provider: The `ImageDataProvider` object contains information about the data. /// - isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the /// wrapped value from outside. /// - Returns: A `KFImage` for future configuration or embedding to a `SwiftUI.View`. public static func dataProvider( _ provider: ImageDataProvider?, isLoaded: Binding = .constant(false) ) -> KFImage { source(provider?.convertToSource(), isLoaded: isLoaded) } /// Creates a builder for some given raw data and a cache key. /// - Parameters: /// - data: The data object from which the image should be created. /// - cacheKey: The key used to store the downloaded image in cache. /// - isLoaded: Whether the image is loaded or not. This provides a way to inspect the internal loading /// state. `true` if the image is loaded successfully. Otherwise, `false`. Do not set the /// wrapped value from outside. /// - Returns: A `KFImage` for future configuration or embedding to a `SwiftUI.View`. public static func data( _ data: Data?, cacheKey: String, isLoaded: Binding = .constant(false) ) -> KFImage { if let data = data { return dataProvider(RawImageDataProvider(data: data, cacheKey: cacheKey), isLoaded: isLoaded) } else { return dataProvider(nil, isLoaded: isLoaded) } } } @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension KFImage { /// Sets a placeholder `View` which shows when loading the image. /// - Parameter content: A view that describes the placeholder. /// - Returns: A `KFImage` view that contains `content` as its placeholder. public func placeholder(@ViewBuilder _ content: () -> Content) -> KFImage { let v = content() var result = self result.context.placeholder = AnyView(v) return result } /// Sets cancelling the download task bound to `self` when the view disappearing. /// - Parameter flag: Whether cancel the task or not. /// - Returns: A `KFImage` view that cancels downloading task when disappears. public func cancelOnDisappear(_ flag: Bool) -> KFImage { var result = self result.context.cancelOnDisappear = flag return result } /// Sets a fade transition for the image task. /// - Parameter duration: The duration of the fade transition. /// - Returns: A `KFImage` with changes applied. /// /// Kingfisher will use the fade transition to animate the image in if it is downloaded from web. /// The transition will not happen when the /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when /// the image being retrieved from cache, also call `forceRefresh()` on the returned `KFImage`. public func fade(duration: TimeInterval) -> KFImage { context.binder.options.transition = .fade(duration) return self } } #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Utility/Box.swift ================================================ // // Box.swift // Kingfisher // // Created by Wei Wang on 2018/3/17. // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation class Box { var value: T init(_ value: T) { self.value = value } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Utility/CallbackQueue.swift ================================================ // // CallbackQueue.swift // Kingfisher // // Created by onevcat on 2018/10/15. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// Represents callback queue behaviors when an calling of closure be dispatched. /// /// - asyncMain: Dispatch the calling to `DispatchQueue.main` with an `async` behavior. /// - currentMainOrAsync: Dispatch the calling to `DispatchQueue.main` with an `async` behavior if current queue is not /// `.main`. Otherwise, call the closure immediately in current main queue. /// - untouch: Do not change the calling queue for closure. /// - dispatch: Dispatches to a specified `DispatchQueue`. public enum CallbackQueue { /// Dispatch the calling to `DispatchQueue.main` with an `async` behavior. case mainAsync /// Dispatch the calling to `DispatchQueue.main` with an `async` behavior if current queue is not /// `.main`. Otherwise, call the closure immediately in current main queue. case mainCurrentOrAsync /// Do not change the calling queue for closure. case untouch /// Dispatches to a specified `DispatchQueue`. case dispatch(DispatchQueue) public func execute(_ block: @escaping () -> Void) { switch self { case .mainAsync: DispatchQueue.main.async { block() } case .mainCurrentOrAsync: DispatchQueue.main.safeAsync { block() } case .untouch: block() case .dispatch(let queue): queue.async { block() } } } var queue: DispatchQueue { switch self { case .mainAsync: return .main case .mainCurrentOrAsync: return .main case .untouch: return OperationQueue.current?.underlyingQueue ?? .main case .dispatch(let queue): return queue } } } extension DispatchQueue { // This method will dispatch the `block` to self. // If `self` is the main queue, and current thread is main thread, the block // will be invoked immediately instead of being dispatched. func safeAsync(_ block: @escaping ()->()) { if self === DispatchQueue.main && Thread.isMainThread { block() } else { async { block() } } } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Utility/Delegate.swift ================================================ // // Delegate.swift // Kingfisher // // Created by onevcat on 2018/10/10. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation /// A class that keeps a weakly reference for `self` when implementing `onXXX` behaviors. /// Instead of remembering to keep `self` as weak in a stored closure: /// /// ```swift /// // MyClass.swift /// var onDone: (() -> Void)? /// func done() { /// onDone?() /// } /// /// // ViewController.swift /// var obj: MyClass? /// /// func doSomething() { /// obj = MyClass() /// obj!.onDone = { [weak self] in /// self?.reportDone() /// } /// } /// ``` /// /// You can create a `Delegate` and observe on `self`. Now, there is no retain cycle inside: /// /// ```swift /// // MyClass.swift /// let onDone = Delegate<(), Void>() /// func done() { /// onDone.call() /// } /// /// // ViewController.swift /// var obj: MyClass? /// /// func doSomething() { /// obj = MyClass() /// obj!.onDone.delegate(on: self) { (self, _) /// // `self` here is shadowed and does not keep a strong ref. /// // So you can release both `MyClass` instance and `ViewController` instance. /// self.reportDone() /// } /// } /// ``` /// public class Delegate { public init() {} private var block: ((Input) -> Output?)? public func delegate(on target: T, block: ((T, Input) -> Output)?) { self.block = { [weak target] input in guard let target = target else { return nil } return block?(target, input) } } public func call(_ input: Input) -> Output? { return block?(input) } public func callAsFunction(_ input: Input) -> Output? { return call(input) } } extension Delegate where Input == Void { public func call() -> Output? { return call(()) } public func callAsFunction() -> Output? { return call() } } extension Delegate where Input == Void, Output: OptionalProtocol { public func call() -> Output { return call(()) } public func callAsFunction() -> Output { return call() } } extension Delegate where Output: OptionalProtocol { public func call(_ input: Input) -> Output { if let result = block?(input) { return result } else { return Output._createNil } } public func callAsFunction(_ input: Input) -> Output { return call(input) } } public protocol OptionalProtocol { static var _createNil: Self { get } } extension Optional : OptionalProtocol { public static var _createNil: Optional { return nil } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Utility/ExtensionHelpers.swift ================================================ // // ExtensionHelpers.swift // Kingfisher // // Created by onevcat on 2018/09/28. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation extension CGFloat { var isEven: Bool { return truncatingRemainder(dividingBy: 2.0) == 0 } } #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit extension NSBezierPath { convenience init(roundedRect rect: NSRect, topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat) { self.init() let maxCorner = min(rect.width, rect.height) / 2 let radiusTopLeft = min(maxCorner, max(0, topLeftRadius)) let radiusTopRight = min(maxCorner, max(0, topRightRadius)) let radiusBottomLeft = min(maxCorner, max(0, bottomLeftRadius)) let radiusBottomRight = min(maxCorner, max(0, bottomRightRadius)) guard !rect.isEmpty else { return } let topLeft = NSPoint(x: rect.minX, y: rect.maxY) let topRight = NSPoint(x: rect.maxX, y: rect.maxY) let bottomRight = NSPoint(x: rect.maxX, y: rect.minY) move(to: NSPoint(x: rect.midX, y: rect.maxY)) appendArc(from: topLeft, to: rect.origin, radius: radiusTopLeft) appendArc(from: rect.origin, to: bottomRight, radius: radiusBottomLeft) appendArc(from: bottomRight, to: topRight, radius: radiusBottomRight) appendArc(from: topRight, to: topLeft, radius: radiusTopRight) close() } convenience init(roundedRect rect: NSRect, byRoundingCorners corners: RectCorner, radius: CGFloat) { let radiusTopLeft = corners.contains(.topLeft) ? radius : 0 let radiusTopRight = corners.contains(.topRight) ? radius : 0 let radiusBottomLeft = corners.contains(.bottomLeft) ? radius : 0 let radiusBottomRight = corners.contains(.bottomRight) ? radius : 0 self.init(roundedRect: rect, topLeftRadius: radiusTopLeft, topRightRadius: radiusTopRight, bottomLeftRadius: radiusBottomLeft, bottomRightRadius: radiusBottomRight) } } extension KFCrossPlatformImage { // macOS does not support scale. This is just for code compatibility across platforms. convenience init?(data: Data, scale: CGFloat) { self.init(data: data) } } #endif #if canImport(UIKit) import UIKit extension RectCorner { var uiRectCorner: UIRectCorner { var result: UIRectCorner = [] if contains(.topLeft) { result.insert(.topLeft) } if contains(.topRight) { result.insert(.topRight) } if contains(.bottomLeft) { result.insert(.bottomLeft) } if contains(.bottomRight) { result.insert(.bottomRight) } return result } } #endif extension Date { var isPast: Bool { return isPast(referenceDate: Date()) } var isFuture: Bool { return !isPast } func isPast(referenceDate: Date) -> Bool { return timeIntervalSince(referenceDate) <= 0 } func isFuture(referenceDate: Date) -> Bool { return !isPast(referenceDate: referenceDate) } // `Date` in memory is a wrap for `TimeInterval`. But in file attribute it can only accept `Int` number. // By default the system will `round` it. But it is not friendly for testing purpose. // So we always `ceil` the value when used for file attributes. var fileAttributeDate: Date { return Date(timeIntervalSince1970: ceil(timeIntervalSince1970)) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Utility/Result.swift ================================================ // // Result.swift // Kingfisher // // Created by onevcat on 2018/09/22. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation // These helper methods are not public since we do not want them to be exposed or cause any conflicting. // However, they are just wrapper of `ResultUtil` static methods. extension Result where Failure: Error { /// Evaluates the given transform closures to create a single output value. /// /// - Parameters: /// - onSuccess: A closure that transforms the success value. /// - onFailure: A closure that transforms the error value. /// - Returns: A single `Output` value. func match( onSuccess: (Success) -> Output, onFailure: (Failure) -> Output) -> Output { switch self { case let .success(value): return onSuccess(value) case let .failure(error): return onFailure(error) } } func matchSuccess(with folder: (Success?) -> Output) -> Output { return match( onSuccess: { value in return folder(value) }, onFailure: { _ in return folder(nil) } ) } func matchFailure(with folder: (Error?) -> Output) -> Output { return match( onSuccess: { _ in return folder(nil) }, onFailure: { error in return folder(error) } ) } func match(with folder: (Success?, Error?) -> Output) -> Output { return match( onSuccess: { return folder($0, nil) }, onFailure: { return folder(nil, $0) } ) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Utility/Runtime.swift ================================================ // // Runtime.swift // Kingfisher // // Created by Wei Wang on 2018/10/12. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation func getAssociatedObject(_ object: Any, _ key: UnsafeRawPointer) -> T? { return objc_getAssociatedObject(object, key) as? T } func setRetainedAssociatedObject(_ object: Any, _ key: UnsafeRawPointer, _ value: T) { objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Utility/SizeExtensions.swift ================================================ // // SizeExtensions.swift // Kingfisher // // Created by onevcat on 2018/09/28. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import CoreGraphics extension CGSize: KingfisherCompatibleValue {} extension KingfisherWrapper where Base == CGSize { /// Returns a size by resizing the `base` size to a target size under a given content mode. /// /// - Parameters: /// - size: The target size to resize to. /// - contentMode: Content mode of the target size should be when resizing. /// - Returns: The resized size under the given `ContentMode`. public func resize(to size: CGSize, for contentMode: ContentMode) -> CGSize { switch contentMode { case .aspectFit: return constrained(size) case .aspectFill: return filling(size) case .none: return size } } /// Returns a size by resizing the `base` size by making it aspect fitting the given `size`. /// /// - Parameter size: The size in which the `base` should fit in. /// - Returns: The size fitted in by the input `size`, while keeps `base` aspect. public func constrained(_ size: CGSize) -> CGSize { let aspectWidth = round(aspectRatio * size.height) let aspectHeight = round(size.width / aspectRatio) return aspectWidth > size.width ? CGSize(width: size.width, height: aspectHeight) : CGSize(width: aspectWidth, height: size.height) } /// Returns a size by resizing the `base` size by making it aspect filling the given `size`. /// /// - Parameter size: The size in which the `base` should fill. /// - Returns: The size be filled by the input `size`, while keeps `base` aspect. public func filling(_ size: CGSize) -> CGSize { let aspectWidth = round(aspectRatio * size.height) let aspectHeight = round(size.width / aspectRatio) return aspectWidth < size.width ? CGSize(width: size.width, height: aspectHeight) : CGSize(width: aspectWidth, height: size.height) } /// Returns a `CGRect` for which the `base` size is constrained to an input `size` at a given `anchor` point. /// /// - Parameters: /// - size: The size in which the `base` should be constrained to. /// - anchor: An anchor point in which the size constraint should happen. /// - Returns: The result `CGRect` for the constraint operation. public func constrainedRect(for size: CGSize, anchor: CGPoint) -> CGRect { let unifiedAnchor = CGPoint(x: anchor.x.clamped(to: 0.0...1.0), y: anchor.y.clamped(to: 0.0...1.0)) let x = unifiedAnchor.x * base.width - unifiedAnchor.x * size.width let y = unifiedAnchor.y * base.height - unifiedAnchor.y * size.height let r = CGRect(x: x, y: y, width: size.width, height: size.height) let ori = CGRect(origin: .zero, size: base) return ori.intersection(r) } private var aspectRatio: CGFloat { return base.height == 0.0 ? 1.0 : base.width / base.height } } extension CGRect { func scaled(_ scale: CGFloat) -> CGRect { return CGRect(x: origin.x * scale, y: origin.y * scale, width: size.width * scale, height: size.height * scale) } } extension Comparable { func clamped(to limits: ClosedRange) -> Self { return min(max(self, limits.lowerBound), limits.upperBound) } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Utility/String+MD5.swift ================================================ // // String+MD5.swift // Kingfisher // // Created by Wei Wang on 18/09/25. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation import CommonCrypto extension String: KingfisherCompatibleValue { } extension KingfisherWrapper where Base == String { var md5: String { guard let data = base.data(using: .utf8) else { return base } let message = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in return [UInt8](bytes) } let MD5Calculator = MD5(message) let MD5Data = MD5Calculator.calculate() var MD5String = String() for c in MD5Data { MD5String += String(format: "%02x", c) } return MD5String } var ext: String? { var ext = "" if let index = base.lastIndex(of: ".") { let extRange = base.index(index, offsetBy: 1).. 0 ? String(firstSeg) : nil } } // array of bytes, little-endian representation func arrayOfBytes(_ value: T, length: Int? = nil) -> [UInt8] { let totalBytes = length ?? (MemoryLayout.size * 8) let valuePointer = UnsafeMutablePointer.allocate(capacity: 1) valuePointer.pointee = value let bytes = valuePointer.withMemoryRebound(to: UInt8.self, capacity: totalBytes) { (bytesPointer) -> [UInt8] in var bytes = [UInt8](repeating: 0, count: totalBytes) for j in 0...size, totalBytes) { bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee } return bytes } valuePointer.deinitialize(count: 1) valuePointer.deallocate() return bytes } extension Int { // Array of bytes with optional padding (little-endian) func bytes(_ totalBytes: Int = MemoryLayout.size) -> [UInt8] { return arrayOfBytes(self, length: totalBytes) } } extension NSMutableData { // Convenient way to append bytes func appendBytes(_ arrayOfBytes: [UInt8]) { append(arrayOfBytes, length: arrayOfBytes.count) } } protocol HashProtocol { var message: [UInt8] { get } // Common part for hash calculation. Prepare header data. func prepare(_ len: Int) -> [UInt8] } extension HashProtocol { func prepare(_ len: Int) -> [UInt8] { var tmpMessage = message // Step 1. Append Padding Bits tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message // append "0" bit until message length in bits ≡ 448 (mod 512) var msgLength = tmpMessage.count var counter = 0 while msgLength % len != (len - 8) { counter += 1 msgLength += 1 } tmpMessage += [UInt8](repeating: 0, count: counter) return tmpMessage } } func toUInt32Array(_ slice: ArraySlice) -> [UInt32] { var result = [UInt32]() result.reserveCapacity(16) for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout.size) { let d0 = UInt32(slice[idx.advanced(by: 3)]) << 24 let d1 = UInt32(slice[idx.advanced(by: 2)]) << 16 let d2 = UInt32(slice[idx.advanced(by: 1)]) << 8 let d3 = UInt32(slice[idx]) let val: UInt32 = d0 | d1 | d2 | d3 result.append(val) } return result } struct BytesIterator: IteratorProtocol { let chunkSize: Int let data: [UInt8] init(chunkSize: Int, data: [UInt8]) { self.chunkSize = chunkSize self.data = data } var offset = 0 mutating func next() -> ArraySlice? { let end = min(chunkSize, data.count - offset) let result = data[offset.. 0 ? result : nil } } struct BytesSequence: Sequence { let chunkSize: Int let data: [UInt8] func makeIterator() -> BytesIterator { return BytesIterator(chunkSize: chunkSize, data: data) } } func rotateLeft(_ value: UInt32, bits: UInt32) -> UInt32 { return ((value << bits) & 0xFFFFFFFF) | (value >> (32 - bits)) } class MD5: HashProtocol { static let size = 16 // 128 / 8 let message: [UInt8] init (_ message: [UInt8]) { self.message = message } // specifies the per-round shift amounts private let shifts: [UInt32] = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21] // binary integer part of the sines of integers (Radians) private let sines: [UInt32] = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391] private let hashes: [UInt32] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476] func calculate() -> [UInt8] { var tmpMessage = prepare(64) tmpMessage.reserveCapacity(tmpMessage.count + 4) // hash values var hh = hashes // Step 2. Append Length a 64-bit representation of lengthInBits let lengthInBits = (message.count * 8) let lengthBytes = lengthInBits.bytes(64 / 8) tmpMessage += lengthBytes.reversed() // Process the message in successive 512-bit chunks: let chunkSizeBytes = 512 / 8 // 64 for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) { // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15 let M = toUInt32Array(chunk) assert(M.count == 16, "Invalid array") // Initialize hash value for this chunk: var A: UInt32 = hh[0] var B: UInt32 = hh[1] var C: UInt32 = hh[2] var D: UInt32 = hh[3] var dTemp: UInt32 = 0 // Main loop for j in 0 ..< sines.count { var g = 0 var F: UInt32 = 0 switch j { case 0...15: F = (B & C) | ((~B) & D) g = j break case 16...31: F = (D & B) | (~D & C) g = (5 * j + 1) % 16 break case 32...47: F = B ^ C ^ D g = (3 * j + 5) % 16 break case 48...63: F = C ^ (B | (~D)) g = (7 * j) % 16 break default: break } dTemp = D D = C C = B B = B &+ rotateLeft((A &+ F &+ sines[j] &+ M[g]), bits: shifts[j]) A = dTemp } hh[0] = hh[0] &+ A hh[1] = hh[1] &+ B hh[2] = hh[2] &+ C hh[3] = hh[3] &+ D } var result = [UInt8]() result.reserveCapacity(hh.count / 4) hh.forEach { let itemLE = $0.littleEndian let r1 = UInt8(itemLE & 0xff) let r2 = UInt8((itemLE >> 8) & 0xff) let r3 = UInt8((itemLE >> 16) & 0xff) let r4 = UInt8((itemLE >> 24) & 0xff) result += [r1, r2, r3, r4] } return result } } ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Views/AnimatedImageView.swift ================================================ // // AnimatableImageView.swift // Kingfisher // // Created by bl4ckra1sond3tre on 4/22/16. // // The AnimatableImageView, AnimatedFrame and Animator is a modified version of // some classes from kaishin's Gifu project (https://github.com/kaishin/Gifu) // // The MIT License (MIT) // // Copyright (c) 2019 Reda Lemeden. // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // The name and characters used in the demo of this software are property of their // respective owners. #if !os(watchOS) #if canImport(UIKit) import UIKit import ImageIO /// Protocol of `AnimatedImageView`. public protocol AnimatedImageViewDelegate: AnyObject { /// Called after the animatedImageView has finished each animation loop. /// /// - Parameters: /// - imageView: The `AnimatedImageView` that is being animated. /// - count: The looped count. func animatedImageView(_ imageView: AnimatedImageView, didPlayAnimationLoops count: UInt) /// Called after the `AnimatedImageView` has reached the max repeat count. /// /// - Parameter imageView: The `AnimatedImageView` that is being animated. func animatedImageViewDidFinishAnimating(_ imageView: AnimatedImageView) } extension AnimatedImageViewDelegate { public func animatedImageView(_ imageView: AnimatedImageView, didPlayAnimationLoops count: UInt) {} public func animatedImageViewDidFinishAnimating(_ imageView: AnimatedImageView) {} } let KFRunLoopModeCommon = RunLoop.Mode.common /// Represents a subclass of `UIImageView` for displaying animated image. /// Different from showing animated image in a normal `UIImageView` (which load all frames at one time), /// `AnimatedImageView` only tries to load several frames (defined by `framePreloadCount`) to reduce memory usage. /// It provides a tradeoff between memory usage and CPU time. If you have a memory issue when using a normal image /// view to load GIF data, you could give this class a try. /// /// Kingfisher supports setting GIF animated data to either `UIImageView` and `AnimatedImageView` out of box. So /// it would be fairly easy to switch between them. open class AnimatedImageView: UIImageView { /// Proxy object for preventing a reference cycle between the `CADDisplayLink` and `AnimatedImageView`. class TargetProxy { private weak var target: AnimatedImageView? init(target: AnimatedImageView) { self.target = target } @objc func onScreenUpdate() { target?.updateFrameIfNeeded() } } /// Enumeration that specifies repeat count of GIF public enum RepeatCount: Equatable { case once case finite(count: UInt) case infinite public static func ==(lhs: RepeatCount, rhs: RepeatCount) -> Bool { switch (lhs, rhs) { case let (.finite(l), .finite(r)): return l == r case (.once, .once), (.infinite, .infinite): return true case (.once, .finite(let count)), (.finite(let count), .once): return count == 1 case (.once, _), (.infinite, _), (.finite, _): return false } } } // MARK: - Public property /// Whether automatically play the animation when the view become visible. Default is `true`. public var autoPlayAnimatedImage = true /// The count of the frames should be preloaded before shown. public var framePreloadCount = 10 /// Specifies whether the GIF frames should be pre-scaled to the image view's size or not. /// If the downloaded image is larger than the image view's size, it will help to reduce some memory use. /// Default is `true`. public var needsPrescaling = true /// Decode the GIF frames in background thread before using. It will decode frames data and do a off-screen /// rendering to extract pixel information in background. This can reduce the main thread CPU usage. public var backgroundDecode = true /// The animation timer's run loop mode. Default is `RunLoop.Mode.common`. /// Set this property to `RunLoop.Mode.default` will make the animation pause during UIScrollView scrolling. public var runLoopMode = KFRunLoopModeCommon { willSet { guard runLoopMode != newValue else { return } stopAnimating() displayLink.remove(from: .main, forMode: runLoopMode) displayLink.add(to: .main, forMode: newValue) startAnimating() } } /// The repeat count. The animated image will keep animate until it the loop count reaches this value. /// Setting this value to another one will reset current animation. /// /// Default is `.infinite`, which means the animation will last forever. public var repeatCount = RepeatCount.infinite { didSet { if oldValue != repeatCount { reset() setNeedsDisplay() layer.setNeedsDisplay() } } } /// Delegate of this `AnimatedImageView` object. See `AnimatedImageViewDelegate` protocol for more. public weak var delegate: AnimatedImageViewDelegate? /// The `Animator` instance that holds the frames of a specific image in memory. public private(set) var animator: Animator? // MARK: - Private property // Dispatch queue used for preloading images. private lazy var preloadQueue: DispatchQueue = { return DispatchQueue(label: "com.onevcat.Kingfisher.Animator.preloadQueue") }() // A flag to avoid invalidating the displayLink on deinit if it was never created, because displayLink is so lazy. private var isDisplayLinkInitialized: Bool = false // A display link that keeps calling the `updateFrame` method on every screen refresh. private lazy var displayLink: CADisplayLink = { isDisplayLinkInitialized = true let displayLink = CADisplayLink(target: TargetProxy(target: self), selector: #selector(TargetProxy.onScreenUpdate)) displayLink.add(to: .main, forMode: runLoopMode) displayLink.isPaused = true return displayLink }() // MARK: - Override override open var image: KFCrossPlatformImage? { didSet { if image != oldValue { reset() } setNeedsDisplay() layer.setNeedsDisplay() } } open override var isHighlighted: Bool { get { super.isHighlighted } set { // Highlighted image is unsupported for animated images. // See https://github.com/onevcat/Kingfisher/issues/1679 if displayLink.isPaused { super.isHighlighted = newValue } } } deinit { if isDisplayLinkInitialized { displayLink.invalidate() } } override open var isAnimating: Bool { if isDisplayLinkInitialized { return !displayLink.isPaused } else { return super.isAnimating } } /// Starts the animation. override open func startAnimating() { guard !isAnimating else { return } guard let animator = animator else { return } guard !animator.isReachMaxRepeatCount else { return } displayLink.isPaused = false } /// Stops the animation. override open func stopAnimating() { super.stopAnimating() if isDisplayLinkInitialized { displayLink.isPaused = true } } override open func display(_ layer: CALayer) { if let currentFrame = animator?.currentFrameImage { layer.contents = currentFrame.cgImage } else { layer.contents = image?.cgImage } } override open func didMoveToWindow() { super.didMoveToWindow() didMove() } override open func didMoveToSuperview() { super.didMoveToSuperview() didMove() } // This is for back compatibility that using regular `UIImageView` to show animated image. override func shouldPreloadAllAnimation() -> Bool { return false } // Reset the animator. private func reset() { animator = nil if let image = image, let imageSource = image.kf.imageSource { let targetSize = bounds.scaled(UIScreen.main.scale).size let animator = Animator( imageSource: imageSource, contentMode: contentMode, size: targetSize, imageSize: image.kf.size, imageScale: image.kf.scale, framePreloadCount: framePreloadCount, repeatCount: repeatCount, preloadQueue: preloadQueue) animator.delegate = self animator.needsPrescaling = needsPrescaling animator.backgroundDecode = backgroundDecode animator.prepareFramesAsynchronously() self.animator = animator } didMove() } private func didMove() { if autoPlayAnimatedImage && animator != nil { if let _ = superview, let _ = window { startAnimating() } else { stopAnimating() } } } /// Update the current frame with the displayLink duration. private func updateFrameIfNeeded() { guard let animator = animator else { return } guard !animator.isFinished else { stopAnimating() delegate?.animatedImageViewDidFinishAnimating(self) return } let duration: CFTimeInterval // CA based display link is opt-out from ProMotion by default. // So the duration and its FPS might not match. // See [#718](https://github.com/onevcat/Kingfisher/issues/718) // By setting CADisableMinimumFrameDuration to YES in Info.plist may // cause the preferredFramesPerSecond being 0 let preferredFramesPerSecond = displayLink.preferredFramesPerSecond if preferredFramesPerSecond == 0 { duration = displayLink.duration } else { // Some devices (like iPad Pro 10.5) will have a different FPS. duration = 1.0 / TimeInterval(preferredFramesPerSecond) } animator.shouldChangeFrame(with: duration) { [weak self] hasNewFrame in if hasNewFrame { self?.layer.setNeedsDisplay() } } } } protocol AnimatorDelegate: AnyObject { func animator(_ animator: AnimatedImageView.Animator, didPlayAnimationLoops count: UInt) } extension AnimatedImageView: AnimatorDelegate { func animator(_ animator: Animator, didPlayAnimationLoops count: UInt) { delegate?.animatedImageView(self, didPlayAnimationLoops: count) } } extension AnimatedImageView { // Represents a single frame in a GIF. struct AnimatedFrame { // The image to display for this frame. Its value is nil when the frame is removed from the buffer. let image: UIImage? // The duration that this frame should remain active. let duration: TimeInterval // A placeholder frame with no image assigned. // Used to replace frames that are no longer needed in the animation. var placeholderFrame: AnimatedFrame { return AnimatedFrame(image: nil, duration: duration) } // Whether this frame instance contains an image or not. var isPlaceholder: Bool { return image == nil } // Returns a new instance from an optional image. // // - parameter image: An optional `UIImage` instance to be assigned to the new frame. // - returns: An `AnimatedFrame` instance. func makeAnimatedFrame(image: UIImage?) -> AnimatedFrame { return AnimatedFrame(image: image, duration: duration) } } } extension AnimatedImageView { // MARK: - Animator /// An animator which used to drive the data behind `AnimatedImageView`. public class Animator { private let size: CGSize private let imageSize: CGSize private let imageScale: CGFloat /// The maximum count of image frames that needs preload. public let maxFrameCount: Int private let imageSource: CGImageSource private let maxRepeatCount: RepeatCount private let maxTimeStep: TimeInterval = 1.0 private let animatedFrames = SafeArray() private var frameCount = 0 private var timeSinceLastFrameChange: TimeInterval = 0.0 private var currentRepeatCount: UInt = 0 var isFinished: Bool = false var needsPrescaling = true var backgroundDecode = true weak var delegate: AnimatorDelegate? // Total duration of one animation loop var loopDuration: TimeInterval = 0 /// The image of the current frame. public var currentFrameImage: UIImage? { return frame(at: currentFrameIndex) } /// The duration of the current active frame duration. public var currentFrameDuration: TimeInterval { return duration(at: currentFrameIndex) } /// The index of the current animation frame. public internal(set) var currentFrameIndex = 0 { didSet { previousFrameIndex = oldValue } } var previousFrameIndex = 0 { didSet { preloadQueue.async { self.updatePreloadedFrames() } } } var isReachMaxRepeatCount: Bool { switch maxRepeatCount { case .once: return currentRepeatCount >= 1 case .finite(let maxCount): return currentRepeatCount >= maxCount case .infinite: return false } } /// Whether the current frame is the last frame or not in the animation sequence. public var isLastFrame: Bool { return currentFrameIndex == frameCount - 1 } var preloadingIsNeeded: Bool { return maxFrameCount < frameCount - 1 } var contentMode = UIView.ContentMode.scaleToFill private lazy var preloadQueue: DispatchQueue = { return DispatchQueue(label: "com.onevcat.Kingfisher.Animator.preloadQueue") }() /// Creates an animator with image source reference. /// /// - Parameters: /// - source: The reference of animated image. /// - mode: Content mode of the `AnimatedImageView`. /// - size: Size of the `AnimatedImageView`. /// - imageSize: Size of the `KingfisherWrapper`. /// - imageScale: Scale of the `KingfisherWrapper`. /// - count: Count of frames needed to be preloaded. /// - repeatCount: The repeat count should this animator uses. /// - preloadQueue: Dispatch queue used for preloading images. init(imageSource source: CGImageSource, contentMode mode: UIView.ContentMode, size: CGSize, imageSize: CGSize, imageScale: CGFloat, framePreloadCount count: Int, repeatCount: RepeatCount, preloadQueue: DispatchQueue) { self.imageSource = source self.contentMode = mode self.size = size self.imageSize = imageSize self.imageScale = imageScale self.maxFrameCount = count self.maxRepeatCount = repeatCount self.preloadQueue = preloadQueue GraphicsContext.begin(size: imageSize, scale: imageScale) } deinit { GraphicsContext.end() } /// Gets the image frame of a given index. /// - Parameter index: The index of desired image. /// - Returns: The decoded image at the frame. `nil` if the index is out of bound or the image is not yet loaded. public func frame(at index: Int) -> KFCrossPlatformImage? { return animatedFrames[index]?.image } public func duration(at index: Int) -> TimeInterval { return animatedFrames[index]?.duration ?? .infinity } func prepareFramesAsynchronously() { frameCount = Int(CGImageSourceGetCount(imageSource)) animatedFrames.reserveCapacity(frameCount) preloadQueue.async { [weak self] in self?.setupAnimatedFrames() } } func shouldChangeFrame(with duration: CFTimeInterval, handler: (Bool) -> Void) { incrementTimeSinceLastFrameChange(with: duration) if currentFrameDuration > timeSinceLastFrameChange { handler(false) } else { resetTimeSinceLastFrameChange() incrementCurrentFrameIndex() handler(true) } } private func setupAnimatedFrames() { resetAnimatedFrames() var duration: TimeInterval = 0 (0.. maxFrameCount { return } animatedFrames[index] = animatedFrames[index]?.makeAnimatedFrame(image: loadFrame(at: index)) } self.loopDuration = duration } private func resetAnimatedFrames() { animatedFrames.removeAll() } private func loadFrame(at index: Int) -> UIImage? { let resize = needsPrescaling && size != .zero let options: [CFString: Any]? if resize { options = [ kCGImageSourceCreateThumbnailFromImageIfAbsent: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) ] } else { options = nil } guard let cgImage = CGImageSourceCreateImageAtIndex(imageSource, index, options as CFDictionary?) else { return nil } let image = KFCrossPlatformImage(cgImage: cgImage) guard let context = GraphicsContext.current(size: imageSize, scale: imageScale, inverting: true, cgImage: cgImage) else { return image } return backgroundDecode ? image.kf.decoded(on: context) : image } private func updatePreloadedFrames() { guard preloadingIsNeeded else { return } animatedFrames[previousFrameIndex] = animatedFrames[previousFrameIndex]?.placeholderFrame preloadIndexes(start: currentFrameIndex).forEach { index in guard let currentAnimatedFrame = animatedFrames[index] else { return } if !currentAnimatedFrame.isPlaceholder { return } animatedFrames[index] = currentAnimatedFrame.makeAnimatedFrame(image: loadFrame(at: index)) } } private func incrementCurrentFrameIndex() { currentFrameIndex = increment(frameIndex: currentFrameIndex) if isLastFrame { currentRepeatCount += 1 if isReachMaxRepeatCount { isFinished = true } delegate?.animator(self, didPlayAnimationLoops: currentRepeatCount) } } private func incrementTimeSinceLastFrameChange(with duration: TimeInterval) { timeSinceLastFrameChange += min(maxTimeStep, duration) } private func resetTimeSinceLastFrameChange() { timeSinceLastFrameChange -= currentFrameDuration } private func increment(frameIndex: Int, by value: Int = 1) -> Int { return (frameIndex + value) % frameCount } private func preloadIndexes(start index: Int) -> [Int] { let nextIndex = increment(frameIndex: index) let lastIndex = increment(frameIndex: index, by: maxFrameCount) if lastIndex >= nextIndex { return [Int](nextIndex...lastIndex) } else { return [Int](nextIndex.. { private var array: Array = [] private let lock = NSLock() subscript(index: Int) -> Element? { get { lock.lock() defer { lock.unlock() } return array.indices ~= index ? array[index] : nil } set { lock.lock() defer { lock.unlock() } if let newValue = newValue, array.indices ~= index { array[index] = newValue } } } var count : Int { lock.lock() defer { lock.unlock() } return array.count } func reserveCapacity(_ count: Int) { lock.lock() defer { lock.unlock() } array.reserveCapacity(count) } func append(_ element: Element) { lock.lock() defer { lock.unlock() } array += [element] } func removeAll() { lock.lock() defer { lock.unlock() } array = [] } } #endif #endif ================================================ FILE: JetChat/Pods/Kingfisher/Sources/Views/Indicator.swift ================================================ // // Indicator.swift // Kingfisher // // Created by João D. Moreira on 30/08/16. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if !os(watchOS) #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit public typealias IndicatorView = NSView #else import UIKit public typealias IndicatorView = UIView #endif /// Represents the activity indicator type which should be added to /// an image view when an image is being downloaded. /// /// - none: No indicator. /// - activity: Uses the system activity indicator. /// - image: Uses an image as indicator. GIF is supported. /// - custom: Uses a custom indicator. The type of associated value should conform to the `Indicator` protocol. public enum IndicatorType { /// No indicator. case none /// Uses the system activity indicator. case activity /// Uses an image as indicator. GIF is supported. case image(imageData: Data) /// Uses a custom indicator. The type of associated value should conform to the `Indicator` protocol. case custom(indicator: Indicator) } /// An indicator type which can be used to show the download task is in progress. public protocol Indicator { /// Called when the indicator should start animating. func startAnimatingView() /// Called when the indicator should stop animating. func stopAnimatingView() /// Center offset of the indicator. Kingfisher will use this value to determine the position of /// indicator in the super view. var centerOffset: CGPoint { get } /// The indicator view which would be added to the super view. var view: IndicatorView { get } /// The size strategy used when adding the indicator to image view. /// - Parameter imageView: The super view of indicator. func sizeStrategy(in imageView: KFCrossPlatformImageView) -> IndicatorSizeStrategy } public enum IndicatorSizeStrategy { case intrinsicSize case full case size(CGSize) } extension Indicator { /// Default implementation of `centerOffset` of `Indicator`. The default value is `.zero`, means that there is /// no offset for the indicator view. public var centerOffset: CGPoint { return .zero } /// Default implementation of `centerOffset` of `Indicator`. The default value is `.full`, means that the indicator /// will pin to the same height and width as the image view. public func sizeStrategy(in imageView: KFCrossPlatformImageView) -> IndicatorSizeStrategy { return .full } } // Displays a NSProgressIndicator / UIActivityIndicatorView final class ActivityIndicator: Indicator { #if os(macOS) private let activityIndicatorView: NSProgressIndicator #else private let activityIndicatorView: UIActivityIndicatorView #endif private var animatingCount = 0 var view: IndicatorView { return activityIndicatorView } func startAnimatingView() { if animatingCount == 0 { #if os(macOS) activityIndicatorView.startAnimation(nil) #else activityIndicatorView.startAnimating() #endif activityIndicatorView.isHidden = false } animatingCount += 1 } func stopAnimatingView() { animatingCount = max(animatingCount - 1, 0) if animatingCount == 0 { #if os(macOS) activityIndicatorView.stopAnimation(nil) #else activityIndicatorView.stopAnimating() #endif activityIndicatorView.isHidden = true } } func sizeStrategy(in imageView: KFCrossPlatformImageView) -> IndicatorSizeStrategy { return .intrinsicSize } init() { #if os(macOS) activityIndicatorView = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16)) activityIndicatorView.controlSize = .small activityIndicatorView.style = .spinning #else let indicatorStyle: UIActivityIndicatorView.Style #if os(tvOS) if #available(tvOS 13.0, *) { indicatorStyle = UIActivityIndicatorView.Style.large } else { indicatorStyle = UIActivityIndicatorView.Style.white } #else if #available(iOS 13.0, * ) { indicatorStyle = UIActivityIndicatorView.Style.medium } else { indicatorStyle = UIActivityIndicatorView.Style.gray } #endif activityIndicatorView = UIActivityIndicatorView(style: indicatorStyle) #endif } } #if canImport(UIKit) extension UIActivityIndicatorView.Style { #if compiler(>=5.1) #else static let large = UIActivityIndicatorView.Style.white #if !os(tvOS) static let medium = UIActivityIndicatorView.Style.gray #endif #endif } #endif // MARK: - ImageIndicator // Displays an ImageView. Supports gif final class ImageIndicator: Indicator { private let animatedImageIndicatorView: KFCrossPlatformImageView var view: IndicatorView { return animatedImageIndicatorView } init?( imageData data: Data, processor: ImageProcessor = DefaultImageProcessor.default, options: KingfisherParsedOptionsInfo? = nil) { var options = options ?? KingfisherParsedOptionsInfo(nil) // Use normal image view to show animations, so we need to preload all animation data. if !options.preloadAllAnimationData { options.preloadAllAnimationData = true } guard let image = processor.process(item: .data(data), options: options) else { return nil } animatedImageIndicatorView = KFCrossPlatformImageView() animatedImageIndicatorView.image = image #if os(macOS) // Need for gif to animate on macOS animatedImageIndicatorView.imageScaling = .scaleNone animatedImageIndicatorView.canDrawSubviewsIntoLayer = true #else animatedImageIndicatorView.contentMode = .center #endif } func startAnimatingView() { #if os(macOS) animatedImageIndicatorView.animates = true #else animatedImageIndicatorView.startAnimating() #endif animatedImageIndicatorView.isHidden = false } func stopAnimatingView() { #if os(macOS) animatedImageIndicatorView.animates = false #else animatedImageIndicatorView.stopAnimating() #endif animatedImageIndicatorView.isHidden = true } } #endif ================================================ FILE: JetChat/Pods/Kingfisher.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 009A9E47D01F8A0C32F66F0B448F46CC /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E518CACDBAA22A0E0F7D6961DAC786B2 /* Accelerate.framework */; }; 01D91DEFCE943FE1652E281B20750557 /* KFImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48ED39E5A43BE5ED6F038D8A66347D4E /* KFImage.swift */; }; 036768B3E3DD45D26BA6E2A3CF5D4877 /* KF.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF98A2E2B5A4ED5ACF144ACA59EAA5D /* KF.swift */; }; 116E8D4B27521314299F86B710B38EF4 /* TVMonogramView+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBF81C72312B11BAA7F67F39B90FA725 /* TVMonogramView+Kingfisher.swift */; }; 15EFC8950D083FBE2FB3315A2716B612 /* SessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E9577EFC69E5728414BDFBDD74FD43 /* SessionDataTask.swift */; }; 18E98EFA5F90947B984F906B55A239E8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4A0AA97B763CD1C6C59DC16DDF1AB9E /* Foundation.framework */; }; 1D63FD969DA4E8DB4F8511884E1D5654 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB538EAFED24250FDEE778B618A00E7 /* SessionDelegate.swift */; }; 20ABCD53B035203F866F73EA0B83731A /* ImageBinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 216DA7D5B16F73B3A52B0CB76305B4D5 /* ImageBinder.swift */; }; 2576D77E0980C2498D305E6BFB92438F /* NSButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59C5FD04D3AB489CF738CBBFDFCE5645 /* NSButton+Kingfisher.swift */; }; 298717B4D8172E7075AE7FAF1E3E3B97 /* Kingfisher-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 1660797AA6A1A171FD765DE92799300F /* Kingfisher-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2FF3FBDC108FD38E9C99D01054084F39 /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87FECBBB0A860F907D2BDDEA825274DC /* MemoryStorage.swift */; }; 33E477C91EDD27D85FF73F24D3EE0EFD /* AuthenticationChallengeResponsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BCBB00723EB97F178C77F2BFEBDD45 /* AuthenticationChallengeResponsable.swift */; }; 39E3A64C2EF9C5347686B57E4FC7AFB2 /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E0FF9F3BA7EE48060442B25E3923D1 /* DiskStorage.swift */; }; 3D980A610BE4423096BDC5E78E0EEAD0 /* Kingfisher-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 152D5D9EEE4D7712656A02D7C3B5FBB0 /* Kingfisher-dummy.m */; }; 40AE4A5231163E23BB5D2E120A0ED41B /* NSTextAttachment+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE28E72CDEE4E7DB38DFC20F531F140B /* NSTextAttachment+Kingfisher.swift */; }; 439CC25AF8061ADA3B3559BD9C82C638 /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F006D807C4BBE9DCDE95AFDCB75783 /* Indicator.swift */; }; 44A7662375A26C49CB4DFB3D545ECD5D /* ImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943D6B9A2FD53F71E3F8CEA1A382B188 /* ImageProcessor.swift */; }; 45B672CC51026E979C8897B02FBAE3AF /* ImagePrefetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB06E8B10338824390D5266D8EAE5E78 /* ImagePrefetcher.swift */; }; 4B27B0C61BD4559F8F1BB5AD52BBF5E6 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2EB7FE01806F8598CC1B5C6C746469B /* RedirectHandler.swift */; }; 51BCC42FFEC8F3D53043A9B8A0BB4D37 /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6352786185F851F454BA7D97F1D6759 /* Delegate.swift */; }; 5276029CD6731C940503B2CFC9D93C7F /* GIFAnimatedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BBF545F6F083EE1DB1DE5D5B69BDCFC /* GIFAnimatedImage.swift */; }; 54CE1092A1E30C04AA7EC2A6DC2547B8 /* UIButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70F13ED40BD782B2FD171DBDFC7298D /* UIButton+Kingfisher.swift */; }; 59DD17E9A9A03043F2CBD1948E781769 /* CallbackQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABD31869B41A448550CA44602EE9EDC /* CallbackQueue.swift */; }; 5D6C540026B79ADC1E1E39B528E44604 /* KFOptionsSetter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC4B5542F3EFBD53D4FBF5E30E74CAB /* KFOptionsSetter.swift */; }; 6410E99320F5871A84099068A7365BDD /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CCE5E028CED8E1C7C2B3C49C7CD302 /* ImageProgressive.swift */; }; 68C266999376A54A23E07826862DA440 /* CacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97062F6396DBC3332A358BF00A05AB1 /* CacheSerializer.swift */; }; 747C1F3BECBC41CA56F392B033C24037 /* ImageDownloaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BF0E964FA29F866CD8B28277961591 /* ImageDownloaderDelegate.swift */; }; 74EBCC7C42D8BCA61F375D98F24EAA93 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C91878B1568D723465D0180ECE2DD3 /* Box.swift */; }; 7FA4133CEE22E228CFEDD8CF1C195F2B /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A02FF3EC55763EA593D42C89449A7B37 /* CFNetwork.framework */; }; 84124AD6027C6BD4D1C44E3570B1F47F /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1ABCFC660C8FFEE085F31A86BC1E5F4 /* Storage.swift */; }; 843DBEF3521809674AA4606A6ADA19A1 /* ImageDataProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5687F7E0F61D656A97BBA71BFF462011 /* ImageDataProcessor.swift */; }; 848AF3BDEF73627067D56F5B6620EDD1 /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AF9789FE18E7B9F33CC543BA3CED2DA /* Resource.swift */; }; 89A86A6FFF5D108B731722F7311366A5 /* String+MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 870F0060843B0434250A105A568B47F7 /* String+MD5.swift */; }; 8A2977E80420FA65D97E80C29DEC35CB /* Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 633AC15E4EE8A866A836986B762DB0B3 /* Kingfisher.swift */; }; 92D89759C2D5F6644DC7C2D2E03B2BDD /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681A3D233AD8CB4CEBEFB3C2C8AE48F9 /* Filter.swift */; }; 97B0EC6E75F956435E01FA1B5BA0CE65 /* KingfisherError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 949C76451365A57DFFCF495B622A2238 /* KingfisherError.swift */; }; 9B80AC78167933EE0DBA568741114B7F /* ImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFF10C376DEEF81E3DB5E6669A39693 /* ImageDataProvider.swift */; }; 9DEAC78AB2C941F250F6BDFCE43586B3 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F51F760B52B5D5095A5BAF956EE8629 /* Image.swift */; }; 9E0229D37EB89FD1DCD5E67634E1A62B /* ExtensionHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8780C2E124F1D5FB5D997FB3AAE2AFC9 /* ExtensionHelpers.swift */; }; 9F8BC3E6AEA88EE0F633648B364BC9FB /* FormatIndicatedCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC4ABF307D42FBBF32D6EF94C31169B /* FormatIndicatedCacheSerializer.swift */; }; A0289CAD357E2176617F6AC5D88AAA48 /* Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAA2DFD301775D3612D223AE20371D6 /* Runtime.swift */; }; A540839A448152A428FA8F04E5B58D99 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02F7451A4E45E7584C83738A0B72256 /* Placeholder.swift */; }; A6797F90FF228586D31EFCB007C04A5A /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1307C8B64AE65B9C79418184E51060BD /* ImageTransition.swift */; }; A6E4BF22B9D00E287EF8D8DBA0CF5BC2 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA3599A36919BB92EEE708D5E3778242 /* ImageDownloader.swift */; }; B58856DD55C5EA413D70153C932F8EAB /* WKInterfaceImage+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB0E65E2B073BB78DB5564B879AB771 /* WKInterfaceImage+Kingfisher.swift */; }; B8AB0023D19E13459FAFBCE711294EA8 /* KingfisherManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813F3E11E4A16360D9AA5E0AF664776B /* KingfisherManager.swift */; }; C2D7FC1E503179BD6E288403BEFB0513 /* SizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF859DBAD5CFEAA9C43C167097CDADD8 /* SizeExtensions.swift */; }; CBB91E6B72B26064566443681BB213AD /* ImageView+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F546F9DE30A288E045A1B84959FB6668 /* ImageView+Kingfisher.swift */; }; D21DD561BCC0D2EF5B9B1319124C8D1A /* ImageDrawing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 879F4067EF153C14EC90AAE4EA118336 /* ImageDrawing.swift */; }; D9590C17E41D62CC27A58AC2836C59DA /* RequestModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DA59BB9E8A87141E073CE72BDD99BC /* RequestModifier.swift */; }; DA9EEC18D35228922F194DA58B15AD5B /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCEB2213725FC494BDA16FA35849487 /* Result.swift */; }; E218B986DC9BA5FC6CAA19C46F03697C /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FE9F26A9A815F87AD017B9B0C7BC197 /* AnimatedImageView.swift */; }; E2DCF4C882BBACD61228861C397BF296 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE0AB9CCD2C4AFC6D8D50F9630647C3E /* Source.swift */; }; E374AA3080D36EE4A62D9A4C7BE0E311 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFFD203F1BC694DEB46318F5988C2F9 /* ImageCache.swift */; }; E8589C3E477B5F56BE06A672933BC3E3 /* ImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA3F3E60C4F43651912DA787A64DAB6 /* ImageFormat.swift */; }; EAC77AC658D564B79E0E9F7760AA23BC /* KFImageOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825F2BAAA183227414C87DF5F878FDDD /* KFImageOptions.swift */; }; F7FE727CE285679D6737186EDABC362B /* ImageModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F50932A7B3F44BDACD5C062F68544E0F /* ImageModifier.swift */; }; F921BFCE551DF93CE51B08E0BA2938DA /* KingfisherOptionsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3430F086F35AD4204EBA7F74CBF9A9E1 /* KingfisherOptionsInfo.swift */; }; FD9F6C848F43A7A6C85CA041C19A1EFD /* RetryStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07878660D9C440CC10E18A09434E4887 /* RetryStrategy.swift */; }; FE6B55BC30EB3F494FA8046D4A378969 /* GraphicsContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB79DDF0F8F366348DE09C405FC2FCF /* GraphicsContext.swift */; }; FE718B42F18FBEE7F7F4558A4A2EAED4 /* AVAssetImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1060E0740C5ADF16A0F6D5574A42AA56 /* AVAssetImageDataProvider.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 07878660D9C440CC10E18A09434E4887 /* RetryStrategy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RetryStrategy.swift; path = Sources/Networking/RetryStrategy.swift; sourceTree = ""; }; 0CFF10C376DEEF81E3DB5E6669A39693 /* ImageDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDataProvider.swift; path = Sources/General/ImageSource/ImageDataProvider.swift; sourceTree = ""; }; 0F51F760B52B5D5095A5BAF956EE8629 /* Image.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Image.swift; path = Sources/Image/Image.swift; sourceTree = ""; }; 0FE9F26A9A815F87AD017B9B0C7BC197 /* AnimatedImageView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedImageView.swift; path = Sources/Views/AnimatedImageView.swift; sourceTree = ""; }; 1060E0740C5ADF16A0F6D5574A42AA56 /* AVAssetImageDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AVAssetImageDataProvider.swift; path = Sources/General/ImageSource/AVAssetImageDataProvider.swift; sourceTree = ""; }; 1307C8B64AE65B9C79418184E51060BD /* ImageTransition.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageTransition.swift; path = Sources/Image/ImageTransition.swift; sourceTree = ""; }; 152D5D9EEE4D7712656A02D7C3B5FBB0 /* Kingfisher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Kingfisher-dummy.m"; sourceTree = ""; }; 1660797AA6A1A171FD765DE92799300F /* Kingfisher-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Kingfisher-umbrella.h"; sourceTree = ""; }; 1AF9789FE18E7B9F33CC543BA3CED2DA /* Resource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Resource.swift; path = Sources/General/ImageSource/Resource.swift; sourceTree = ""; }; 1CB0E65E2B073BB78DB5564B879AB771 /* WKInterfaceImage+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "WKInterfaceImage+Kingfisher.swift"; path = "Sources/Extensions/WKInterfaceImage+Kingfisher.swift"; sourceTree = ""; }; 216DA7D5B16F73B3A52B0CB76305B4D5 /* ImageBinder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageBinder.swift; path = Sources/SwiftUI/ImageBinder.swift; sourceTree = ""; }; 2CCEB2213725FC494BDA16FA35849487 /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Sources/Utility/Result.swift; sourceTree = ""; }; 2EB79DDF0F8F366348DE09C405FC2FCF /* GraphicsContext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GraphicsContext.swift; path = Sources/Image/GraphicsContext.swift; sourceTree = ""; }; 33BF0E964FA29F866CD8B28277961591 /* ImageDownloaderDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloaderDelegate.swift; path = Sources/Networking/ImageDownloaderDelegate.swift; sourceTree = ""; }; 3430F086F35AD4204EBA7F74CBF9A9E1 /* KingfisherOptionsInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherOptionsInfo.swift; path = Sources/General/KingfisherOptionsInfo.swift; sourceTree = ""; }; 39088D402ED82C4218E19B8EF8D280F4 /* Kingfisher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Kingfisher.modulemap; sourceTree = ""; }; 435D5BCE722A5F39DD54924CC255406F /* Kingfisher */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Kingfisher; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4748A76BE82A9DC1879F1A198128AC3E /* Kingfisher.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Kingfisher.release.xcconfig; sourceTree = ""; }; 48ED39E5A43BE5ED6F038D8A66347D4E /* KFImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFImage.swift; path = Sources/SwiftUI/KFImage.swift; sourceTree = ""; }; 5687F7E0F61D656A97BBA71BFF462011 /* ImageDataProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDataProcessor.swift; path = Sources/Networking/ImageDataProcessor.swift; sourceTree = ""; }; 59C5FD04D3AB489CF738CBBFDFCE5645 /* NSButton+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSButton+Kingfisher.swift"; path = "Sources/Extensions/NSButton+Kingfisher.swift"; sourceTree = ""; }; 62DA59BB9E8A87141E073CE72BDD99BC /* RequestModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestModifier.swift; path = Sources/Networking/RequestModifier.swift; sourceTree = ""; }; 633AC15E4EE8A866A836986B762DB0B3 /* Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Kingfisher.swift; path = Sources/General/Kingfisher.swift; sourceTree = ""; }; 63E9577EFC69E5728414BDFBDD74FD43 /* SessionDataTask.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDataTask.swift; path = Sources/Networking/SessionDataTask.swift; sourceTree = ""; }; 681A3D233AD8CB4CEBEFB3C2C8AE48F9 /* Filter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Filter.swift; path = Sources/Image/Filter.swift; sourceTree = ""; }; 74C91878B1568D723465D0180ECE2DD3 /* Box.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Box.swift; path = Sources/Utility/Box.swift; sourceTree = ""; }; 7AC4ABF307D42FBBF32D6EF94C31169B /* FormatIndicatedCacheSerializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FormatIndicatedCacheSerializer.swift; path = Sources/Cache/FormatIndicatedCacheSerializer.swift; sourceTree = ""; }; 7AC4B5542F3EFBD53D4FBF5E30E74CAB /* KFOptionsSetter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFOptionsSetter.swift; path = Sources/General/KFOptionsSetter.swift; sourceTree = ""; }; 7BB538EAFED24250FDEE778B618A00E7 /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Sources/Networking/SessionDelegate.swift; sourceTree = ""; }; 813F3E11E4A16360D9AA5E0AF664776B /* KingfisherManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherManager.swift; path = Sources/General/KingfisherManager.swift; sourceTree = ""; }; 825F2BAAA183227414C87DF5F878FDDD /* KFImageOptions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFImageOptions.swift; path = Sources/SwiftUI/KFImageOptions.swift; sourceTree = ""; }; 870F0060843B0434250A105A568B47F7 /* String+MD5.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+MD5.swift"; path = "Sources/Utility/String+MD5.swift"; sourceTree = ""; }; 8780C2E124F1D5FB5D997FB3AAE2AFC9 /* ExtensionHelpers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExtensionHelpers.swift; path = Sources/Utility/ExtensionHelpers.swift; sourceTree = ""; }; 879F4067EF153C14EC90AAE4EA118336 /* ImageDrawing.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDrawing.swift; path = Sources/Image/ImageDrawing.swift; sourceTree = ""; }; 87FECBBB0A860F907D2BDDEA825274DC /* MemoryStorage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MemoryStorage.swift; path = Sources/Cache/MemoryStorage.swift; sourceTree = ""; }; 88CCE5E028CED8E1C7C2B3C49C7CD302 /* ImageProgressive.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProgressive.swift; path = Sources/Image/ImageProgressive.swift; sourceTree = ""; }; 8ABD31869B41A448550CA44602EE9EDC /* CallbackQueue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CallbackQueue.swift; path = Sources/Utility/CallbackQueue.swift; sourceTree = ""; }; 8BBF545F6F083EE1DB1DE5D5B69BDCFC /* GIFAnimatedImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GIFAnimatedImage.swift; path = Sources/Image/GIFAnimatedImage.swift; sourceTree = ""; }; 9419B8317819EC649DB926295C2A6030 /* Kingfisher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Kingfisher-Info.plist"; sourceTree = ""; }; 943D6B9A2FD53F71E3F8CEA1A382B188 /* ImageProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProcessor.swift; path = Sources/Image/ImageProcessor.swift; sourceTree = ""; }; 949C76451365A57DFFCF495B622A2238 /* KingfisherError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherError.swift; path = Sources/General/KingfisherError.swift; sourceTree = ""; }; A02FF3EC55763EA593D42C89449A7B37 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; A4A0AA97B763CD1C6C59DC16DDF1AB9E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; AE5735A709E5B9B77B8C0F50A8A361A7 /* Kingfisher.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Kingfisher.debug.xcconfig; sourceTree = ""; }; AEA3F3E60C4F43651912DA787A64DAB6 /* ImageFormat.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageFormat.swift; path = Sources/Image/ImageFormat.swift; sourceTree = ""; }; BB06E8B10338824390D5266D8EAE5E78 /* ImagePrefetcher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImagePrefetcher.swift; path = Sources/Networking/ImagePrefetcher.swift; sourceTree = ""; }; BCF98A2E2B5A4ED5ACF144ACA59EAA5D /* KF.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KF.swift; path = Sources/General/KF.swift; sourceTree = ""; }; BE0AB9CCD2C4AFC6D8D50F9630647C3E /* Source.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Source.swift; path = Sources/General/ImageSource/Source.swift; sourceTree = ""; }; C70F13ED40BD782B2FD171DBDFC7298D /* UIButton+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIButton+Kingfisher.swift"; path = "Sources/Extensions/UIButton+Kingfisher.swift"; sourceTree = ""; }; CF859DBAD5CFEAA9C43C167097CDADD8 /* SizeExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SizeExtensions.swift; path = Sources/Utility/SizeExtensions.swift; sourceTree = ""; }; D1ABCFC660C8FFEE085F31A86BC1E5F4 /* Storage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Storage.swift; path = Sources/Cache/Storage.swift; sourceTree = ""; }; D6352786185F851F454BA7D97F1D6759 /* Delegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Delegate.swift; path = Sources/Utility/Delegate.swift; sourceTree = ""; }; D6BCBB00723EB97F178C77F2BFEBDD45 /* AuthenticationChallengeResponsable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthenticationChallengeResponsable.swift; path = Sources/Networking/AuthenticationChallengeResponsable.swift; sourceTree = ""; }; DA3599A36919BB92EEE708D5E3778242 /* ImageDownloader.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloader.swift; path = Sources/Networking/ImageDownloader.swift; sourceTree = ""; }; DBFFD203F1BC694DEB46318F5988C2F9 /* ImageCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageCache.swift; path = Sources/Cache/ImageCache.swift; sourceTree = ""; }; DE28E72CDEE4E7DB38DFC20F531F140B /* NSTextAttachment+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSTextAttachment+Kingfisher.swift"; path = "Sources/Extensions/NSTextAttachment+Kingfisher.swift"; sourceTree = ""; }; E02F7451A4E45E7584C83738A0B72256 /* Placeholder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Placeholder.swift; path = Sources/Image/Placeholder.swift; sourceTree = ""; }; E518CACDBAA22A0E0F7D6961DAC786B2 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Accelerate.framework; sourceTree = DEVELOPER_DIR; }; E5E0FF9F3BA7EE48060442B25E3923D1 /* DiskStorage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DiskStorage.swift; path = Sources/Cache/DiskStorage.swift; sourceTree = ""; }; E8F006D807C4BBE9DCDE95AFDCB75783 /* Indicator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Indicator.swift; path = Sources/Views/Indicator.swift; sourceTree = ""; }; E97062F6396DBC3332A358BF00A05AB1 /* CacheSerializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CacheSerializer.swift; path = Sources/Cache/CacheSerializer.swift; sourceTree = ""; }; F2EB7FE01806F8598CC1B5C6C746469B /* RedirectHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedirectHandler.swift; path = Sources/Networking/RedirectHandler.swift; sourceTree = ""; }; F50932A7B3F44BDACD5C062F68544E0F /* ImageModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageModifier.swift; path = Sources/Networking/ImageModifier.swift; sourceTree = ""; }; F546F9DE30A288E045A1B84959FB6668 /* ImageView+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ImageView+Kingfisher.swift"; path = "Sources/Extensions/ImageView+Kingfisher.swift"; sourceTree = ""; }; FAAA2DFD301775D3612D223AE20371D6 /* Runtime.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Runtime.swift; path = Sources/Utility/Runtime.swift; sourceTree = ""; }; FBF81C72312B11BAA7F67F39B90FA725 /* TVMonogramView+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TVMonogramView+Kingfisher.swift"; path = "Sources/Extensions/TVMonogramView+Kingfisher.swift"; sourceTree = ""; }; FD6F699AC27735BA88574D327D06C88D /* Kingfisher-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Kingfisher-prefix.pch"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ BE6AB1CD62FB670FAFEB33207A0B597E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 009A9E47D01F8A0C32F66F0B448F46CC /* Accelerate.framework in Frameworks */, 7FA4133CEE22E228CFEDD8CF1C195F2B /* CFNetwork.framework in Frameworks */, 18E98EFA5F90947B984F906B55A239E8 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 079F20D08577B6F43BEA1CEFE96BEB9C /* Frameworks */ = { isa = PBXGroup; children = ( 8F013E1EAD5571A2A7CAABA9CC641BDA /* iOS */, ); name = Frameworks; sourceTree = ""; }; 20CA6EA7FB899863F7254E4183CC9A57 = { isa = PBXGroup; children = ( 079F20D08577B6F43BEA1CEFE96BEB9C /* Frameworks */, F7D1741736415F44BA49769E6BDABE36 /* Kingfisher */, A755E2513C05448C4156B48674672668 /* Products */, ); sourceTree = ""; }; 8F013E1EAD5571A2A7CAABA9CC641BDA /* iOS */ = { isa = PBXGroup; children = ( E518CACDBAA22A0E0F7D6961DAC786B2 /* Accelerate.framework */, A02FF3EC55763EA593D42C89449A7B37 /* CFNetwork.framework */, A4A0AA97B763CD1C6C59DC16DDF1AB9E /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; 93E68C97367E9D75A46CD5B186E40992 /* Support Files */ = { isa = PBXGroup; children = ( 39088D402ED82C4218E19B8EF8D280F4 /* Kingfisher.modulemap */, 152D5D9EEE4D7712656A02D7C3B5FBB0 /* Kingfisher-dummy.m */, 9419B8317819EC649DB926295C2A6030 /* Kingfisher-Info.plist */, FD6F699AC27735BA88574D327D06C88D /* Kingfisher-prefix.pch */, 1660797AA6A1A171FD765DE92799300F /* Kingfisher-umbrella.h */, AE5735A709E5B9B77B8C0F50A8A361A7 /* Kingfisher.debug.xcconfig */, 4748A76BE82A9DC1879F1A198128AC3E /* Kingfisher.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/Kingfisher"; sourceTree = ""; }; A755E2513C05448C4156B48674672668 /* Products */ = { isa = PBXGroup; children = ( 435D5BCE722A5F39DD54924CC255406F /* Kingfisher */, ); name = Products; sourceTree = ""; }; F7D1741736415F44BA49769E6BDABE36 /* Kingfisher */ = { isa = PBXGroup; children = ( 0FE9F26A9A815F87AD017B9B0C7BC197 /* AnimatedImageView.swift */, D6BCBB00723EB97F178C77F2BFEBDD45 /* AuthenticationChallengeResponsable.swift */, 1060E0740C5ADF16A0F6D5574A42AA56 /* AVAssetImageDataProvider.swift */, 74C91878B1568D723465D0180ECE2DD3 /* Box.swift */, E97062F6396DBC3332A358BF00A05AB1 /* CacheSerializer.swift */, 8ABD31869B41A448550CA44602EE9EDC /* CallbackQueue.swift */, D6352786185F851F454BA7D97F1D6759 /* Delegate.swift */, E5E0FF9F3BA7EE48060442B25E3923D1 /* DiskStorage.swift */, 8780C2E124F1D5FB5D997FB3AAE2AFC9 /* ExtensionHelpers.swift */, 681A3D233AD8CB4CEBEFB3C2C8AE48F9 /* Filter.swift */, 7AC4ABF307D42FBBF32D6EF94C31169B /* FormatIndicatedCacheSerializer.swift */, 8BBF545F6F083EE1DB1DE5D5B69BDCFC /* GIFAnimatedImage.swift */, 2EB79DDF0F8F366348DE09C405FC2FCF /* GraphicsContext.swift */, 0F51F760B52B5D5095A5BAF956EE8629 /* Image.swift */, 216DA7D5B16F73B3A52B0CB76305B4D5 /* ImageBinder.swift */, DBFFD203F1BC694DEB46318F5988C2F9 /* ImageCache.swift */, 5687F7E0F61D656A97BBA71BFF462011 /* ImageDataProcessor.swift */, 0CFF10C376DEEF81E3DB5E6669A39693 /* ImageDataProvider.swift */, DA3599A36919BB92EEE708D5E3778242 /* ImageDownloader.swift */, 33BF0E964FA29F866CD8B28277961591 /* ImageDownloaderDelegate.swift */, 879F4067EF153C14EC90AAE4EA118336 /* ImageDrawing.swift */, AEA3F3E60C4F43651912DA787A64DAB6 /* ImageFormat.swift */, F50932A7B3F44BDACD5C062F68544E0F /* ImageModifier.swift */, BB06E8B10338824390D5266D8EAE5E78 /* ImagePrefetcher.swift */, 943D6B9A2FD53F71E3F8CEA1A382B188 /* ImageProcessor.swift */, 88CCE5E028CED8E1C7C2B3C49C7CD302 /* ImageProgressive.swift */, 1307C8B64AE65B9C79418184E51060BD /* ImageTransition.swift */, F546F9DE30A288E045A1B84959FB6668 /* ImageView+Kingfisher.swift */, E8F006D807C4BBE9DCDE95AFDCB75783 /* Indicator.swift */, BCF98A2E2B5A4ED5ACF144ACA59EAA5D /* KF.swift */, 48ED39E5A43BE5ED6F038D8A66347D4E /* KFImage.swift */, 825F2BAAA183227414C87DF5F878FDDD /* KFImageOptions.swift */, 7AC4B5542F3EFBD53D4FBF5E30E74CAB /* KFOptionsSetter.swift */, 633AC15E4EE8A866A836986B762DB0B3 /* Kingfisher.swift */, 949C76451365A57DFFCF495B622A2238 /* KingfisherError.swift */, 813F3E11E4A16360D9AA5E0AF664776B /* KingfisherManager.swift */, 3430F086F35AD4204EBA7F74CBF9A9E1 /* KingfisherOptionsInfo.swift */, 87FECBBB0A860F907D2BDDEA825274DC /* MemoryStorage.swift */, 59C5FD04D3AB489CF738CBBFDFCE5645 /* NSButton+Kingfisher.swift */, DE28E72CDEE4E7DB38DFC20F531F140B /* NSTextAttachment+Kingfisher.swift */, E02F7451A4E45E7584C83738A0B72256 /* Placeholder.swift */, F2EB7FE01806F8598CC1B5C6C746469B /* RedirectHandler.swift */, 62DA59BB9E8A87141E073CE72BDD99BC /* RequestModifier.swift */, 1AF9789FE18E7B9F33CC543BA3CED2DA /* Resource.swift */, 2CCEB2213725FC494BDA16FA35849487 /* Result.swift */, 07878660D9C440CC10E18A09434E4887 /* RetryStrategy.swift */, FAAA2DFD301775D3612D223AE20371D6 /* Runtime.swift */, 63E9577EFC69E5728414BDFBDD74FD43 /* SessionDataTask.swift */, 7BB538EAFED24250FDEE778B618A00E7 /* SessionDelegate.swift */, CF859DBAD5CFEAA9C43C167097CDADD8 /* SizeExtensions.swift */, BE0AB9CCD2C4AFC6D8D50F9630647C3E /* Source.swift */, D1ABCFC660C8FFEE085F31A86BC1E5F4 /* Storage.swift */, 870F0060843B0434250A105A568B47F7 /* String+MD5.swift */, FBF81C72312B11BAA7F67F39B90FA725 /* TVMonogramView+Kingfisher.swift */, C70F13ED40BD782B2FD171DBDFC7298D /* UIButton+Kingfisher.swift */, 1CB0E65E2B073BB78DB5564B879AB771 /* WKInterfaceImage+Kingfisher.swift */, 93E68C97367E9D75A46CD5B186E40992 /* Support Files */, ); name = Kingfisher; path = Kingfisher; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 36C09C6814A638531F3A051D6C6ED380 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 298717B4D8172E7075AE7FAF1E3E3B97 /* Kingfisher-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 05BB3BFEAA03766641420DCD88978468 /* Kingfisher */ = { isa = PBXNativeTarget; buildConfigurationList = B6926DC4A63433720DB0689FB26B5A83 /* Build configuration list for PBXNativeTarget "Kingfisher" */; buildPhases = ( 36C09C6814A638531F3A051D6C6ED380 /* Headers */, 96561980FB10934EF184955554212A9D /* Sources */, BE6AB1CD62FB670FAFEB33207A0B597E /* Frameworks */, EBC00B065C36DA88889E574C73984F4B /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Kingfisher; productName = Kingfisher; productReference = 435D5BCE722A5F39DD54924CC255406F /* Kingfisher */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ A17CCB4923D030263240808F7BC3C92D /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 3E251839724B92B7A217EDE187B2C60F /* Build configuration list for PBXProject "Kingfisher" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 20CA6EA7FB899863F7254E4183CC9A57; productRefGroup = A755E2513C05448C4156B48674672668 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 05BB3BFEAA03766641420DCD88978468 /* Kingfisher */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ EBC00B065C36DA88889E574C73984F4B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 96561980FB10934EF184955554212A9D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E218B986DC9BA5FC6CAA19C46F03697C /* AnimatedImageView.swift in Sources */, 33E477C91EDD27D85FF73F24D3EE0EFD /* AuthenticationChallengeResponsable.swift in Sources */, FE718B42F18FBEE7F7F4558A4A2EAED4 /* AVAssetImageDataProvider.swift in Sources */, 74EBCC7C42D8BCA61F375D98F24EAA93 /* Box.swift in Sources */, 68C266999376A54A23E07826862DA440 /* CacheSerializer.swift in Sources */, 59DD17E9A9A03043F2CBD1948E781769 /* CallbackQueue.swift in Sources */, 51BCC42FFEC8F3D53043A9B8A0BB4D37 /* Delegate.swift in Sources */, 39E3A64C2EF9C5347686B57E4FC7AFB2 /* DiskStorage.swift in Sources */, 9E0229D37EB89FD1DCD5E67634E1A62B /* ExtensionHelpers.swift in Sources */, 92D89759C2D5F6644DC7C2D2E03B2BDD /* Filter.swift in Sources */, 9F8BC3E6AEA88EE0F633648B364BC9FB /* FormatIndicatedCacheSerializer.swift in Sources */, 5276029CD6731C940503B2CFC9D93C7F /* GIFAnimatedImage.swift in Sources */, FE6B55BC30EB3F494FA8046D4A378969 /* GraphicsContext.swift in Sources */, 9DEAC78AB2C941F250F6BDFCE43586B3 /* Image.swift in Sources */, 20ABCD53B035203F866F73EA0B83731A /* ImageBinder.swift in Sources */, E374AA3080D36EE4A62D9A4C7BE0E311 /* ImageCache.swift in Sources */, 843DBEF3521809674AA4606A6ADA19A1 /* ImageDataProcessor.swift in Sources */, 9B80AC78167933EE0DBA568741114B7F /* ImageDataProvider.swift in Sources */, A6E4BF22B9D00E287EF8D8DBA0CF5BC2 /* ImageDownloader.swift in Sources */, 747C1F3BECBC41CA56F392B033C24037 /* ImageDownloaderDelegate.swift in Sources */, D21DD561BCC0D2EF5B9B1319124C8D1A /* ImageDrawing.swift in Sources */, E8589C3E477B5F56BE06A672933BC3E3 /* ImageFormat.swift in Sources */, F7FE727CE285679D6737186EDABC362B /* ImageModifier.swift in Sources */, 45B672CC51026E979C8897B02FBAE3AF /* ImagePrefetcher.swift in Sources */, 44A7662375A26C49CB4DFB3D545ECD5D /* ImageProcessor.swift in Sources */, 6410E99320F5871A84099068A7365BDD /* ImageProgressive.swift in Sources */, A6797F90FF228586D31EFCB007C04A5A /* ImageTransition.swift in Sources */, CBB91E6B72B26064566443681BB213AD /* ImageView+Kingfisher.swift in Sources */, 439CC25AF8061ADA3B3559BD9C82C638 /* Indicator.swift in Sources */, 036768B3E3DD45D26BA6E2A3CF5D4877 /* KF.swift in Sources */, 01D91DEFCE943FE1652E281B20750557 /* KFImage.swift in Sources */, EAC77AC658D564B79E0E9F7760AA23BC /* KFImageOptions.swift in Sources */, 5D6C540026B79ADC1E1E39B528E44604 /* KFOptionsSetter.swift in Sources */, 8A2977E80420FA65D97E80C29DEC35CB /* Kingfisher.swift in Sources */, 3D980A610BE4423096BDC5E78E0EEAD0 /* Kingfisher-dummy.m in Sources */, 97B0EC6E75F956435E01FA1B5BA0CE65 /* KingfisherError.swift in Sources */, B8AB0023D19E13459FAFBCE711294EA8 /* KingfisherManager.swift in Sources */, F921BFCE551DF93CE51B08E0BA2938DA /* KingfisherOptionsInfo.swift in Sources */, 2FF3FBDC108FD38E9C99D01054084F39 /* MemoryStorage.swift in Sources */, 2576D77E0980C2498D305E6BFB92438F /* NSButton+Kingfisher.swift in Sources */, 40AE4A5231163E23BB5D2E120A0ED41B /* NSTextAttachment+Kingfisher.swift in Sources */, A540839A448152A428FA8F04E5B58D99 /* Placeholder.swift in Sources */, 4B27B0C61BD4559F8F1BB5AD52BBF5E6 /* RedirectHandler.swift in Sources */, D9590C17E41D62CC27A58AC2836C59DA /* RequestModifier.swift in Sources */, 848AF3BDEF73627067D56F5B6620EDD1 /* Resource.swift in Sources */, DA9EEC18D35228922F194DA58B15AD5B /* Result.swift in Sources */, FD9F6C848F43A7A6C85CA041C19A1EFD /* RetryStrategy.swift in Sources */, A0289CAD357E2176617F6AC5D88AAA48 /* Runtime.swift in Sources */, 15EFC8950D083FBE2FB3315A2716B612 /* SessionDataTask.swift in Sources */, 1D63FD969DA4E8DB4F8511884E1D5654 /* SessionDelegate.swift in Sources */, C2D7FC1E503179BD6E288403BEFB0513 /* SizeExtensions.swift in Sources */, E2DCF4C882BBACD61228861C397BF296 /* Source.swift in Sources */, 84124AD6027C6BD4D1C44E3570B1F47F /* Storage.swift in Sources */, 89A86A6FFF5D108B731722F7311366A5 /* String+MD5.swift in Sources */, 116E8D4B27521314299F86B710B38EF4 /* TVMonogramView+Kingfisher.swift in Sources */, 54CE1092A1E30C04AA7EC2A6DC2547B8 /* UIButton+Kingfisher.swift in Sources */, B58856DD55C5EA413D70153C932F8EAB /* WKInterfaceImage+Kingfisher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 5760C9AC75169B9426B31209A4D6235B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; 7E3EAFE2FE67BDDA5058FBB2BFE6CABD /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = AE5735A709E5B9B77B8C0F50A8A361A7 /* Kingfisher.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/Kingfisher/Kingfisher-prefix.pch"; INFOPLIST_FILE = "Target Support Files/Kingfisher/Kingfisher-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/Kingfisher/Kingfisher.modulemap"; PRODUCT_MODULE_NAME = Kingfisher; PRODUCT_NAME = Kingfisher; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; BF9BD488678208045B4D71F7CE6EBBCF /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4748A76BE82A9DC1879F1A198128AC3E /* Kingfisher.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/Kingfisher/Kingfisher-prefix.pch"; INFOPLIST_FILE = "Target Support Files/Kingfisher/Kingfisher-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/Kingfisher/Kingfisher.modulemap"; PRODUCT_MODULE_NAME = Kingfisher; PRODUCT_NAME = Kingfisher; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; EE7961669F9E9DBEDA9BAFA8940E873C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3E251839724B92B7A217EDE187B2C60F /* Build configuration list for PBXProject "Kingfisher" */ = { isa = XCConfigurationList; buildConfigurations = ( EE7961669F9E9DBEDA9BAFA8940E873C /* Debug */, 5760C9AC75169B9426B31209A4D6235B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B6926DC4A63433720DB0689FB26B5A83 /* Build configuration list for PBXNativeTarget "Kingfisher" */ = { isa = XCConfigurationList; buildConfigurations = ( 7E3EAFE2FE67BDDA5058FBB2BFE6CABD /* Debug */, BF9BD488678208045B4D71F7CE6EBBCF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = A17CCB4923D030263240808F7BC3C92D /* Project object */; } ================================================ FILE: JetChat/Pods/Localize-Swift/LICENSE ================================================ Copyright (c) 2015 Roy Marmelstein (http://roysapps.com/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/Localize-Swift/README.md ================================================ [![Platform](https://img.shields.io/cocoapods/p/Localize-Swift.svg?maxAge=2592000)](http://cocoapods.org/?q=Localize-Swift) [![Version](http://img.shields.io/cocoapods/v/Localize-Swift.svg)](http://cocoapods.org/?q=Localize-Swift) [![Build Status](https://travis-ci.org/marmelroy/Localize-Swift.svg?branch=master)](https://travis-ci.org/marmelroy/Localize-Swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) # Localize-Swift Localize-Swift is a simple framework that improves i18n and localization in Swift iOS apps - providing cleaner syntax and in-app language switching.

## Features - Keep the Localizable.strings file your app already uses. - Allow your users to change the app's language without changing their device language. - Use .localized() instead of NSLocalizedString(key,comment) - a more Swifty syntax. - Generate your strings with a new genstrings swift/python script that recognises .localized(). ## Usage Import Localize at the top of each Swift file that will contain localized text. If CocoaPods - ```swift import Localize_Swift ``` Add `.localized()` following any `String` object you want translated: ```swift textLabel.text = "Hello World".localized() ``` To get an array of available localizations: ```swift Localize.availableLanguages() ``` To change the current language: ```swift Localize.setCurrentLanguage("fr") ``` To update the UI in the view controller where a language change can take place, observe LCLLanguageChangeNotification: ```swift NotificationCenter.default.addObserver(self, selector: #selector(setText), name: NSNotification.Name(LCLLanguageChangeNotification), object: nil) ``` To reset back to the default app language: ```swift Localize.resetCurrentLanguageToDefault() ``` ## genstrings To support this new i18n syntax, Localize-Swift includes custom genstrings swift script. Copy the genstrings.swift file into your project's root folder and run with ```bash ./genstrings.swift ``` This will print the collected strings in the terminal. Select and copy to your default Localizable.strings. The script includes the ability to specify excluded directories and files (by editing the script). ### [Preferrred] Setting up with [Swift Package Manager](https://swiftpm.co/?query=Localize-Swift) The [Swift Package Manager](https://swift.org/package-manager/) is now the preferred tool for distributing Localize-Swift. From Xcode 11+ : 1. Select File > Swift Packages > Add Package Dependency. Enter `https://github.com/marmelroy/Localize-Swift.git` in the "Choose Package Repository" dialog. 2. In the next page, specify the version resolving rule as "Up to Next Major" with "3.2.0". 3. After Xcode checked out the source and resolving the version, you can choose the "Localize-Swift" library and add it to your app target. For more info, read [Adding Package Dependencies to Your App](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app) from Apple. Alternatively, you can also add Localize-Swift to your `Package.swift` file: ```swift dependencies: [ .package(url: "https://github.com/marmelroy/Localize-Swift.git", .upToNextMajor(from: "3.2.0")) ] ``` ### Setting up with Carthage [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application. You can install Carthage with [Homebrew](http://brew.sh/) using the following command: ```bash $ brew update $ brew install carthage ``` To integrate Localize-Swift into your Xcode project using Carthage, specify it in your `Cartfile`: ```ogdl github "marmelroy/Localize-Swift" ``` ### Setting up with [CocoaPods](http://cocoapods.org/?q=Localize-Swift) ```ruby source 'https://github.com/CocoaPods/Specs.git' pod 'Localize-Swift', '~> 3.2' ``` ================================================ FILE: JetChat/Pods/Localize-Swift/Sources/Localize.swift ================================================ // // Localize.swift // Localize // // Created by Roy Marmelstein on 05/08/2015. // Copyright © 2015 Roy Marmelstein. All rights reserved. // import Foundation /// Internal current language key let LCLCurrentLanguageKey = "LCLCurrentLanguageKey" /// Default language. English. If English is unavailable defaults to base localization. let LCLDefaultLanguage = "en" /// Base bundle as fallback. let LCLBaseBundle = "Base" /// Name for language change notification public let LCLLanguageChangeNotification = "LCLLanguageChangeNotification" // MARK: Localization Syntax /** Swift 1.x friendly localization syntax, replaces NSLocalizedString - Parameter string: Key to be localized. - Returns: The localized string. */ public func Localized(_ string: String) -> String { return string.localized() } /** Swift 1.x friendly localization syntax with format arguments, replaces String(format:NSLocalizedString) - Parameter string: Key to be localized. - Returns: The formatted localized string with arguments. */ public func Localized(_ string: String, arguments: CVarArg...) -> String { return String(format: string.localized(), arguments: arguments) } /** Swift 1.x friendly plural localization syntax with a format argument - parameter string: String to be formatted - parameter argument: Argument to determine pluralisation - returns: Pluralized localized string. */ public func LocalizedPlural(_ string: String, argument: CVarArg) -> String { return string.localizedPlural(argument) } public extension String { /** Swift 2 friendly localization syntax, replaces NSLocalizedString - Returns: The localized string. */ func localized() -> String { return localized(using: nil, in: .main) } /** Swift 2 friendly localization syntax with format arguments, replaces String(format:NSLocalizedString) - Returns: The formatted localized string with arguments. */ func localizedFormat(_ arguments: CVarArg...) -> String { return String(format: localized(), arguments: arguments) } /** Swift 2 friendly plural localization syntax with a format argument - parameter argument: Argument to determine pluralisation - returns: Pluralized localized string. */ func localizedPlural(_ argument: CVarArg) -> String { return NSString.localizedStringWithFormat(localized() as NSString, argument) as String } /** Add comment for NSLocalizedString - Returns: The localized string. */ func commented(_ argument: String) -> String { return self } } // MARK: Language Setting Functions open class Localize: NSObject { /** List available languages - Returns: Array of available languages. */ open class func availableLanguages(_ excludeBase: Bool = false) -> [String] { var availableLanguages = Bundle.main.localizations // If excludeBase = true, don't include "Base" in available languages if let indexOfBase = availableLanguages.firstIndex(of: "Base") , excludeBase == true { availableLanguages.remove(at: indexOfBase) } return availableLanguages } /** Current language - Returns: The current language. String. */ open class func currentLanguage() -> String { if let currentLanguage = UserDefaults.standard.object(forKey: LCLCurrentLanguageKey) as? String { return currentLanguage } return defaultLanguage() } /** Change the current language - Parameter language: Desired language. */ open class func setCurrentLanguage(_ language: String) { let selectedLanguage = availableLanguages().contains(language) ? language : defaultLanguage() if (selectedLanguage != currentLanguage()){ UserDefaults.standard.set(selectedLanguage, forKey: LCLCurrentLanguageKey) UserDefaults.standard.synchronize() NotificationCenter.default.post(name: Notification.Name(rawValue: LCLLanguageChangeNotification), object: nil) } } /** Default language - Returns: The app's default language. String. */ open class func defaultLanguage() -> String { var defaultLanguage: String = String() guard let preferredLanguage = Bundle.main.preferredLocalizations.first else { return LCLDefaultLanguage } let availableLanguages: [String] = self.availableLanguages() if (availableLanguages.contains(preferredLanguage)) { defaultLanguage = preferredLanguage } else { defaultLanguage = LCLDefaultLanguage } return defaultLanguage } /** Resets the current language to the default */ open class func resetCurrentLanguageToDefault() { setCurrentLanguage(self.defaultLanguage()) } /** Get the current language's display name for a language. - Parameter language: Desired language. - Returns: The localized string. */ open class func displayNameForLanguage(_ language: String) -> String { let locale : NSLocale = NSLocale(localeIdentifier: currentLanguage()) if let displayName = locale.displayName(forKey: NSLocale.Key.identifier, value: language) { return displayName } return String() } } ================================================ FILE: JetChat/Pods/Localize-Swift/Sources/Localize_Swift.h ================================================ // // Localize_Swift.h // Localize_Swift // // Created by Roy Marmelstein on 21/01/2016. // Copyright © 2020 Roy Marmelstein. All rights reserved. // @import Foundation; //! Project version number for Localize_Swift. FOUNDATION_EXPORT double Localize_SwiftVersionNumber; //! Project version string for Localize_Swift. FOUNDATION_EXPORT const unsigned char Localize_SwiftVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import ================================================ FILE: JetChat/Pods/Localize-Swift/Sources/String+LocalizeBundle.swift ================================================ // // String+LocalizeBundle.swift // Localize_Swift // // Copyright © 2020 Roy Marmelstein. All rights reserved. // import Foundation /// bundle friendly extension public extension String { /** Swift 2 friendly localization syntax, replaces NSLocalizedString. - parameter bundle: The receiver’s bundle to search. If bundle is `nil`, the method attempts to use main bundle. - returns: The localized string. */ func localized(in bundle: Bundle?) -> String { return localized(using: nil, in: bundle) } /** Swift 2 friendly localization syntax with format arguments, replaces String(format:NSLocalizedString). - parameter arguments: arguments values for temlpate (substituted according to the user’s default locale). - parameter bundle: The receiver’s bundle to search. If bundle is `nil`, the method attempts to use main bundle. - returns: The formatted localized string with arguments. */ func localizedFormat(arguments: CVarArg..., in bundle: Bundle?) -> String { return String(format: localized(in: bundle), arguments: arguments) } /** Swift 2 friendly plural localization syntax with a format argument. - parameter argument: Argument to determine pluralisation. - parameter bundle: The receiver’s bundle to search. If bundle is `nil`, the method attempts to use main bundle. - returns: Pluralized localized string. */ func localizedPlural(argument: CVarArg, in bundle: Bundle?) -> String { return NSString.localizedStringWithFormat(localized(in: bundle) as NSString, argument) as String } } ================================================ FILE: JetChat/Pods/Localize-Swift/Sources/String+LocalizeTableName.swift ================================================ // // String+LocalizeTableName.swift // Localize_Swift // // Copyright © 2020 Vitalii Budnik. All rights reserved. // import Foundation /// tableName friendly extension public extension String { /** Swift 2 friendly localization syntax, replaces NSLocalizedString. - parameter tableName: The receiver’s string table to search. If tableName is `nil` or is an empty string, the method attempts to use `Localizable.strings`. - returns: The localized string. */ func localized(using tableName: String?) -> String { return localized(using: tableName, in: .main) } /** Swift 2 friendly localization syntax with format arguments, replaces String(format:NSLocalizedString). - parameter arguments: arguments values for temlpate (substituted according to the user’s default locale). - parameter tableName: The receiver’s string table to search. If tableName is `nil` or is an empty string, the method attempts to use `Localizable.strings`. - returns: The formatted localized string with arguments. */ func localizedFormat(arguments: CVarArg..., using tableName: String?) -> String { return String(format: localized(using: tableName), arguments: arguments) } /** Swift 2 friendly plural localization syntax with a format argument. - parameter argument: Argument to determine pluralisation. - parameter tableName: The receiver’s string table to search. If tableName is `nil` or is an empty string, the method attempts to use `Localizable.strings`. - returns: Pluralized localized string. */ func localizedPlural(argument: CVarArg, using tableName: String?) -> String { return NSString.localizedStringWithFormat(localized(using: tableName) as NSString, argument) as String } } ================================================ FILE: JetChat/Pods/Localize-Swift/Sources/String+LocalizedBundleTableName.swift ================================================ // // String+LocalizedBundleTableName.swift // Localize_Swift // // Copyright © 2020 Roy Marmelstein. All rights reserved. // import Foundation /// bundle & tableName friendly extension public extension String { /** Swift 2 friendly localization syntax, replaces NSLocalizedString. - parameter tableName: The receiver’s string table to search. If tableName is `nil` or is an empty string, the method attempts to use `Localizable.strings`. - parameter bundle: The receiver’s bundle to search. If bundle is `nil`, the method attempts to use main bundle. - returns: The localized string. */ func localized(using tableName: String?, in bundle: Bundle?) -> String { let bundle: Bundle = bundle ?? .main if let path = bundle.path(forResource: Localize.currentLanguage(), ofType: "lproj"), let bundle = Bundle(path: path) { return bundle.localizedString(forKey: self, value: nil, table: tableName) } else if let path = bundle.path(forResource: LCLBaseBundle, ofType: "lproj"), let bundle = Bundle(path: path) { return bundle.localizedString(forKey: self, value: nil, table: tableName) } return self } /** Swift 2 friendly localization syntax with format arguments, replaces String(format:NSLocalizedString). - parameter arguments: arguments values for temlpate (substituted according to the user’s default locale). - parameter tableName: The receiver’s string table to search. If tableName is `nil` or is an empty string, the method attempts to use `Localizable.strings`. - parameter bundle: The receiver’s bundle to search. If bundle is `nil`, the method attempts to use main bundle. - returns: The formatted localized string with arguments. */ func localizedFormat(arguments: CVarArg..., using tableName: String?, in bundle: Bundle?) -> String { return String(format: localized(using: tableName, in: bundle), arguments: arguments) } /** Swift 2 friendly plural localization syntax with a format argument. - parameter argument: Argument to determine pluralisation. - parameter tableName: The receiver’s string table to search. If tableName is `nil` or is an empty string, the method attempts to use `Localizable.strings`. - parameter bundle: The receiver’s bundle to search. If bundle is `nil`, the method attempts to use main bundle. - returns: Pluralized localized string. */ func localizedPlural(argument: CVarArg, using tableName: String?, in bundle: Bundle?) -> String { return NSString.localizedStringWithFormat(localized(using: tableName, in: bundle) as NSString, argument) as String } } ================================================ FILE: JetChat/Pods/Localize-Swift/Sources/UI/IBDesignable+Localize.swift ================================================ // // IBDesignable+Localize1.swift // Localize-Swift // // Copyright © 2020 Roy Marmelstein. All rights reserved. // import Foundation import UIKit // MARK: - UILabel localize Key extention for language in story board @IBDesignable public extension UILabel { @IBInspectable var localizeKey: String? { set { // set new value from dictionary DispatchQueue.main.async { self.text = newValue?.localized() } } get { return self.text } } } // MARK: - UIButton localize Key extention for language in story board @IBDesignable public extension UIButton { @IBInspectable var localizeKey: String? { set { // set new value from dictionary DispatchQueue.main.async { self.setTitle(newValue?.localized(), for: .normal) } } get { return self.titleLabel?.text } } } // MARK: - UITextView localize Key extention for language in story board @IBDesignable public extension UITextView { @IBInspectable var localizeKey: String? { set { // set new value from dictionary DispatchQueue.main.async { self.text = newValue?.localized() } } get { return self.text } } } // MARK: - UITextField localize Key extention for language in story board @IBDesignable public extension UITextField { @IBInspectable var localizeKey: String? { set { // set new value from dictionary DispatchQueue.main.async { self.placeholder = newValue?.localized() } } get { return self.placeholder } } } // MARK: - UINavigationItem localize Key extention for language in story board @IBDesignable public extension UINavigationItem { @IBInspectable var localizeKey: String? { set { // set new value from dictionary DispatchQueue.main.async { self.title = newValue?.localized() } } get { return self.title } } } ================================================ FILE: JetChat/Pods/Localize-Swift.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 562526A350A3507214D3C248022FB091 /* String+LocalizedBundleTableName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CD8A1F339E0CAF64F2BC78E227D6CB /* String+LocalizedBundleTableName.swift */; }; 6B8BAF555A67FE32A7C71E265E38F0FE /* Localize.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D9DD3DDF1A73DDC70C590747D8BD71 /* Localize.swift */; }; 78BBE5569B4DB715BD90BDC5AA134151 /* Localize-Swift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D9BD93C04BE9C93E4A3D1033D4235DC /* Localize-Swift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; B411877E8124EDB88B5D582067CD6B59 /* String+LocalizeBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA940ECA7A240A32F79EC87848CA6F90 /* String+LocalizeBundle.swift */; }; C5F78ABF9F36CB943539E2E201C9F9BA /* Localize-Swift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 01147648C02CB56994428A2600F9208A /* Localize-Swift-dummy.m */; }; CB86A43185D4CFBB866879904305717B /* Localize_Swift.h in Headers */ = {isa = PBXBuildFile; fileRef = E24A4FFF4D0A1FD3608740D4FFEB3E2D /* Localize_Swift.h */; settings = {ATTRIBUTES = (Public, ); }; }; D24B08C7B6F806622C5984D3D842A9A4 /* String+LocalizeTableName.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7689FCF052350A6D1C5525D60C155E /* String+LocalizeTableName.swift */; }; E0BF16131EDF69B9B7313997FA0959BA /* IBDesignable+Localize.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C057DE0BFD50CC0D7DFB3F87D7339F /* IBDesignable+Localize.swift */; }; E76DAABA3904A3CD69C06E8C09C7DF05 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D299050059E30E53CD0769FFA27C6768 /* Foundation.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 002AAFD136E4C8B2E48B8C68210316F5 /* Localize-Swift */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Localize-Swift"; path = Localize_Swift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 01147648C02CB56994428A2600F9208A /* Localize-Swift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Localize-Swift-dummy.m"; sourceTree = ""; }; 3B71C32C6D826D5CC8C63FCF82B2CBED /* Localize-Swift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Localize-Swift-Info.plist"; sourceTree = ""; }; 3D9BD93C04BE9C93E4A3D1033D4235DC /* Localize-Swift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Localize-Swift-umbrella.h"; sourceTree = ""; }; 4721524987FEC6C5BDB4A0366F0C7499 /* Localize-Swift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Localize-Swift-prefix.pch"; sourceTree = ""; }; 5440ABCAA9347E21354405211B81AE16 /* Localize-Swift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Localize-Swift.release.xcconfig"; sourceTree = ""; }; 84CD8A1F339E0CAF64F2BC78E227D6CB /* String+LocalizedBundleTableName.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+LocalizedBundleTableName.swift"; path = "Sources/String+LocalizedBundleTableName.swift"; sourceTree = ""; }; 84EC2E07DB1D4548029171AEA2EAFCF9 /* Localize-Swift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Localize-Swift.debug.xcconfig"; sourceTree = ""; }; AA0C561E6EB1FFA48C77CF4E53AAB4C5 /* Localize-Swift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Localize-Swift.modulemap"; sourceTree = ""; }; AA940ECA7A240A32F79EC87848CA6F90 /* String+LocalizeBundle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+LocalizeBundle.swift"; path = "Sources/String+LocalizeBundle.swift"; sourceTree = ""; }; B8D9DD3DDF1A73DDC70C590747D8BD71 /* Localize.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Localize.swift; path = Sources/Localize.swift; sourceTree = ""; }; BC7689FCF052350A6D1C5525D60C155E /* String+LocalizeTableName.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+LocalizeTableName.swift"; path = "Sources/String+LocalizeTableName.swift"; sourceTree = ""; }; C4C057DE0BFD50CC0D7DFB3F87D7339F /* IBDesignable+Localize.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IBDesignable+Localize.swift"; path = "Sources/UI/IBDesignable+Localize.swift"; sourceTree = ""; }; D299050059E30E53CD0769FFA27C6768 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; E24A4FFF4D0A1FD3608740D4FFEB3E2D /* Localize_Swift.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Localize_Swift.h; path = Sources/Localize_Swift.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 00D3A1E09722D209C11F74F00B60F521 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E76DAABA3904A3CD69C06E8C09C7DF05 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 133FDE709B55EF5E7A71C12F79E3A170 = { isa = PBXGroup; children = ( A3555A78EB2EF0B12CE1B4D61D4C10B8 /* Frameworks */, CA73BBB67B807D9C9897AD34EF14B0E2 /* Localize-Swift */, AC877C340C697CE4125CA540C3A5A59A /* Products */, ); sourceTree = ""; }; 60DADA81B85834ECB5E1AE7FBE062D38 /* Support Files */ = { isa = PBXGroup; children = ( AA0C561E6EB1FFA48C77CF4E53AAB4C5 /* Localize-Swift.modulemap */, 01147648C02CB56994428A2600F9208A /* Localize-Swift-dummy.m */, 3B71C32C6D826D5CC8C63FCF82B2CBED /* Localize-Swift-Info.plist */, 4721524987FEC6C5BDB4A0366F0C7499 /* Localize-Swift-prefix.pch */, 3D9BD93C04BE9C93E4A3D1033D4235DC /* Localize-Swift-umbrella.h */, 84EC2E07DB1D4548029171AEA2EAFCF9 /* Localize-Swift.debug.xcconfig */, 5440ABCAA9347E21354405211B81AE16 /* Localize-Swift.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/Localize-Swift"; sourceTree = ""; }; 7DA4DCAC9825D9C14B7EE9C05FE423A8 /* UIKit */ = { isa = PBXGroup; children = ( C4C057DE0BFD50CC0D7DFB3F87D7339F /* IBDesignable+Localize.swift */, ); name = UIKit; sourceTree = ""; }; A3555A78EB2EF0B12CE1B4D61D4C10B8 /* Frameworks */ = { isa = PBXGroup; children = ( D4278DF2AE209B5A1139F710A820BE42 /* iOS */, ); name = Frameworks; sourceTree = ""; }; A740427EF45B02D297264BDC9DEC6587 /* LocalizeSwiftCore */ = { isa = PBXGroup; children = ( B8D9DD3DDF1A73DDC70C590747D8BD71 /* Localize.swift */, E24A4FFF4D0A1FD3608740D4FFEB3E2D /* Localize_Swift.h */, AA940ECA7A240A32F79EC87848CA6F90 /* String+LocalizeBundle.swift */, 84CD8A1F339E0CAF64F2BC78E227D6CB /* String+LocalizedBundleTableName.swift */, BC7689FCF052350A6D1C5525D60C155E /* String+LocalizeTableName.swift */, ); name = LocalizeSwiftCore; sourceTree = ""; }; AC877C340C697CE4125CA540C3A5A59A /* Products */ = { isa = PBXGroup; children = ( 002AAFD136E4C8B2E48B8C68210316F5 /* Localize-Swift */, ); name = Products; sourceTree = ""; }; CA73BBB67B807D9C9897AD34EF14B0E2 /* Localize-Swift */ = { isa = PBXGroup; children = ( A740427EF45B02D297264BDC9DEC6587 /* LocalizeSwiftCore */, 60DADA81B85834ECB5E1AE7FBE062D38 /* Support Files */, 7DA4DCAC9825D9C14B7EE9C05FE423A8 /* UIKit */, ); name = "Localize-Swift"; path = "Localize-Swift"; sourceTree = ""; }; D4278DF2AE209B5A1139F710A820BE42 /* iOS */ = { isa = PBXGroup; children = ( D299050059E30E53CD0769FFA27C6768 /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ C806DE5A93110DB5C8D076787B0A117B /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 78BBE5569B4DB715BD90BDC5AA134151 /* Localize-Swift-umbrella.h in Headers */, CB86A43185D4CFBB866879904305717B /* Localize_Swift.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 6C27EEC63FEC06A9DAB03531B0989F47 /* Localize-Swift */ = { isa = PBXNativeTarget; buildConfigurationList = 43AE869A729607C5FDE5CEB3341E886A /* Build configuration list for PBXNativeTarget "Localize-Swift" */; buildPhases = ( C806DE5A93110DB5C8D076787B0A117B /* Headers */, 718D544A94F77CFF58BFDAC4214F9667 /* Sources */, 00D3A1E09722D209C11F74F00B60F521 /* Frameworks */, 71EE512872BE967A3321D215949A0B27 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "Localize-Swift"; productName = Localize_Swift; productReference = 002AAFD136E4C8B2E48B8C68210316F5 /* Localize-Swift */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F452240E32906E5EF4F114447023D9FB /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 293D25F2BDA86DFC063B8C0C9B5881A4 /* Build configuration list for PBXProject "Localize-Swift" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 133FDE709B55EF5E7A71C12F79E3A170; productRefGroup = AC877C340C697CE4125CA540C3A5A59A /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 6C27EEC63FEC06A9DAB03531B0989F47 /* Localize-Swift */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 71EE512872BE967A3321D215949A0B27 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 718D544A94F77CFF58BFDAC4214F9667 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E0BF16131EDF69B9B7313997FA0959BA /* IBDesignable+Localize.swift in Sources */, 6B8BAF555A67FE32A7C71E265E38F0FE /* Localize.swift in Sources */, C5F78ABF9F36CB943539E2E201C9F9BA /* Localize-Swift-dummy.m in Sources */, B411877E8124EDB88B5D582067CD6B59 /* String+LocalizeBundle.swift in Sources */, 562526A350A3507214D3C248022FB091 /* String+LocalizedBundleTableName.swift in Sources */, D24B08C7B6F806622C5984D3D842A9A4 /* String+LocalizeTableName.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ A131D05C19BD446370EF28BF2AD2A0B9 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5440ABCAA9347E21354405211B81AE16 /* Localize-Swift.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/Localize-Swift/Localize-Swift-prefix.pch"; INFOPLIST_FILE = "Target Support Files/Localize-Swift/Localize-Swift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/Localize-Swift/Localize-Swift.modulemap"; PRODUCT_MODULE_NAME = Localize_Swift; PRODUCT_NAME = Localize_Swift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; D4216941692F09DBB4495AC138CFB93E /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 84EC2E07DB1D4548029171AEA2EAFCF9 /* Localize-Swift.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/Localize-Swift/Localize-Swift-prefix.pch"; INFOPLIST_FILE = "Target Support Files/Localize-Swift/Localize-Swift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/Localize-Swift/Localize-Swift.modulemap"; PRODUCT_MODULE_NAME = Localize_Swift; PRODUCT_NAME = Localize_Swift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; EE37C7C4B8026456DDAC4325C66CEB5A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; F9C08840DEB3545189ED9FB3B9A104C9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 293D25F2BDA86DFC063B8C0C9B5881A4 /* Build configuration list for PBXProject "Localize-Swift" */ = { isa = XCConfigurationList; buildConfigurations = ( F9C08840DEB3545189ED9FB3B9A104C9 /* Debug */, EE37C7C4B8026456DDAC4325C66CEB5A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 43AE869A729607C5FDE5CEB3341E886A /* Build configuration list for PBXNativeTarget "Localize-Swift" */ = { isa = XCConfigurationList; buildConfigurations = ( D4216941692F09DBB4495AC138CFB93E /* Debug */, A131D05C19BD446370EF28BF2AD2A0B9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F452240E32906E5EF4F114447023D9FB /* Project object */; } ================================================ FILE: JetChat/Pods/LookinServer/LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: JetChat/Pods/LookinServer/README.md ================================================ ![Preview](https://cdn.lookin.work/public/style/images/independent/homepage/preview_en_1x.jpg "Preview") Lookin 可以查看与修改 iOS App 里的 UI 对象,类似于 Xcode 自带的 UI Inspector 工具,或另一款叫做 Reveal 的软件。 官网:https://lookin.work/ 如果这是你的 iOS 项目第一次使用 Lookin,则需要先把 Lookin 的 iOS Framework 嵌入到你的 iOS 项目中,教程: https://lookin.work/faq/integration-guide/ ---------- You can inspect and modify views in iOS app via Lookin, just like UI Inspector in Xcode, or another app called Reveal. Website:https://lookin.work/ To use Lookin, you need to embed the iOS Framework of Lookin into your iOS project. Tutorial: https://lookin.work/faq/integration-guide/ ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/CALayer+LookinServer.h ================================================ // // UIView+LookinMobile.h // WeRead // // Created by Li Kai on 2018/11/30. // Copyright © 2018 tencent. All rights reserved. // #import "LookinDefines.h" #import "TargetConditionals.h" #import @interface CALayer (LookinServer) @property(nonatomic, weak) UIView *lks_hostView; - (UIWindow *)lks_window; - (CGRect)lks_frameInWindow:(UIWindow *)window; @property(nonatomic, assign) BOOL lks_isLookinPrivateLayer; /// 如果该属性为 YES,则该 layer 及所有下级 layers 均不会被转为 LookinDisplayItem @property(nonatomic, assign) BOOL lks_avoidCapturing; - (UIImage *)lks_groupScreenshotWithLowQuality:(BOOL)lowQuality; /// 当没有 sublayers 时,该方法返回 nil - (UIImage *)lks_soloScreenshotWithLowQuality:(BOOL)lowQuality; /// 获取和该对象有关的对象的 Class 层级树 - (NSArray *> *)lks_relatedClassChainList; - (NSArray *)lks_selfRelation; @property(nonatomic, strong) UIColor *lks_backgroundColor; @property(nonatomic, strong) UIColor *lks_borderColor; @property(nonatomic, strong) UIColor *lks_shadowColor; @property(nonatomic, assign) CGFloat lks_shadowOffsetWidth; @property(nonatomic, assign) CGFloat lks_shadowOffsetHeight; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/CALayer+LookinServer.m ================================================ // // UIView+LookinMobile.m // WeRead // // Created by Li Kai on 2018/11/30. // Copyright © 2018 tencent. All rights reserved. // #import "CALayer+LookinServer.h" #import "LKS_HierarchyDisplayItemsMaker.h" #import "LookinDisplayItem.h" #import "LKS_LocalInspectManager.h" #import #import "LKS_ConnectionManager.h" #import "LookinIvarTrace.h" #import "LookinServerDefines.h" @implementation CALayer (LookinServer) - (void)setLks_isLookinPrivateLayer:(BOOL)lks_isLookinPrivateLayer { [self lookin_bindBOOL:lks_isLookinPrivateLayer forKey:@"lks_isLookinPrivateLayer"]; } - (BOOL)lks_isLookinPrivateLayer { return [self lookin_getBindBOOLForKey:@"lks_isLookinPrivateLayer"]; } - (UIWindow *)lks_window { CALayer *layer = self; while (layer) { UIView *hostView = layer.lks_hostView; if (hostView.window) { return hostView.window; } else if ([hostView isKindOfClass:[UIWindow class]]) { return (UIWindow *)hostView; } layer = layer.superlayer; } return nil; } - (BOOL)lks_inLookinPrivateHierarchy { BOOL boolValue = NO; CALayer *layer = self; while (layer) { if (layer.lks_isLookinPrivateLayer) { boolValue = YES; break; } layer = layer.superlayer; } return boolValue; } - (CGRect)lks_frameInWindow:(UIWindow *)window { UIWindow *selfWindow = [self lks_window]; if (!selfWindow) { return CGRectZero; } CGRect rectInSelfWindow = [selfWindow.layer convertRect:self.frame fromLayer:self.superlayer]; CGRect rectInWindow = [window convertRect:rectInSelfWindow fromWindow:selfWindow]; return rectInWindow; } - (void)setLks_avoidCapturing:(BOOL)lks_avoidCapturing { [self lookin_bindBOOL:lks_avoidCapturing forKey:@"lks_avoidCapturing"]; } - (BOOL)lks_avoidCapturing { return [self lookin_getBindBOOLForKey:@"lks_avoidCapturing"]; } #pragma mark - Host View - (void)setLks_hostView:(UIView *)lks_hostView { [self lookin_bindObjectWeakly:lks_hostView forKey:@"lks_hostView"]; } - (UIView *)lks_hostView { return [self lookin_getBindObjectForKey:@"lks_hostView"]; } #pragma mark - Screenshot - (UIImage *)lks_groupScreenshotWithLowQuality:(BOOL)lowQuality { CGFloat screenScale = [UIScreen mainScreen].scale; CGFloat pixelWidth = self.bounds.size.width * screenScale; CGFloat pixelHeight = self.bounds.size.height * screenScale; if (pixelWidth <= 0 || pixelHeight <= 0) { return nil; } CGFloat renderScale = lowQuality ? 1 : 0; CGFloat maxLength = MAX(pixelWidth, pixelHeight); if (maxLength > LookinNodeImageMaxLengthInPx) { // 确保最终绘制出的图片长和宽都不能超过 LookinNodeImageMaxLengthInPx // 如果算出的 renderScale 大于 1 则取 1,因为似乎用 1 渲染的速度要比一个别的奇怪的带小数点的数字要更快 renderScale = MIN(screenScale * LookinNodeImageMaxLengthInPx / maxLength, 1); } UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, renderScale); CGContextRef context = UIGraphicsGetCurrentContext(); if (self.lks_hostView && !self.lks_hostView.lks_isChildrenViewOfTabBar) { [self.lks_hostView drawViewHierarchyInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) afterScreenUpdates:YES]; } else { [self renderInContext:context]; } UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } - (UIImage *)lks_soloScreenshotWithLowQuality:(BOOL)lowQuality { if (!self.sublayers.count) { return nil; } CGFloat screenScale = [UIScreen mainScreen].scale; CGFloat pixelWidth = self.bounds.size.width * screenScale; CGFloat pixelHeight = self.bounds.size.height * screenScale; if (pixelWidth <= 0 || pixelHeight <= 0) { return nil; } CGFloat renderScale = lowQuality ? 1 : 0; CGFloat maxLength = MAX(pixelWidth, pixelHeight); if (maxLength > LookinNodeImageMaxLengthInPx) { // 确保最终绘制出的图片长和宽都不能超过 LookinNodeImageMaxLengthInPx // 如果算出的 renderScale 大于 1 则取 1,因为似乎用 1 渲染的速度要比一个别的奇怪的带小数点的数字要更快 renderScale = MIN(screenScale * LookinNodeImageMaxLengthInPx / maxLength, 1); } if (self.sublayers.count) { NSArray *sublayers = [self.sublayers copy]; NSMutableArray *visibleSublayers = [NSMutableArray arrayWithCapacity:sublayers.count]; [sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) { if (!sublayer.hidden) { sublayer.hidden = YES; [visibleSublayers addObject:sublayer]; } }]; UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, renderScale); CGContextRef context = UIGraphicsGetCurrentContext(); if (self.lks_hostView && !self.lks_hostView.lks_isChildrenViewOfTabBar) { [self.lks_hostView drawViewHierarchyInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) afterScreenUpdates:YES]; } else { [self renderInContext:context]; } UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); [visibleSublayers enumerateObjectsUsingBlock:^(CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) { sublayer.hidden = NO; }]; return image; } return nil; } - (NSArray *> *)lks_relatedClassChainList { NSMutableArray *array = [NSMutableArray arrayWithCapacity:2]; if (self.lks_hostView) { [array addObject:[CALayer lks_getClassListOfObject:self.lks_hostView endingClass:@"UIView"]]; if (self.lks_hostView.lks_hostViewController) { [array addObject:[CALayer lks_getClassListOfObject:self.lks_hostView.lks_hostViewController endingClass:@"UIViewController"]]; } } else { [array addObject:[CALayer lks_getClassListOfObject:self endingClass:@"CALayer"]]; } return array.copy; } + (NSArray *)lks_getClassListOfObject:(id)object endingClass:(NSString *)endingClass { NSArray *completedList = [object lks_classChainListWithSwiftPrefix:NO]; NSUInteger endingIdx = [completedList indexOfObject:endingClass]; if (endingIdx != NSNotFound) { completedList = [completedList subarrayWithRange:NSMakeRange(0, endingIdx + 1)]; } return completedList; } - (NSArray *)lks_selfRelation { NSMutableArray *array = [NSMutableArray array]; NSMutableArray *ivarTraces = [NSMutableArray array]; if (self.lks_hostView) { if (self.lks_hostView.lks_hostViewController) { [array addObject:[NSString stringWithFormat:@"(%@ *).view", NSStringFromClass(self.lks_hostView.lks_hostViewController.class)]]; [ivarTraces addObjectsFromArray:self.lks_hostView.lks_hostViewController.lks_ivarTraces]; } [ivarTraces addObjectsFromArray:self.lks_hostView.lks_ivarTraces]; } else { [ivarTraces addObjectsFromArray:self.lks_ivarTraces]; } if (ivarTraces.count) { [array addObjectsFromArray:[ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *value) { return [NSString stringWithFormat:@"(%@ *) -> %@", value.hostClassName, value.ivarName]; }]]; } return array.count ? array.copy : nil; } - (UIColor *)lks_backgroundColor { return [UIColor colorWithCGColor:self.backgroundColor]; } - (void)setLks_backgroundColor:(UIColor *)lks_backgroundColor { self.backgroundColor = lks_backgroundColor.CGColor; } - (UIColor *)lks_borderColor { return [UIColor colorWithCGColor:self.borderColor]; } - (void)setLks_borderColor:(UIColor *)lks_borderColor { self.borderColor = lks_borderColor.CGColor; } - (UIColor *)lks_shadowColor { return [UIColor colorWithCGColor:self.shadowColor]; } - (void)setLks_shadowColor:(UIColor *)lks_shadowColor { self.shadowColor = lks_shadowColor.CGColor; } - (CGFloat)lks_shadowOffsetWidth { return self.shadowOffset.width; } - (void)setLks_shadowOffsetWidth:(CGFloat)lks_shadowOffsetWidth { self.shadowOffset = CGSizeMake(lks_shadowOffsetWidth, self.shadowOffset.height); } - (CGFloat)lks_shadowOffsetHeight { return self.shadowOffset.height; } - (void)setLks_shadowOffsetHeight:(CGFloat)lks_shadowOffsetHeight { self.shadowOffset = CGSizeMake(self.shadowOffset.width, lks_shadowOffsetHeight); } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/NSObject+LookinServer.h ================================================ // // NSObject+LookinServer.h // LookinServer // // Created by Li Kai on 2019/4/21. // https://lookin.work // #import "LookinDefines.h" #import @class LookinIvarTrace; @interface NSObject (LookinServer) #pragma mark - oid /// 如果 oid 不存在则会创建新的 oid - (unsigned long)lks_registerOid; /// 0 表示不存在 @property(nonatomic, assign) unsigned long lks_oid; + (NSObject *)lks_objectWithOid:(unsigned long)oid; #pragma mark - trace @property(nonatomic, copy) NSArray *lks_ivarTraces; @property(nonatomic, copy) NSString *lks_specialTrace; + (void)lks_clearAllObjectsTraces; /** 获取当前对象的 Class 层级树,如 @[@"UIView", @"UIResponder", @"NSObject"] hasSwiftPrefix 决定了是否在 Swift 项目中显示类名前缀 */ - (NSArray *)lks_classChainListWithSwiftPrefix:(BOOL)hasSwiftPrefix; /// 返回当前类名,Swift 项目下将返回不带前缀的名称 - (NSString *)lks_shortClassName; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/NSObject+LookinServer.m ================================================ // // NSObject+LookinServer.m // LookinServer // // Created by Li Kai on 2019/4/21. // https://lookin.work // #import "NSObject+Lookin.h" #import "NSObject+LookinServer.h" #import "LookinServerDefines.h" #import "LKS_ObjectRegistry.h" #import @implementation NSObject (LookinServer) #pragma mark - oid - (unsigned long)lks_registerOid { if (!self.lks_oid) { unsigned long oid = [[LKS_ObjectRegistry sharedInstance] addObject:self]; self.lks_oid = oid; } return self.lks_oid; } - (void)setLks_oid:(unsigned long)lks_oid { [self lookin_bindObject:@(lks_oid) forKey:@"lks_oid"]; } - (unsigned long)lks_oid { NSNumber *number = [self lookin_getBindObjectForKey:@"lks_oid"]; return [number unsignedLongValue]; } + (NSObject *)lks_objectWithOid:(unsigned long)oid { return [[LKS_ObjectRegistry sharedInstance] objectWithOid:oid]; } #pragma mark - trace - (void)setLks_ivarTraces:(NSArray *)lks_ivarTraces { [self lookin_bindObject:lks_ivarTraces.copy forKey:@"lks_ivarTraces"]; if (lks_ivarTraces) { [[NSObject lks_allObjectsWithTraces] addPointer:(void *)self]; } } - (NSArray *)lks_ivarTraces { return [self lookin_getBindObjectForKey:@"lks_ivarTraces"]; } - (void)setLks_specialTrace:(NSString *)lks_specialTrace { [self lookin_bindObject:lks_specialTrace forKey:@"lks_specialTrace"]; if (lks_specialTrace) { [[NSObject lks_allObjectsWithTraces] addPointer:(void *)self]; } } - (NSString *)lks_specialTrace { return [self lookin_getBindObjectForKey:@"lks_specialTrace"]; } + (void)lks_clearAllObjectsTraces { [[[NSObject lks_allObjectsWithTraces] allObjects] enumerateObjectsUsingBlock:^(NSObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.lks_ivarTraces = nil; obj.lks_specialTrace = nil; }]; [NSObject lks_allObjectsWithTraces].count = 0; } + (NSPointerArray *)lks_allObjectsWithTraces { static dispatch_once_t onceToken; static NSPointerArray *lks_allObjectsWithTraces = nil; dispatch_once(&onceToken,^{ lks_allObjectsWithTraces = [NSPointerArray weakObjectsPointerArray]; }); return lks_allObjectsWithTraces; } - (NSArray *)lks_classChainListWithSwiftPrefix:(BOOL)hasSwiftPrefix { NSMutableArray *classChainList = [NSMutableArray array]; Class currentClass = self.class; while (currentClass) { NSString *rawClassName = NSStringFromClass(currentClass); NSString *currentClassName = hasSwiftPrefix ? rawClassName : [rawClassName lookin_shortClassNameString]; if (currentClassName) { [classChainList addObject:currentClassName]; } currentClass = [currentClass superclass]; } return classChainList.copy; } - (NSString *)lks_shortClassName { NSString *rawName = NSStringFromClass([self class]); NSString *shortName = [rawName lookin_shortClassNameString]; return shortName; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIBlurEffect+LookinServer.h ================================================ // // UIBlurEffect+LookinServer.h // LookinServer // // Created by Li Kai on 2019/10/8. // https://lookin.work // #import @interface UIBlurEffect (LookinServer) /// 该 number 包装的对象是 UIBlurEffectStyle,之所以用 NSNumber 是因为想把 0 和 nil 区分开,毕竟这里是在 hook 系统,稳一点好。 @property(nonatomic, strong) NSNumber *lks_effectStyleNumber; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIBlurEffect+LookinServer.m ================================================ // // UIBlurEffect+LookinServer.m // LookinServer // // Created by Li Kai on 2019/10/8. // https://lookin.work // #import "UIBlurEffect+LookinServer.h" #import "NSObject+Lookin.h" #import @implementation UIBlurEffect (LookinServer) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method oriMethod = class_getClassMethod([self class], @selector(effectWithStyle:)); Method newMethod = class_getClassMethod([self class], @selector(lks_effectWithStyle:)); method_exchangeImplementations(oriMethod, newMethod); }); } + (UIBlurEffect *)lks_effectWithStyle:(UIBlurEffectStyle)style { id effect = [self lks_effectWithStyle:style]; if ([effect respondsToSelector:@selector(setLks_effectStyleNumber:)]) { [effect setLks_effectStyleNumber:@(style)]; } return effect; } - (void)setLks_effectStyleNumber:(NSNumber *)lks_effectStyleNumber { [self lookin_bindObject:lks_effectStyleNumber forKey:@"lks_effectStyleNumber"]; } - (NSNumber *)lks_effectStyleNumber { return [self lookin_getBindObjectForKey:@"lks_effectStyleNumber"]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIColor+LookinServer.h ================================================ // // UIColor+LookinServer.h // LookinServer // // Created by Li Kai on 2019/6/5. // https://lookin.work // #import @interface UIColor (LookinServer) - (NSArray *)lks_rgbaComponents; + (instancetype)lks_colorFromRGBAComponents:(NSArray *)components; - (NSString *)lks_rgbaString; - (NSString *)lks_hexString; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIColor+LookinServer.m ================================================ // // UIColor+LookinServer.m // LookinServer // // Created by Li Kai on 2019/6/5. // https://lookin.work // #import "UIColor+LookinServer.h" @implementation UIColor (LookinServer) - (NSArray *)lks_rgbaComponents { CGFloat r, g, b, a; CGColorRef cgColor = [self CGColor]; const CGFloat *components = CGColorGetComponents(cgColor); if (CGColorGetNumberOfComponents(cgColor) == 4) { r = components[0]; g = components[1]; b = components[2]; a = components[3]; } else if (CGColorGetNumberOfComponents(cgColor) == 2) { r = components[0]; g = components[0]; b = components[0]; a = components[1]; } else if (CGColorGetNumberOfComponents(cgColor) == 1) { r = components[0]; g = components[0]; b = components[0]; a = components[0]; } else { r = 0; g = 0; b = 0; a = 0; NSAssert(NO, @""); } NSArray *rgba = @[@(r), @(g), @(b), @(a)]; return rgba; } + (instancetype)lks_colorFromRGBAComponents:(NSArray *)components { if (!components) { return nil; } if (components.count != 4) { NSAssert(NO, @""); return nil; } UIColor *color = [UIColor colorWithRed:components[0].doubleValue green:components[1].doubleValue blue:components[2].doubleValue alpha:components[3].doubleValue]; return color; } - (NSString *)lks_rgbaString { CGFloat r, g, b, a; CGColorRef cgColor = [self CGColor]; const CGFloat *components = CGColorGetComponents(cgColor); if (CGColorGetNumberOfComponents(cgColor) == 4) { r = components[0]; g = components[1]; b = components[2]; a = components[3]; } else if (CGColorGetNumberOfComponents(cgColor) == 2) { r = components[0]; g = components[0]; b = components[0]; a = components[1]; } else { r = 0; g = 0; b = 0; a = 0; NSAssert(NO, @""); } if (a >= 1) { return [NSString stringWithFormat:@"(%.0f, %.0f, %.0f)", r * 255, g * 255, b * 255]; } else { return [NSString stringWithFormat:@"(%.0f, %.0f, %.0f, %.2f)", r * 255, g * 255, b * 255, a]; } } - (NSString *)lks_hexString { CGFloat r, g, b, a; CGColorRef cgColor = [self CGColor]; const CGFloat *components = CGColorGetComponents(cgColor); if (CGColorGetNumberOfComponents(cgColor) == 4) { r = components[0]; g = components[1]; b = components[2]; a = components[3]; } else if (CGColorGetNumberOfComponents(cgColor) == 2) { r = components[0]; g = components[0]; b = components[0]; a = components[1]; } else { r = 0; g = 0; b = 0; a = 0; NSAssert(NO, @""); } NSInteger red = r * 255; NSInteger green = g * 255; NSInteger blue = b * 255; NSInteger alpha = a * 255; return [[NSString stringWithFormat:@"#%@%@%@%@", [UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:alpha]], [UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:red]], [UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:green]], [UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:blue]]] lowercaseString]; } // 对于色值只有单位数的,在前面补一个0,例如“F”会补齐为“0F” + (NSString *)_alignColorHexStringLength:(NSString *)hexString { return hexString.length < 2 ? [@"0" stringByAppendingString:hexString] : hexString; } + (NSString *)_hexStringWithInteger:(NSInteger)integer { NSString *hexString = @""; NSInteger remainder = 0; for (NSInteger i = 0; i < 9; i++) { remainder = integer % 16; integer = integer / 16; NSString *letter = [self _hexLetterStringWithInteger:remainder]; hexString = [letter stringByAppendingString:hexString]; if (integer == 0) { break; } } return hexString; } + (NSString *)_hexLetterStringWithInteger:(NSInteger)integer { NSAssert(integer < 16, @"要转换的数必须是16进制里的个位数,也即小于16,但你传给我是%@", @(integer)); NSString *letter = nil; switch (integer) { case 10: letter = @"A"; break; case 11: letter = @"B"; break; case 12: letter = @"C"; break; case 13: letter = @"D"; break; case 14: letter = @"E"; break; case 15: letter = @"F"; break; default: letter = [[NSString alloc]initWithFormat:@"%@", @(integer)]; break; } return letter; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIGestureRecognizer+LookinServer.h ================================================ // // UIGestureRecognizer+LookinServer.h // LookinServer // // Created by Li Kai on 2019/8/14. // https://lookin.work // #import @class LookinTwoTuple; @interface UIGestureRecognizer (LookinServer) /// tuple.first => LookinWeakContainer(包裹着 target),tuple.second => action(方法名字符串) @property(nonatomic, strong) NSMutableArray *lks_targetActions; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIGestureRecognizer+LookinServer.m ================================================ // // UIGestureRecognizer+LookinServer.m // LookinServer // // Created by Li Kai on 2019/8/14. // https://lookin.work // #import "UIGestureRecognizer+LookinServer.h" #import #import "NSObject+Lookin.h" #import "LookinTuple.h" #import "LookinWeakContainer.h" #import "LookinServerDefines.h" @implementation UIGestureRecognizer (LookinServer) #pragma mark - Hook + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method oriMethod = class_getInstanceMethod([self class], @selector(initWithTarget:action:)); Method newMethod = class_getInstanceMethod([self class], @selector(lks_initWithTarget:action:)); method_exchangeImplementations(oriMethod, newMethod); oriMethod = class_getInstanceMethod([self class], @selector(addTarget:action:)); newMethod = class_getInstanceMethod([self class], @selector(lks_addTarget:action:)); method_exchangeImplementations(oriMethod, newMethod); oriMethod = class_getInstanceMethod([self class], @selector(removeTarget:action:)); newMethod = class_getInstanceMethod([self class], @selector(lks_removeTarget:action:)); method_exchangeImplementations(oriMethod, newMethod); }); } - (instancetype)lks_initWithTarget:(nullable id)target action:(nullable SEL)action { UIGestureRecognizer *instance = [self lks_initWithTarget:target action:action]; [instance lks_didAddTarget:target action:NSStringFromSelector(action)]; return instance; } - (void)lks_addTarget:(id)target action:(SEL)action { [self lks_addTarget:target action:action]; [self lks_didAddTarget:target action:NSStringFromSelector(action)]; } - (void)lks_removeTarget:(id)target action:(SEL)action { [self lks_removeTarget:target action:action]; [self lks_didRemoveTarget:target action:NSStringFromSelector(action)]; } #pragma mark - Main - (void)lks_didAddTarget:(id)target action:(NSString *)action { if (!target || !action.length) { return; } BOOL alreadyExist = [self.lks_targetActions lookin_any:^BOOL(LookinTwoTuple *obj) { id existTarget = ((LookinWeakContainer *)obj.first).object; NSString *existAction = (NSString *)obj.second; if (target == existTarget && [action isEqualToString:existAction]) { return YES; } return NO; }]; if (alreadyExist) { return; } LookinTwoTuple *newTuple = [LookinTwoTuple new]; newTuple.first = [LookinWeakContainer containerWithObject:target]; newTuple.second = action; if (!self.lks_targetActions) { self.lks_targetActions = [NSMutableArray array]; } [self.lks_targetActions addObject:newTuple]; } - (void)lks_didRemoveTarget:(id)target action:(NSString *)action { if (target == nil && action == nil) { // target 为 nil,action 为 nil 时,表示移除所有 target 的所有已注册监听方法 [self.lks_targetActions removeAllObjects]; return; } if (target == nil) { // target 为 nil,action 为 handleTap 时,表示移除所有 target 的名为 handleTap 的监听方法 [self.lks_targetActions lookin_removeObjectsPassingTest:^BOOL(NSUInteger idx, LookinTwoTuple *obj) { NSString *currentAction = (NSString *)obj.second; if ([currentAction isEqualToString:action]) { return YES; } return NO; }]; return; } if (action == nil) { // target 为 abc,action 为 nil 时,表示移除 abc 的所有已注册监听方法 [self.lks_targetActions lookin_removeObjectsPassingTest:^BOOL(NSUInteger idx, LookinTwoTuple *obj) { id currentTarget = ((LookinWeakContainer *)obj.first).object; if (currentTarget == target) { return YES; } return NO; }]; return; } [self.lks_targetActions lookin_removeObjectsPassingTest:^BOOL(NSUInteger idx, LookinTwoTuple *obj) { id currentTarget = ((LookinWeakContainer *)obj.first).object; NSString *currentAction = (NSString *)obj.second; if (currentTarget == target && [currentAction isEqualToString:action]) { return YES; } return NO; }]; } - (void)setLks_targetActions:(NSMutableArray *)lks_targetActions { [self lookin_bindObject:lks_targetActions forKey:@"lks_targetActions"]; } - (NSMutableArray *)lks_targetActions { return [self lookin_getBindObjectForKey:@"lks_targetActions"]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIImage+LookinServer.h ================================================ // // UIImage+LookinServer.h // LookinServer // // Created by Li Kai on 2019/5/14. // https://lookin.work // #import @interface UIImage (LookinServer) @property(nonatomic, copy) NSString *lks_imageSourceName; - (NSData *)lookin_data; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIImage+LookinServer.m ================================================ // // UIImage+LookinServer.m // LookinServer // // Created by Li Kai on 2019/5/14. // https://lookin.work // #import "UIImage+LookinServer.h" #import "Objc/runtime.h" #import "LookinServerDefines.h" @implementation UIImage (LookinServer) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method oriMethod = class_getClassMethod([self class], @selector(imageNamed:)); Method newMethod = class_getClassMethod([self class], @selector(lks_imageNamed:)); method_exchangeImplementations(oriMethod, newMethod); oriMethod = class_getClassMethod([self class], @selector(imageWithContentsOfFile:)); newMethod = class_getClassMethod([self class], @selector(lks_imageWithContentsOfFile:)); method_exchangeImplementations(oriMethod, newMethod); }); } + (UIImage *)lks_imageNamed:(NSString *)name { UIImage *image = [self lks_imageNamed:name]; image.lks_imageSourceName = name; return image; } + (UIImage *)lks_imageWithContentsOfFile:(NSString *)path { UIImage *image = [self lks_imageWithContentsOfFile:path]; NSString *fileName = [[path componentsSeparatedByString:@"/"].lastObject componentsSeparatedByString:@"."].firstObject; image.lks_imageSourceName = fileName; return image; } - (void)setLks_imageSourceName:(NSString *)lks_imageSourceName { [self lookin_bindObject:lks_imageSourceName.copy forKey:@"lks_imageSourceName"]; } - (NSString *)lks_imageSourceName { return [self lookin_getBindObjectForKey:@"lks_imageSourceName"]; } - (NSData *)lookin_data { return UIImagePNGRepresentation(self); } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIImageView+LookinServer.h ================================================ // // UIImageView+LookinServer.h // LookinServer // // Created by Li Kai on 2019/9/18. // https://lookin.work // #import @interface UIImageView (LookinServer) - (NSString *)lks_imageSourceName; - (NSNumber *)lks_imageViewOidIfHasImage; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIImageView+LookinServer.m ================================================ // // UIImageView+LookinServer.m // LookinServer // // Created by Li Kai on 2019/9/18. // https://lookin.work // #import "UIImageView+LookinServer.h" #import "UIImage+LookinServer.h" #import "NSObject+LookinServer.h" @implementation UIImageView (LookinServer) - (NSString *)lks_imageSourceName { return self.image.lks_imageSourceName; } - (NSNumber *)lks_imageViewOidIfHasImage { if (!self.image) { return nil; } unsigned long oid = [self lks_registerOid]; return @(oid); } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UILabel+LookinServer.h ================================================ // // UILabel+LookinServer.h // LookinServer // // Created by Li Kai on 2019/2/26. // https://lookin.work // #import @interface UILabel (LookinServer) @property(nonatomic, assign) CGFloat lks_fontSize; - (NSString *)lks_fontName; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UILabel+LookinServer.m ================================================ // // UILabel+LookinServer.m // LookinServer // // Created by Li Kai on 2019/2/26. // https://lookin.work // #import "UILabel+LookinServer.h" @implementation UILabel (LookinServer) - (CGFloat)lks_fontSize { return self.font.pointSize; } - (void)setLks_fontSize:(CGFloat)lks_fontSize { UIFont *font = [self.font fontWithSize:lks_fontSize]; self.font = font; } - (NSString *)lks_fontName { return self.font.fontName; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UITableView+LookinServer.h ================================================ // // UITableView+LookinServer.h // LookinServer // // Created by Li Kai on 2019/9/5. // https://lookin.work // #import @interface UITableView (LookinServer) - (NSArray *)lks_numberOfRows; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UITableView+LookinServer.m ================================================ // // UITableView+LookinServer.m // LookinServer // // Created by Li Kai on 2019/9/5. // https://lookin.work // #import "UITableView+LookinServer.h" #import "LookinServerDefines.h" @implementation UITableView (LookinServer) - (NSArray *)lks_numberOfRows { NSUInteger sectionsCount = MIN(self.numberOfSections, 10); NSArray *rowsCount = [NSArray lookin_arrayWithCount:sectionsCount block:^id(NSUInteger idx) { return @([self numberOfRowsInSection:idx]); }]; if (rowsCount.count == 0) { return nil; } return rowsCount; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UITextField+LookinServer.h ================================================ // // UITextField+LookinServer.h // LookinServer // // Created by Li Kai on 2019/2/26. // https://lookin.work // #import @interface UITextField (LookinServer) @property(nonatomic, assign) CGFloat lks_fontSize; - (NSString *)lks_fontName; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UITextField+LookinServer.m ================================================ // // UITextField+LookinServer.m // LookinServer // // Created by Li Kai on 2019/2/26. // https://lookin.work // #import "UITextField+LookinServer.h" @implementation UITextField (LookinServer) - (CGFloat)lks_fontSize { return self.font.pointSize; } - (void)setLks_fontSize:(CGFloat)lks_fontSize { UIFont *font = [self.font fontWithSize:lks_fontSize]; self.font = font; } - (NSString *)lks_fontName { return self.font.fontName; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UITextView+LookinServer.h ================================================ // // UITextView+LookinServer.h // LookinServer // // Created by Li Kai on 2019/2/26. // https://lookin.work // #import @interface UITextView (LookinServer) @property(nonatomic, assign) CGFloat lks_fontSize; - (NSString *)lks_fontName; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UITextView+LookinServer.m ================================================ // // UITextView+LookinServer.m // LookinServer // // Created by Li Kai on 2019/2/26. // https://lookin.work // #import "UITextView+LookinServer.h" @implementation UITextView (LookinServer) - (CGFloat)lks_fontSize { return self.font.pointSize; } - (void)setLks_fontSize:(CGFloat)lks_fontSize { UIFont *font = [self.font fontWithSize:lks_fontSize]; self.font = font; } - (NSString *)lks_fontName { return self.font.fontName; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIView+LookinServer.h ================================================ // // UIView+LookinServer.h // LookinServer // // Created by Li Kai on 2019/3/19. // https://lookin.work // #import "LookinDefines.h" #import @interface UIView (LookinServer) @property(nonatomic, weak) UIViewController *lks_hostViewController; /// 是否是 UITabBar 的 childrenView,如果是的话,则截图时需要强制使用 renderInContext: 的方式而非 drawViewHierarchyInRect:afterScreenUpdates: 否则在 iOS 13 上获取到的图像是空的不知道为什么 @property(nonatomic, assign) BOOL lks_isChildrenViewOfTabBar; /// point 是相对于 receiver 自身的坐标系 - (UIView *)lks_subviewAtPoint:(CGPoint)point preferredClasses:(NSArray *)preferredClasses; - (CGFloat)lks_bestWidth; - (CGFloat)lks_bestHeight; - (CGSize)lks_bestSize; @property(nonatomic, assign) float lks_horizontalContentHuggingPriority; @property(nonatomic, assign) float lks_verticalContentHuggingPriority; @property(nonatomic, assign) float lks_horizontalContentCompressionResistancePriority; @property(nonatomic, assign) float lks_verticalContentCompressionResistancePriority; /// 遍历全局的 view 并给他们设置 lks_involvedRawConstraints 属性 + (void)lks_rebuildGlobalInvolvedRawConstraints; /// 该属性保存了牵扯到当前 view 的所有 constraints,包括那些没有生效的 @property(nonatomic, strong) NSMutableArray *lks_involvedRawConstraints; - (NSArray *> *)lks_constraints; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIView+LookinServer.m ================================================ // // UIView+LookinServer.m // LookinServer // // Created by Li Kai on 2019/3/19. // https://lookin.work // #import "UIView+LookinServer.h" #import #import "LookinObject.h" #import "LookinAutoLayoutConstraint.h" #import "LookinServerDefines.h" @implementation UIView (LookinServer) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method oriMethod = class_getInstanceMethod([self class], @selector(initWithFrame:)); Method newMethod = class_getInstanceMethod([self class], @selector(lks_initWithFrame:)); method_exchangeImplementations(oriMethod, newMethod); oriMethod = class_getInstanceMethod([self class], @selector(initWithCoder:)); newMethod = class_getInstanceMethod([self class], @selector(lks_initWithCoder:)); method_exchangeImplementations(oriMethod, newMethod); }); } - (instancetype)lks_initWithFrame:(CGRect)frame { UIView *view = [self lks_initWithFrame:frame]; view.layer.lks_hostView = view; return view; } - (instancetype)lks_initWithCoder:(NSCoder *)coder { UIView *view = [self lks_initWithCoder:coder]; view.layer.lks_hostView = view; return view; } - (void)setLks_hostViewController:(UIViewController *)lks_hostViewController { [self lookin_bindObjectWeakly:lks_hostViewController forKey:@"lks_hostViewController"]; } - (UIViewController *)lks_hostViewController { return [self lookin_getBindObjectForKey:@"lks_hostViewController"]; } - (UIView *)lks_subviewAtPoint:(CGPoint)point preferredClasses:(NSArray *)preferredClasses { BOOL isPreferredClassForSelf = [preferredClasses lookin_any:^BOOL(Class obj) { return [self isKindOfClass:obj]; }]; if (isPreferredClassForSelf) { return self; } UIView *targetView = [self.subviews lookin_lastFiltered:^BOOL(__kindof UIView *obj) { if (obj.layer.lks_isLookinPrivateLayer) { return NO; } if (obj.hidden || obj.alpha <= 0.01) { return NO; } BOOL contains = CGRectContainsPoint(obj.frame, point); return contains; }]; if (!targetView) { return self; } CGPoint newPoint = [targetView convertPoint:point fromView:self]; targetView = [targetView lks_subviewAtPoint:newPoint preferredClasses:preferredClasses]; return targetView; } - (CGSize)lks_bestSize { return [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; } - (CGFloat)lks_bestWidth { return self.lks_bestSize.width; } - (CGFloat)lks_bestHeight { return self.lks_bestSize.height; } - (void)setLks_isChildrenViewOfTabBar:(BOOL)lks_isChildrenViewOfTabBar { [self lookin_bindBOOL:lks_isChildrenViewOfTabBar forKey:@"lks_isChildrenViewOfTabBar"]; } - (BOOL)lks_isChildrenViewOfTabBar { return [self lookin_getBindBOOLForKey:@"lks_isChildrenViewOfTabBar"]; } - (void)setLks_verticalContentHuggingPriority:(float)lks_verticalContentHuggingPriority { [self setContentHuggingPriority:lks_verticalContentHuggingPriority forAxis:UILayoutConstraintAxisVertical]; } - (float)lks_verticalContentHuggingPriority { return [self contentHuggingPriorityForAxis:UILayoutConstraintAxisVertical]; } - (void)setLks_horizontalContentHuggingPriority:(float)lks_horizontalContentHuggingPriority { [self setContentHuggingPriority:lks_horizontalContentHuggingPriority forAxis:UILayoutConstraintAxisHorizontal]; } - (float)lks_horizontalContentHuggingPriority { return [self contentHuggingPriorityForAxis:UILayoutConstraintAxisHorizontal]; } - (void)setLks_verticalContentCompressionResistancePriority:(float)lks_verticalContentCompressionResistancePriority { [self setContentCompressionResistancePriority:lks_verticalContentCompressionResistancePriority forAxis:UILayoutConstraintAxisVertical]; } - (float)lks_verticalContentCompressionResistancePriority { return [self contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisVertical]; } - (void)setLks_horizontalContentCompressionResistancePriority:(float)lks_horizontalContentCompressionResistancePriority { [self setContentCompressionResistancePriority:lks_horizontalContentCompressionResistancePriority forAxis:UILayoutConstraintAxisHorizontal]; } - (float)lks_horizontalContentCompressionResistancePriority { return [self contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisHorizontal]; } + (void)lks_rebuildGlobalInvolvedRawConstraints { [[[UIApplication sharedApplication].windows copy] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { [self lks_removeInvolvedRawConstraintsForViewsRootedByView:window]; }]; [[[UIApplication sharedApplication].windows copy] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { [self lks_addInvolvedRawConstraintsForViewsRootedByView:window]; }]; } + (void)lks_addInvolvedRawConstraintsForViewsRootedByView:(UIView *)rootView { [rootView.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull constraint, NSUInteger idx, BOOL * _Nonnull stop) { UIView *firstView = constraint.firstItem; if ([firstView isKindOfClass:[UIView class]] && ![firstView.lks_involvedRawConstraints containsObject:constraint]) { if (!firstView.lks_involvedRawConstraints) { firstView.lks_involvedRawConstraints = [NSMutableArray array]; } [firstView.lks_involvedRawConstraints addObject:constraint]; } UIView *secondView = constraint.secondItem; if ([secondView isKindOfClass:[UIView class]] && ![secondView.lks_involvedRawConstraints containsObject:constraint]) { if (!secondView.lks_involvedRawConstraints) { secondView.lks_involvedRawConstraints = [NSMutableArray array]; } [secondView.lks_involvedRawConstraints addObject:constraint]; } }]; [rootView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) { [self lks_addInvolvedRawConstraintsForViewsRootedByView:subview]; }]; } + (void)lks_removeInvolvedRawConstraintsForViewsRootedByView:(UIView *)rootView { [rootView.lks_involvedRawConstraints removeAllObjects]; [rootView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) { [self lks_removeInvolvedRawConstraintsForViewsRootedByView:subview]; }]; } - (void)setLks_involvedRawConstraints:(NSMutableArray *)lks_involvedRawConstraints { [self lookin_bindObject:lks_involvedRawConstraints forKey:@"lks_involvedRawConstraints"]; } - (NSMutableArray *)lks_involvedRawConstraints { return [self lookin_getBindObjectForKey:@"lks_involvedRawConstraints"]; } - (NSArray *)lks_constraints { /** - lks_involvedRawConstraints 保存了牵扯到了 self 的所有的 constraints(包括未生效的,但不包括 inactive 的,整个产品逻辑都是直接忽略 inactive 的 constraints) - 通过 constraintsAffectingLayoutForAxis 可以拿到会影响 self 布局的所有已生效的 constraints(这里称之为 effectiveConstraints) - 很多情况下,一条 constraint 会出现在 effectiveConstraints 里但不会出现在 lks_involvedRawConstraints 里,比如: · UIWindow 拥有 minX, minY, width, height 四个 effectiveConstraints,但 lks_involvedRawConstraints 为空,因为它的 constraints 属性为空(这一点不知道为啥,但 Xcode Inspector 和 Reveal 确实也不会显示这四个 constraints) · 如果设置了 View1 的 center 和 superview 的 center 保持一致,则 superview 的 width 和 height 也会出现在 effectiveConstraints 里,但不会出现在 lks_involvedRawConstraints 里(这点可以理解,毕竟这种场景下 superview 的 width 和 height 确实会影响到 View1) */ NSMutableArray *effectiveConstraints = [NSMutableArray array]; [effectiveConstraints addObjectsFromArray:[self constraintsAffectingLayoutForAxis:UILayoutConstraintAxisHorizontal]]; [effectiveConstraints addObjectsFromArray:[self constraintsAffectingLayoutForAxis:UILayoutConstraintAxisVertical]]; NSArray *lookinConstraints = [self.lks_involvedRawConstraints lookin_map:^id(NSUInteger idx, __kindof NSLayoutConstraint *constraint) { BOOL isEffective = [effectiveConstraints containsObject:constraint]; LookinConstraintItemType firstItemType = [self _lks_constraintItemTypeForItem:constraint.firstItem]; LookinConstraintItemType secondItemType = [self _lks_constraintItemTypeForItem:constraint.secondItem]; LookinAutoLayoutConstraint *lookinConstraint = [LookinAutoLayoutConstraint instanceFromNSConstraint:constraint isEffective:isEffective firstItemType:firstItemType secondItemType:secondItemType]; return lookinConstraint; }]; return lookinConstraints.count ? lookinConstraints : nil; } - (LookinConstraintItemType)_lks_constraintItemTypeForItem:(id)item { if (!item) { return LookinConstraintItemTypeNil; } if (item == self) { return LookinConstraintItemTypeSelf; } if (item == self.superview) { return LookinConstraintItemTypeSuper; } // 在 runtime 时,这里会遇到的 UILayoutGuide 和 _UILayoutGuide 居然是 UIView 的子类,不知道是看错了还是有什么玄机,所以在判断是否是 UIView 之前要先判断这个 if (@available(iOS 9.0, *)) { if ([item isKindOfClass:[UILayoutGuide class]]) { return LookinConstraintItemTypeLayoutGuide; } } if ([[item lks_shortClassName] isEqualToString:@"_UILayoutGuide"]) { return LookinConstraintItemTypeLayoutGuide; } if ([item isKindOfClass:[UIView class]]) { return LookinConstraintItemTypeView; } NSAssert(NO, @""); return LookinConstraintItemTypeUnknown; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIViewController+LookinServer.h ================================================ // // UIViewController+LookinServer.h // LookinServer // // Created by Li Kai on 2019/4/22. // https://lookin.work // #import @interface UIViewController (LookinServer) + (UIViewController *)lks_visibleViewController; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIViewController+LookinServer.m ================================================ // // UIViewController+LookinServer.m // LookinServer // // Created by Li Kai on 2019/4/22. // https://lookin.work // #import "UIViewController+LookinServer.h" #import "UIView+LookinServer.h" #import @implementation UIViewController (LookinServer) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method oriMethod = class_getInstanceMethod([self class], @selector(viewDidLoad)); Method newMethod = class_getInstanceMethod([self class], @selector(lks_viewDidLoad)); method_exchangeImplementations(oriMethod, newMethod); }); } - (void)lks_viewDidLoad { [self lks_viewDidLoad]; self.view.lks_hostViewController = self; } + (nullable UIViewController *)lks_visibleViewController { UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController; UIViewController *visibleViewController = [rootViewController lks_visibleViewControllerIfExist]; return visibleViewController; } - (UIViewController *)lks_visibleViewControllerIfExist { if (self.presentedViewController) { return [self.presentedViewController lks_visibleViewControllerIfExist]; } if ([self isKindOfClass:[UINavigationController class]]) { return [((UINavigationController *)self).visibleViewController lks_visibleViewControllerIfExist]; } if ([self isKindOfClass:[UITabBarController class]]) { return [((UITabBarController *)self).selectedViewController lks_visibleViewControllerIfExist]; } if (self.isViewLoaded && !self.view.hidden && self.view.alpha > 0.01) { return self; } else { return nil; } } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIVisualEffectView+LookinServer.h ================================================ // // UIVisualEffectView+LookinServer.h // LookinServer // // Created by Li Kai on 2019/10/8. // https://lookin.work // #import @interface UIVisualEffectView (LookinServer) - (void)setLks_blurEffectStyleNumber:(NSNumber *)lks_blurEffectStyleNumber; - (NSNumber *)lks_blurEffectStyleNumber; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Category/UIVisualEffectView+LookinServer.m ================================================ // // UIVisualEffectView+LookinServer.m // LookinServer // // Created by Li Kai on 2019/10/8. // https://lookin.work // #import "UIVisualEffectView+LookinServer.h" #import "UIBlurEffect+LookinServer.h" @implementation UIVisualEffectView (LookinServer) - (void)setLks_blurEffectStyleNumber:(NSNumber *)lks_blurEffectStyleNumber { UIBlurEffectStyle style = [lks_blurEffectStyleNumber integerValue]; UIBlurEffect *effect = [UIBlurEffect effectWithStyle:style]; self.effect = effect; } - (NSNumber *)lks_blurEffectStyleNumber { UIVisualEffect *effect = self.effect; if (![effect isKindOfClass:[UIBlurEffect class]]) { return nil; } UIBlurEffect *blurEffect = (UIBlurEffect *)effect; return blurEffect.lks_effectStyleNumber; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Connection/LKS_ConnectionManager.h ================================================ // // Lookin.h // Lookin // // Created by Li Kai on 2018/8/5. // https://lookin.work // #import extern NSString *const LKS_ConnectionDidEndNotificationName; @class LookinConnectionResponseAttachment; @interface LKS_ConnectionManager : NSObject + (instancetype)sharedInstance; @property(nonatomic, assign) BOOL applicationIsActive; - (BOOL)isConnected; - (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag; - (void)pushData:(NSObject *)data type:(uint32_t)type; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Connection/LKS_ConnectionManager.m ================================================ // // LookinServer.m // LookinServer // // Created by Li Kai on 2018/8/5. // https://lookin.work // #import "LKS_ConnectionManager.h" #import "Lookin_PTChannel.h" #import "LKS_RequestHandler.h" #import "LookinConnectionResponseAttachment.h" #import "LKS_LocalInspectManager.h" #import "LKS_ExportManager.h" #import "LKS_PerspectiveManager.h" #import "LookinServerDefines.h" NSString *const LKS_ConnectionDidEndNotificationName = @"LKS_ConnectionDidEndNotificationName"; @interface LKS_ConnectionManager () @property(nonatomic, weak) Lookin_PTChannel *peerChannel_; @property(nonatomic, strong) LKS_RequestHandler *requestHandler; @end @implementation LKS_ConnectionManager + (instancetype)sharedInstance { static LKS_ConnectionManager *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[LKS_ConnectionManager alloc] init]; }); return sharedInstance; } + (void)load { // 触发 init 方法 [LKS_ConnectionManager sharedInstance]; } - (instancetype)init { if (self = [super init]) { NSLog(@"LookinServer - Will launch. Framework version: %@", LOOKIN_SERVER_READABLE_VERSION); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleApplicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspectIn2D:) name:@"Lookin_2D" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspectIn3D:) name:@"Lookin_3D" object:nil]; [[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_Export" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { [[LKS_ExportManager sharedInstance] exportAndShare]; }]; self.requestHandler = [LKS_RequestHandler new]; } return self; } - (void)_handleWillResignActiveNotification { self.applicationIsActive = NO; } - (void)_handleApplicationDidBecomeActive { // NSLog(@"LookinServer(0.8.0) - UIApplicationDidBecomeActiveNotification"); self.applicationIsActive = YES; if (self.peerChannel_ && (self.peerChannel_.isConnected || self.peerChannel_.isListening)) { return; } NSLog(@"LookinServer - Trying to connect ..."); if ([self isiOSAppOnMac]) { [self _tryToListenOnPortFrom:LookinSimulatorIPv4PortNumberStart to:LookinSimulatorIPv4PortNumberEnd current:LookinSimulatorIPv4PortNumberStart]; } else { [self _tryToListenOnPortFrom:LookinUSBDeviceIPv4PortNumberStart to:LookinUSBDeviceIPv4PortNumberEnd current:LookinUSBDeviceIPv4PortNumberStart]; } } - (BOOL)isiOSAppOnMac { #if TARGET_OS_SIMULATOR return YES; #endif #ifdef IOS14_SDK_ALLOWED if (@available(iOS 14.0, *)) { return [NSProcessInfo processInfo].isiOSAppOnMac || [NSProcessInfo processInfo].isMacCatalystApp; } #endif if (@available(iOS 13.0, tvOS 13.0, *)) { return [NSProcessInfo processInfo].isMacCatalystApp; } return NO; } - (void)_tryToListenOnPortFrom:(int)fromPort to:(int)toPort current:(int)currentPort { Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self]; [channel listenOnPort:currentPort IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) { if (error) { if (error.code == 48) { // 该地址已被占用 } else { // 未知失败 } if (currentPort < toPort) { // 尝试下一个端口 NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@). Will try anothor address ...", currentPort, error); [self _tryToListenOnPortFrom:fromPort to:toPort current:(currentPort + 1)]; } else { // 所有端口都尝试完毕,全部失败 NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@).", currentPort, error); NSLog(@"LookinServer - Connect failed in the end. Ask for help: lookin@lookin.work"); } } else { // 成功 NSLog(@"LookinServer - Connected successfully on 127.0.0.1:%d", currentPort); // UIAlertController *alert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"%@", @(currentPort)] message:nil preferredStyle:UIAlertControllerStyleAlert]; // [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil]; } }]; } - (void)dealloc { if (self.peerChannel_) { [self.peerChannel_ close]; } [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (BOOL)isConnected { return self.peerChannel_ && self.peerChannel_.isConnected; } - (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag { [self _sendData:data frameOfType:requestType tag:tag]; } - (void)pushData:(NSObject *)data type:(uint32_t)type { [self _sendData:data frameOfType:type tag:0]; } - (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag { if (self.peerChannel_) { NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data]; dispatch_data_t payload = [archivedData createReferencingDispatchData]; [self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) { if (error) { } }]; } } #pragma mark - Lookin_PTChannelDelegate - (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize { if (channel != self.peerChannel_) { return NO; } else if ([self.requestHandler canHandleRequestType:type]) { return YES; } else { [channel close]; return NO; } } - (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload { id object = nil; if (payload) { id unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithContentsOfDispatchData:payload.dispatchData]]; if ([unarchivedObject isKindOfClass:[LookinConnectionAttachment class]]) { LookinConnectionAttachment *attachment = (LookinConnectionAttachment *)unarchivedObject; object = attachment.data; } else { object = unarchivedObject; } } [self.requestHandler handleRequestType:type tag:tag object:object]; } /// 当连接过 Lookin 客户端,然后 Lookin 客户端又被关闭时,会走到这里 - (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error { [[NSNotificationCenter defaultCenter] postNotificationName:LKS_ConnectionDidEndNotificationName object:self]; } - (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address { if (self.peerChannel_) { [self.peerChannel_ cancel]; } self.peerChannel_ = otherChannel; self.peerChannel_.userInfo = address; } #pragma mark - Handler - (void)_handleLocalInspectIn2D:(NSNotification *)note { dispatch_async(dispatch_get_main_queue(), ^{ NSArray *includedWindows = nil; NSArray *excludedWindows = nil; [self parseUserInfo:note.userInfo toIncludedWindows:&includedWindows excludedWindows:&excludedWindows]; [[LKS_LocalInspectManager sharedInstance] startLocalInspectWithIncludedWindows:includedWindows excludedWindows:excludedWindows]; }); } - (void)_handleLocalInspectIn3D:(NSNotification *)note { NSArray *includedWindows = nil; NSArray *excludedWindows = nil; [self parseUserInfo:note.userInfo toIncludedWindows:&includedWindows excludedWindows:&excludedWindows]; [[LKS_PerspectiveManager sharedInstance] showWithIncludedWindows:includedWindows excludedWindows:excludedWindows]; } - (void)parseUserInfo:(NSDictionary *)info toIncludedWindows:(NSArray **)includedWindowsPtr excludedWindows:(NSArray **)excludedWindowsPtr { if (info[@"includedWindows"] && info[@"excludedWindows"]) { NSLog(@"LookinServer - Do not pass 'includedWindows' and 'excludedWindows' in the same time. Learn more: https://lookin.work/faq/lookin-ios/"); } [info enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { if ([key isEqual:@"includedWindows"] || [key isEqual:@"excludedWindows"]) { return; } NSLog(@"LookinServer - The key '%@' you passed is not valid. Learn more: https://lookin.work/faq/lookin-ios/", key); }]; NSArray *includedWindows = [info objectForKey:@"includedWindows"]; if (includedWindows) { if ([includedWindows isKindOfClass:[NSArray class]]) { includedWindows = [includedWindows lookin_filter:^BOOL(UIWindow *obj) { if ([obj isKindOfClass:[UIWindow class]]) { return YES; } NSLog(@"LookinServer - Error. The class of element in 'includedWindows' array must be UIWindow, but you've passed '%@'. Learn more: https://lookin.work/faq/lookin-ios/", NSStringFromClass(obj.class)); return NO; }]; } else { NSLog(@"LookinServer - Error. The 'includedWindows' must be a NSArray, but you've passed '%@'. Learn more: https://lookin.work/faq/lookin-ios/", NSStringFromClass([includedWindows class])); includedWindows = nil; } } NSArray *excludedWindows = nil; // 只有当 includedWindows 无效时,才会应用 excludedWindows if (includedWindows.count == 0) { excludedWindows = [info objectForKey:@"excludedWindows"]; if (excludedWindows) { if ([excludedWindows isKindOfClass:[NSArray class]]) { excludedWindows = [excludedWindows lookin_filter:^BOOL(UIWindow *obj) { if ([obj isKindOfClass:[UIWindow class]]) { return YES; } NSLog(@"LookinServer - Error. The class of element in 'excludedWindows' array must be UIWindow, but you've passed '%@'. Learn more: https://lookin.work/faq/lookin-ios/", NSStringFromClass(obj.class)); return NO; }]; } else { NSLog(@"LookinServer - Error. The 'excludedWindows' must be a NSArray, but you've passed '%@'. Learn more: https://lookin.work/faq/lookin-ios/", NSStringFromClass([excludedWindows class])); excludedWindows = nil; } } } if (includedWindowsPtr) { *includedWindowsPtr = includedWindows; } if (excludedWindowsPtr) { *excludedWindowsPtr = excludedWindows; } } @end /// 这个类使得用户可以通过 NSClassFromString(@"Lookin") 来判断 LookinServer 是否被编译进了项目里 @interface Lookin : NSObject @end @implementation Lookin @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Connection/LKS_RequestHandler.h ================================================ // // LKS_RequestHandler.h // LookinServer // // Created by Li Kai on 2019/1/15. // https://lookin.work // #import @interface LKS_RequestHandler : NSObject - (BOOL)canHandleRequestType:(uint32_t)requestType; - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Connection/LKS_RequestHandler.m ================================================ // // LKS_RequestHandler.m // LookinServer // // Created by Li Kai on 2019/1/15. // https://lookin.work // #import "LKS_RequestHandler.h" #import "NSObject+LookinServer.h" #import "UIImage+LookinServer.h" #import "LKS_ConnectionManager.h" #import "LookinConnectionResponseAttachment.h" #import "LookinAttributeModification.h" #import "LookinDisplayItemDetail.h" #import "LookinHierarchyInfo.h" #import "LookinServerDefines.h" #import #import "LookinObject.h" #import "LKS_LocalInspectManager.h" #import "LookinAppInfo.h" #import "LKS_MethodTraceManager.h" #import "LKS_AttrGroupsMaker.h" #import "LKS_AttrModificationHandler.h" #import "LKS_AttrModificationPatchHandler.h" #import "LKS_HierarchyDetailsHandler.h" #import "LookinStaticAsyncUpdateTask.h" @interface LKS_RequestHandler () @end @implementation LKS_RequestHandler { NSSet *_validRequestTypes; } - (instancetype)init { if (self = [super init]) { _validRequestTypes = [NSSet setWithObjects:@(LookinRequestTypePing), @(LookinRequestTypeApp), @(LookinRequestTypeHierarchy), @(LookinRequestTypeModification), @(LookinRequestTypeAttrModificationPatch), @(LookinRequestTypeHierarchyDetails), @(LookinRequestTypeFetchObject), @(LookinRequestTypeAllAttrGroups), @(LookinRequestTypeAllSelectorNames), @(LookinRequestTypeAddMethodTrace), @(LookinRequestTypeDeleteMethodTrace), @(LookinRequestTypeClassesAndMethodTraceLit), @(LookinRequestTypeInvokeMethod), @(LookinRequestTypeFetchImageViewImage), @(LookinRequestTypeModifyRecognizerEnable), @(LookinPush_BringForwardScreenshotTask), @(LookinPush_CanceHierarchyDetails), nil]; } return self; } - (BOOL)canHandleRequestType:(uint32_t)requestType { if ([_validRequestTypes containsObject:@(requestType)]) { return YES; } NSAssert(NO, @""); return NO; } - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object { if (requestType == LookinRequestTypePing) { LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new]; // 当 app 处于后台时,可能可以执行代码也可能不能执行代码,如果运气好了可以执行代码,则这里直接主动使用 appIsInBackground 标识 app 处于后台,不要让 Lookin 客户端傻傻地等待超时了 if (![LKS_ConnectionManager sharedInstance].applicationIsActive) { responseAttachment.appIsInBackground = YES; } [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag]; } else if (requestType == LookinRequestTypeApp) { // 请求可用设备信息 NSDictionary *params = object; BOOL needImages = ((NSNumber *)params[@"needImages"]).boolValue; NSArray *localIdentifiers = params[@"local"]; LookinAppInfo *appInfo = [LookinAppInfo currentInfoWithScreenshot:needImages icon:needImages localIdentifiers:localIdentifiers]; LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new]; responseAttachment.data = appInfo; [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag]; } else if (requestType == LookinRequestTypeHierarchy) { LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new]; responseAttachment.data = [LookinHierarchyInfo staticInfo]; [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag]; } else if (requestType == LookinRequestTypeModification) { // 请求修改某个属性 [LKS_AttrModificationHandler handleModification:object completion:^(LookinDisplayItemDetail *data, NSError *error) { LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new]; if (error) { attachment.error = error; } else { attachment.data = data; } [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag]; }]; } else if (requestType == LookinRequestTypeAttrModificationPatch) { NSArray *tasks = object; NSUInteger dataTotalCount = tasks.count; [LKS_AttrModificationHandler handlePatchWithTasks:tasks block:^(LookinDisplayItemDetail *data) { LookinConnectionResponseAttachment *attrAttachment = [LookinConnectionResponseAttachment new]; attrAttachment.data = data; attrAttachment.dataTotalCount = dataTotalCount; attrAttachment.currentDataCount = 1; [[LKS_ConnectionManager sharedInstance] respond:attrAttachment requestType:LookinRequestTypeAttrModificationPatch tag:tag]; }]; } else if (requestType == LookinRequestTypeHierarchyDetails) { NSArray *packages = object; NSUInteger responsesDataTotalCount = [packages lookin_reduceInteger:^NSInteger(NSInteger accumulator, NSUInteger idx, LookinStaticAsyncUpdateTasksPackage *package) { accumulator += package.tasks.count; return accumulator; } initialAccumlator:0]; [[LKS_HierarchyDetailsHandler sharedInstance] startWithPackages:packages block:^(NSArray *details, NSError *error) { LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new]; attachment.error = error; attachment.data = details; attachment.dataTotalCount = responsesDataTotalCount; attachment.currentDataCount = details.count; [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:LookinRequestTypeHierarchyDetails tag:tag]; }]; } else if (requestType == LookinRequestTypeFetchObject) { unsigned long oid = ((NSNumber *)object).unsignedLongValue; NSObject *object = [NSObject lks_objectWithOid:oid]; LookinObject *lookinObj = [LookinObject instanceWithObject:object]; LookinConnectionResponseAttachment *attach = [LookinConnectionResponseAttachment new]; attach.data = lookinObj; [[LKS_ConnectionManager sharedInstance] respond:attach requestType:requestType tag:tag]; } else if (requestType == LookinRequestTypeAllAttrGroups) { unsigned long oid = ((NSNumber *)object).unsignedLongValue; CALayer *layer = (CALayer *)[NSObject lks_objectWithOid:oid]; if (![layer isKindOfClass:[CALayer class]]) { [self _submitResponseWithError:LookinErr_ObjNotFound requestType:LookinRequestTypeAllAttrGroups tag:tag]; return; } NSArray *list = [LKS_AttrGroupsMaker attrGroupsForLayer:layer]; [self _submitResponseWithData:list requestType:LookinRequestTypeAllAttrGroups tag:tag]; } else if (requestType == LookinRequestTypeAllSelectorNames) { NSDictionary *params = object; Class targetClass = NSClassFromString(params[@"className"]); BOOL hasArg = [(NSNumber *)params[@"hasArg"] boolValue]; if (!targetClass) { NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"Didn't find the class named \"%@\". Please input another class and try again."), object]; [self _submitResponseWithError:LookinErrorMake(errorMsg, @"") requestType:requestType tag:tag]; return; } NSArray *selNames = [self _methodNameListForClass:targetClass hasArg:hasArg]; [self _submitResponseWithData:selNames requestType:requestType tag:tag]; } else if (requestType == LookinRequestTypeAddMethodTrace) { if (![object isKindOfClass:[NSDictionary class]]) { [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag]; return; } NSDictionary *dict = object; NSString *className = dict[@"className"]; NSString *selName = dict[@"selName"]; Class targetClass = NSClassFromString(className); if (!targetClass) { NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"Didn't find the class named \"%@\". Please input another class and try again."), object]; [self _submitResponseWithError:LookinErrorMake(errorMsg, @"") requestType:requestType tag:tag]; return; } SEL targetSelector = NSSelectorFromString(selName); if (class_getInstanceMethod(targetClass, targetSelector) == NULL) { NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"%@ doesn't have a method called \"%@\". Please input another method name and try again."), className, selName]; [self _submitResponseWithError:LookinErrorMake(errorMsg, @"") requestType:requestType tag:tag]; return; } [[LKS_MethodTraceManager sharedInstance] addWithClassName:className selName:selName]; [self _submitResponseWithData:[LKS_MethodTraceManager sharedInstance].currentActiveTraceList requestType:requestType tag:tag]; } else if (requestType == LookinRequestTypeDeleteMethodTrace) { if (![object isKindOfClass:[NSDictionary class]]) { [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag]; return; } NSDictionary *dict = object; NSString *className = dict[@"className"]; NSString *selName = dict[@"selName"]; [[LKS_MethodTraceManager sharedInstance] removeWithClassName:className selName:selName]; [self _submitResponseWithData:[LKS_MethodTraceManager sharedInstance].currentActiveTraceList requestType:requestType tag:tag]; } else if (requestType == LookinRequestTypeClassesAndMethodTraceLit) { LKS_MethodTraceManager *mng = [LKS_MethodTraceManager sharedInstance]; NSDictionary *dict = @{@"classes":mng.allClassesListInApp, @"activeList":mng.currentActiveTraceList}; [self _submitResponseWithData:dict requestType:requestType tag:tag]; } else if (requestType == LookinRequestTypeInvokeMethod) { NSDictionary *param = object; unsigned long oid = [param[@"oid"] unsignedLongValue]; NSString *text = param[@"text"]; if (!text.length) { [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag]; return; } NSObject *targerObj = [NSObject lks_objectWithOid:oid]; if (!targerObj) { [self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag]; return; } SEL targetSelector = NSSelectorFromString(text); if (targetSelector && [targerObj respondsToSelector:targetSelector]) { NSString *resultDescription; NSObject *resultObject; NSError *error; [self _handleInvokeWithObject:targerObj selector:targetSelector resultDescription:&resultDescription resultObject:&resultObject error:&error]; if (error) { [self _submitResponseWithError:error requestType:requestType tag:tag]; return; } NSMutableDictionary *responseData = [NSMutableDictionary dictionaryWithCapacity:2]; if (resultDescription) { responseData[@"description"] = resultDescription; } if (resultObject) { responseData[@"object"] = resultObject; } [self _submitResponseWithData:responseData requestType:requestType tag:tag]; } else { NSString *errMsg = [NSString stringWithFormat:LKS_Localized(@"%@ doesn't have an instance method called \"%@\"."), NSStringFromClass(targerObj.class), text]; [self _submitResponseWithError:LookinErrorMake(errMsg, @"") requestType:requestType tag:tag]; } } else if (requestType == LookinPush_BringForwardScreenshotTask) { [[LKS_HierarchyDetailsHandler sharedInstance] bringForwardWithPackages:object]; } else if (requestType == LookinPush_CanceHierarchyDetails) { [[LKS_HierarchyDetailsHandler sharedInstance] cancel]; } else if (requestType == LookinRequestTypeFetchImageViewImage) { if (![object isKindOfClass:[NSNumber class]]) { [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag]; return; } unsigned long imageViewOid = [(NSNumber *)object unsignedLongValue]; UIImageView *imageView = (UIImageView *)[NSObject lks_objectWithOid:imageViewOid]; if (!imageView) { [self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag]; return; } if (![imageView isKindOfClass:[UIImageView class]]) { [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag]; return; } UIImage *image = imageView.image; NSData *imageData = [image lookin_data]; [self _submitResponseWithData:imageData requestType:requestType tag:tag]; } else if (requestType == LookinRequestTypeModifyRecognizerEnable) { NSDictionary *params = object; unsigned long recognizerOid = ((NSNumber *)params[@"oid"]).unsignedLongValue; BOOL shouldBeEnabled = ((NSNumber *)params[@"enable"]).boolValue; UIGestureRecognizer *recognizer = (UIGestureRecognizer *)[NSObject lks_objectWithOid:recognizerOid]; if (!recognizer) { [self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag]; return; } if (![recognizer isKindOfClass:[UIGestureRecognizer class]]) { [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag]; return; } recognizer.enabled = shouldBeEnabled; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // dispatch 以确保拿到的 enabled 是比较新的 [self _submitResponseWithData:@(recognizer.enabled) requestType:requestType tag:tag]; }); } } - (NSArray *)_methodNameListForClass:(Class)aClass hasArg:(BOOL)hasArg { NSSet *prefixesToVoid = [NSSet setWithObjects:@"_", @"CA_", @"cpl", @"mf_", @"vs_", @"pep_", @"isNS", @"avkit_", @"PG_", @"px_", @"pl_", @"nsli_", @"pu_", @"pxg_", nil]; NSMutableArray *array = [NSMutableArray array]; Class currentClass = aClass; while (currentClass) { NSString *className = NSStringFromClass(currentClass); BOOL isSystemClass = ([className hasPrefix:@"UI"] || [className hasPrefix:@"CA"] || [className hasPrefix:@"NS"]); unsigned int methodCount = 0; Method *methods = class_copyMethodList(currentClass, &methodCount); for (unsigned int i = 0; i < methodCount; i++) { NSString *selName = NSStringFromSelector(method_getName(methods[i])); if (!hasArg && [selName containsString:@":"]) { continue; } if (isSystemClass) { BOOL invalid = [prefixesToVoid lookin_any:^BOOL(NSString *prefix) { return [selName hasPrefix:prefix]; }]; if (invalid) { continue; } } if (selName.length && ![array containsObject:selName]) { [array addObject:selName]; } } if (methods) free(methods); currentClass = [currentClass superclass]; } return [array lookin_sortedArrayByStringLength]; } - (void)_handleInvokeWithObject:(NSObject *)obj selector:(SEL)selector resultDescription:(NSString **)description resultObject:(LookinObject **)resultObject error:(NSError **)error { NSMethodSignature *signature = [obj methodSignatureForSelector:selector]; if (signature.numberOfArguments > 2) { *error = LookinErrorMake(LKS_Localized(@"Lookin doesn't support invoking methods with arguments yet."), @""); return; } NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; [invocation setTarget:obj]; [invocation setSelector:selector]; [invocation invoke]; const char *returnType = [signature methodReturnType]; if (strcmp(returnType, @encode(void)) == 0) { //void, do nothing *description = LookinStringFlag_VoidReturn; } else if (strcmp(returnType, @encode(char)) == 0) { char charValue; [invocation getReturnValue:&charValue]; *description = [NSString stringWithFormat:@"%@", @(charValue)]; } else if (strcmp(returnType, @encode(int)) == 0) { int intValue; [invocation getReturnValue:&intValue]; if (intValue == INT_MAX) { *description = @"INT_MAX"; } else if (intValue == INT_MIN) { *description = @"INT_MIN"; } else { *description = [NSString stringWithFormat:@"%@", @(intValue)]; } } else if (strcmp(returnType, @encode(short)) == 0) { short shortValue; [invocation getReturnValue:&shortValue]; if (shortValue == SHRT_MAX) { *description = @"SHRT_MAX"; } else if (shortValue == SHRT_MIN) { *description = @"SHRT_MIN"; } else { *description = [NSString stringWithFormat:@"%@", @(shortValue)]; } } else if (strcmp(returnType, @encode(long)) == 0) { long longValue; [invocation getReturnValue:&longValue]; if (longValue == NSNotFound) { *description = @"NSNotFound"; } else if (longValue == LONG_MAX) { *description = @"LONG_MAX"; } else if (longValue == LONG_MIN) { *description = @"LONG_MAX"; } else { *description = [NSString stringWithFormat:@"%@", @(longValue)]; } } else if (strcmp(returnType, @encode(long long)) == 0) { long long longLongValue; [invocation getReturnValue:&longLongValue]; if (longLongValue == LLONG_MAX) { *description = @"LLONG_MAX"; } else if (longLongValue == LLONG_MIN) { *description = @"LLONG_MIN"; } else { *description = [NSString stringWithFormat:@"%@", @(longLongValue)]; } } else if (strcmp(returnType, @encode(unsigned char)) == 0) { unsigned char ucharValue; [invocation getReturnValue:&ucharValue]; if (ucharValue == UCHAR_MAX) { *description = @"UCHAR_MAX"; } else { *description = [NSString stringWithFormat:@"%@", @(ucharValue)]; } } else if (strcmp(returnType, @encode(unsigned int)) == 0) { unsigned int uintValue; [invocation getReturnValue:&uintValue]; if (uintValue == UINT_MAX) { *description = @"UINT_MAX"; } else { *description = [NSString stringWithFormat:@"%@", @(uintValue)]; } } else if (strcmp(returnType, @encode(unsigned short)) == 0) { unsigned short ushortValue; [invocation getReturnValue:&ushortValue]; if (ushortValue == USHRT_MAX) { *description = @"USHRT_MAX"; } else { *description = [NSString stringWithFormat:@"%@", @(ushortValue)]; } } else if (strcmp(returnType, @encode(unsigned long)) == 0) { unsigned long ulongValue; [invocation getReturnValue:&ulongValue]; if (ulongValue == ULONG_MAX) { *description = @"ULONG_MAX"; } else { *description = [NSString stringWithFormat:@"%@", @(ulongValue)]; } } else if (strcmp(returnType, @encode(unsigned long long)) == 0) { unsigned long long ulongLongValue; [invocation getReturnValue:&ulongLongValue]; if (ulongLongValue == ULONG_LONG_MAX) { *description = @"ULONG_LONG_MAX"; } else { *description = [NSString stringWithFormat:@"%@", @(ulongLongValue)]; } } else if (strcmp(returnType, @encode(float)) == 0) { float floatValue; [invocation getReturnValue:&floatValue]; if (floatValue == FLT_MAX) { *description = @"FLT_MAX"; } else if (floatValue == FLT_MIN) { *description = @"FLT_MIN"; } else { *description = [NSString stringWithFormat:@"%@", @(floatValue)]; } } else if (strcmp(returnType, @encode(double)) == 0) { double doubleValue; [invocation getReturnValue:&doubleValue]; if (doubleValue == DBL_MAX) { *description = @"DBL_MAX"; } else if (doubleValue == DBL_MIN) { *description = @"DBL_MIN"; } else { *description = [NSString stringWithFormat:@"%@", @(doubleValue)]; } } else if (strcmp(returnType, @encode(BOOL)) == 0) { BOOL boolValue; [invocation getReturnValue:&boolValue]; *description = boolValue ? @"YES" : @"NO"; } else if (strcmp(returnType, @encode(SEL)) == 0) { SEL selValue; [invocation getReturnValue:&selValue]; *description = [NSString stringWithFormat:@"SEL(%@)", NSStringFromSelector(selValue)]; } else if (strcmp(returnType, @encode(Class)) == 0) { Class classValue; [invocation getReturnValue:&classValue]; *description = [NSString stringWithFormat:@"<%@>", NSStringFromClass(classValue)]; } else if (strcmp(returnType, @encode(CGPoint)) == 0) { CGPoint targetValue; [invocation getReturnValue:&targetValue]; *description = NSStringFromCGPoint(targetValue); } else if (strcmp(returnType, @encode(CGVector)) == 0) { CGVector targetValue; [invocation getReturnValue:&targetValue]; *description = NSStringFromCGVector(targetValue); } else if (strcmp(returnType, @encode(CGSize)) == 0) { CGSize targetValue; [invocation getReturnValue:&targetValue]; *description = NSStringFromCGSize(targetValue); } else if (strcmp(returnType, @encode(CGRect)) == 0) { CGRect rectValue; [invocation getReturnValue:&rectValue]; *description = NSStringFromCGRect(rectValue); } else if (strcmp(returnType, @encode(CGAffineTransform)) == 0) { CGAffineTransform rectValue; [invocation getReturnValue:&rectValue]; *description = NSStringFromCGAffineTransform(rectValue); } else if (strcmp(returnType, @encode(UIEdgeInsets)) == 0) { UIEdgeInsets targetValue; [invocation getReturnValue:&targetValue]; *description = NSStringFromUIEdgeInsets(targetValue); } else if (strcmp(returnType, @encode(UIOffset)) == 0) { UIOffset targetValue; [invocation getReturnValue:&targetValue]; *description = NSStringFromUIOffset(targetValue); } else { if (@available(iOS 11.0, tvOS 11.0, *)) { if (strcmp(returnType, @encode(NSDirectionalEdgeInsets)) == 0) { NSDirectionalEdgeInsets targetValue; [invocation getReturnValue:&targetValue]; *description = NSStringFromDirectionalEdgeInsets(targetValue); return; } } NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:returnType]; if ([argType_string hasPrefix:@"@"] || [argType_string hasPrefix:@"^{"]) { __unsafe_unretained id returnObjValue; [invocation getReturnValue:&returnObjValue]; if (returnObjValue) { *description = [NSString stringWithFormat:@"%@", returnObjValue]; LookinObject *parsedLookinObj = [LookinObject instanceWithObject:returnObjValue]; *resultObject = parsedLookinObj; } else { *description = @"nil"; } } else { *description = [NSString stringWithFormat:LKS_Localized(@"%@ was invoked successfully, but Lookin can't parse the return value:%@"), NSStringFromSelector(selector), argType_string]; } } } - (void)_submitResponseWithError:(NSError *)error requestType:(uint32_t)requestType tag:(uint32_t)tag { LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new]; attachment.error = error; [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag]; } - (void)_submitResponseWithData:(NSObject *)data requestType:(uint32_t)requestType tag:(uint32_t)tag { LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new]; attachment.data = data; [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Connection/RequestHandler/LKS_AttrModificationHandler.h ================================================ // // LKS_AttrModificationHandler.h // LookinServer // // Created by Li Kai on 2019/6/12. // https://lookin.work // #import @class LookinAttributeModification, LookinDisplayItemDetail, LookinStaticAsyncUpdateTask; @interface LKS_AttrModificationHandler : NSObject + (void)handleModification:(LookinAttributeModification *)modification completion:(void (^)(LookinDisplayItemDetail *data, NSError *error))completion; + (void)handlePatchWithTasks:(NSArray *)tasks block:(void (^)(LookinDisplayItemDetail *data))block; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Connection/RequestHandler/LKS_AttrModificationHandler.m ================================================ // // LKS_AttrModificationHandler.m // LookinServer // // Created by Li Kai on 2019/6/12. // https://lookin.work // #import "LKS_AttrModificationHandler.h" #import "UIColor+LookinServer.h" #import "LookinAttributeModification.h" #import "LKS_AttrGroupsMaker.h" #import "LookinDisplayItemDetail.h" #import "LookinStaticAsyncUpdateTask.h" #import "LookinServerDefines.h" @implementation LKS_AttrModificationHandler + (void)handleModification:(LookinAttributeModification *)modification completion:(void (^)(LookinDisplayItemDetail *data, NSError *error))completion { if (!completion) { NSAssert(NO, @""); return; } if (!modification || ![modification isKindOfClass:[LookinAttributeModification class]]) { completion(nil, LookinErr_Inner); return; } NSObject *receiver = [NSObject lks_objectWithOid:modification.targetOid]; if (!receiver) { completion(nil, LookinErr_ObjNotFound); return; } NSMethodSignature *setterSignature = [receiver methodSignatureForSelector:modification.setterSelector]; NSInvocation *setterInvocation = [NSInvocation invocationWithMethodSignature:setterSignature]; setterInvocation.target = receiver; setterInvocation.selector = modification.setterSelector; if (setterSignature.numberOfArguments != 3 || ![receiver respondsToSelector:modification.setterSelector]) { completion(nil, LookinErr_Inner); return; } switch (modification.attrType) { case LookinAttrTypeNone: case LookinAttrTypeVoid: { completion(nil, LookinErr_Inner); return; } case LookinAttrTypeChar: { char expectedValue = [(NSNumber *)modification.value charValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeInt: case LookinAttrTypeEnumInt: { int expectedValue = [(NSNumber *)modification.value intValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeShort: { short expectedValue = [(NSNumber *)modification.value shortValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeLong: case LookinAttrTypeEnumLong: { long expectedValue = [(NSNumber *)modification.value longValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeLongLong: { long long expectedValue = [(NSNumber *)modification.value longLongValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeUnsignedChar: { unsigned char expectedValue = [(NSNumber *)modification.value unsignedCharValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeUnsignedInt: { unsigned int expectedValue = [(NSNumber *)modification.value unsignedIntValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeUnsignedShort: { unsigned short expectedValue = [(NSNumber *)modification.value unsignedShortValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeUnsignedLong: { unsigned long expectedValue = [(NSNumber *)modification.value unsignedLongValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeUnsignedLongLong: { unsigned long long expectedValue = [(NSNumber *)modification.value unsignedLongLongValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeFloat: { float expectedValue = [(NSNumber *)modification.value floatValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeDouble: { double expectedValue = [(NSNumber *)modification.value doubleValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeBOOL: { BOOL expectedValue = [(NSNumber *)modification.value boolValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeSel: { SEL expectedValue = NSSelectorFromString(modification.value); [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeClass: { Class expectedValue = NSClassFromString(modification.value); [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeCGPoint: { CGPoint expectedValue = [(NSValue *)modification.value CGPointValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeCGVector: { CGVector expectedValue = [(NSValue *)modification.value CGVectorValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeCGSize: { CGSize expectedValue = [(NSValue *)modification.value CGSizeValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeCGRect: { CGRect expectedValue = [(NSValue *)modification.value CGRectValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeCGAffineTransform: { CGAffineTransform expectedValue = [(NSValue *)modification.value CGAffineTransformValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeUIEdgeInsets: { UIEdgeInsets expectedValue = [(NSValue *)modification.value UIEdgeInsetsValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeUIOffset: { UIOffset expectedValue = [(NSValue *)modification.value UIOffsetValue]; [setterInvocation setArgument:&expectedValue atIndex:2]; break; } case LookinAttrTypeCustomObj: case LookinAttrTypeNSString: { NSObject *expectedValue = modification.value; [setterInvocation setArgument:&expectedValue atIndex:2]; [setterInvocation retainArguments]; break; } case LookinAttrTypeUIColor: { NSArray *rgba = modification.value; UIColor *expectedValue = [UIColor lks_colorFromRGBAComponents:rgba]; [setterInvocation setArgument:&expectedValue atIndex:2]; [setterInvocation retainArguments]; break; } default: { completion(nil, LookinErr_Inner); return; } } NSError *error = nil; @try { [setterInvocation invoke]; } @catch (NSException *exception) { NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"<%@: %p>: an exception was raised when invoking %@. (%@)"), NSStringFromClass(receiver.class), receiver, NSStringFromSelector(modification.setterSelector), exception.reason]; error = [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_Exception userInfo:@{NSLocalizedDescriptionKey:LKS_Localized(@"The modification may failed."), NSLocalizedRecoverySuggestionErrorKey:errorMsg}]; } @finally { } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ CALayer *layer = nil; if ([receiver isKindOfClass:[CALayer class]]) { layer = (CALayer *)receiver; } else if ([receiver isKindOfClass:[UIView class]]) { layer = ((UIView *)receiver).layer; } else { completion(nil, LookinErr_ObjNotFound); return; } // 比如试图更改 frame 时,这个改动很有可能触发用户业务的 relayout,因此这时 dispatch 一下以确保拿到的 attrGroups 数据是最新的 LookinDisplayItemDetail *detail = [LookinDisplayItemDetail new]; detail.displayItemOid = modification.targetOid; detail.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer]; detail.frameValue = [NSValue valueWithCGRect:layer.frame]; detail.boundsValue = [NSValue valueWithCGRect:layer.bounds]; detail.hiddenValue = [NSNumber numberWithBool:layer.isHidden]; detail.alphaValue = @(layer.opacity); completion(detail, error); }); } + (void)handlePatchWithTasks:(NSArray *)tasks block:(void (^)(LookinDisplayItemDetail *data))block { if (!block) { NSAssert(NO, @""); return; } [tasks enumerateObjectsUsingBlock:^(LookinStaticAsyncUpdateTask * _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) { LookinDisplayItemDetail *itemDetail = [LookinDisplayItemDetail new]; itemDetail.displayItemOid = task.oid; id object = [NSObject lks_objectWithOid:task.oid]; if (!object || ![object isKindOfClass:[CALayer class]]) { block(itemDetail); return; } CALayer *layer = object; if (task.taskType == LookinStaticAsyncUpdateTaskTypeSoloScreenshot) { UIImage *image = [layer lks_soloScreenshotWithLowQuality:NO]; itemDetail.soloScreenshot = image; } else if (task.taskType == LookinStaticAsyncUpdateTaskTypeGroupScreenshot) { UIImage *image = [layer lks_groupScreenshotWithLowQuality:NO]; itemDetail.groupScreenshot = image; } block(itemDetail); }]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Connection/RequestHandler/LKS_AttrModificationPatchHandler.h ================================================ // // LKS_AttrModificationPatchHandler.h // LookinServer // // Created by Li Kai on 2019/6/12. // https://lookin.work // #import @class LookinDisplayItemDetail; @interface LKS_AttrModificationPatchHandler : NSObject /** @param oids 数组内 idx 较小的应该为 displayItems 里的 subItem,idx 较大的应该为 superItem @param lowImageQuality 是否采用低图像质量 @param block 该 block 会被多次调用,其中 tasksTotalCount 是总的调用次数(即可被用来作为 TotalResponseCount) */ + (void)handleLayerOids:(NSArray *)oids lowImageQuality:(BOOL)lowImageQuality block:(void (^)(LookinDisplayItemDetail *detail, NSUInteger tasksTotalCount, NSError *error))block; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Connection/RequestHandler/LKS_AttrModificationPatchHandler.m ================================================ // // LKS_AttrModificationPatchHandler.m // LookinServer // // Created by Li Kai on 2019/6/12. // https://lookin.work // #import "LKS_AttrModificationPatchHandler.h" #import "LookinDisplayItemDetail.h" #import "LookinServerDefines.h" @implementation LKS_AttrModificationPatchHandler + (void)handleLayerOids:(NSArray *)oids lowImageQuality:(BOOL)lowImageQuality block:(void (^)(LookinDisplayItemDetail *detail, NSUInteger tasksTotalCount, NSError *error))block { if (!block) { NSAssert(NO, @""); return; } if (![oids isKindOfClass:[NSArray class]]) { block(nil, 1, LookinErr_Inner); return; } [oids enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { unsigned long oid = [obj unsignedLongValue]; LookinDisplayItemDetail *detail = [LookinDisplayItemDetail new]; detail.displayItemOid = oid; CALayer *layer = (CALayer *)[NSObject lks_objectWithOid:oid]; if (![layer isKindOfClass:[CALayer class]]) { block(nil, idx + 1, LookinErr_ObjNotFound); *stop = YES; return; } if (idx == 0) { detail.soloScreenshot = [layer lks_soloScreenshotWithLowQuality:lowImageQuality]; detail.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowImageQuality]; } else { detail.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowImageQuality]; } block(detail, oids.count, nil); }]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Connection/RequestHandler/LKS_HierarchyDetailsHandler.h ================================================ // // LKS_HierarchyDetailsHandler.h // LookinServer // // Created by Li Kai on 2019/6/20. // https://lookin.work // #import @class LookinDisplayItemDetail, LookinStaticAsyncUpdateTasksPackage; typedef void (^LKS_HierarchyDetailsHandler_Block)(NSArray *details, NSError *error); @interface LKS_HierarchyDetailsHandler : NSObject + (instancetype)sharedInstance; /// packages 会按照 idx 从大到小的顺序被执行 - (void)startWithPackages:(NSArray *)packages block:(LKS_HierarchyDetailsHandler_Block)block; - (void)bringForwardWithPackages:(NSArray *)packages; /// 取消所有任务 - (void)cancel; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Connection/RequestHandler/LKS_HierarchyDetailsHandler.m ================================================ // // LKS_HierarchyDetailsHandler.m // LookinServer // // Created by Li Kai on 2019/6/20. // https://lookin.work // #import "LKS_HierarchyDetailsHandler.h" #import "LookinDisplayItemDetail.h" #import "LKS_AttrGroupsMaker.h" #import "LookinStaticAsyncUpdateTask.h" #import "LKS_ConnectionManager.h" #import "LookinServerDefines.h" @interface LKS_HierarchyDetailsHandler () @property(nonatomic, strong) NSMutableArray *taskPackages; @property(nonatomic, strong) NSMutableSet *finishedTasks; /// 标识哪些 oid 已经拉取过 attrGroups 了 @property(nonatomic, strong) NSMutableSet *attrGroupsSyncedOids; @property(nonatomic, copy) LKS_HierarchyDetailsHandler_Block handlerBlock; @property(nonatomic, assign) NSUInteger bbb; @end @implementation LKS_HierarchyDetailsHandler + (instancetype)sharedInstance { static dispatch_once_t onceToken; static LKS_HierarchyDetailsHandler *instance = nil; dispatch_once(&onceToken,^{ instance = [[super allocWithZone:NULL] init]; }); return instance; } + (id)allocWithZone:(struct _NSZone *)zone{ return [self sharedInstance]; } - (instancetype)init { if (self = [super init]) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleConnectionDidEnd:) name:LKS_ConnectionDidEndNotificationName object:nil]; self.attrGroupsSyncedOids = [NSMutableSet set]; self.finishedTasks = [NSMutableSet set]; } return self; } - (void)startWithPackages:(NSArray *)packages block:(LKS_HierarchyDetailsHandler_Block)block { if (!block) { NSAssert(NO, @""); return; } if (!packages.count) { block(nil, LookinErr_Inner); return; } [self.finishedTasks removeAllObjects]; [self.attrGroupsSyncedOids removeAllObjects]; self.taskPackages = [packages mutableCopy]; self.handlerBlock = block; [UIView lks_rebuildGlobalInvolvedRawConstraints]; [self _dequeueAndHandlePackage]; } - (void)bringForwardWithPackages:(NSArray *)packages { NSLog(@"LookinServer - willBringForward"); NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, packages.count)]; [self.taskPackages insertObjects:packages atIndexes:indexSet]; } - (void)cancel { [self.taskPackages removeAllObjects]; } - (void)_dequeueAndHandlePackage { dispatch_async(dispatch_get_main_queue(), ^{ LookinStaticAsyncUpdateTasksPackage *package = self.taskPackages.firstObject; if (!package) { return; } // NSLog(@"LookinServer - will handle tasks, count: %@", @(tasks.count)); NSArray *details = [package.tasks lookin_map:^id(NSUInteger idx, LookinStaticAsyncUpdateTask *task) { if ([self.finishedTasks containsObject:task]) { return nil; } [self.finishedTasks addObject:task]; LookinDisplayItemDetail *itemDetail = [LookinDisplayItemDetail new]; itemDetail.displayItemOid = task.oid; id object = [NSObject lks_objectWithOid:task.oid]; if (!object || ![object isKindOfClass:[CALayer class]]) { return itemDetail; } CALayer *layer = object; if (![self.attrGroupsSyncedOids containsObject:@(task.oid)]) { itemDetail.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer]; [self.attrGroupsSyncedOids addObject:@(task.oid)]; } if (task.taskType == LookinStaticAsyncUpdateTaskTypeSoloScreenshot) { UIImage *image = [layer lks_soloScreenshotWithLowQuality:NO]; itemDetail.soloScreenshot = image; } else if (task.taskType == LookinStaticAsyncUpdateTaskTypeGroupScreenshot) { UIImage *image = [layer lks_groupScreenshotWithLowQuality:NO]; itemDetail.groupScreenshot = image; } return itemDetail; }]; self.handlerBlock(details, nil); [self.taskPackages removeObjectAtIndex:0]; [self _dequeueAndHandlePackage]; }); } - (void)_handleConnectionDidEnd:(id)obj { [self cancel]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Inspect/LKS_LocalInspectManager.h ================================================ // // LKS_LocalInspectManager.h // LookinServer // // Created by Li Kai on 2019/5/8. // https://lookin.work // #import "LookinDefines.h" @interface LKS_LocalInspectContainerWindow : UIWindow @end @interface LKS_LocalInspectManager : NSObject + (instancetype)sharedInstance; - (void)startLocalInspectWithIncludedWindows:(NSArray *)includedWindows excludedWindows:(NSArray *)excludedWindows; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Inspect/LKS_LocalInspectManager.m ================================================ // // LKS_LocalInspectManager.m // LookinServer // // Created by Li Kai on 2019/5/8. // https://lookin.work // #import "LKS_LocalInspectManager.h" #import "LKS_ConnectionManager.h" #import "LKS_TraceManager.h" #import "LKS_LocalInspectViewController.h" @implementation LKS_LocalInspectContainerWindow @end @interface LKS_LocalInspectManager () @property(nonatomic, weak) UIWindow *previousKeyWindow; @property(nonatomic, strong) LKS_LocalInspectContainerWindow *inspectorWindow; @property(nonatomic, strong) LKS_LocalInspectViewController *viewController; @property(nonatomic, strong) UITapGestureRecognizer *tapRecognizer; @property(nonatomic, assign) BOOL isInspecting; @property(nonatomic, copy) NSArray *includedWindows; @end @implementation LKS_LocalInspectManager + (instancetype)sharedInstance { static dispatch_once_t onceToken; static LKS_LocalInspectManager *instance = nil; dispatch_once(&onceToken,^{ instance = [[super allocWithZone:NULL] init]; }); return instance; } + (id)allocWithZone:(struct _NSZone *)zone{ return [self sharedInstance]; } - (void)startLocalInspectWithIncludedWindows:(NSArray *)includedWindows excludedWindows:(NSArray *)excludedWindows { NSLog(@"LookinServer - Will start inspecting in 2D"); [[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_WillEnter2D" object:nil]; self.isInspecting = YES; [[LKS_TraceManager sharedInstance] reload]; [self _setupWindowIfNeeded]; self.viewController.showTitleButton = YES; self.inspectorWindow.userInteractionEnabled = YES; [self.viewController clearContents]; self.viewController.prevKeyWindow = self.previousKeyWindow; self.viewController.includedWindows = includedWindows; self.viewController.excludedWindows = excludedWindows; [self.viewController startTitleButtonAnimIfNeeded]; } - (void)_endLocalInspect { self.isInspecting = NO; self.viewController.showTitleButton = NO; self.viewController.includedWindows = nil; self.viewController.excludedWindows = nil; [self _removeWindowIfNeeded]; self.inspectorWindow.userInteractionEnabled = NO; [[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_DidExit2D" object:nil]; NSLog(@"LookinServer - Did end inspecting in 2D"); } - (void)_setupWindowIfNeeded { if (!self.inspectorWindow) { self.inspectorWindow = [LKS_LocalInspectContainerWindow new]; self.inspectorWindow.windowLevel = UIWindowLevelAlert - 1; self.inspectorWindow.backgroundColor = [UIColor clearColor]; } if (!self.viewController) { self.viewController = [LKS_LocalInspectViewController new]; __weak __typeof(self)weakSelf = self; self.viewController.didSelectExit = ^{ [weakSelf _endLocalInspect]; }; self.inspectorWindow.rootViewController = self.viewController; } if (!self.inspectorWindow.hidden) { return; } self.previousKeyWindow = [UIApplication sharedApplication].keyWindow; self.viewController.prevKeyWindow = self.previousKeyWindow; [self.inspectorWindow makeKeyAndVisible]; } - (void)_removeWindowIfNeeded { if (!self.inspectorWindow || self.inspectorWindow.hidden) { return; } if ([[UIApplication sharedApplication] keyWindow] == self.inspectorWindow) { if (self.previousKeyWindow.hidden) { ///TODO 到底该用 keyWindow 还是 delegate.window [[UIApplication sharedApplication].delegate.window makeKeyWindow]; } else { [self.previousKeyWindow makeKeyWindow]; } } self.inspectorWindow.hidden = YES; self.previousKeyWindow = nil; self.viewController.prevKeyWindow = nil; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Inspect/LKS_LocalInspectPanelLabelView.h ================================================ // // LKS_LocalInspectPanelLabelView.h // LookinServer // // Created by Li Kai on 2019/5/15. // https://lookin.work // #import @interface LKS_LocalInspectPanelLabelView : UIView @property(nonatomic, strong) UILabel *leftLabel; @property(nonatomic, strong) UILabel *rightLabel; @property(nonatomic, assign) CGFloat verInset; @property(nonatomic, assign) CGFloat interspace; @property(nonatomic, strong) CALayer *bottomBorderLayer; - (void)addBottomBorderLayer; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Inspect/LKS_LocalInspectPanelLabelView.m ================================================ // // LKS_LocalInspectPanelLabelView.m // LookinServer // // Created by Li Kai on 2019/5/15. // https://lookin.work // #import "LKS_LocalInspectPanelLabelView.h" #import "LookinServerDefines.h" @implementation LKS_LocalInspectPanelLabelView { CGFloat _horInset; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { _horInset = 8; _interspace = 10; self.userInteractionEnabled = NO; self.leftLabel = [UILabel new]; self.leftLabel.textAlignment = NSTextAlignmentLeft; [self addSubview:self.leftLabel]; self.rightLabel = [UILabel new]; self.rightLabel.textAlignment = NSTextAlignmentRight; [self addSubview:self.rightLabel]; } return self; } - (void)layoutSubviews { [super layoutSubviews]; self.leftLabel.frame = CGRectMake(_horInset, 0, self.leftLabel.lks_bestWidth, self.bounds.size.height); if (self.rightLabel.text.length) { CGFloat rightLabelWidth = self.bounds.size.width - _horInset - _interspace - CGRectGetMaxX(self.leftLabel.frame); if (rightLabelWidth <= 0) { self.rightLabel.frame = CGRectZero; } else { self.rightLabel.frame = CGRectMake(CGRectGetMaxX(self.leftLabel.frame) + _interspace, 0, rightLabelWidth, self.bounds.size.height); } } self.bottomBorderLayer.frame = CGRectMake(_horInset, self.bounds.size.height, self.bounds.size.width - _horInset * 2, 1 / [[UIScreen mainScreen] scale]); } - (CGSize)sizeThatFits:(CGSize)size { CGSize leftSize = [self.leftLabel sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; size.height = leftSize.height + self.verInset; size.width = _horInset * 2 + leftSize.width; if (self.rightLabel.text.length) { size.width += self.rightLabel.lks_bestWidth + _interspace; } return size; } - (void)addBottomBorderLayer { if (self.bottomBorderLayer) { return; } self.bottomBorderLayer = [CALayer new]; [self.bottomBorderLayer lookin_removeImplicitAnimations]; self.bottomBorderLayer.backgroundColor = [UIColor colorWithRed:222/255.0 green:224/255.0 blue:226/255.0 alpha:1].CGColor; [self.layer addSublayer:self.bottomBorderLayer]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Inspect/LKS_LocalInspectViewController.h ================================================ // // LKS_LocalInspectViewController.h // LookinServer // // Created by Li Kai on 2019/5/15. // https://lookin.work // #import @interface LKS_LocalInspectViewController : UIViewController /// 用户点击了“退出” @property(nonatomic, copy) void (^didSelectExit)(void); - (void)highlightLayer:(CALayer *)layer; @property(nonatomic, assign) BOOL showTitleButton; - (void)clearContents; - (void)startTitleButtonAnimIfNeeded; @property(nonatomic, copy) NSArray *includedWindows; @property(nonatomic, copy) NSArray *excludedWindows; @property(nonatomic, weak) UIWindow *prevKeyWindow; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Inspect/LKS_LocalInspectViewController.m ================================================ // // LKS_LocalInspectViewController.m // LookinServer // // Created by Li Kai on 2019/5/15. // https://lookin.work // #import "LKS_LocalInspectViewController.h" #import "LKS_LocalInspectPanelLabelView.h" #import "LookinIvarTrace.h" #import "LookinHierarchyInfo.h" #import "UIImage+LookinServer.h" #import "LookinServerDefines.h" static CGRect const kInvalidRect = (CGRect){-2, -2, 0, 0}; @interface LKS_LocalInspectViewController () @property(nonatomic, strong) CALayer *highlightLayer; @property(nonatomic, strong) CALayer *referLayer; @property(nonatomic, assign) CGRect highlightRect; @property(nonatomic, assign) CGRect referRect; @property(nonatomic, copy) NSArray *rulerLayers; @property(nonatomic, copy) NSArray *rulerLabels; @property(nonatomic, strong) UIButton *titleButton; @property(nonatomic, strong) UIView *panelView; @property(nonatomic, strong) LKS_LocalInspectPanelLabelView *titleLabelView; @property(nonatomic, strong) NSArray *contentLabelViews; @property(nonatomic, strong) UITapGestureRecognizer *tapRecognizer; @property(nonatomic, strong) UIPanGestureRecognizer *panRecognizer; @end @implementation LKS_LocalInspectViewController { CGFloat _panelContentsMarginTop; CGFloat _panelInsetTop; CGFloat _panelInsetBottom; } - (void)viewDidLoad { [super viewDidLoad]; _panelContentsMarginTop = 3; _panelInsetTop = 1; _panelInsetBottom = 5; self.view.layer.lks_isLookinPrivateLayer = YES; self.highlightLayer = [CALayer layer]; self.highlightLayer.backgroundColor = [UIColor colorWithRed:69/255.0 green:143/255.0 blue:208/255.0 alpha:.4].CGColor; [self.highlightLayer lookin_removeImplicitAnimations]; [self.view.layer addSublayer:self.highlightLayer]; self.referLayer = [CALayer layer]; self.referLayer.backgroundColor = [UIColor colorWithRed:69/255.0 green:143/255.0 blue:208/255.0 alpha:.09].CGColor; [self.referLayer lookin_removeImplicitAnimations]; [self.view.layer addSublayer:self.referLayer]; self.titleButton = [UIButton new]; self.titleButton.hidden = YES; self.titleButton.clipsToBounds = YES; self.titleButton.contentEdgeInsets = UIEdgeInsetsMake(6, 10, 6, 10); self.titleButton.layer.backgroundColor = [UIColor colorWithRed:208/255.0 green:2/255.0 blue:27/255.0 alpha:9].CGColor; [self.titleButton addTarget:self action:@selector(_handleExitButton) forControlEvents:UIControlEventTouchUpInside]; [self.titleButton setAttributedTitle:({ NSAttributedString *str1 = [[NSAttributedString alloc] initWithString:LKS_Localized(@"Tap or swipe to inspect") attributes:@{NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont boldSystemFontOfSize:13]}]; NSAttributedString *str2 = [[NSAttributedString alloc] initWithString:@" | " attributes:@{NSForegroundColorAttributeName:[UIColor colorWithWhite:1 alpha:.5], NSFontAttributeName:[UIFont systemFontOfSize:13]}]; NSAttributedString *str3 = [[NSAttributedString alloc] initWithString:LKS_Localized(@"Exit") attributes:@{NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont boldSystemFontOfSize:13]}]; NSMutableAttributedString *combinedStr = [NSMutableAttributedString new]; [combinedStr appendAttributedString:str1]; [combinedStr appendAttributedString:str2]; [combinedStr appendAttributedString:str3]; combinedStr; }) forState:UIControlStateNormal]; [self.view addSubview:self.titleButton]; self.rulerLayers = [NSArray lookin_arrayWithCount:4 block:^id(NSUInteger idx) { CALayer *layer = [CALayer new]; [layer lookin_removeImplicitAnimations]; layer.backgroundColor = [UIColor colorWithRed:69/255.0 green:143/255.0 blue:208/255.0 alpha:.4].CGColor; [self.view.layer addSublayer:layer]; return layer; }]; self.rulerLabels = [NSArray lookin_arrayWithCount:4 block:^id(NSUInteger idx) { UILabel *label = [UILabel new]; label.userInteractionEnabled = NO; label.backgroundColor = [UIColor colorWithRed:26/255.0 green:154/255.0 blue:251/255.0 alpha:1]; label.font = [UIFont systemFontOfSize:10]; label.textColor = [UIColor whiteColor]; label.textAlignment = NSTextAlignmentCenter; label.clipsToBounds = YES; [self.view addSubview:label]; return label; }]; self.panelView = [UIView new]; self.panelView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:.9]; self.panelView.layer.borderColor = [UIColor lightGrayColor].CGColor; self.panelView.layer.borderWidth = 1 / [[UIScreen mainScreen] scale]; self.panelView.userInteractionEnabled = NO; self.panelView.layer.cornerRadius = 5; self.panelView.hidden = YES; [self.view addSubview:self.panelView]; self.titleLabelView = [LKS_LocalInspectPanelLabelView new]; self.titleLabelView.verInset = 10; self.titleLabelView.leftLabel.font = [UIFont boldSystemFontOfSize:13]; self.titleLabelView.rightLabel.font = [UIFont systemFontOfSize:13]; self.titleLabelView.leftLabel.textColor = [UIColor blackColor]; self.titleLabelView.rightLabel.textColor = [UIColor blackColor]; self.titleLabelView.interspace = 20; [self.titleLabelView addBottomBorderLayer]; [self.panelView addSubview:self.titleLabelView]; self.contentLabelViews = [NSArray array]; self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_handleTapRecognizer:)]; [self.view addGestureRecognizer:self.tapRecognizer]; self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_handlePanRecognizer:)]; [self.view addGestureRecognizer:self.panRecognizer]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleEnterForegound) name:UIApplicationWillEnterForegroundNotification object:nil]; } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; self.highlightLayer.frame = self.highlightRect; self.referLayer.frame = self.referRect; if (!self.panelView.hidden) { CGRect contentRect = CGRectEqualToRect(kInvalidRect, self.referRect) ? self.highlightRect : self.referRect; [self _layoutPanelViewWithContentRect:contentRect]; } [self _renderAndLayoutRulersWithHighlightRect:self.highlightRect referRect:self.referRect]; if (!self.titleButton.hidden) { [self _layoutTitleButtonReferToPanelView]; } } - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; [self clearContents]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)highlightLayer:(CALayer *)layer { BOOL isValidLayer = ([layer isKindOfClass:[CALayer class]] && self.view.window && layer.lks_window); if (!isValidLayer) { self.highlightRect = kInvalidRect; self.referRect = kInvalidRect; self.panelView.hidden = YES; [self.view setNeedsLayout]; return; } self.highlightRect = [layer lks_frameInWindow:self.view.window]; self.referRect = ({ CALayer *referLayer = nil; CALayer *temp_referLayer = layer.superlayer; while (temp_referLayer) { if (temp_referLayer && !CGRectEqualToRect(temp_referLayer.bounds, layer.frame)) { referLayer = temp_referLayer; break; } temp_referLayer = temp_referLayer.superlayer; } referLayer ? [referLayer lks_frameInWindow:self.view.window] : kInvalidRect; }); self.titleLabelView.leftLabel.text = [self _titleStringForLayer:layer]; self.titleLabelView.rightLabel.text = [self _subtitleStringForLayer:layer]; NSArray *> *contents = [self _contentStringsForLayer:layer]; self.contentLabelViews = [self.contentLabelViews lookin_resizeWithCount:contents.count add:^LKS_LocalInspectPanelLabelView *(NSUInteger idx) { LKS_LocalInspectPanelLabelView *view = [LKS_LocalInspectPanelLabelView new]; view.verInset = 4; view.leftLabel.font = [UIFont systemFontOfSize:13]; view.rightLabel.font = [UIFont systemFontOfSize:13]; view.leftLabel.textColor = [UIColor grayColor]; view.rightLabel.textColor = [UIColor blackColor]; [self.panelView addSubview:view]; return view; } remove:^(NSUInteger idx, LKS_LocalInspectPanelLabelView *obj) { [obj removeFromSuperview]; } doNext:^(NSUInteger idx, LKS_LocalInspectPanelLabelView *obj) { NSArray *strings = contents[idx]; obj.leftLabel.text = strings.firstObject; obj.rightLabel.text = strings.lastObject; }]; self.panelView.hidden = NO; [self.view setNeedsLayout]; } - (void)setShowTitleButton:(BOOL)showTitleButton { _showTitleButton = showTitleButton; if (showTitleButton) { self.titleButton.hidden = NO; [self _layoutTitleButtonReferToPanelView]; } else { self.titleButton.hidden = YES; [self.titleButton.layer removeAllAnimations]; } } - (void)startTitleButtonAnimIfNeeded { if (self.titleButton.hidden) { return; } CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"backgroundColor"]; anim.fromValue = (id)[UIColor colorWithRed:208/255.0 green:2/255.0 blue:27/255.0 alpha:9].CGColor; anim.toValue = (id)[UIColor colorWithRed:208/255.0 green:2/255.0 blue:27/255.0 alpha:.7].CGColor; anim.duration = .8; anim.repeatCount = HUGE_VALF; anim.autoreverses = YES; [self.titleButton.layer removeAllAnimations]; [self.titleButton.layer addAnimation:anim forKey:nil]; } #pragma mark - Setter - (void)setReferRect:(CGRect)referRect { _referRect = referRect; [self _didSetReferOrHighlightLayer]; } - (void)setHighlightRect:(CGRect)highlightRect { _highlightRect = highlightRect; [self _didSetReferOrHighlightLayer]; } - (void)_didSetReferOrHighlightLayer { if (!CGRectEqualToRect(kInvalidRect, self.referRect) && !CGRectIntersectsRect(self.referRect, self.highlightRect)) { self.referLayer.backgroundColor = [UIColor colorWithRed:69/255.0 green:143/255.0 blue:208/255.0 alpha:.4].CGColor; } else { self.referLayer.backgroundColor = [UIColor colorWithRed:69/255.0 green:143/255.0 blue:208/255.0 alpha:.07].CGColor; } } #pragma mark - Layout - (void)_layoutPanelViewWithContentRect:(CGRect)contentRect { if (CGRectEqualToRect(contentRect, CGRectNull)) { self.panelView.frame = kInvalidRect; return; } CGSize containerSize = self.view.bounds.size; CGFloat panelWidth = self.titleLabelView.lks_bestWidth; panelWidth = [self.contentLabelViews lookin_reduceCGFloat:^CGFloat(CGFloat accumulator, NSUInteger idx, LKS_LocalInspectPanelLabelView *obj) { return MAX(accumulator, obj.lks_bestWidth); } initialAccumlator:panelWidth]; CGFloat screenInset = 10; panelWidth = MIN(panelWidth, containerSize.width - screenInset * 2); self.titleLabelView.frame = CGRectMake(0, _panelInsetTop, panelWidth, self.titleLabelView.lks_bestHeight); __block CGFloat posY = CGRectGetMaxY(self.titleLabelView.frame) + _panelContentsMarginTop; [self.contentLabelViews enumerateObjectsUsingBlock:^(LKS_LocalInspectPanelLabelView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.frame = CGRectMake(0, posY, panelWidth, obj.lks_bestHeight); posY = CGRectGetMaxY(obj.frame); }]; posY += _panelInsetBottom; CGSize panelSize = CGSizeMake(panelWidth, posY); CGFloat panelMargin = 10; CGFloat panelY = ({ CGFloat y = 0; CGFloat panelMinY = screenInset; if (@available(iOS 11.0, tvOS 11.0, *)) { panelMinY = self.view.safeAreaInsets.top; } CGFloat panelMinBottomNeeded = screenInset; if (@available(iOS 11.0, tvOS 11.0, *)) { panelMinBottomNeeded = self.view.safeAreaInsets.bottom; } panelMinBottomNeeded += panelSize.height; if (contentRect.origin.y - panelSize.height >= panelMinY) { // 放到目标上方 y = contentRect.origin.y - panelMargin - panelSize.height; } else { CGFloat targetBottom = containerSize.height - CGRectGetMaxY(contentRect); if (targetBottom > panelMinBottomNeeded) { // 放到目标下方 y = CGRectGetMaxY(contentRect) + panelMargin; } else { // 放到目标内部的上方 y = contentRect.origin.y + panelMargin; if (@available(iOS 11.0, tvOS 11.0, *)) { y = MAX(y, self.view.safeAreaInsets.top); } } } y; }); CGFloat panelX = ({ CGFloat x = 0; // 先尝试和目标居中 x = CGRectGetMidX(contentRect) - panelSize.width / 2.0; if (x <= 0) { // 如果超出了左边屏幕,则挪到距离屏幕左边 x = screenInset; } else if (x + panelSize.width > containerSize.width) { // 如果超出了右边屏幕,则挪到距离屏幕右边 labelMargin 的距离 x = containerSize.width - screenInset - panelSize.width; } x; }); self.panelView.frame = CGRectMake(panelX, panelY, panelSize.width, panelSize.height); } - (void)_renderAndLayoutRulersWithHighlightRect:(CGRect)highlightRect referRect:(CGRect)referRect { BOOL showRulers = !CGRectEqualToRect(highlightRect, kInvalidRect) && !CGRectEqualToRect(referRect, kInvalidRect); if (!showRulers) { [self.rulerLayers enumerateObjectsUsingBlock:^(CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.frame = CGRectZero; }]; [self.rulerLabels enumerateObjectsUsingBlock:^(UILabel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.frame = CGRectZero; }]; return; } CGFloat horInset = 8; CGFloat verInset = 4; CALayer *layer = self.rulerLayers[0]; UILabel *label = self.rulerLabels[0]; // top if (CGRectGetMinY(highlightRect) > CGRectGetMinY(referRect)) { CGFloat value = 0; if (CGRectGetMinY(highlightRect) > CGRectGetMaxY(referRect)) { value = CGRectGetMinY(highlightRect) - CGRectGetMaxY(referRect); } else { value = CGRectGetMinY(highlightRect) - CGRectGetMinY(referRect); } layer.frame = CGRectMake(CGRectGetMidX(highlightRect), CGRectGetMinY(highlightRect) - value, 1, value); label.text = [NSString lookin_stringFromDouble:value decimal:1];; CGSize bestSize = label.lks_bestSize; CGSize adjustedSize = CGSizeMake(bestSize.width + horInset, bestSize.height + verInset); label.layer.cornerRadius = adjustedSize.height / 2.0; label.frame = CGRectMake(CGRectGetMaxX(layer.frame) + 2, CGRectGetMidY(layer.frame) - adjustedSize.height / 2.0, adjustedSize.width, adjustedSize.height); } else { layer.frame = CGRectZero; label.frame = CGRectZero; } // left layer = self.rulerLayers[1]; label = self.rulerLabels[1]; if (CGRectGetMinX(highlightRect) > CGRectGetMinX(referRect)) { CGFloat value = 0; if (CGRectGetMinX(highlightRect) > CGRectGetMaxX(referRect)) { value = CGRectGetMinX(highlightRect) - CGRectGetMaxX(referRect); } else { value = CGRectGetMinX(highlightRect) - CGRectGetMinX(referRect); } layer.frame = CGRectMake(CGRectGetMinX(highlightRect) - value, CGRectGetMidY(highlightRect), value, 1); label.text = [NSString lookin_stringFromDouble:value decimal:1];; CGSize bestSize = label.lks_bestSize; CGSize adjustedSize = CGSizeMake(bestSize.width + horInset, bestSize.height + verInset); label.layer.cornerRadius = adjustedSize.height / 2.0; label.frame = CGRectMake(CGRectGetMidX(layer.frame), CGRectGetMinY(layer.frame) - 2 - adjustedSize.height, adjustedSize.width, adjustedSize.height); } else { layer.frame = CGRectZero; label.frame = CGRectZero; } // bottom layer = self.rulerLayers[2]; label = self.rulerLabels[2]; if (CGRectGetMaxY(highlightRect) < CGRectGetMaxY(referRect)) { CGFloat value = 0; if (CGRectGetMaxY(highlightRect) < CGRectGetMinY(referRect)) { value = CGRectGetMinY(referRect) - CGRectGetMaxY(highlightRect); } else { value = CGRectGetMaxY(referRect) - CGRectGetMaxY(highlightRect); } layer.frame = CGRectMake(CGRectGetMidX(highlightRect), CGRectGetMaxY(highlightRect), 1, value); label.text = [NSString lookin_stringFromDouble:value decimal:1];; CGSize bestSize = label.lks_bestSize; CGSize adjustedSize = CGSizeMake(bestSize.width + horInset, bestSize.height + verInset); label.layer.cornerRadius = adjustedSize.height / 2.0; label.frame = CGRectMake(CGRectGetMaxX(layer.frame) + 2, CGRectGetMidY(layer.frame) - adjustedSize.height / 2.0, adjustedSize.width, adjustedSize.height); } else { layer.frame = CGRectZero; label.frame = CGRectZero; } // right layer = self.rulerLayers[3]; label = self.rulerLabels[3]; if (CGRectGetMaxX(highlightRect) < CGRectGetMaxX(referRect)) { CGFloat value = 0; if (CGRectGetMaxX(highlightRect) < CGRectGetMinX(referRect)) { value = CGRectGetMinX(referRect) - CGRectGetMaxX(highlightRect); } else { value = CGRectGetMaxX(referRect) - CGRectGetMaxX(highlightRect); } layer.frame = CGRectMake(CGRectGetMaxX(highlightRect), CGRectGetMidY(highlightRect), value, 1); label.text = [NSString lookin_stringFromDouble:value decimal:1];; CGSize bestSize = label.lks_bestSize; CGSize adjustedSize = CGSizeMake(bestSize.width + horInset, bestSize.height + verInset); label.layer.cornerRadius = adjustedSize.height / 2.0; label.frame = CGRectMake(CGRectGetMidX(layer.frame) - adjustedSize.width / 2.0, CGRectGetMinY(layer.frame) - 2 - adjustedSize.height, adjustedSize.width, adjustedSize.height); } else { layer.frame = CGRectZero; label.frame = CGRectZero; } } - (void)_layoutTitleButtonReferToPanelView { /** 0: 放到屏幕顶部或底部都可以 1:需要放到屏幕底部 2: 需要放到屏幕顶部 */ NSUInteger positionType = 0; if (self.panelView.hidden) { positionType = 0; } else { if (CGRectGetMinY(self.panelView.frame) <= 200) { positionType = 1; } else if (CGRectGetMaxY(self.panelView.frame) >= self.view.bounds.size.height - 200) { positionType = 2; } else { positionType = 0; } } if (positionType == 0) { if (self.titleButton.frame.origin.y <= 0 || self.titleButton.frame.origin.y > self.view.bounds.size.height / 2.0) { positionType = 1; } else { positionType = 2; } } self.titleButton.frame = ({ CGSize size = [self.titleButton sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; CGFloat x = self.view.bounds.size.width / 2.0 - size.width / 2.0; CGFloat y; if (positionType == 1) { // 放到屏幕底部 if (@available(iOS 11.0, tvOS 11.0, *)) { y = self.view.bounds.size.height - size.height - MAX(self.view.safeAreaInsets.bottom, 20); } else { y = self.view.bounds.size.height - size.height - 20; } } else { NSAssert(positionType == 2, @""); // 放到屏幕顶部 if (@available(iOS 11.0, tvOS 11.0, *)) { y = MAX(self.view.safeAreaInsets.top, 20); } else { y = 20; } } CGRectMake(x, y, size.width, size.height); }); self.titleButton.layer.cornerRadius = self.titleButton.bounds.size.height / 2.0; } #pragma mark - Event Handler - (void)_handleTapRecognizer:(UITapGestureRecognizer *)recognizer { CGPoint point = [recognizer locationInView:self.view]; UIView *view = [self _targetViewAtPoint:point]; if (view) { [self highlightLayer:view.layer]; } else { self.highlightRect = kInvalidRect; self.referRect = kInvalidRect; [self.view setNeedsLayout]; NSLog(@"LookinServer - No valid view was found at tap position %@ in 2D inspecting.", NSStringFromCGPoint(point)); } } - (void)_handlePanRecognizer:(UIPanGestureRecognizer *)recognizer { self.panelView.hidden = YES; if (recognizer.state == UIGestureRecognizerStateBegan) { CGPoint point = [recognizer locationInView:self.view]; UIView *view = [self _targetViewAtPoint:point]; if (view) { CGRect newHighlightRect = [view.layer lks_frameInWindow:self.view.window]; CGFloat offsetX = ABS(CGRectGetMidX(self.highlightRect) - CGRectGetMidX(newHighlightRect)); CGFloat offsetY = ABS(CGRectGetMidY(self.highlightRect) - CGRectGetMidY(newHighlightRect)); if (offsetX > 200 || offsetY > 200) { self.highlightRect = newHighlightRect; } } self.referRect = kInvalidRect; } else if (recognizer.state == UIGestureRecognizerStateChanged) { if (CGRectEqualToRect(kInvalidRect, self.highlightRect)) { return; } CGPoint point = [recognizer locationInView:self.view]; UIView *endView = [self _targetViewAtPoint:point]; if (endView) { self.referRect = [endView.layer lks_frameInWindow:self.view.window]; } else { self.referRect = kInvalidRect; } [self.view setNeedsLayout]; } } - (void)_handleExitButton { if (self.didSelectExit) { self.didSelectExit(); } } - (void)_handleEnterForegound { [self startTitleButtonAnimIfNeeded]; } #pragma mark - Others /// 该 point 是在 self.view 的坐标系下 - (UIView *)_targetViewAtPoint:(CGPoint)point { __block UIView *targetView = nil; [[UIApplication sharedApplication].windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { if (targetView) { *stop = YES; return; } if (self.includedWindows.count) { if (![self.includedWindows containsObject:window]) { return; } } else if ([self.excludedWindows containsObject:window]) { return; } if (window == self.view.window) { return; } if (window.hidden) { return; } CGPoint newPoint = [window convertPoint:point fromWindow:self.view.window]; if (window == self.prevKeyWindow) { targetView = [window lks_subviewAtPoint:newPoint preferredClasses:[self _preferredClassesInSelecting]]; } else { targetView = [window hitTest:point withEvent:nil]; } }]; if (!targetView) { return nil; } // 特殊处理一下 if ([NSStringFromClass([targetView class]) isEqualToString:@"UITableViewCellContentView"] && [targetView.superview isKindOfClass:[UITableViewCell class]]) { targetView = targetView.superview; } else if ([targetView.superview isKindOfClass:[UITableView class]] && targetView == ((UITableView *)targetView.superview).backgroundView) { targetView = targetView.superview; } return targetView; } - (NSArray *)_preferredClassesInSelecting { static dispatch_once_t onceToken; static NSArray *classes = nil; dispatch_once(&onceToken,^{ #if TARGET_OS_TV NSMutableArray *array = @[[UILabel class], [UIProgressView class], [UIActivityIndicatorView class], [UITextView class], [UITextField class], [UIVisualEffectView class]].mutableCopy; #else NSMutableArray *array = @[[UILabel class], [UIProgressView class], [UIActivityIndicatorView class], [UITextView class], [UITextField class], [UISlider class], [UISwitch class], [UIVisualEffectView class]].mutableCopy; #endif NSArray *custom = [LookinHierarchyInfo collapsedClassList]; if (custom.count) { NSArray *customClasses = [custom lookin_map:^id(NSUInteger idx, NSString *value) { return NSClassFromString(value); }]; [array addObjectsFromArray:customClasses]; } classes = array; }); return classes; } - (void)clearContents { self.highlightRect = kInvalidRect; self.referRect = kInvalidRect; self.panelView.hidden = YES; [self.view setNeedsLayout]; } #pragma mark - Strings - (NSString *)_titleStringForLayer:(CALayer *)layer { NSObject *targetObject = layer.lks_hostView ? : layer; NSString *classNameString = NSStringFromClass([targetObject class]) ? : @""; return classNameString; } - (NSString *)_subtitleStringForLayer:(CALayer *)layer { NSString *traceString = nil; NSObject *targetObject = layer.lks_hostView ? : layer; if (layer.lks_hostView.lks_hostViewController) { traceString = [NSString stringWithFormat:@"%@.view", NSStringFromClass([layer.lks_hostView.lks_hostViewController class])]; } else { if (targetObject.lks_specialTrace.length) { traceString = targetObject.lks_specialTrace; } else if (targetObject.lks_ivarTraces.count) { traceString = [[[targetObject.lks_ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *value) { return value.ivarName; }] lookin_nonredundantArray] componentsJoinedByString:@", "]; } } return traceString; } - (NSArray *> *)_contentStringsForLayer:(CALayer *)layer { NSMutableArray *> *resultArray = [NSMutableArray array]; [resultArray addObject:@[@"Frame", [NSString lookin_stringFromRect:layer.frame]]]; if (layer.backgroundColor) { [resultArray addObject:@[@"BackgroundColor", [NSString lookin_rgbaStringFromColor:[UIColor colorWithCGColor:layer.backgroundColor]]]]; } if ([layer.lks_hostView isKindOfClass:[UIImageView class]]) { UIImage *image = ((UIImageView *)layer.lks_hostView).image; if (image.lks_imageSourceName.length) { [resultArray addObject:@[@"ImageName", [NSString stringWithFormat:@"\"%@\"", image.lks_imageSourceName]]]; } } else if ([layer.lks_hostView isKindOfClass:[UIButton class]]) { UIButton *button = (UIButton *)layer.lks_hostView; // 不要直接访问 button.titleLabel。因为如果 title 不存在的话,访问 button.titleLabel 会触发初始化 titleLabel 进而触发 dynamic hierarhy push if ([button titleForState:UIControlStateNormal].length) { [resultArray addObject:@[@"FontSize", [NSString lookin_stringFromDouble:button.titleLabel.font.pointSize decimal:2]]]; [resultArray addObject:@[@"FontName", button.titleLabel.font.fontName]]; [resultArray addObject:@[@"TextColor", [NSString lookin_rgbaStringFromColor:button.titleLabel.textColor]]]; } // 不要直接访问 button.imageView。因为如果 image 不存在的话,访问 button.image 会触发初始化 imageView 进而触发 dynamic hierarhy push if ([button imageForState:UIControlStateNormal]) { NSString *imageSourceName = button.imageView.image.lks_imageSourceName; if (imageSourceName.length) { [resultArray addObject:@[@"ImageName", [NSString stringWithFormat:@"\"%@\"", imageSourceName]]]; } } } else if ([layer.lks_hostView isKindOfClass:[UILabel class]]) { UILabel *label = (UILabel *)layer.lks_hostView; [resultArray addObject:@[@"FontSize", [NSString lookin_stringFromDouble:label.font.pointSize decimal:2]]]; [resultArray addObject:@[@"FontName", label.font.fontName]]; [resultArray addObject:@[@"TextColor", [NSString lookin_rgbaStringFromColor:label.textColor]]]; [resultArray addObject:@[@"NumberOfLines", [NSString stringWithFormat:@"%@", @(label.numberOfLines)]]]; } else if ([layer.lks_hostView isKindOfClass:[UIScrollView class]]) { UIScrollView *scrollView = (UIScrollView *)layer.lks_hostView; [resultArray addObject:@[@"ContentSize", [NSString lookin_stringFromSize:scrollView.contentSize]]]; [resultArray addObject:@[@"ContentOffset", [NSString lookin_stringFromPoint:scrollView.contentOffset]]]; [resultArray addObject:@[@"ContentInset", [NSString lookin_stringFromInset:scrollView.contentInset]]]; if (@available(iOS 11.0, tvOS 11.0, *)) { [resultArray addObject:@[@"AdjustedContentInset", [NSString lookin_stringFromInset:scrollView.adjustedContentInset]]]; } if ([scrollView isKindOfClass:[UITextView class]]) { UITextView *textView = (UITextView *)scrollView; [resultArray addObject:@[@"FontSize", [NSString lookin_stringFromDouble:textView.font.pointSize decimal:2]]]; [resultArray addObject:@[@"FontName", textView.font.fontName]]; [resultArray addObject:@[@"TextColor", [NSString lookin_rgbaStringFromColor:textView.textColor]]]; } } else if ([layer.lks_hostView isKindOfClass:[UITextField class]]) { UITextField *textField = (UITextField *)layer.lks_hostView; [resultArray addObject:@[@"FontSize", [NSString lookin_stringFromDouble:textField.font.pointSize decimal:2]]]; [resultArray addObject:@[@"FontName", textField.font.fontName]]; [resultArray addObject:@[@"TextColor", [NSString lookin_rgbaStringFromColor:textField.textColor]]]; } if (layer.borderColor && layer.borderWidth > 0) { [resultArray addObject:@[@"BorderColor", [NSString lookin_rgbaStringFromColor:[UIColor colorWithCGColor:layer.borderColor]]]]; [resultArray addObject:@[@"BorderWidth", [NSString lookin_stringFromDouble:layer.borderWidth decimal:2]]]; } if (layer.cornerRadius > 0) { [resultArray addObject:@[@"CornerRadius", [NSString lookin_stringFromDouble:layer.cornerRadius decimal:2]]]; } if (layer.opacity < 1) { [resultArray addObject:@[@"Opacity", [NSString lookin_stringFromDouble:layer.opacity decimal:2]]]; } if (layer.shadowColor && layer.shadowOpacity > 0) { [resultArray addObject:@[@"ShadowColor", [NSString lookin_rgbaStringFromColor:[UIColor colorWithCGColor:layer.shadowColor]]]]; [resultArray addObject:@[@"ShadowOpacity", [NSString lookin_stringFromDouble:layer.shadowOpacity decimal:2]]]; [resultArray addObject:@[@"ShadowOffset", [NSString lookin_stringFromSize:layer.shadowOffset]]]; [resultArray addObject:@[@"ShadowRadius", [NSString lookin_stringFromDouble:layer.shadowRadius decimal:2]]]; } return resultArray; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/LookinServer.h ================================================ // // LookinServer.h // LookinServer // // Created by Li Kai on 2019/7/20. // https://lookin.work // #ifndef LookinServer_h #define LookinServer_h #endif /* LookinServer_h */ ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_AttrGroupsMaker.h ================================================ // // LKS_AttrGroupsMaker.h // LookinServer // // Created by Li Kai on 2019/6/6. // https://lookin.work // #import "LookinDefines.h" @class LookinAttributesGroup; @interface LKS_AttrGroupsMaker : NSObject + (NSArray *)attrGroupsForLayer:(CALayer *)layer; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_AttrGroupsMaker.m ================================================ // // LKS_AttrGroupsMaker.m // LookinServer // // Created by Li Kai on 2019/6/6. // https://lookin.work // #import "LKS_AttrGroupsMaker.h" #import "LookinAttributesGroup.h" #import "LookinAttributesSection.h" #import "LookinAttribute.h" #import "LookinDashboardBlueprint.h" #import "LookinIvarTrace.h" #import "UIColor+LookinServer.h" #import "LookinServerDefines.h" @implementation LKS_AttrGroupsMaker + (NSArray *)attrGroupsForLayer:(CALayer *)layer { if (!layer) { NSAssert(NO, @""); return nil; } NSArray *groups = [[LookinDashboardBlueprint groupIDs] lookin_map:^id(NSUInteger idx, LookinAttrGroupIdentifier groupID) { LookinAttributesGroup *group = [LookinAttributesGroup new]; group.identifier = groupID; NSArray *secIDs = [LookinDashboardBlueprint sectionIDsForGroupID:groupID]; group.attrSections = [secIDs lookin_map:^id(NSUInteger idx, LookinAttrSectionIdentifier secID) { LookinAttributesSection *sec = [LookinAttributesSection new]; sec.identifier = secID; NSArray *attrIDs = [LookinDashboardBlueprint attrIDsForSectionID:secID]; sec.attributes = [attrIDs lookin_map:^id(NSUInteger idx, LookinAttrIdentifier attrID) { NSInteger minAvailableVersion = [LookinDashboardBlueprint minAvailableOSVersionWithAttrID:attrID]; if (minAvailableVersion > 0 && (NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < minAvailableVersion)) { // iOS 版本过低不支持该属性 return nil; } id targetObj = nil; if ([LookinDashboardBlueprint isUIViewPropertyWithAttrID:attrID]) { targetObj = layer.lks_hostView; } else { targetObj = layer; } if (targetObj) { Class targetClass = NSClassFromString([LookinDashboardBlueprint classNameWithAttrID:attrID]); if (![targetObj isKindOfClass:targetClass]) { return nil; } LookinAttribute *attr = [self _attributeWithIdentifer:attrID targetObject:targetObj]; return attr; } else { return nil; } }]; if (sec.attributes.count) { return sec; } else { return nil; } }]; if ([groupID isEqualToString:LookinAttrGroup_AutoLayout]) { // 这里特殊处理一下,如果 AutoLayout 里面不包含 Constraints 的话(只有 Hugging 和 Resistance),就丢弃掉这整个 AutoLayout 不显示 BOOL hasConstraits = [group.attrSections lookin_any:^BOOL(LookinAttributesSection *obj) { return [obj.identifier isEqualToString:LookinAttrSec_AutoLayout_Constraints]; }]; if (!hasConstraits) { return nil; } } if (group.attrSections.count) { return group; } else { return nil; } }]; return groups; } + (LookinAttribute *)_attributeWithIdentifer:(LookinAttrIdentifier)identifier targetObject:(id)target { if (!target) { NSAssert(NO, @""); return nil; } LookinAttribute *attribute = [LookinAttribute new]; attribute.identifier = identifier; SEL getter = [LookinDashboardBlueprint getterWithAttrID:identifier]; if (!getter) { NSAssert(NO, @""); return nil; } if (![target respondsToSelector:getter]) { // 比如某些 QMUI 的属性,不引入 QMUI 就会走到这个分支里 return nil; } NSMethodSignature *signature = [target methodSignatureForSelector:getter]; if (signature.numberOfArguments > 2) { NSAssert(NO, @"getter 不可以有参数"); return nil; } if (strcmp([signature methodReturnType], @encode(void)) == 0) { NSAssert(NO, @"getter 返回值不能为 void"); return nil; } NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; invocation.target = target; invocation.selector = getter; [invocation invoke]; const char *returnType = [signature methodReturnType]; if (strcmp(returnType, @encode(char)) == 0) { char targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeChar; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(int)) == 0) { int targetValue; [invocation getReturnValue:&targetValue]; attribute.value = @(targetValue); if ([LookinDashboardBlueprint enumListNameWithAttrID:identifier]) { attribute.attrType = LookinAttrTypeEnumInt; } else { attribute.attrType = LookinAttrTypeInt; } } else if (strcmp(returnType, @encode(short)) == 0) { short targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeShort; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(long)) == 0) { long targetValue; [invocation getReturnValue:&targetValue]; attribute.value = @(targetValue); if ([LookinDashboardBlueprint enumListNameWithAttrID:identifier]) { attribute.attrType = LookinAttrTypeEnumLong; } else { attribute.attrType = LookinAttrTypeLong; } } else if (strcmp(returnType, @encode(long long)) == 0) { long long targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeLongLong; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(unsigned char)) == 0) { unsigned char targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeUnsignedChar; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(unsigned int)) == 0) { unsigned int targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeUnsignedInt; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(unsigned short)) == 0) { unsigned short targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeUnsignedShort; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(unsigned long)) == 0) { unsigned long targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeUnsignedLong; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(unsigned long long)) == 0) { unsigned long long targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeUnsignedLongLong; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(float)) == 0) { float targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeFloat; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(double)) == 0) { double targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeDouble; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(BOOL)) == 0) { BOOL targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeBOOL; attribute.value = @(targetValue); } else if (strcmp(returnType, @encode(SEL)) == 0) { SEL targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeSel; attribute.value = NSStringFromSelector(targetValue); } else if (strcmp(returnType, @encode(Class)) == 0) { Class targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeClass; attribute.value = NSStringFromClass(targetValue); } else if (strcmp(returnType, @encode(CGPoint)) == 0) { CGPoint targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeCGPoint; attribute.value = [NSValue valueWithCGPoint:targetValue]; } else if (strcmp(returnType, @encode(CGVector)) == 0) { CGVector targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeCGVector; attribute.value = [NSValue valueWithCGVector:targetValue]; } else if (strcmp(returnType, @encode(CGSize)) == 0) { CGSize targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeCGSize; attribute.value = [NSValue valueWithCGSize:targetValue]; } else if (strcmp(returnType, @encode(CGRect)) == 0) { CGRect targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeCGRect; attribute.value = [NSValue valueWithCGRect:targetValue]; } else if (strcmp(returnType, @encode(CGAffineTransform)) == 0) { CGAffineTransform targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeCGAffineTransform; attribute.value = [NSValue valueWithCGAffineTransform:targetValue]; } else if (strcmp(returnType, @encode(UIEdgeInsets)) == 0) { UIEdgeInsets targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeUIEdgeInsets; attribute.value = [NSValue valueWithUIEdgeInsets:targetValue]; } else if (strcmp(returnType, @encode(UIOffset)) == 0) { UIOffset targetValue; [invocation getReturnValue:&targetValue]; attribute.attrType = LookinAttrTypeUIOffset; attribute.value = [NSValue valueWithUIOffset:targetValue]; } else { NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:returnType]; if ([argType_string hasPrefix:@"@"]) { __unsafe_unretained id returnObjValue; [invocation getReturnValue:&returnObjValue]; if (!returnObjValue && [LookinDashboardBlueprint hideIfNilWithAttrID:identifier]) { // 对于某些属性,若 value 为 nil 则不显示 return nil; } attribute.attrType = [LookinDashboardBlueprint objectAttrTypeWithAttrID:identifier]; if (attribute.attrType == LookinAttrTypeUIColor) { attribute.value = [returnObjValue lks_rgbaComponents]; } else { attribute.value = returnObjValue; } } else { NSAssert(NO, @"不支持解析该类型的返回值"); return nil; } } return attribute; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_EventHandlerMaker.h ================================================ // // LKS_EventHandlerMaker.h // LookinServer // // Created by Li Kai on 2019/8/7. // https://lookin.work // #import "LookinDefines.h" @class LookinEventHandler; @interface LKS_EventHandlerMaker : NSObject + (NSArray *)makeForView:(UIView *)view; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_EventHandlerMaker.m ================================================ // // LKS_EventHandlerMaker.m // LookinServer // // Created by Li Kai on 2019/8/7. // https://lookin.work // #import "LKS_EventHandlerMaker.h" #import "LookinTuple.h" #import "LookinEventHandler.h" #import "LookinObject.h" #import "UIGestureRecognizer+LookinServer.h" #import "LookinWeakContainer.h" #import "LookinIvarTrace.h" #import "LookinServerDefines.h" @implementation LKS_EventHandlerMaker + (NSArray *)makeForView:(UIView *)view { if (!view) { return nil; } NSMutableArray *allHandlers = nil; if ([view isKindOfClass:[UIControl class]]) { NSArray *targetActionHandlers = [self _targetActionHandlersForControl:(UIControl *)view]; if (targetActionHandlers.count) { if (!allHandlers) { allHandlers = [NSMutableArray array]; } [allHandlers addObjectsFromArray:targetActionHandlers]; } } NSArray *gestureHandlers = [self _gestureHandlersForView:view]; if (gestureHandlers.count) { if (!allHandlers) { allHandlers = [NSMutableArray array]; } [allHandlers addObjectsFromArray:gestureHandlers]; } return allHandlers.copy; } + (NSArray *)_gestureHandlersForView:(UIView *)view { if (view.gestureRecognizers.count == 0) { return nil; } NSArray *handlers = [view.gestureRecognizers lookin_map:^id(NSUInteger idx, __kindof UIGestureRecognizer *recognizer) { LookinEventHandler *handler = [LookinEventHandler new]; handler.handlerType = LookinEventHandlerTypeGesture; handler.eventName = [recognizer lks_shortClassName]; handler.targetActions = [[recognizer lks_targetActions] lookin_map:^id(NSUInteger idx, LookinTwoTuple *rawTuple) { NSObject *target = ((LookinWeakContainer *)rawTuple.first).object; if (!target) { // 该 target 已被释放 return nil; } LookinStringTwoTuple *newTuple = [LookinStringTwoTuple new]; newTuple.first = [LKS_Helper descriptionOfObject:target]; newTuple.second = (NSString *)rawTuple.second; return newTuple; }]; handler.inheritedRecognizerName = [self _inheritedRecognizerNameForRecognizer:recognizer]; handler.gestureRecognizerIsEnabled = recognizer.enabled; if (recognizer.delegate) { handler.gestureRecognizerDelegator = [LKS_Helper descriptionOfObject:recognizer.delegate]; } handler.recognizerIvarTraces = [recognizer.lks_ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *trace) { return [NSString stringWithFormat:@"(%@ *) -> %@", trace.hostClassName, trace.ivarName]; }]; handler.recognizerOid = [recognizer lks_registerOid]; return handler; }]; return handlers; } + (NSString *)_inheritedRecognizerNameForRecognizer:(UIGestureRecognizer *)recognizer { if (!recognizer) { NSAssert(NO, @""); return nil; } static NSArray *baseRecognizers; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 注意这里 UIScreenEdgePanGestureRecognizer 在 UIPanGestureRecognizer 前面,因为 UIScreenEdgePanGestureRecognizer 继承于 UIPanGestureRecognizer #if TARGET_OS_TV baseRecognizers = @[[UILongPressGestureRecognizer class], [UIPanGestureRecognizer class], [UISwipeGestureRecognizer class], [UITapGestureRecognizer class]]; #else baseRecognizers = @[[UILongPressGestureRecognizer class], [UIScreenEdgePanGestureRecognizer class], [UIPanGestureRecognizer class], [UISwipeGestureRecognizer class], [UIRotationGestureRecognizer class], [UIPinchGestureRecognizer class], [UITapGestureRecognizer class]]; #endif }); __block NSString *result = @"UIGestureRecognizer"; [baseRecognizers enumerateObjectsUsingBlock:^(Class _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([recognizer isMemberOfClass:obj]) { // 自身就是基本款,则直接置为 nil result = nil; *stop = YES; return; } if ([recognizer isKindOfClass:obj]) { result = NSStringFromClass(obj); *stop = YES; return; } }]; return result; } + (NSArray *)_targetActionHandlersForControl:(UIControl *)control { static dispatch_once_t onceToken; static NSArray *allEvents = nil; dispatch_once(&onceToken,^{ allEvents = @[@(UIControlEventTouchDown), @(UIControlEventTouchDownRepeat), @(UIControlEventTouchDragInside), @(UIControlEventTouchDragOutside), @(UIControlEventTouchDragEnter), @(UIControlEventTouchDragExit), @(UIControlEventTouchUpInside), @(UIControlEventTouchUpOutside), @(UIControlEventTouchCancel), @(UIControlEventValueChanged), @(UIControlEventEditingDidBegin), @(UIControlEventEditingChanged), @(UIControlEventEditingDidEnd), @(UIControlEventEditingDidEndOnExit)]; if (@available(iOS 9.0, *)) { allEvents = [allEvents arrayByAddingObject:@(UIControlEventPrimaryActionTriggered)]; } }); NSSet *allTargets = control.allTargets; if (!allTargets.count) { return nil; } NSMutableArray *handlers = [NSMutableArray array]; [allEvents enumerateObjectsUsingBlock:^(NSNumber * _Nonnull eventNum, NSUInteger idx, BOOL * _Nonnull stop) { UIControlEvents event = [eventNum unsignedIntegerValue]; NSMutableArray *targetActions = [NSMutableArray array]; [allTargets enumerateObjectsUsingBlock:^(id _Nonnull target, BOOL * _Nonnull stop) { NSArray *actions = [control actionsForTarget:target forControlEvent:event]; [actions enumerateObjectsUsingBlock:^(NSString * _Nonnull action, NSUInteger idx, BOOL * _Nonnull stop) { LookinStringTwoTuple *tuple = [LookinStringTwoTuple new]; tuple.first = [LKS_Helper descriptionOfObject:target]; tuple.second = action; [targetActions addObject:tuple]; }]; }]; if (targetActions.count) { LookinEventHandler *handler = [LookinEventHandler new]; handler.handlerType = LookinEventHandlerTypeTargetAction; handler.eventName = [self _nameFromControlEvent:event]; handler.targetActions = targetActions.copy; [handlers addObject:handler]; } }]; return handlers; } + (NSString *)_nameFromControlEvent:(UIControlEvents)event { static dispatch_once_t onceToken; static NSDictionary *eventsAndNames = nil; dispatch_once(&onceToken,^{ NSMutableDictionary *eventsAndNames_m = @{ @(UIControlEventTouchDown): @"UIControlEventTouchDown", @(UIControlEventTouchDownRepeat): @"UIControlEventTouchDownRepeat", @(UIControlEventTouchDragInside): @"UIControlEventTouchDragInside", @(UIControlEventTouchDragOutside): @"UIControlEventTouchDragOutside", @(UIControlEventTouchDragEnter): @"UIControlEventTouchDragEnter", @(UIControlEventTouchDragExit): @"UIControlEventTouchDragExit", @(UIControlEventTouchUpInside): @"UIControlEventTouchUpInside", @(UIControlEventTouchUpOutside): @"UIControlEventTouchUpOutside", @(UIControlEventTouchCancel): @"UIControlEventTouchCancel", @(UIControlEventValueChanged): @"UIControlEventValueChanged", @(UIControlEventEditingDidBegin): @"UIControlEventEditingDidBegin", @(UIControlEventEditingChanged): @"UIControlEventEditingChanged", @(UIControlEventEditingDidEnd): @"UIControlEventEditingDidEnd", @(UIControlEventEditingDidEndOnExit): @"UIControlEventEditingDidEndOnExit", }.mutableCopy; if (@available(iOS 9.0, *)) { eventsAndNames_m[@(UIControlEventPrimaryActionTriggered)] = @"UIControlEventPrimaryActionTriggered"; } eventsAndNames = eventsAndNames_m.copy; }); NSString *name = eventsAndNames[@(event)]; return name; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_ExportManager.h ================================================ // // LKS_ExportManager.h // LookinServer // // Created by Li Kai on 2019/5/13. // https://lookin.work // #import @interface LKS_ExportManager : NSObject + (instancetype)sharedInstance; - (void)exportAndShare; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_ExportManager.m ================================================ // // LKS_ExportManager.m // LookinServer // // Created by Li Kai on 2019/5/13. // https://lookin.work // #import "LKS_ExportManager.h" #import "UIViewController+LookinServer.h" #import "LookinHierarchyInfo.h" #import "LookinHierarchyFile.h" #import "LookinAppInfo.h" #import "LookinServerDefines.h" @interface LKS_ExportManagerMaskView : UIView @property(nonatomic, strong) UIView *tipsView; @property(nonatomic, strong) UILabel *firstLabel; @property(nonatomic, strong) UILabel *secondLabel; @property(nonatomic, strong) UILabel *thirdLabel; @end @implementation LKS_ExportManagerMaskView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.35]; self.layer.lks_isLookinPrivateLayer = YES; self.layer.lks_avoidCapturing = YES; self.tipsView = [UIView new]; self.tipsView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.88]; self.tipsView.layer.cornerRadius = 6; self.tipsView.layer.masksToBounds = YES; [self addSubview:self.tipsView]; self.firstLabel = [UILabel new]; self.firstLabel.text = LKS_Localized(@"Creating File…"); self.firstLabel.textColor = [UIColor whiteColor]; self.firstLabel.font = [UIFont boldSystemFontOfSize:14]; self.firstLabel.textAlignment = NSTextAlignmentCenter; self.firstLabel.numberOfLines = 0; [self.tipsView addSubview:self.firstLabel]; self.secondLabel = [UILabel new]; self.secondLabel.text = LKS_Localized(@"May take 8 or more seconds according to the UI complexity."); self.secondLabel.textColor = [UIColor colorWithRed:173/255.0 green:180/255.0 blue:190/255.0 alpha:1]; self.secondLabel.font = [UIFont systemFontOfSize:12]; self.secondLabel.textAlignment = NSTextAlignmentLeft; self.secondLabel.numberOfLines = 0; [self.tipsView addSubview:self.secondLabel]; self.thirdLabel = [UILabel new]; self.thirdLabel.text = LKS_Localized(@"The file can be opend by Lookin.app in macOS."); self.thirdLabel.textColor = [UIColor colorWithRed:173/255.0 green:180/255.0 blue:190/255.0 alpha:1]; self.thirdLabel.font = [UIFont systemFontOfSize:12]; self.thirdLabel.textAlignment = NSTextAlignmentCenter; self.thirdLabel.numberOfLines = 0; [self.tipsView addSubview:self.thirdLabel]; } return self; } - (void)layoutSubviews { [super layoutSubviews]; UIEdgeInsets insets = UIEdgeInsetsMake(8, 10, 8, 10); CGFloat maxLabelWidth = self.bounds.size.width * .8 - insets.left - insets.right; CGSize firstSize = [self.firstLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)]; CGSize secondSize = [self.secondLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)]; CGSize thirdSize = [self.thirdLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)]; CGFloat tipsWidth = MAX(MAX(firstSize.width, secondSize.width), thirdSize.width) + insets.left + insets.right; self.firstLabel.frame = CGRectMake(tipsWidth / 2.0 - firstSize.width / 2.0, insets.top, firstSize.width, firstSize.height); self.secondLabel.frame = CGRectMake(tipsWidth / 2.0 - secondSize.width / 2.0, CGRectGetMaxY(self.firstLabel.frame) + 10, secondSize.width, secondSize.height); self.thirdLabel.frame = CGRectMake(tipsWidth / 2.0 - thirdSize.width / 2.0, CGRectGetMaxY(self.secondLabel.frame) + 5, thirdSize.width, thirdSize.height); self.tipsView.frame = ({ CGFloat height = CGRectGetMaxY(self.thirdLabel.frame) + insets.bottom; CGRectMake(self.bounds.size.width / 2.0 - tipsWidth / 2.0, self.bounds.size.height / 2.0 - height / 2.0, tipsWidth, height); }); } @end @interface LKS_ExportManager () #if TARGET_OS_TV #else @property(nonatomic, strong) UIDocumentInteractionController *documentController; #endif @property(nonatomic, strong) LKS_ExportManagerMaskView *maskView; @end @implementation LKS_ExportManager + (instancetype)sharedInstance { static dispatch_once_t onceToken; static LKS_ExportManager *instance = nil; dispatch_once(&onceToken,^{ instance = [[super allocWithZone:NULL] init]; }); return instance; } + (id)allocWithZone:(struct _NSZone *)zone{ return [self sharedInstance]; } #if TARGET_OS_TV - (void)exportAndShare { NSAssert(NO, @"not supported"); } #else - (void)exportAndShare { UIViewController *visibleVc = [UIViewController lks_visibleViewController]; if (!visibleVc) { NSLog(@"LookinServer - Failed to export because we didn't find any visible view controller."); return; } [[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_WillExport" object:nil]; if (!self.maskView) { self.maskView = [LKS_ExportManagerMaskView new]; } [visibleVc.view.window addSubview:self.maskView]; self.maskView.frame = visibleVc.view.window.bounds; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ LookinHierarchyInfo *info = [LookinHierarchyInfo exportedInfo]; LookinHierarchyFile *file = [LookinHierarchyFile new]; file.serverVersion = info.serverVersion; file.hierarchyInfo = info; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:file]; if (!data) { return; } NSString *fileName = ({ NSString *timeString = ({ NSDate *date = [NSDate date]; NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"MMddHHmm"]; [formatter stringFromDate:date]; }); NSString *iOSVersion = ({ NSString *str = info.appInfo.osDescription; NSUInteger dotIdx = [str rangeOfString:@"."].location; if (dotIdx != NSNotFound) { str = [str substringToIndex:dotIdx]; } str; }); [NSString stringWithFormat:@"%@_ios%@_%@.lookin", info.appInfo.appName, iOSVersion, timeString]; }); NSString *path = [NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), fileName]; [data writeToFile:path atomically:YES]; [self.maskView removeFromSuperview]; if (!self.documentController) { self.documentController = [UIDocumentInteractionController new]; } self.documentController.URL = [NSURL fileURLWithPath:path]; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { [self.documentController presentOpenInMenuFromRect:CGRectMake(0, 0, 1, 1) inView:visibleVc.view animated:YES]; } else { [self.documentController presentOpenInMenuFromRect:visibleVc.view.bounds inView:visibleVc.view animated:YES]; } [[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_DidFinishExport" object:nil]; // [self.documentController presentOptionsMenuFromRect:visibleVc.view.bounds inView:visibleVc.view animated:YES]; // CFTimeInterval endTime = CACurrentMediaTime(); // CFTimeInterval consumingTime = endTime - startTime; // NSLog(@"LookinServer - 导出 UI 结构耗时:%@", @(consumingTime)); }); } #endif @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_Helper.h ================================================ // // LKS_Helper.h // LookinServer // // Created by Li Kai on 2019/7/20. // https://lookin.work // #import "LookinDefines.h" #import #define LKS_Localized(stringKey) NSLocalizedStringFromTableInBundle(stringKey, nil, [NSBundle bundleForClass:self.class], nil) @interface LKS_Helper : NSObject /// 如果 object 为 nil 则返回字符串 “nil”,否则返回字符串格式类似于 (UIView *) + (NSString *)descriptionOfObject:(id)object; /// 返回当前的bundle + (NSBundle *)bundle; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_Helper.m ================================================ // // LKS_Helper.m // LookinServer // // Created by Li Kai on 2019/7/20. // https://lookin.work // #import "LKS_Helper.h" #import "NSObject+LookinServer.h" @implementation LKS_Helper + (NSString *)descriptionOfObject:(id)object { if (!object) { return @"nil"; } NSString *className; if ([object respondsToSelector:@selector(lks_shortClassName)]) { className = [object lks_shortClassName]; } else { className = NSStringFromClass([object class]); } return [NSString stringWithFormat:@"(%@ *)", className]; } + (NSBundle *)bundle { static id bundle = nil; if (bundle != nil) { #ifdef SPM_RESOURCE_BUNDLE_IDENTIFITER bundle = [NSBundle bundleWithIdentifier:SPM_RESOURCE_BUNDLE_IDENTIFITER]; #else bundle = [NSBundle bundleForClass:self.class]; #endif } return bundle; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_HierarchyDisplayItemsMaker.h ================================================ // // LKS_HierarchyDisplayItemsMaker.h // LookinServer // // Created by Li Kai on 2019/2/19. // https://lookin.work // #import "LookinDefines.h" @class LookinDisplayItem; @interface LKS_HierarchyDisplayItemsMaker : NSObject /// @param hasScreenshots 是否包含 soloScreenshots 和 groupScreenshot 属性 /// @param hasAttrList 是否包含 attributesGroupList 属性 /// @param lowQuality screenshots 是否为低质量(当 hasScreenshots 为 NO 时,该属性无意义) /// @param includedWindows 当传入的该参数有效时(即 count 大于 0),将仅抓取该数组指定的 window 的数据 /// @param excludedWindows 当 includedWindows 无效时,将不抓取 excludedWindows 指定的 window 的数据 + (NSArray *)itemsWithScreenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality includedWindows:(NSArray *)includedWindows excludedWindows:(NSArray *)excludedWindows; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_HierarchyDisplayItemsMaker.m ================================================ // // LKS_HierarchyDisplayItemsMaker.m // LookinServer // // Created by Li Kai on 2019/2/19. // https://lookin.work // #import "LKS_HierarchyDisplayItemsMaker.h" #import "LookinDisplayItem.h" #import "LKS_TraceManager.h" #import "LKS_AttrGroupsMaker.h" #import "LKS_EventHandlerMaker.h" #import "LookinServerDefines.h" @implementation LKS_HierarchyDisplayItemsMaker + (NSArray *)itemsWithScreenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality includedWindows:(NSArray *)includedWindows excludedWindows:(NSArray *)excludedWindows { [[LKS_TraceManager sharedInstance] reload]; NSArray *windows = [[UIApplication sharedApplication].windows copy]; NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:windows.count]; [windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { if (includedWindows.count) { if (![includedWindows containsObject:window]) { return; } } else if ([excludedWindows containsObject:window]) { return; } LookinDisplayItem *item = [self _displayItemWithLayer:window.layer screenshots:hasScreenshots attrList:hasAttrList lowImageQuality:lowQuality]; item.representedAsKeyWindow = window.isKeyWindow; if (item) { [resultArray addObject:item]; } }]; return [resultArray copy]; } + (LookinDisplayItem *)_displayItemWithLayer:(CALayer *)layer screenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality { if (!layer || layer.lks_avoidCapturing) { return nil; } LookinDisplayItem *item = [LookinDisplayItem new]; if ([self validateFrame:layer.frame]) { item.frame = layer.frame; } else { NSLog(@"LookinServer - 该 layer 的 frame(%@) 不太寻常,可能导致 Lookin 客户端中图像渲染错误,因此这里暂时将其视为 CGRectZero", NSStringFromCGRect(layer.frame)); item.frame = CGRectZero; } item.bounds = layer.bounds; if (hasScreenshots) { item.soloScreenshot = [layer lks_soloScreenshotWithLowQuality:lowQuality]; item.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowQuality]; item.screenshotEncodeType = LookinDisplayItemImageEncodeTypeNSData; } if (hasAttrList) { item.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer]; } item.isHidden = layer.isHidden; item.alpha = layer.opacity; item.layerObject = [LookinObject instanceWithObject:layer]; if (layer.lks_hostView) { UIView *view = layer.lks_hostView; item.viewObject = [LookinObject instanceWithObject:view]; item.eventHandlers = [LKS_EventHandlerMaker makeForView:view]; item.backgroundColor = view.backgroundColor; if (view.lks_hostViewController) { item.hostViewControllerObject = [LookinObject instanceWithObject:view.lks_hostViewController]; } } else { item.backgroundColor = [UIColor colorWithCGColor:layer.backgroundColor]; } if (layer.sublayers.count) { NSArray *sublayers = [layer.sublayers copy]; NSMutableArray *array = [NSMutableArray arrayWithCapacity:sublayers.count]; [sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) { LookinDisplayItem *sublayer_item = [self _displayItemWithLayer:sublayer screenshots:hasScreenshots attrList:hasAttrList lowImageQuality:lowQuality]; if (sublayer_item) { [array addObject:sublayer_item]; } }]; item.subitems = [array copy]; } return item; } + (BOOL)validateFrame:(CGRect)frame { return !CGRectIsNull(frame) && !CGRectIsInfinite(frame) && ![self cgRectIsNaN:frame] && ![self cgRectIsInf:frame] && ![self cgRectIsUnreasonable:frame]; } + (BOOL)cgRectIsNaN:(CGRect)rect { return isnan(rect.origin.x) || isnan(rect.origin.y) || isnan(rect.size.width) || isnan(rect.size.height); } + (BOOL)cgRectIsInf:(CGRect)rect { return isinf(rect.origin.x) || isinf(rect.origin.y) || isinf(rect.size.width) || isinf(rect.size.height); } + (BOOL)cgRectIsUnreasonable:(CGRect)rect { return ABS(rect.origin.x) > 100000 || ABS(rect.origin.y) > 100000 || rect.size.width < 0 || rect.size.height < 0 || rect.size.width > 100000 || rect.size.height > 100000; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_MethodTraceManager.h ================================================ // // LKS_MethodTraceManager.h // LookinServer // // Created by Li Kai on 2019/5/22. // https://lookin.work // #import @interface LKS_MethodTraceManager : NSObject + (instancetype)sharedInstance; /// selName 不可以是 "dealloc" - (void)addWithClassName:(NSString *)className selName:(NSString *)selName; - (void)removeWithClassName:(NSString *)className selName:(NSString *)selName; /** @[ @{@"class": @"UIViewController", @"sels": @[@"init", @"viewDidAppear:"]}, @{@"class": @"UIView", @"sels": @[@"init", @"layoutSubviews"]} ]; */ - (NSArray *> *)currentActiveTraceList; - (NSArray *)allClassesListInApp; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_MethodTraceManager.m ================================================ // // LKS_MethodTraceManager.m // LookinServer // // Created by Li Kai on 2019/5/22. // https://lookin.work // #import "LKS_MethodTraceManager.h" #import #import #import "LKS_ConnectionManager.h" #import "LookinMethodTraceRecord.h" #import "LookinServerDefines.h" static NSString * const kActiveListKey_Class = @"class"; static NSString * const kActiveListKey_Sels = @"sels"; static NSArray *LKS_ArgumentsDescriptionsFromInvocation(NSInvocation *invocation) { NSMethodSignature *signature = [invocation methodSignature]; NSUInteger argsCount = signature.numberOfArguments; NSArray *strings = [NSArray lookin_arrayWithCount:(argsCount - 2) block:^id(NSUInteger idx) { NSUInteger argIdx = idx + 2; const char *argType = [signature getArgumentTypeAtIndex:argIdx]; ///TODO:v, *, , [array type], {name=type...}, (name=type...), bnum, ^type, ? // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 if (strcmp(argType, @encode(char)) == 0) { char charValue; [invocation getArgument:&charValue atIndex:argIdx]; return [NSString stringWithFormat:@"%@", @(charValue)]; } else if (strcmp(argType, @encode(int)) == 0) { int intValue; [invocation getArgument:&intValue atIndex:argIdx]; if (intValue == INT_MAX) { return @"INT_MAX"; } else if (intValue == INT_MIN) { return @"INT_MIN"; } else { return [NSString stringWithFormat:@"%@", @(intValue)]; } } else if (strcmp(argType, @encode(short)) == 0) { short shortValue; [invocation getArgument:&shortValue atIndex:argIdx]; if (shortValue == SHRT_MAX) { return @"SHRT_MAX"; } else if (shortValue == SHRT_MIN) { return @"SHRT_MIN"; } else { return [NSString stringWithFormat:@"%@", @(shortValue)]; } } else if (strcmp(argType, @encode(long)) == 0) { long longValue; [invocation getArgument:&longValue atIndex:argIdx]; if (longValue == NSNotFound) { return @"NSNotFound"; } else if (longValue == LONG_MAX) { return @"LONG_MAX"; } else if (longValue == LONG_MIN) { return @"LONG_MAX"; } else { return [NSString stringWithFormat:@"%@", @(longValue)]; } } else if (strcmp(argType, @encode(long long)) == 0) { long long longLongValue; [invocation getArgument:&longLongValue atIndex:argIdx]; if (longLongValue == LLONG_MAX) { return @"LLONG_MAX"; } else if (longLongValue == LLONG_MIN) { return @"LLONG_MIN"; } else { return [NSString stringWithFormat:@"%@", @(longLongValue)]; } } else if (strcmp(argType, @encode(unsigned char)) == 0) { unsigned char ucharValue; [invocation getArgument:&ucharValue atIndex:argIdx]; if (ucharValue == UCHAR_MAX) { return @"UCHAR_MAX"; } else { return [NSString stringWithFormat:@"%@", @(ucharValue)]; } } else if (strcmp(argType, @encode(unsigned int)) == 0) { unsigned int uintValue; [invocation getArgument:&uintValue atIndex:argIdx]; if (uintValue == UINT_MAX) { return @"UINT_MAX"; } else { return [NSString stringWithFormat:@"%@", @(uintValue)]; } } else if (strcmp(argType, @encode(unsigned short)) == 0) { unsigned short ushortValue; [invocation getArgument:&ushortValue atIndex:argIdx]; if (ushortValue == USHRT_MAX) { return @"USHRT_MAX"; } else { return [NSString stringWithFormat:@"%@", @(ushortValue)]; } } else if (strcmp(argType, @encode(unsigned long)) == 0) { unsigned long ulongValue; [invocation getArgument:&ulongValue atIndex:argIdx]; if (ulongValue == ULONG_MAX) { return @"ULONG_MAX"; } else { return [NSString stringWithFormat:@"%@", @(ulongValue)]; } } else if (strcmp(argType, @encode(unsigned long long)) == 0) { unsigned long long ulongLongValue; [invocation getArgument:&ulongLongValue atIndex:argIdx]; if (ulongLongValue == ULONG_LONG_MAX) { return @"ULONG_LONG_MAX"; } else { return [NSString stringWithFormat:@"%@", @(ulongLongValue)]; } } else if (strcmp(argType, @encode(float)) == 0) { float floatValue; [invocation getArgument:&floatValue atIndex:argIdx]; if (floatValue == FLT_MAX) { return @"FLT_MAX"; } else if (floatValue == FLT_MIN) { return @"FLT_MIN"; } else { return [NSString stringWithFormat:@"%@", @(floatValue)]; } } else if (strcmp(argType, @encode(double)) == 0) { double doubleValue; [invocation getArgument:&doubleValue atIndex:argIdx]; if (doubleValue == DBL_MAX) { return @"DBL_MAX"; } else if (doubleValue == DBL_MIN) { return @"DBL_MIN"; } else { return [NSString stringWithFormat:@"%@", @(doubleValue)]; } } else if (strcmp(argType, @encode(BOOL)) == 0) { BOOL boolValue; [invocation getArgument:&boolValue atIndex:argIdx]; return boolValue ? @"YES" : @"NO"; } else if (strcmp(argType, @encode(SEL)) == 0) { SEL selValue; [invocation getArgument:&selValue atIndex:argIdx]; return [NSString stringWithFormat:@"SEL(%@)", NSStringFromSelector(selValue)]; } else if (strcmp(argType, @encode(Class)) == 0) { Class classValue; [invocation getArgument:&classValue atIndex:argIdx]; return [NSString stringWithFormat:@"<%@>", NSStringFromClass(classValue)]; } else if (strcmp(argType, @encode(CGPoint)) == 0) { CGPoint targetValue; [invocation getArgument:&targetValue atIndex:argIdx]; return NSStringFromCGPoint(targetValue); } else if (strcmp(argType, @encode(CGVector)) == 0) { CGVector targetValue; [invocation getArgument:&targetValue atIndex:argIdx]; return NSStringFromCGVector(targetValue); } else if (strcmp(argType, @encode(CGSize)) == 0) { CGSize targetValue; [invocation getArgument:&targetValue atIndex:argIdx]; return NSStringFromCGSize(targetValue); } else if (strcmp(argType, @encode(CGRect)) == 0) { CGRect targetValue; [invocation getArgument:&targetValue atIndex:argIdx]; return NSStringFromCGRect(targetValue); } else if (strcmp(argType, @encode(CGAffineTransform)) == 0) { CGAffineTransform targetValue; [invocation getArgument:&targetValue atIndex:argIdx]; return NSStringFromCGAffineTransform(targetValue); } else if (strcmp(argType, @encode(UIEdgeInsets)) == 0) { UIEdgeInsets targetValue; [invocation getArgument:&targetValue atIndex:argIdx]; return NSStringFromUIEdgeInsets(targetValue); } else if (strcmp(argType, @encode(UIOffset)) == 0) { UIOffset targetValue; [invocation getArgument:&targetValue atIndex:argIdx]; return NSStringFromUIOffset(targetValue); } else if (strcmp(argType, @encode(NSRange)) == 0) { NSRange targetValue; [invocation getArgument:&targetValue atIndex:argIdx]; return NSStringFromRange(targetValue); } else { if (@available(iOS 11.0, tvOS 11.0, *)) { if (strcmp(argType, @encode(NSDirectionalEdgeInsets)) == 0) { NSDirectionalEdgeInsets targetValue; [invocation getArgument:&targetValue atIndex:argIdx]; return NSStringFromDirectionalEdgeInsets(targetValue); } } NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:argType]; if ([argType_string hasPrefix:@"@"]) { __unsafe_unretained id objValue; [invocation getArgument:&objValue atIndex:argIdx]; if (objValue) { if ([objValue isKindOfClass:[NSString class]]) { return [NSString stringWithFormat:@"\"%@\"", objValue]; } NSString *objDescription = [objValue description]; if (objDescription.length > 20) { return [NSString stringWithFormat:@"(%@ *)%p", NSStringFromClass([objValue class]), objValue]; } else { return objDescription; } } else { return @"nil"; } } } return @"?"; }]; return strings.copy; } static SEL LKS_AltSelectorFromSelector(SEL originalSelector) { NSString *selectorName = NSStringFromSelector(originalSelector); return NSSelectorFromString([@"lks_alt_" stringByAppendingString:selectorName]); } static NSMutableDictionary *> *LKS_HookedDict() { static NSMutableDictionary *dict; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ dict = [NSMutableDictionary dictionary]; }); return dict; } static NSMutableArray *> *LKS_ActiveList() { static NSMutableArray *list; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ list = [NSMutableArray array]; }); return list; } static void Lookin_PleaseRemoveMethodTraceInLookinAppIfCrashHere(Class targetClass) { SEL forwardInvocationSel = @selector(forwardInvocation:); Method forwardInvocationMethod = class_getInstanceMethod(targetClass, forwardInvocationSel); void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL; if (forwardInvocationMethod != NULL) { originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod); } id newForwardInvocation = ^(id target, NSInvocation *invocation) { __block BOOL isHookedSel = NO; __block BOOL shouldNotify = NO; [LKS_HookedDict() enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull enumeratedClassName, NSMutableSet * _Nonnull obj, BOOL * _Nonnull stop) { if ([target isKindOfClass:NSClassFromString(enumeratedClassName)]) { NSString *invocationSelName = NSStringFromSelector(invocation.selector); isHookedSel = [obj containsObject:invocationSelName]; NSArray *activeSels = [[LKS_ActiveList() lookin_firstFiltered:^BOOL(NSDictionary *obj) { return [obj[kActiveListKey_Class] isEqualToString:enumeratedClassName]; }] objectForKey:kActiveListKey_Sels]; shouldNotify = [activeSels lookin_any:^BOOL(NSString *obj) { return [obj isEqualToString:invocationSelName]; }]; *stop = YES; } }]; if (isHookedSel) { if (shouldNotify) { LookinMethodTraceRecord *record = [LookinMethodTraceRecord new]; record.targetAddress = [NSString stringWithFormat:@"%p", invocation.target]; record.selClassName = NSStringFromClass([invocation.target class]); record.selName = NSStringFromSelector(invocation.selector); record.callStacks = [NSThread callStackSymbols]; record.args = LKS_ArgumentsDescriptionsFromInvocation(invocation); record.date = [NSDate date]; [[LKS_ConnectionManager sharedInstance] pushData:record type:LookinPush_MethodTraceRecord]; } invocation.selector = LKS_AltSelectorFromSelector(invocation.selector); [invocation invoke]; return; } if (originalForwardInvocation == NULL) { [target doesNotRecognizeSelector:invocation.selector]; } else { originalForwardInvocation(target, forwardInvocationSel, invocation); } }; class_replaceMethod(targetClass, forwardInvocationSel, imp_implementationWithBlock(newForwardInvocation), "v@:@"); [LKS_HookedDict() setValue:[NSMutableSet set] forKey:NSStringFromClass(targetClass)]; } @interface LKS_MethodTraceManager () @end @implementation LKS_MethodTraceManager + (instancetype)sharedInstance { static dispatch_once_t onceToken; static LKS_MethodTraceManager *instance = nil; dispatch_once(&onceToken,^{ instance = [[super allocWithZone:NULL] init]; }); return instance; } + (id)allocWithZone:(struct _NSZone *)zone{ return [self sharedInstance]; } - (void)removeWithClassName:(NSString *)className selName:(NSString *)selName { if (!className.length) { return; } NSUInteger classIdx = [LKS_ActiveList() indexOfObjectPassingTest:^BOOL(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { return [obj[kActiveListKey_Class] isEqualToString:className]; }]; if (classIdx == NSNotFound) { return; } if (selName) { NSDictionary *classDict = [LKS_ActiveList() objectAtIndex:classIdx]; NSMutableArray *sels = classDict[kActiveListKey_Sels]; [sels removeObject:selName]; if (sels.count == 0) { [LKS_ActiveList() removeObjectAtIndex:classIdx]; } } else { [LKS_ActiveList() removeObjectAtIndex:classIdx]; } } - (void)addWithClassName:(NSString *)targetClassName selName:(NSString *)targetSelName { BOOL isValid = [self _isValidWithClassName:targetClassName selName:targetSelName]; if (!isValid) { return; } BOOL addSucc = [self _addToActiveListWithClassName:targetClassName selName:targetSelName]; if (!addSucc) { return; } Class targetClass = NSClassFromString(targetClassName); SEL targetSel = NSSelectorFromString(targetSelName); Method targetMethod = class_getInstanceMethod(targetClass, targetSel); @synchronized (self) { if (![LKS_HookedDict() valueForKey:targetClassName]) { Lookin_PleaseRemoveMethodTraceInLookinAppIfCrashHere(targetClass); } NSMutableSet *hookedSelNames = [LKS_HookedDict() objectForKey:targetClassName]; if ([hookedSelNames containsObject:targetSelName]) { return; } class_addMethod(targetClass, LKS_AltSelectorFromSelector(targetSel), method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod)); if (method_getImplementation(targetMethod) != _objc_msgForward) { class_replaceMethod(targetClass, targetSel, _objc_msgForward, method_getTypeEncoding(targetMethod)); } [hookedSelNames addObject:targetSelName]; } } - (BOOL)_addToActiveListWithClassName:(NSString *)targetClassName selName:(NSString *)targetSelName { __block BOOL addSuccessfully = YES; NSDictionary *activeList_dict = [LKS_ActiveList() lookin_firstFiltered:^BOOL(NSDictionary *obj) { return [obj[kActiveListKey_Class] isEqualToString:targetClassName]; }]; if (activeList_dict) { NSMutableArray *sels = activeList_dict[kActiveListKey_Sels]; if ([sels containsObject:targetSelName]) { addSuccessfully = NO; } else { [sels addObject:targetSelName]; } } else { activeList_dict = @{kActiveListKey_Class:targetClassName, kActiveListKey_Sels: @[targetSelName].mutableCopy}; [LKS_ActiveList() addObject:activeList_dict]; } return addSuccessfully; } - (BOOL)_isValidWithClassName:(NSString *)targetClassName selName:(NSString *)targetSelName { if ([targetSelName isEqualToString:@"dealloc"]) { return NO; } Class targetClass = NSClassFromString(targetClassName); if (!targetClass) { return NO; } SEL targetSel = NSSelectorFromString(targetSelName); Method targetMethod = class_getInstanceMethod(targetClass, targetSel); if (targetSel == NULL || targetMethod == NULL) { return NO; } return YES; } - (NSArray *> *)currentActiveTraceList { return LKS_ActiveList(); } - (NSArray *)allClassesListInApp { NSSet *prefixesToAvoid = [NSSet setWithObjects:@"OS_", @"IBA", @"SKUI", @"HM", @"WBS", @"CDP", @"DMF", @"TimerSupport", @"Swift.", @"Foundation", @"CEM", @"PSUI", @"CPL", @"IPA", @"NSKeyValue", @"ICS", @"INIntent", @"NWConcrete", @"NSSQL", @"SASetting", @"SAM", @"GEO", @"PBBProto", @"AWD", @"MTL", @"PKPhysics", @"TIKeyEvent", @"TITypologyRecord", @"IDS", @"AVCapture", @"AVAsset", @"AVContent", nil]; int numClasses; Class * classes = NULL; classes = NULL; numClasses = objc_getClassList(NULL, 0); NSMutableArray *array = [NSMutableArray array]; if (numClasses > 0) { classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); for (int i = 0; i < numClasses; i++) { Class c = classes[i]; NSString *className = NSStringFromClass(c); if (className) { BOOL shouldAvoid = [prefixesToAvoid lookin_any:^BOOL(NSString *prefix) { return [className hasPrefix:prefix]; }]; if (!shouldAvoid) { [array addObject:className]; } } } free(classes); } return array.copy; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_ObjectRegistry.h ================================================ // // LKS_ObjectRegistry.h // LookinServer // // Created by Li Kai on 2019/4/21. // https://lookin.work // #import @interface LKS_ObjectRegistry : NSObject + (instancetype)sharedInstance; - (unsigned long)addObject:(NSObject *)object; - (NSObject *)objectWithOid:(unsigned long)oid; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_ObjectRegistry.m ================================================ // // LKS_ObjectRegistry.m // LookinServer // // Created by Li Kai on 2019/4/21. // https://lookin.work // #import "LKS_ObjectRegistry.h" #import @interface LKS_ObjectRegistry () @property(nonatomic, strong) NSPointerArray *data; @end @implementation LKS_ObjectRegistry + (instancetype)sharedInstance { static dispatch_once_t onceToken; static LKS_ObjectRegistry *instance = nil; dispatch_once(&onceToken,^{ instance = [[super allocWithZone:NULL] init]; }); return instance; } + (id)allocWithZone:(struct _NSZone *)zone{ return [self sharedInstance]; } - (instancetype)init { if (self = [super init]) { self.data = [NSPointerArray weakObjectsPointerArray]; // index 为 0 用 Null 填充 self.data.count = 1; } return self; } - (unsigned long)addObject:(NSObject *)object { if (!object) { return 0; } [self.data addPointer:(void *)object]; return self.data.count - 1; } - (NSObject *)objectWithOid:(unsigned long)oid { if (self.data.count <= oid) { return nil; } id object = [self.data pointerAtIndex:oid]; return object; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_TraceManager.h ================================================ // // LKS_TraceManager.h // LookinServer // // Created by Li Kai on 2019/5/5. // https://lookin.work // #import @class LookinIvarTrace; @interface LKS_TraceManager : NSObject + (instancetype)sharedInstance; - (void)reload; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LKS_TraceManager.m ================================================ // // LKS_TraceManager.m // LookinServer // // Created by Li Kai on 2019/5/5. // https://lookin.work // #import "LKS_TraceManager.h" #import #import "LookinIvarTrace.h" #import "LookinServerDefines.h" #import "LKS_LocalInspectManager.h" @implementation LKS_TraceManager + (instancetype)sharedInstance { static dispatch_once_t onceToken; static LKS_TraceManager *instance = nil; dispatch_once(&onceToken,^{ instance = [[super allocWithZone:NULL] init]; }); return instance; } + (id)allocWithZone:(struct _NSZone *)zone { return [self sharedInstance]; } - (void)reload { // 把旧的先都清理掉 [NSObject lks_clearAllObjectsTraces]; [[[UIApplication sharedApplication].windows copy] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { [self _addTraceForLayersRootedByLayer:window.layer]; }]; } - (void)_addTraceForLayersRootedByLayer:(CALayer *)layer { UIView *view = layer.lks_hostView; if ([view.superview lks_isChildrenViewOfTabBar]) { view.lks_isChildrenViewOfTabBar = YES; } else if ([view isKindOfClass:[UITabBar class]]) { view.lks_isChildrenViewOfTabBar = YES; } if (view) { [self _markIVarsInAllClassLevelsOfObject:view]; if (view.lks_hostViewController) { [self _markIVarsInAllClassLevelsOfObject:view.lks_hostViewController]; } [self _buildSpecialTraceForView:view]; } else { [self _markIVarsInAllClassLevelsOfObject:layer]; } [[layer.sublayers copy] enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) { [self _addTraceForLayersRootedByLayer:sublayer]; }]; } - (void)_buildSpecialTraceForView:(UIView *)view { if (view.lks_hostViewController) { view.lks_specialTrace = [NSString stringWithFormat:@"%@.view", NSStringFromClass(view.lks_hostViewController.class)]; } else if ([view isKindOfClass:[UIWindow class]]) { CGFloat currentWindowLevel = ((UIWindow *)view).windowLevel; if ([view isKindOfClass:[LKS_LocalInspectContainerWindow class]]) { view.lks_specialTrace = [NSString stringWithFormat:@"Lookin Private Window ( Level: %@ )", @(currentWindowLevel)]; } else if (((UIWindow *)view).isKeyWindow) { view.lks_specialTrace = [NSString stringWithFormat:@"KeyWindow ( Level: %@ )", @(currentWindowLevel)]; } else { view.lks_specialTrace = [NSString stringWithFormat:@"WindowLevel: %@", @(currentWindowLevel)]; } } else if ([view isKindOfClass:[UITableViewCell class]]) { ((UITableViewCell *)view).backgroundView.lks_specialTrace = @"cell.backgroundView"; ((UITableViewCell *)view).accessoryView.lks_specialTrace = @"cell.accessoryView"; } else if ([view isKindOfClass:[UITableView class]]) { UITableView *tableView = (UITableView *)view; NSMutableArray *relatedSectionIdx = [NSMutableArray array]; [[tableView visibleCells] enumerateObjectsUsingBlock:^(__kindof UITableViewCell * _Nonnull cell, NSUInteger idx, BOOL * _Nonnull stop) { NSIndexPath *indexPath = [tableView indexPathForCell:cell]; cell.lks_specialTrace = [NSString stringWithFormat:@"{ sec:%@, row:%@ }", @(indexPath.section), @(indexPath.row)]; if (![relatedSectionIdx containsObject:@(indexPath.section)]) { [relatedSectionIdx addObject:@(indexPath.section)]; } }]; [relatedSectionIdx enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSUInteger secIdx = [obj unsignedIntegerValue]; UIView *secHeaderView = [tableView headerViewForSection:secIdx]; secHeaderView.lks_specialTrace = [NSString stringWithFormat:@"sectionHeader { sec: %@ }", @(secIdx)]; UIView *secFooterView = [tableView footerViewForSection:secIdx]; secFooterView.lks_specialTrace = [NSString stringWithFormat:@"sectionFooter { sec: %@ }", @(secIdx)]; }]; } else if ([view isKindOfClass:[UICollectionView class]]) { UICollectionView *collectionView = (UICollectionView *)view; collectionView.backgroundView.lks_specialTrace = @"collectionView.backgroundView"; if (@available(iOS 9.0, *)) { [[collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionHeader] enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull indexPath, NSUInteger idx, BOOL * _Nonnull stop) { UIView *headerView = [collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:indexPath]; headerView.lks_specialTrace = [NSString stringWithFormat:@"sectionHeader { sec:%@ }", @(indexPath.section)]; }]; [[collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionFooter] enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull indexPath, NSUInteger idx, BOOL * _Nonnull stop) { UIView *footerView = [collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:indexPath]; footerView.lks_specialTrace = [NSString stringWithFormat:@"sectionFooter { sec:%@ }", @(indexPath.section)]; }]; } [[collectionView visibleCells] enumerateObjectsUsingBlock:^(__kindof UICollectionViewCell * _Nonnull cell, NSUInteger idx, BOOL * _Nonnull stop) { NSIndexPath *indexPath = [collectionView indexPathForCell:cell]; cell.lks_specialTrace = [NSString stringWithFormat:@"{ item:%@, sec:%@ }", @(indexPath.item), @(indexPath.section)]; }]; } else if ([view isKindOfClass:[UITableViewHeaderFooterView class]]) { UITableViewHeaderFooterView *headerFooterView = (UITableViewHeaderFooterView *)view; headerFooterView.textLabel.lks_specialTrace = @"sectionHeaderFooter.textLabel"; headerFooterView.detailTextLabel.lks_specialTrace = @"sectionHeaderFooter.detailTextLabel"; } } - (void)_markIVarsInAllClassLevelsOfObject:(NSObject *)object { [self _markIVarsOfObject:object class:object.class]; } - (void)_markIVarsOfObject:(NSObject *)hostObject class:(Class)targetClass { if (!targetClass) { return; } NSArray *prefixesToTerminateRecursion = @[@"NSObject", @"UIResponder", @"UIButton", @"UIButtonLabel"]; BOOL hasPrefix = [prefixesToTerminateRecursion lookin_any:^BOOL(NSString *prefix) { return [NSStringFromClass(targetClass) hasPrefix:prefix]; }]; if (hasPrefix) { return; } unsigned int outCount = 0; Ivar *ivars = class_copyIvarList(targetClass, &outCount); for (unsigned int i = 0; i < outCount; i ++) { Ivar ivar = ivars[i]; NSString *ivarType = [[NSString alloc] lookin_safeInitWithUTF8String:ivar_getTypeEncoding(ivar)]; if (![ivarType hasPrefix:@"@"] || ivarType.length <= 3) { continue; } NSString *ivarClassName = [ivarType substringWithRange:NSMakeRange(2, ivarType.length - 3)]; Class ivarClass = NSClassFromString(ivarClassName); if (![ivarClass isSubclassOfClass:[UIView class]] && ![ivarClass isSubclassOfClass:[CALayer class]] && ![ivarClass isSubclassOfClass:[UIViewController class]] && ![ivarClass isSubclassOfClass:[UIGestureRecognizer class]]) { continue; } const char * ivarNameChar = ivar_getName(ivar); if (!ivarNameChar) { continue; } // 这个 ivarObject 可能的类型:UIView, CALayer, UIViewController, UIGestureRecognizer NSObject *ivarObject = object_getIvar(hostObject, ivar); if (!ivarObject) { continue; } LookinIvarTrace *ivarTrace = [LookinIvarTrace new]; ivarTrace.hostObject = hostObject; ivarTrace.hostClassName = NSStringFromClass(targetClass); ivarTrace.ivarName = [[NSString alloc] lookin_safeInitWithUTF8String:ivarNameChar]; if (hostObject == ivarObject) { ivarTrace.relation = LookinIvarTraceRelationValue_Self; } else if ([hostObject isKindOfClass:[UIView class]]) { CALayer *ivarLayer = nil; if ([ivarObject isKindOfClass:[CALayer class]]) { ivarLayer = (CALayer *)ivarObject; } else if ([ivarObject isKindOfClass:[UIView class]]) { ivarLayer = ((UIView *)ivarObject).layer; } if (ivarLayer && (ivarLayer.superlayer == ((UIView *)hostObject).layer)) { ivarTrace.relation = @"superview"; } } if ([LKS_InvalidIvarTraces() containsObject:ivarTrace]) { continue; } if (!ivarObject.lks_ivarTraces) { ivarObject.lks_ivarTraces = [NSArray array]; } if (![ivarObject.lks_ivarTraces containsObject:ivarTrace]) { ivarObject.lks_ivarTraces = [ivarObject.lks_ivarTraces arrayByAddingObject:ivarTrace]; } } free(ivars); Class superClass = [targetClass superclass]; [self _markIVarsOfObject:hostObject class:superClass]; } static NSSet *LKS_InvalidIvarTraces() { static NSSet *list; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSMutableSet *set = [NSMutableSet set]; [set addObject:({ LookinIvarTrace *trace = [LookinIvarTrace new]; trace.hostClassName = @"UIView"; trace.ivarName = @"_window"; trace; })]; [set addObject:({ LookinIvarTrace *trace = [LookinIvarTrace new]; trace.hostClassName = @"UIViewController"; trace.ivarName = @"_view"; trace; })]; [set addObject:({ LookinIvarTrace *trace = [LookinIvarTrace new]; trace.hostClassName = @"UIView"; trace.ivarName = @"_viewDelegate"; trace; })]; [set addObject:({ LookinIvarTrace *trace = [LookinIvarTrace new]; trace.hostClassName = @"UIViewController"; trace.ivarName = @"_parentViewController"; trace; })]; list = set.copy; }); return list; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Others/LookinServerDefines.h ================================================ // // LookinServer_PrefixHeader.pch // LookinServer // // Created by Li Kai on 2018/12/21. // https://lookin.work // #import "TargetConditionals.h" #import "LookinDefines.h" #import "LKS_Helper.h" #import "NSObject+LookinServer.h" #import "NSArray+Lookin.h" #import "NSSet+Lookin.h" #import "CALayer+Lookin.h" #import "UIView+LookinServer.h" #import "CALayer+LookinServer.h" #import "NSObject+Lookin.h" #import "NSString+Lookin.h" ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveDataSource.h ================================================ // // LKS_PerspectiveDataSource.h // LookinServer // // Created by Li Kai on 2019/5/17. // https://lookin.work // #import "LookinDefines.h" @class LookinHierarchyInfo, LookinDisplayItem, LKS_PerspectiveDataSource; @protocol LKS_PerspectiveDataSourceDelegate @optional - (void)dataSourceDidChangeSelectedItem:(LKS_PerspectiveDataSource *)dataSource; - (void)dataSourceDidChangeDisplayItems:(LKS_PerspectiveDataSource *)dataSource; - (void)dataSourceDidChangeNoPreview:(LKS_PerspectiveDataSource *)dataSource; @end @interface LKS_PerspectiveDataSource : NSObject @property(nonatomic, weak) id perspectiveLayer; @property(nonatomic, weak) id hierarchyView; - (instancetype)initWithHierarchyInfo:(LookinHierarchyInfo *)info; /// 一维数组,包含所有 hierarchy 树中可见和不可见的 displayItems @property(nonatomic, copy, readonly) NSArray *flatItems; /// 一维数组,只包括在 hierarchy 树中可见的 displayItems @property(nonatomic, copy, readonly) NSArray *displayingFlatItems; /// 当前应该被显示的 rows 行数 - (NSInteger)numberOfRows; /// 获取指定行的 item - (LookinDisplayItem *)itemAtRow:(NSInteger)index; /// 获取指定 item 的 row,可能为 NSNotFound - (NSInteger)rowForItem:(LookinDisplayItem *)item; /// 当前选中的 item @property(nonatomic, weak) LookinDisplayItem *selectedItem; /// 将 item 折叠起来,如果该 item 没有 subitems 或已经被折叠,则该方法不起任何作用 - (void)collapseItem:(LookinDisplayItem *)item; /// 将 item 展开,如果该 item 没有 subitems 或已经被展开,则该方法不起任何作用 - (void)expandItem:(LookinDisplayItem *)item; /// 某个颜色的业务别名,如果不存在则返回 nil - (NSArray *)aliasForColor:(UIColor *)color; @property(nonatomic, strong, readonly) LookinHierarchyInfo *rawHierarchyInfo; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveDataSource.m ================================================ // // LKS_PerspectiveDataSource.m // LookinServer // // Created by Li Kai on 2019/5/17. // https://lookin.work // #import "LKS_PerspectiveDataSource.h" #import "UIColor+LookinServer.h" #import "LookinDisplayItem.h" #import "LookinHierarchyInfo.h" #import "LookinServerDefines.h" #import "LKS_PerspectiveLayer.h" @interface LKS_PerspectiveDataSource () @property(nonatomic, copy, readwrite) NSArray *flatItems; @property(nonatomic, copy, readwrite) NSArray *displayingFlatItems; /** key 是 rgba 字符串,value 是 alias 字符串数组,比如: @{ @"(255, 255, 255, 1)": @[@"MyWhite", @"MainThemeWhite"], @"(255, 0, 0, 0.5)": @[@"BestRed", @"TransparentRed"] }; */ @property(nonatomic, strong) NSDictionary *> *colorToAliasMap; @end @implementation LKS_PerspectiveDataSource - (instancetype)initWithHierarchyInfo:(LookinHierarchyInfo *)info { if (self = [self init]) { _rawHierarchyInfo = info; // [self _setUpColors]; // 打平为二维数组 self.flatItems = [LookinDisplayItem flatItemsFromHierarchicalItems:info.displayItems]; // 设置 preferToBeCollapsed 属性 NSSet *classesPreferredToCollapse = [NSSet setWithObjects:@"UILabel", @"UIPickerView", @"UIProgressView", @"UIActivityIndicatorView", @"UIAlertView", @"UIActionSheet", @"UISearchBar", @"UIButton", @"UITextView", @"UIDatePicker", @"UIPageControl", @"UISegmentedControl", @"UITextField", @"UISlider", @"UISwitch", @"UIVisualEffectView", @"UIImageView", @"WKCommonWebView", @"UITextEffectsWindow", @"LKS_LocalInspectContainerWindow", nil]; if (info.collapsedClassList.count) { classesPreferredToCollapse = [classesPreferredToCollapse setByAddingObjectsFromArray:info.collapsedClassList]; } // no preview NSSet *classesWithNoPreview = [NSSet setWithArray:@[@"UITextEffectsWindow", @"UIRemoteKeyboardWindow", @"LKS_LocalInspectContainerWindow"]]; [self.flatItems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj itemIsKindOfClassesWithNames:classesPreferredToCollapse]) { [obj enumerateSelfAndChildren:^(LookinDisplayItem *item) { item.preferToBeCollapsed = YES; }]; } if (obj.indentLevel == 0) { if ([obj itemIsKindOfClassesWithNames:classesWithNoPreview]) { obj.noPreview = YES; } } }]; // 设置展开和折叠 LookinDisplayItem *shouldSelectedItem; [self _adjustExpansionWithPreferedSelectedItem:&shouldSelectedItem]; // 设置选中 if (!shouldSelectedItem) { shouldSelectedItem = self.flatItems.firstObject; } self.selectedItem = shouldSelectedItem; } return self; } - (NSInteger)numberOfRows { return self.displayingFlatItems.count; } - (LookinDisplayItem *)itemAtRow:(NSInteger)index { return [self.displayingFlatItems lookin_safeObjectAtIndex:index]; } - (NSInteger)rowForItem:(LookinDisplayItem *)item { NSInteger row = [self.displayingFlatItems indexOfObject:item]; return row; } - (void)setSelectedItem:(LookinDisplayItem *)selectedItem { if (_selectedItem == selectedItem) { return; } _selectedItem.isSelected = NO; _selectedItem = selectedItem; _selectedItem.isSelected = YES; if ([self.hierarchyView respondsToSelector:@selector(dataSourceDidChangeSelectedItem:)]) { [self.hierarchyView dataSourceDidChangeSelectedItem:self]; } if ([self.perspectiveLayer respondsToSelector:@selector(dataSourceDidChangeSelectedItem:)]) { [self.perspectiveLayer dataSourceDidChangeSelectedItem:self]; } } - (void)collapseItem:(LookinDisplayItem *)item { if (!item.isExpandable) { return; } if (!item.isExpanded) { return; } item.isExpanded = NO; [self _updateDisplayingFlatItems]; } - (void)expandItem:(LookinDisplayItem *)item { if (!item.isExpandable) { return; } if (item.isExpanded) { return; } item.isExpanded = YES; [self _updateDisplayingFlatItems]; } #pragma mark - Colors - (NSArray *)aliasForColor:(UIColor *)color { if (!color) { return nil; } NSString *rgbaString = color.lks_rgbaString; NSArray *names = self.colorToAliasMap[rgbaString]; return names; } //- (void)_setUpColors { // NSMutableDictionary *> *colorToAliasMap = [NSMutableDictionary dictionary]; // // /** // hierarchyInfo.colorAlias 可以有三种结构: // 1)key 是颜色别名,value 是 UIColor/UIColor。即 // 2)key 是一组颜色的标题,value 是 NSDictionary,而这个 NSDictionary 的 key 是颜色别名,value 是 UIColor / UIColor。即 *> // 3)以上两者混在一起 // */ // [self.rawHierarchyInfo.colorAlias enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id _Nonnull colorOrDict, BOOL * _Nonnull stop) { // if ([colorOrDict isKindOfClass:[UIColor class]]) { // NSString *colorDesc = [((UIColor *)colorOrDict) lks_rgbaString]; // if (colorDesc) { // if (!colorToAliasMap[colorDesc]) { // colorToAliasMap[colorDesc] = [NSMutableArray array]; // } // [colorToAliasMap[colorDesc] addObject:key]; // } // // } else if ([colorOrDict isKindOfClass:[NSDictionary class]]) { // [((NSDictionary *)colorOrDict) enumerateKeysAndObjectsUsingBlock:^(NSString *colorAliaName, UIColor *colorObj, BOOL * _Nonnull stop) { // NSString *colorDesc = colorObj.lks_rgbaString; // if (colorDesc) { // if (!colorToAliasMap[colorDesc]) { // colorToAliasMap[colorDesc] = [NSMutableArray array]; // } // [colorToAliasMap[colorDesc] addObject:colorAliaName]; // } // }]; // // } else { // NSAssert(NO, @""); // } // }]; // self.colorToAliasMap = colorToAliasMap; //} #pragma mark - Others - (void)_adjustExpansionWithPreferedSelectedItem:(LookinDisplayItem **)selectedItem { [self.flatItems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.hasDeterminedExpansion = NO; if (!obj.isExpandable) { obj.hasDeterminedExpansion = YES; return; } }]; LookinDisplayItem *keyWindowItem = [self.rawHierarchyInfo.displayItems lookin_firstFiltered:^BOOL(LookinDisplayItem *windowItem) { return windowItem.representedAsKeyWindow; }]; if (!keyWindowItem) { keyWindowItem = self.rawHierarchyInfo.displayItems.firstObject; } [self.rawHierarchyInfo.displayItems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull windowItem, NSUInteger idx, BOOL * _Nonnull stop) { if (windowItem == keyWindowItem) { return; } // 非 keyWindow 上的都折叠起来 [[LookinDisplayItem flatItemsFromHierarchicalItems:@[windowItem]] enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (obj.hasDeterminedExpansion) { return; } obj.isExpanded = NO; obj.hasDeterminedExpansion = YES; }]; }]; NSArray *UITransitionViewItems = [keyWindowItem.subitems lookin_filter:^BOOL(LookinDisplayItem *obj) { return [obj.title isEqualToString:@"UITransitionView"]; }]; [UITransitionViewItems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (obj.hasDeterminedExpansion) { return; } if (idx == (UITransitionViewItems.count - 1)) { // 展开最后一个 UITransitionView obj.isExpanded = YES; } else { // 折叠前几个 UITransitionView obj.isExpanded = NO; } obj.hasDeterminedExpansion = YES; }]; NSMutableArray *viewControllerItems = [NSMutableArray array]; [[LookinDisplayItem flatItemsFromHierarchicalItems:@[keyWindowItem]] enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (!!obj.hostViewControllerObject) { [viewControllerItems addObject:obj]; return; } if (obj.hasDeterminedExpansion) { return; } if (obj.inNoPreviewHierarchy || obj.preferToBeCollapsed || obj.inHiddenHierarchy) { // 把 noPreview 和 UIButton 之类常用控件叠起来 obj.isExpanded = NO; obj.hasDeterminedExpansion = YES; return; } if ([obj itemIsKindOfClassesWithNames:[NSSet setWithObjects:@"UINavigationBar", @"UITabBar", nil]]) { // 把 NavigationBar 和 TabBar 折叠起来 [obj enumerateSelfAndChildren:^(LookinDisplayItem *item) { if (item.hasDeterminedExpansion) { return; } item.isExpanded = NO; item.hasDeterminedExpansion = YES; }]; return; } }]; // 从 viewController 开始算向 leaf 多推 3 层 [viewControllerItems enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(LookinDisplayItem * _Nonnull viewControllerItem, NSUInteger idx, BOOL * _Nonnull stop) { [viewControllerItem enumerateAncestors:^(LookinDisplayItem *item, BOOL *stop) { // 把 viewController 的 ancestors 都展开 if (item.hasDeterminedExpansion) { return; } item.isExpanded = YES; item.hasDeterminedExpansion = YES; }]; BOOL hasTableOrCollectionView = [viewControllerItem.subitems.firstObject itemIsKindOfClassesWithNames:[NSSet setWithObjects:@"UITableView", @"UICollectionView", nil]]; // 如果是那种典型的 UITableView 或 UICollectionView 的话,则向 leaf 方向推进 2 层(这样就可以让 cell 恰好露出来而不露出来 cell 的 contentView),否则就推 3 层 NSUInteger indentsForward = hasTableOrCollectionView ? 2 : 3; [viewControllerItem enumerateSelfAndChildren:^(LookinDisplayItem *item) { if (item.hasDeterminedExpansion) { return; } // 向 leaf 方向推 2 或 3 层 if (item.indentLevel < viewControllerItem.indentLevel + indentsForward) { item.isExpanded = YES; item.hasDeterminedExpansion = YES; } }]; }]; // 剩下未处理的都折叠 [self.flatItems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (obj.hasDeterminedExpansion) { return; } obj.isExpanded = NO; }]; if (selectedItem) { *selectedItem = viewControllerItems.lastObject; } [self _updateDisplayingFlatItems]; } - (void)_updateDisplayingFlatItems { __block NSInteger maxIndentationLevel = 0; NSMutableArray *displayingItems = [NSMutableArray array]; [self.flatItems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (obj.displayingInHierarchy) { maxIndentationLevel = MAX(maxIndentationLevel, obj.indentLevel); [displayingItems addObject:obj]; } }]; self.displayingFlatItems = displayingItems; if ([self.hierarchyView respondsToSelector:@selector(dataSourceDidChangeDisplayItems:)]) { [self.hierarchyView dataSourceDidChangeDisplayItems:self]; } if ([self.perspectiveLayer respondsToSelector:@selector(dataSourceDidChangeDisplayItems:)]) { [self.perspectiveLayer dataSourceDidChangeDisplayItems:self]; } } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveHierarchyCell.h ================================================ // // LKS_PerspectiveHierarchyCell.h // LookinServer // // Created by Li Kai on 2018/12/24. // https://lookin.work // #import @class LookinDisplayItem; @interface LKS_PerspectiveHierarchyCell : UITableViewCell @property(nonatomic, strong) LookinDisplayItem *displayItem; - (void)reRender; @property(nonatomic, strong, readonly) UIButton *indicatorButton; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveHierarchyCell.m ================================================ // // LKS_PerspectiveHierarchyCell.m // LookinServer // // Created by Li Kai on 2018/12/24. // https://lookin.work // #import "LKS_PerspectiveHierarchyCell.h" #import "LookinDisplayItem.h" #import "LookinIvarTrace.h" #import "LookinServerDefines.h" @interface LKS_PerspectiveHierarchyCell () @property(nonatomic, strong) UILabel *titleLabel; @property(nonatomic, strong) UILabel *subtitleLabel; @property(nonatomic, strong) CALayer *strikethroughLayer; @property(nonatomic, assign) CGFloat cachedContentWidth; @end @implementation LKS_PerspectiveHierarchyCell { CGFloat _horInset; CGFloat _indicatorWidth; CGFloat _iconImageMarginLeft; CGFloat _indentUnitWidth; CGFloat _titleLeft; CGFloat _subtitleLeft; } - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { _horInset = 10; _indicatorWidth = 15; _iconImageMarginLeft = 5; _indentUnitWidth = 10; _titleLeft = 6; _subtitleLeft = 10; _indicatorButton = [UIButton new]; [self.contentView addSubview:self.indicatorButton]; self.titleLabel = [UILabel new]; self.titleLabel.font = [UIFont systemFontOfSize:12]; [self.contentView addSubview:self.titleLabel]; self.subtitleLabel = [UILabel new]; self.subtitleLabel.font = [UIFont systemFontOfSize:11]; [self.contentView addSubview:self.subtitleLabel]; } return self; } - (void)layoutSubviews { [super layoutSubviews]; self.indicatorButton.frame = ({ CGFloat x = self.displayItem.indentLevel * _indentUnitWidth + _horInset; CGRectMake(x, 0, _indicatorWidth, self.bounds.size.height); }); self.titleLabel.frame = ({ CGFloat width = self.titleLabel.lks_bestWidth; CGRectMake(CGRectGetMaxX(self.indicatorButton.frame) + _titleLeft, 0, width, self.bounds.size.height); }); CGFloat labelMaxX = CGRectGetMaxX(self.titleLabel.frame); if (!self.subtitleLabel.hidden) { self.subtitleLabel.frame = ({ CGFloat width = self.subtitleLabel.lks_bestWidth; CGRectMake(CGRectGetMaxX(self.titleLabel.frame) + _subtitleLeft, 0, width, self.bounds.size.height); }); labelMaxX = CGRectGetMaxX(self.subtitleLabel.frame); } if (self.strikethroughLayer && !self.strikethroughLayer.hidden) { self.strikethroughLayer.frame = ({ CGFloat x = CGRectGetMinX(self.titleLabel.frame) - 2; CGFloat maxX = self.subtitleLabel.hidden ? (CGRectGetMaxX(self.titleLabel.frame) + 2) : (CGRectGetMaxX(self.subtitleLabel.frame) + 2); CGFloat width = maxX - x; CGRectMake(x, CGRectGetMidY(self.bounds), width, 1); }); } } - (CGSize)sizeThatFits:(CGSize)size { size.width = self.cachedContentWidth; return size; } - (void)setDisplayItem:(LookinDisplayItem *)displayItem { _displayItem = displayItem; [self reRender]; } - (void)reRender { // text self.titleLabel.text = self.displayItem.title; // subtitle self.subtitleLabel.text = self.displayItem.subtitle; self.subtitleLabel.hidden = (self.displayItem.subtitle.length == 0); // select if (self.displayItem.isSelected) { self.backgroundColor = LookinColorRGBAMake(172, 177, 191, .4); self.subtitleLabel.textColor = [UIColor whiteColor]; } else { self.backgroundColor = [UIColor clearColor]; self.subtitleLabel.textColor = LookinColorMake(133, 140, 150); } // icon if (!self.displayItem.isExpandable) { self.indicatorButton.hidden = YES; } else if (self.displayItem.isExpanded) { [self.indicatorButton setImage:[self _arrowDownImage] forState:UIControlStateNormal]; self.indicatorButton.hidden = NO; } else { [self.indicatorButton setImage:[self _arrowRightImage] forState:UIControlStateNormal]; self.indicatorButton.hidden = NO; } // strike if (self.displayItem.inNoPreviewHierarchy) { if (!self.strikethroughLayer) { self.strikethroughLayer = [CALayer layer]; [self.strikethroughLayer lookin_removeImplicitAnimations]; self.strikethroughLayer.backgroundColor = LookinColorRGBAMake(255, 255, 255, .3).CGColor; [self.layer addSublayer:self.strikethroughLayer]; } self.strikethroughLayer.hidden = NO; if (self.displayItem.isSelected) { self.titleLabel.textColor = [UIColor whiteColor]; } else { self.titleLabel.textColor = LookinColorMake(113, 120, 130); } } else { self.strikethroughLayer.hidden = YES; self.titleLabel.textColor = [UIColor whiteColor]; } [self setNeedsLayout]; self.cachedContentWidth = ({ CGFloat width = 0; width = _horInset + self.displayItem.indentLevel * _indentUnitWidth + _indicatorWidth + _iconImageMarginLeft + _titleLeft + self.titleLabel.lks_bestWidth + _horInset; if (!self.subtitleLabel.hidden) { width += self.subtitleLabel.lks_bestWidth + _subtitleLeft; } width; }); } - (UIImage *)_arrowRightImage { static UIImage *image = nil; if (image) { return image; } CGFloat width = 10; UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, width), NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(0, 0)]; [path addLineToPoint:CGPointMake(width - 2, width / 2.0)]; [path addLineToPoint:CGPointMake(0, width)]; [path closePath]; CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); [path fill]; image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } - (UIImage *)_arrowDownImage { static UIImage *image = nil; if (image) { return image; } CGFloat width = 10; UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, width), NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(0, 0)]; [path addLineToPoint:CGPointMake(width, 0)]; [path addLineToPoint:CGPointMake(width / 2.0, width - 2)]; [path closePath]; CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); [path fill]; image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveHierarchyView.h ================================================ // // LKS_PerspectiveHierarchyView.h // LookinServer // // Created by Li Kai on 2018/12/24. // https://lookin.work // #import #import "LKS_PerspectiveDataSource.h" @interface LKS_PerspectiveHierarchyView : UIView - (instancetype)initWithDataSource:(LKS_PerspectiveDataSource *)dataSource; @property(nonatomic, assign) BOOL isHorizontalLayout; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveHierarchyView.m ================================================ // // LKS_PerspectiveHierarchyView.m // LookinServer // // Created by Li Kai on 2018/12/24. // https://lookin.work // #import "LKS_PerspectiveHierarchyView.h" #import "LKS_PerspectiveHierarchyCell.h" #import "LookinDisplayItem.h" #import "LookinServerDefines.h" @interface LKS_PerspectiveHierarchyView () @property(nonatomic, strong) UIVisualEffectView *effectBgView; @property(nonatomic, strong) UIScrollView *scrollView; @property(nonatomic, strong) UITableView *tableView; @property(nonatomic, strong) LKS_PerspectiveDataSource *dataSource; @property(nonatomic, strong) CALayer *dragHintLayer; @property(nonatomic, assign) CGFloat currentMaxCellWidth; @property(nonatomic, weak) LKS_PerspectiveHierarchyCell *selectedCell; @end @implementation LKS_PerspectiveHierarchyView - (instancetype)initWithDataSource:(LKS_PerspectiveDataSource *)dataSource { if (self = [self initWithFrame:CGRectZero]) { self.dataSource = dataSource; self.dataSource.hierarchyView = self; self.clipsToBounds = YES; UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; self.effectBgView = [[UIVisualEffectView alloc] initWithEffect:effect]; [self addSubview:self.effectBgView]; self.dragHintLayer = [CALayer layer]; self.dragHintLayer.backgroundColor = LookinColorMake(70, 70, 70).CGColor; [self.dragHintLayer lookin_removeImplicitAnimations]; [self.layer addSublayer:self.dragHintLayer]; self.scrollView = [UIScrollView new]; self.scrollView.bounces = YES; [self addSubview:self.scrollView]; self.tableView = [[UITableView alloc] init]; self.tableView.backgroundColor = [UIColor clearColor]; #if TARGET_OS_TV #else self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; #endif self.tableView.delegate = self; self.tableView.dataSource = self; [self.scrollView addSubview:self.tableView]; // [self _updateCurrentMaxCellWidth]; } return self; } - (void)layoutSubviews { [super layoutSubviews]; self.effectBgView.frame = self.effectBgView.superview.bounds; // 留出一部分区域用来上下拖拽 CGFloat dragAreaLength = 26; if (self.isHorizontalLayout) { self.dragHintLayer.frame = ({ CGFloat width = 5; CGFloat height = 38; CGRectMake(self.layer.bounds.size.width - dragAreaLength / 2.0 - width / 2.0, self.layer.bounds.size.height / 2.0 - height / 2.0, width, height); }); self.dragHintLayer.cornerRadius = self.dragHintLayer.bounds.size.width / 2.0; self.scrollView.frame = CGRectMake(0, 0, self.layer.bounds.size.width - dragAreaLength, self.layer.bounds.size.height); } else { self.dragHintLayer.frame = ({ CGFloat width = 38; CGFloat height = 5; CGRectMake(self.layer.bounds.size.width / 2.0 - width / 2.0, dragAreaLength / 2.0 - height / 2.0, width, height); }); self.dragHintLayer.cornerRadius = self.dragHintLayer.bounds.size.height / 2.0; self.scrollView.frame = CGRectMake(0, dragAreaLength, self.layer.bounds.size.width, self.layer.bounds.size.height - dragAreaLength); } CGSize tableSize = CGSizeMake(MAX(self.currentMaxCellWidth, self.scrollView.bounds.size.width), self.scrollView.bounds.size.height); self.scrollView.contentSize = tableSize; self.tableView.frame = CGRectMake(0, 0, tableSize.width, tableSize.height); } - (void)setIsHorizontalLayout:(BOOL)isHorizontalLayout { _isHorizontalLayout = isHorizontalLayout; if (isHorizontalLayout) { // 顶部给 menu 留一点位置 self.tableView.contentInset = UIEdgeInsetsMake(40, 0, 0, 0); } else { self.tableView.contentInset = UIEdgeInsetsZero; } [self setNeedsLayout]; } #pragma mark - UITableView - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.dataSource numberOfRows]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { LKS_PerspectiveHierarchyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; if (!cell) { cell = [[LKS_PerspectiveHierarchyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; cell.selectionStyle = UITableViewCellSelectionStyleNone; [cell.indicatorButton addTarget:self action:@selector(_handleCellExpansionButton:) forControlEvents:UIControlEventTouchUpInside]; } LookinDisplayItem *item = [self.dataSource itemAtRow:indexPath.row]; cell.displayItem = item; cell.indicatorButton.tag = indexPath.row; if (item.isSelected) { self.selectedCell = cell; } CGFloat cellWidth = [cell sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].width; if (self.currentMaxCellWidth < cellWidth) { self.currentMaxCellWidth = cellWidth; [self setNeedsLayout]; } return cell; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 26; } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { return 0; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return 0; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { LookinDisplayItem *item = [self.dataSource itemAtRow:indexPath.row]; if (self.dataSource.selectedItem != item) { self.dataSource.selectedItem = item; } } #pragma mark - - (void)dataSourceDidChangeDisplayItems:(LKS_PerspectiveDataSource *)dataSource { [self _tableViewReloadData]; } - (void)dataSourceDidChangeSelectedItem:(LKS_PerspectiveDataSource *)dataSource { NSInteger row = [dataSource rowForItem:dataSource.selectedItem]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0]; [self.selectedCell reRender]; if (!indexPath) { return; } // 去掉旧的 cell 的点击态 [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; NSArray *visibleIndexPaths = [self.tableView indexPathsForVisibleRows]; if (![visibleIndexPaths containsObject:indexPath]) { [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; } } #pragma mark - Others - (void)_tableViewReloadData { self.currentMaxCellWidth = 0; [self.tableView reloadData]; } - (void)_handleCellExpansionButton:(UIButton *)button { NSUInteger row = button.tag; LookinDisplayItem *item = [self.dataSource itemAtRow:row]; if (!item) { return; } if (!item.isExpandable) { return; } if (item.isExpanded) { [self.dataSource collapseItem:item]; } else { [self.dataSource expandItem:item]; } } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveItemLayer.h ================================================ // // LKS_PerspectiveItemLayer.h // LookinServer // // Created by Li Kai on 2019/5/17. // https://lookin.work // #import #import "LookinDisplayItem.h" @interface LKS_PerspectiveItemLayer : CALayer @property(nonatomic, strong) LookinDisplayItem *displayItem; - (void)reRender; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveItemLayer.m ================================================ // // LKS_PerspectiveItemLayer.m // LookinServer // // Created by Li Kai on 2019/5/17. // https://lookin.work // #import "LKS_PerspectiveItemLayer.h" @interface LKS_PerspectiveItemUnselectableLayer : CALayer @end @implementation LKS_PerspectiveItemUnselectableLayer - (CALayer *)hitTest:(CGPoint)p { return nil; } @end @interface LKS_PerspectiveItemLayer () @property(nonatomic, strong) LKS_PerspectiveItemUnselectableLayer *selectedMaskLayer; @property(nonatomic, strong) LKS_PerspectiveItemUnselectableLayer *contentLayer; @end @implementation LKS_PerspectiveItemLayer - (instancetype)init { if (self = [super init]) { self.borderWidth = 1; self.contentLayer = [LKS_PerspectiveItemUnselectableLayer layer]; [self addSublayer:self.contentLayer]; self.selectedMaskLayer = [LKS_PerspectiveItemUnselectableLayer layer]; self.selectedMaskLayer.backgroundColor = LookinColorRGBAMake(74, 144, 226, .25).CGColor; self.selectedMaskLayer.opacity = 0; [self addSublayer:self.selectedMaskLayer]; NSDictionary> *actions = @{NSStringFromSelector(@selector(bounds)) : [NSNull null], NSStringFromSelector(@selector(position)) : [NSNull null], NSStringFromSelector(@selector(borderColor)) : [NSNull null], }; self.actions = actions; } return self; } - (void)layoutSublayers { [super layoutSublayers]; self.selectedMaskLayer.frame = self.bounds; self.contentLayer.frame = self.bounds; } - (void)setDisplayItem:(LookinDisplayItem *)displayItem { _displayItem = displayItem; [self reRender]; } - (void)reRender { if (!self.displayItem) { NSAssert(NO, @""); return; } if (self.displayItem.isExpandable && self.displayItem.isExpanded) { self.contentLayer.contents = (__bridge id)(self.displayItem.soloScreenshot.CGImage); } else { self.contentLayer.contents = (__bridge id)(self.displayItem.groupScreenshot.CGImage); } if (self.displayItem.isSelected) { self.selectedMaskLayer.opacity = 1; self.borderColor = LookinColorRGBAMake(74, 144, 226, 1).CGColor; } else { self.selectedMaskLayer.opacity = 0; self.borderColor = LookinColorRGBAMake(160, 168, 189, .6).CGColor; } if (self.displayItem.displayingInHierarchy && !self.displayItem.inHiddenHierarchy) { self.contentLayer.opacity = 1; self.opacity = 1; } else { self.opacity = 0; self.contentLayer.opacity = 0; } } - (CALayer *)hitTest:(CGPoint)p { if (self.hidden || self.opacity == 0) { return nil; } return [super hitTest:p]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveLayer.h ================================================ // // LKS_PerspectiveLayer.h // LookinServer // // Created by Li Kai on 2019/5/17. // https://lookin.work // #import #import "LKS_PerspectiveDataSource.h" typedef NS_ENUM (NSUInteger, LKS_PerspectiveDimension) { LKS_PerspectiveDimension2D, LKS_PerspectiveDimension3D }; @interface LKS_PerspectiveLayer : CALayer - (instancetype)initWithDataSource:(LKS_PerspectiveDataSource *)dataSource; /// 2D 还是 3D @property(nonatomic, assign) LKS_PerspectiveDimension dimension; /// 旋转的角度 @property(nonatomic, assign, readonly) CGFloat rotation; - (void)setRotation:(CGFloat)rotation animated:(BOOL)animated completion:(void (^)(void))completionBlock; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveLayer.m ================================================ // // LKS_PerspectiveLayer.m // LookinServer // // Created by Li Kai on 2019/5/17. // https://lookin.work // #import "LKS_PerspectiveLayer.h" #import "LKS_PerspectiveDataSource.h" #import "LKS_PerspectiveItemLayer.h" #import "LookinAppInfo.h" #import "LookinHierarchyInfo.h" #import "LookinServerDefines.h" @interface LookinDisplayItem (LKS_PerspectiveLayer) @property(nonatomic, weak) LKS_PerspectiveItemLayer *lks_itemLayer; @end @implementation LookinDisplayItem (LKS_PerspectiveLayer) - (void)setLks_itemLayer:(LKS_PerspectiveItemLayer *)lks_itemLayer { [self lookin_bindObjectWeakly:lks_itemLayer forKey:@"lks_itemLayer"]; } - (LKS_PerspectiveItemLayer *)lks_itemLayer { return [self lookin_getBindObjectForKey:@"lks_itemLayer"]; } @end @interface LKS_PerspectiveLayer () @property(nonatomic, strong) CALayer *rotateLayer; @property(nonatomic, copy) NSArray *itemLayers; @property(nonatomic, strong) LKS_PerspectiveDataSource *dataSource; @property(nonatomic, strong) LKS_PerspectiveItemLayer *selectedLayer; @end @implementation LKS_PerspectiveLayer - (instancetype)initWithDataSource:(LKS_PerspectiveDataSource *)dataSource { if (self = [self init]) { self.dataSource = dataSource; dataSource.perspectiveLayer = self; // [self lookin_removeImplicitAnimations]; self.rotateLayer = [CALayer layer]; [self addSublayer:self.rotateLayer]; self.itemLayers = [NSArray array]; [self _rebuildPreviewLayers]; } return self; } - (void)layoutSublayers { [super layoutSublayers]; LookinAppInfo *appInfo = self.dataSource.rawHierarchyInfo.appInfo; CGSize size = CGSizeMake(appInfo.screenWidth, appInfo.screenHeight); self.rotateLayer.bounds = CGRectMake(0, 0, size.width, size.height); self.rotateLayer.anchorPoint = CGPointMake(.5, .5); self.rotateLayer.position = CGPointMake(self.rotateLayer.superlayer.bounds.size.width / 2.0, self.rotateLayer.superlayer.bounds.size.height / 2.0); } - (void)setDimension:(LKS_PerspectiveDimension)dimension { _dimension = dimension; if (dimension == LKS_PerspectiveDimension2D) { self.rotateLayer.sublayerTransform = CATransform3DIdentity; [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull layer, NSUInteger idx, BOOL * _Nonnull stop) { layer.transform = CATransform3DIdentity; }]; } else if (dimension == LKS_PerspectiveDimension3D) { CGFloat targetRotation = (self.rotation == 0 ? .6 : self.rotation); [self setRotation:targetRotation animated:YES completion:nil]; [self _updateZIndex]; } else { NSAssert(NO, @""); } } - (void)setRotation:(CGFloat)rotation { _rotation = rotation; CATransform3D transform = CATransform3DIdentity; transform.m34 = - 1 / 3000.0; transform = CATransform3DRotate(transform, rotation, 0, 1, 0); self.rotateLayer.sublayerTransform = transform; } - (void)setRotation:(CGFloat)rotation animated:(BOOL)animated completion:(void (^)(void))completionBlock { [CATransaction begin]; [CATransaction setCompletionBlock:completionBlock]; [CATransaction setDisableActions:!animated]; [self setRotation:rotation]; [CATransaction commit]; } - (void)_rebuildPreviewLayers { NSArray *validItems = [self.dataSource.flatItems lookin_filter:^BOOL(LookinDisplayItem *obj) { return !obj.inNoPreviewHierarchy; }]; self.itemLayers = [self.itemLayers lookin_resizeWithCount:validItems.count add:^LKS_PerspectiveItemLayer *(NSUInteger idx) { LKS_PerspectiveItemLayer *layer = [LKS_PerspectiveItemLayer new]; [self.rotateLayer addSublayer:layer]; return layer; } remove:^(NSUInteger idx, LKS_PerspectiveItemLayer *layer) { [layer removeFromSuperlayer]; } doNext:^(NSUInteger idx, LKS_PerspectiveItemLayer *layer) { LookinDisplayItem *item = validItems[idx]; layer.displayItem = item; layer.frame = item.frameToRoot; item.lks_itemLayer = layer; if (item.isSelected) { self.selectedLayer = layer; } }]; [self _updateZIndex]; } /** 重新计算每个 item 的 zIndex,并依 zIndex 设置对应的图层在 z 轴上的 translation。同时根据 fold 等属性来显示或隐藏图层。 */ - (void)_updateZIndex { [[self.dataSource.flatItems lookin_filter:^BOOL(LookinDisplayItem *obj) { return !obj.inNoPreviewHierarchy; }] enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [self _updateZIndexForItem:obj]; }]; [self _updateZTranslationByZIndex]; } - (void)_updateZIndexForItem:(LookinDisplayItem *)item { item.previewZIndex = -1; if (item.displayingInHierarchy) { LookinDisplayItem *referenceItem = [self _maxZIndexForOverlappedItemUnderItem:item]; if (referenceItem) { // 如果 item 和另一个 itemA 重叠了,则 item.previewZIndex 应该比 itemA.previewZIndex 高一级 item.previewZIndex = referenceItem.previewZIndex + 1; } else { item.previewZIndex = 0; } } else { if (item.superItem) { item.previewZIndex = item.superItem.previewZIndex; } else { NSAssert(NO, @""); } } if (item.previewZIndex < 0) { NSAssert(NO, @""); item.previewZIndex = 0; } } - (void)_updateZTranslationByZIndex { CGFloat interspace = 20; // key 是 zIndex,value 是该 zIndex 下有多少 item,作用是避免下文提到的 offsetToAvoidOverlapBug NSMutableDictionary *zIndexAndCountDict = [NSMutableDictionary dictionary]; __block NSUInteger maxZIndex = 0; [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { maxZIndex = MAX(maxZIndex, obj.displayItem.previewZIndex); }]; [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull layer, NSUInteger idx, BOOL * _Nonnull stop) { LookinDisplayItem *item = layer.displayItem; // 将 "1, 2, 3, 4, 5 ..." 这样的 zIndex 排序调整为 “-2,-1,0,1,2 ...”,这样旋转时 Y 轴就会位于 zIndex 为中间值的那个 layer 的位置 NSInteger adjustedZIndex = item.previewZIndex - round(maxZIndex / 2.0); NSUInteger countOfCurrentZIndex = [[zIndexAndCountDict objectForKey:@(adjustedZIndex)] unsignedIntegerValue]; countOfCurrentZIndex++; [zIndexAndCountDict setObject:@(countOfCurrentZIndex) forKey:@(adjustedZIndex)]; /// 当两个重叠的 layer 在 z 轴上具有相同的 translate 时,理论上更远离用户的那个 layer 应该被遮住,但不知道为什么某些旋转角度下会出现不合理论的情况,感觉是系统 bug,因此这里把更靠近用户的那个 layer 的 translate 增大一丁点,避免两个 layer 有完全相同的 translate 从而避免这个 bug CGFloat offsetToAvoidOverlapBug = countOfCurrentZIndex * 0.01; layer.transform = CATransform3DTranslate(CATransform3DIdentity, 0, 0, interspace * adjustedZIndex + offsetToAvoidOverlapBug); }]; } /** 传入 itemA,返回另一个 itemB,itemB 满足以下条件: - itemB 在 preview 中可见 - itemB 的层级比 itemA 要低(即 itemB 在 flatItems 里的 index 要比 itemA 小) - itemB 和 itemA 的 frameToRoot 有重叠,即视觉上它们是彼此遮挡的 - itemB 是满足以上两个条件中的所有 items 里的 zIndex 值最高的 @note 如果没有找到任何符合条件的 itemB,则返回 nil */ - (LookinDisplayItem *)_maxZIndexForOverlappedItemUnderItem:(LookinDisplayItem *)item { NSArray *flatItems = [self.dataSource.flatItems lookin_filter:^BOOL(LookinDisplayItem *obj) { return !obj.inNoPreviewHierarchy; }]; NSUInteger itemIndex = [flatItems indexOfObject:item]; if (itemIndex == 0) { return nil; } if (itemIndex == NSNotFound) { NSAssert(NO, @""); return nil; } NSIndexSet *indexesBelow = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, itemIndex)]; __block LookinDisplayItem *targetItem = nil; [flatItems enumerateObjectsAtIndexes:indexesBelow options:NSEnumerationReverse usingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (!obj.inHiddenHierarchy) { if (CGRectIntersectsRect(item.frameToRoot, obj.frameToRoot)) { if (!targetItem) { targetItem = obj; } else { if (obj.previewZIndex > targetItem.previewZIndex) { targetItem = obj; } } } } }]; return targetItem; } #pragma mark - - (void)dataSourceDidChangeSelectedItem:(LKS_PerspectiveDataSource *)dataSource { [self.selectedLayer reRender]; LookinDisplayItem *item = dataSource.selectedItem; [item.lks_itemLayer reRender]; self.selectedLayer = item.lks_itemLayer; } - (void)dataSourceDidChangeDisplayItems:(LKS_PerspectiveDataSource *)dataSource { [self _updateZIndex]; [self.itemLayers enumerateObjectsUsingBlock:^(LKS_PerspectiveItemLayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [obj reRender]; }]; } - (void)dataSourceDidChangeNoPreview:(LKS_PerspectiveDataSource *)dataSource { [self _rebuildPreviewLayers]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveManager.h ================================================ // // LKS_PerspectiveManager.h // LookinServer // // Created by Li Kai on 2019/5/17. // https://lookin.work // #import "LookinDefines.h" @interface LKS_PerspectiveContainerWindow : UIWindow @end @interface LKS_PerspectiveManager : NSObject + (instancetype)sharedInstance; - (void)showWithIncludedWindows:(NSArray *)includedWindows excludedWindows:(NSArray *)excludedWindows; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveManager.m ================================================ // // LKS_PerspectiveManager.m // LookinServer // // Created by Li Kai on 2019/5/17. // https://lookin.work // #import "LKS_PerspectiveManager.h" #import "LKS_PerspectiveViewController.h" #import "UIViewController+LookinServer.h" #import "LookinHierarchyInfo.h" #import "LookinServerDefines.h" #import "LKS_PerspectiveToolbarButtons.h" @interface LKS_PerspectiveLoadingMaskView : UIView @property(nonatomic, strong) UIView *tipsView; @property(nonatomic, strong) UILabel *firstLabel; @property(nonatomic, strong) UILabel *secondLabel; @end @implementation LKS_PerspectiveLoadingMaskView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.35]; self.layer.lks_isLookinPrivateLayer = YES; self.layer.lks_avoidCapturing = YES; self.tipsView = [UIView new]; self.tipsView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.88]; self.tipsView.layer.cornerRadius = 6; self.tipsView.layer.masksToBounds = YES; [self addSubview:self.tipsView]; self.firstLabel = [UILabel new]; self.firstLabel.text = LKS_Localized(@"Building…"); self.firstLabel.textColor = [UIColor whiteColor]; self.firstLabel.font = [UIFont boldSystemFontOfSize:14]; self.firstLabel.textAlignment = NSTextAlignmentCenter; [self.tipsView addSubview:self.firstLabel]; self.secondLabel = [UILabel new]; self.secondLabel.numberOfLines = 0; self.secondLabel.textAlignment = NSTextAlignmentCenter; self.secondLabel.text = LKS_Localized(@"May take 8 or more seconds according to the UI complexity."); self.secondLabel.textColor = [UIColor colorWithRed:173/255.0 green:180/255.0 blue:190/255.0 alpha:1]; self.secondLabel.font = [UIFont systemFontOfSize:12]; self.secondLabel.textAlignment = NSTextAlignmentLeft; [self.tipsView addSubview:self.secondLabel]; } return self; } - (void)layoutSubviews { [super layoutSubviews]; UIEdgeInsets insets = UIEdgeInsetsMake(8, 10, 8, 10); CGFloat maxLabelWidth = self.bounds.size.width * .8 - insets.left - insets.right; CGSize firstSize = [self.firstLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)]; CGSize secondSize = [self.secondLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)]; CGFloat tipsWidth = MAX(firstSize.width, secondSize.width) + insets.left + insets.right; self.firstLabel.frame = CGRectMake(tipsWidth / 2.0 - firstSize.width / 2.0, insets.top, firstSize.width, firstSize.height); self.secondLabel.frame = CGRectMake(tipsWidth / 2.0 - secondSize.width / 2.0, CGRectGetMaxY(self.firstLabel.frame) + 7, secondSize.width, secondSize.height); self.tipsView.frame = ({ CGFloat height = CGRectGetMaxY(self.secondLabel.frame) + insets.bottom; CGRectMake(self.bounds.size.width / 2.0 - tipsWidth / 2.0, self.bounds.size.height / 2.0 - height / 2.0, tipsWidth, height); }); } @end @implementation LKS_PerspectiveContainerWindow @end @interface LKS_PerspectiveManager () @property(nonatomic, strong) LKS_PerspectiveLoadingMaskView *loadingView; @property(nonatomic, weak) UIWindow *previousKeyWindow; @property(nonatomic, strong) LKS_PerspectiveContainerWindow *contentWindow; @property(nonatomic, strong) LKS_PerspectiveViewController *viewController; @end @implementation LKS_PerspectiveManager + (instancetype)sharedInstance { static dispatch_once_t onceToken; static LKS_PerspectiveManager *instance = nil; dispatch_once(&onceToken,^{ instance = [[super allocWithZone:NULL] init]; }); return instance; } + (id)allocWithZone:(struct _NSZone *)zone{ return [self sharedInstance]; } - (void)showWithIncludedWindows:(NSArray *)includedWindows excludedWindows:(NSArray *)excludedWindows { UIViewController *visibleVc = [UIViewController lks_visibleViewController]; if (!visibleVc) { NSLog(@"LookinServer - Failed to start inspecting in 3D because we didn't find any visible view controller."); return; } [[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_WillEnter3D" object:nil]; if (!self.loadingView) { self.loadingView = [LKS_PerspectiveLoadingMaskView new]; } [visibleVc.view.window addSubview:self.loadingView]; self.loadingView.frame = visibleVc.view.window.bounds; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ LookinHierarchyInfo *info = [LookinHierarchyInfo perspectiveInfoWithIncludedWindows:includedWindows excludedWindows:excludedWindows]; [self.loadingView removeFromSuperview]; self.loadingView = nil; self.contentWindow = [LKS_PerspectiveContainerWindow new]; self.contentWindow.windowLevel = UIWindowLevelAlert - 2; self.contentWindow.backgroundColor = [UIColor clearColor]; self.viewController = [[LKS_PerspectiveViewController alloc] initWithHierarchyInfo:info]; self.contentWindow.rootViewController = self.viewController; self.previousKeyWindow = [UIApplication sharedApplication].keyWindow; [self.contentWindow makeKeyAndVisible]; [self.viewController.closeButton addTarget:self action:@selector(_exit) forControlEvents:UIControlEventTouchUpInside]; }); } - (void)_exit { if (!self.contentWindow) { return; } if ([[UIApplication sharedApplication] keyWindow] == self.contentWindow) { if (self.previousKeyWindow.hidden) { [[UIApplication sharedApplication].delegate.window makeKeyWindow]; } else { [self.previousKeyWindow makeKeyWindow]; } } self.contentWindow.hidden = YES; self.contentWindow = nil; self.viewController = nil; self.previousKeyWindow = nil; [[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_DidExit3D" object:nil]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveToolbarButtons.h ================================================ // // LKS_PerspectiveToolbarButtons.h // qmuidemo // // Created by Li Kai on 2018/12/20. // Copyright © 2018 QMUI Team. All rights reserved. // #import @interface LKS_PerspectiveToolbarCloseButton : UIButton @end @interface LKS_PerspectiveToolbarDimensionButtonsView : UIView @property(nonatomic, strong, readonly) UIButton *button2D; @property(nonatomic, strong, readonly) UIButton *button3D; @end @interface LKS_PerspectiveToolbarLayoutButtonsView : UIView @property(nonatomic, strong, readonly) UIButton *verticalLayoutButton; @property(nonatomic, strong, readonly) UIButton *horizontalLayoutButton; @end @interface LKS_PerspectiveToolbarPropertyButton : UIButton @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveToolbarButtons.m ================================================ // // LKS_PerspectiveToolbarButtons.m // qmuidemo // // Created by Li Kai on 2018/12/20. // Copyright © 2018 QMUI Team. All rights reserved. // #import "LKS_PerspectiveToolbarButtons.h" static CGFloat const kConerRadius = 6; @implementation LKS_PerspectiveToolbarCloseButton - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setImage:[self _image] forState:UIControlStateNormal]; self.backgroundColor = [UIColor blackColor]; self.layer.cornerRadius = kConerRadius; } return self; } - (UIImage *)_image { CGFloat width = 13; UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, width), NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(0, 0)]; [path addLineToPoint:CGPointMake(width, width)]; [path moveToPoint:CGPointMake(width, 0)]; [path addLineToPoint:CGPointMake(0, width)]; CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); [path setLineWidth:1]; [path stroke]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } @end @implementation LKS_PerspectiveToolbarDimensionButtonsView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor blackColor]; self.layer.cornerRadius = kConerRadius; self.clipsToBounds = YES; _button2D = [UIButton new]; [self.button2D setImage:[self _image2D] forState:UIControlStateNormal]; [self addSubview:self.button2D]; _button3D = [UIButton new]; [self.button3D setImage:[self _image3D] forState:UIControlStateNormal]; [self addSubview:self.button3D]; } return self; } - (void)layoutSubviews { [super layoutSubviews]; CGFloat halfWidth = self.bounds.size.width / 2.0; CGFloat height = self.bounds.size.height; self.button2D.frame = CGRectMake(0, 0, halfWidth, height); self.button3D.frame = CGRectMake(halfWidth, 0, halfWidth, height); } - (UIImage *)_image2D { CGFloat width = 16; UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, width), NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(1, 1)]; [path addLineToPoint:CGPointMake(width - 1, 1)]; [path addLineToPoint:CGPointMake(width - 1, width - 1)]; [path addLineToPoint:CGPointMake(1, width - 1)]; [path closePath]; CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); [path setLineWidth:1]; [path stroke]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } - (UIImage *)_image3D { CGFloat width = 16; CGFloat height = 18; UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(width / 2.0, 1)]; [path addLineToPoint:CGPointMake(width - 1, 4)]; [path addLineToPoint:CGPointMake(width / 2.0, 7)]; [path addLineToPoint:CGPointMake(1, 4)]; [path closePath]; [path moveToPoint:CGPointMake(1, 4)]; [path addLineToPoint:CGPointMake(1, height - 4)]; [path addLineToPoint:CGPointMake(width / 2.0, height - 1)]; [path addLineToPoint:CGPointMake(width - 1, height - 4)]; [path addLineToPoint:CGPointMake(width - 1, 4)]; [path moveToPoint:CGPointMake(width / 2.0, 7)]; [path addLineToPoint:CGPointMake(width / 2.0, height - 1)]; CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); [path setLineWidth:1]; [path stroke]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } @end @implementation LKS_PerspectiveToolbarLayoutButtonsView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor blackColor]; self.layer.cornerRadius = kConerRadius; self.clipsToBounds = YES; _verticalLayoutButton = [UIButton new]; [self.verticalLayoutButton setImage:[self _imageVertical] forState:UIControlStateNormal]; [self addSubview:self.verticalLayoutButton]; _horizontalLayoutButton = [UIButton new]; [self.horizontalLayoutButton setImage:[self _imageHorizontal] forState:UIControlStateNormal]; [self addSubview:self.horizontalLayoutButton]; } return self; } - (void)layoutSubviews { [super layoutSubviews]; CGFloat halfWidth = self.bounds.size.width / 2.0; CGFloat height = self.bounds.size.height; self.verticalLayoutButton.frame = CGRectMake(0, 0, halfWidth, height); self.horizontalLayoutButton.frame = CGRectMake(halfWidth, 0, halfWidth, height); } - (UIImage *)_imageHorizontal { CGFloat width = 19; CGFloat height = 17; UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); CGRect outRect = CGRectMake(1, 1, width - 2, height - 2); UIBezierPath *path = [UIBezierPath bezierPathWithRect:outRect]; CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); [path setLineWidth:1]; [path stroke]; path = [UIBezierPath bezierPathWithRect:CGRectMake(CGRectGetMinX(outRect) + 3, CGRectGetMinY(outRect) + 3, 2, CGRectGetHeight(outRect) - 6)]; CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); [path fill]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } - (UIImage *)_imageVertical { CGFloat width = 19; CGFloat height = 17; UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); CGRect outRect = CGRectMake(1, 1, width - 2, height - 2); UIBezierPath *path = [UIBezierPath bezierPathWithRect:outRect]; CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); [path setLineWidth:1]; [path stroke]; path = [UIBezierPath bezierPathWithRect:CGRectMake(CGRectGetMinX(outRect) + 3, CGRectGetMaxY(outRect) - 5, CGRectGetWidth(outRect) - 6, 2)]; CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); [path fill]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } @end @implementation LKS_PerspectiveToolbarPropertyButton - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setImage:[self _image] forState:UIControlStateNormal]; self.backgroundColor = [UIColor blackColor]; self.layer.cornerRadius = kConerRadius; } return self; } - (UIImage *)_image { CGFloat width = 20; UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, width), NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); CGFloat ovalRadius = 3; UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(width / 2.0 - ovalRadius / 2.0, 4, ovalRadius, ovalRadius)]; CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); [path fill]; path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(width / 2.0, 9)]; [path addLineToPoint:CGPointMake(width / 2.0, width - 4)]; [path setLineWidth:2]; CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); [path stroke]; path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(1, 1, width - 2, width - 2)]; [path setLineWidth:1]; [path stroke]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveViewController.h ================================================ // // LKS_PerspectiveViewController.h // LookinServer // // Created by Li Kai on 2019/5/17. // https://lookin.work // #import @class LookinHierarchyInfo, LKS_PerspectiveToolbarCloseButton; @interface LKS_PerspectiveViewController : UIViewController - (instancetype)initWithHierarchyInfo:(LookinHierarchyInfo *)info; @property(nonatomic, strong, readonly) LKS_PerspectiveToolbarCloseButton *closeButton; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Server/Perspective/LKS_PerspectiveViewController.m ================================================ // // LKS_PerspectiveViewController.m // LookinServer // // Created by Li Kai on 2019/5/17. // https://lookin.work // #import "LKS_PerspectiveViewController.h" #import "LKS_PerspectiveDataSource.h" #import "LKS_PerspectiveLayer.h" #import "LKS_PerspectiveHierarchyView.h" #import "LKS_PerspectiveToolbarButtons.h" #import "LKS_PerspectiveItemLayer.h" #import "LookinServerDefines.h" typedef NS_ENUM(NSUInteger, LKS_PerspectiveLayout) { LKS_PerspectiveLayoutFullScreen, LKS_PerspectiveLayoutHorizontal, LKS_PerspectiveLayoutVertical }; @interface LKS_PerspectiveViewController () @property(nonatomic, strong) LKS_PerspectiveLayer *previewLayer; @property(nonatomic, strong) LKS_PerspectiveHierarchyView *hierarchyView; @property(nonatomic, strong) LKS_PerspectiveToolbarDimensionButtonsView *dimensionButtonsView; @property(nonatomic, strong) LKS_PerspectiveToolbarLayoutButtonsView *layoutButtonsView; @property(nonatomic, strong) LKS_PerspectiveToolbarPropertyButton *propertyButton; @property(nonatomic, strong) LKS_PerspectiveDataSource *dataSource; @property(nonatomic, assign) LKS_PerspectiveLayout layoutType; @property(nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer; @property(nonatomic, strong) UIPanGestureRecognizer *rotationGestureRecognizer; @property(nonatomic, strong) UIPanGestureRecognizer *hierarchyDragGestureRecognizer; @property(nonatomic, strong) UIPanGestureRecognizer *twoFingersGestureRecognizer; /** 当 layout 为 horizontal 时,该值表示 hierarchyView 最右侧的位置。 当 layout 为 vertical 时,该值表示 hierarchyView 的最顶部的位置。 当 layout 为 fullscreen 时,该值无意义 */ @property(nonatomic, assign) CGFloat previewAndHierarchySepPosition; @property(nonatomic, assign) CGFloat scale; @property(nonatomic, assign) CGPoint translation; @end @implementation LKS_PerspectiveViewController { UIColor *_selectedButtonColor; } - (instancetype)initWithHierarchyInfo:(LookinHierarchyInfo *)info { if (self = [self initWithNibName:nil bundle:nil]) { self.dataSource = [[LKS_PerspectiveDataSource alloc] initWithHierarchyInfo:info]; } return self; } - (void)viewDidLoad { [super viewDidLoad]; _selectedButtonColor = LookinColorMake(59, 130, 183); self.view.backgroundColor = LookinColorMake(31, 32, 34); self.previewLayer = [[LKS_PerspectiveLayer alloc] initWithDataSource:self.dataSource]; [self.view.layer addSublayer:self.previewLayer]; self.hierarchyView = [[LKS_PerspectiveHierarchyView alloc] initWithDataSource:self.dataSource]; [self.view addSubview:self.hierarchyView]; _closeButton = [LKS_PerspectiveToolbarCloseButton new]; [self.view addSubview:self.closeButton]; self.dimensionButtonsView = [LKS_PerspectiveToolbarDimensionButtonsView new]; [self.dimensionButtonsView.button2D addTarget:self action:@selector(_handle2D) forControlEvents:UIControlEventTouchUpInside]; [self.dimensionButtonsView.button3D addTarget:self action:@selector(_handle3D) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.dimensionButtonsView]; self.layoutButtonsView = [LKS_PerspectiveToolbarLayoutButtonsView new]; [self.layoutButtonsView.verticalLayoutButton addTarget:self action:@selector(_handleVerticalLayout) forControlEvents:UIControlEventTouchUpInside]; [self.layoutButtonsView.horizontalLayoutButton addTarget:self action:@selector(_handleHorizontalLayout) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.layoutButtonsView]; self.propertyButton = [LKS_PerspectiveToolbarPropertyButton new]; // [self.view addSubview:self.propertyButton]; self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_handleTapGesture:)]; self.tapGestureRecognizer.delegate = self; [self.view addGestureRecognizer:self.tapGestureRecognizer]; self.hierarchyDragGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_handleHierarchyDragGestureRecognizer:)]; #if TARGET_OS_TV #else self.hierarchyDragGestureRecognizer.maximumNumberOfTouches = 1; #endif self.hierarchyDragGestureRecognizer.delegate = self; [self.view addGestureRecognizer:self.hierarchyDragGestureRecognizer]; self.rotationGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_handleRotationGestureRecognizer:)]; #if TARGET_OS_TV #else self.rotationGestureRecognizer.maximumNumberOfTouches = 1; #endif self.rotationGestureRecognizer.delegate = self; [self.rotationGestureRecognizer requireGestureRecognizerToFail:self.hierarchyDragGestureRecognizer]; [self.view addGestureRecognizer:self.rotationGestureRecognizer]; self.twoFingersGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_handleTwoFingersGestureRecognizer:)]; #if TARGET_OS_TV #else self.twoFingersGestureRecognizer.minimumNumberOfTouches = 2; self.twoFingersGestureRecognizer.maximumNumberOfTouches = 2; #endif [self.view addGestureRecognizer:self.twoFingersGestureRecognizer]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self _startEnterAnim]; }); } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; self.previewLayer.bounds = [UIScreen mainScreen].bounds; self.previewLayer.anchorPoint = CGPointMake(.5, .5); self.previewLayer.position = CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0); CGFloat buttonHeight = 30; CGFloat y = 20; if (@available(iOS 11, tvOS 11.0, *)) { y = MAX(self.view.safeAreaInsets.top, 20); } self.closeButton.frame = ({ CGFloat x = 20; if (@available(iOS 11, tvOS 11.0, *)) { x = MAX(self.view.safeAreaInsets.left, 20); } CGRectMake(x, y, buttonHeight, buttonHeight); }); CGFloat buttonGroupWidth = 70; self.layoutButtonsView.frame = ({ CGFloat right = 20; if (@available(iOS 11, tvOS 11.0, *)) { right = MAX(self.view.safeAreaInsets.right, 20); } CGRectMake(self.view.bounds.size.width - right - buttonGroupWidth, y, buttonGroupWidth, buttonHeight); }); self.dimensionButtonsView.frame = CGRectMake(CGRectGetMinX(self.layoutButtonsView.frame) - 15 - buttonGroupWidth, CGRectGetMinY(self.layoutButtonsView.frame), buttonGroupWidth, buttonHeight); // self.propertyButton.frame = CGRectMake(CGRectGetMinX(self.layoutButtonsView.frame) + 15, y, buttonHeight, buttonHeight); if (self.layoutType == LKS_PerspectiveLayoutFullScreen) { // preview 全屏 self.hierarchyView.frame = CGRectZero; } else if (self.layoutType == LKS_PerspectiveLayoutVertical) { // 上下布局 CGFloat width = self.view.bounds.size.width; self.hierarchyView.frame = CGRectMake(0, self.previewAndHierarchySepPosition, width, self.view.bounds.size.height - self.previewAndHierarchySepPosition); } else if (self.layoutType == LKS_PerspectiveLayoutHorizontal) { // 左右布局 CGFloat height = self.view.bounds.size.height; self.hierarchyView.frame = CGRectMake(0, 0, self.previewAndHierarchySepPosition, height); } else { NSAssert(NO, @""); } } #pragma mark - Event Handler - (void)_handleTapGesture:(UITapGestureRecognizer *)recognizer { CGPoint point = [recognizer locationInView:self.view]; CALayer *layer = [self.view.layer hitTest:point]; if ([layer isKindOfClass:[LKS_PerspectiveItemLayer class]]) { LookinDisplayItem *item = ((LKS_PerspectiveItemLayer *)layer).displayItem; if (self.dataSource.selectedItem != item) { self.dataSource.selectedItem = item; } } } - (void)_handleRotationGestureRecognizer:(UIPanGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateBegan) { CGFloat initialRotation = self.previewLayer.rotation; [recognizer lookin_bindDouble:initialRotation forKey:@"initialRotation"]; } else if (recognizer.state == UIGestureRecognizerStateChanged) { CGFloat rotationVelocity = 0.01; CGFloat gestureRotationOffset = [recognizer translationInView:self.view].x * rotationVelocity; CGFloat initialRotation = [recognizer lookin_getBindDoubleForKey:@"initialRotation"]; CGFloat currentRotation = initialRotation + gestureRotationOffset; [self.previewLayer setRotation:currentRotation animated:NO completion:nil]; } } - (void)_handleHierarchyDragGestureRecognizer:(UIPanGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateBegan) { if (self.layoutType == LKS_PerspectiveLayoutFullScreen) { self.layoutType = LKS_PerspectiveLayoutVertical; // self.layoutButtonsView.verticalLayoutButton.selected = YES; self.previewAndHierarchySepPosition = self.view.bounds.size.height; } [self.hierarchyView lookin_bindDouble:self.previewAndHierarchySepPosition forKey:@"initialSepPosition"]; } else if (recognizer.state == UIGestureRecognizerStateChanged) { CGFloat initialSepPosition = [self.hierarchyView lookin_getBindDoubleForKey:@"initialSepPosition"]; if (self.layoutType == LKS_PerspectiveLayoutHorizontal) { CGFloat translationX = [recognizer translationInView:self.view].x; CGFloat maxSepPosition = self.view.bounds.size.width * .7; self.previewAndHierarchySepPosition = MIN(MAX(initialSepPosition + translationX, 0), maxSepPosition); } else { CGFloat translationY = [recognizer translationInView:self.view].y; CGFloat minSepPosition = self.view.bounds.size.height * .3; self.previewAndHierarchySepPosition = MAX(MIN(initialSepPosition + translationY, self.view.bounds.size.height), minSepPosition); } [self.view setNeedsLayout]; } else { if (self.layoutType == LKS_PerspectiveLayoutHorizontal) { CGFloat minXToHideHierarchy = 100; if (self.previewAndHierarchySepPosition <= minXToHideHierarchy) { self.previewAndHierarchySepPosition = 0; // self.layoutButtonsView.horizontalLayoutButton.selected = NO; [UIView animateWithDuration:.2 animations:^{ [self.view setNeedsLayout]; [self.view layoutIfNeeded]; } completion:^(BOOL finished) { self.layoutType = LKS_PerspectiveLayoutFullScreen; }]; } } else if (self.layoutType == LKS_PerspectiveLayoutVertical) { // 当距离屏幕底部的值小于该值时,hierarchy 将自动隐藏 CGFloat minBottomToHideHierarchy = 100; if (self.previewAndHierarchySepPosition >= self.view.bounds.size.height - minBottomToHideHierarchy) { self.previewAndHierarchySepPosition = self.view.bounds.size.height; // self.layoutButtonsView.verticalLayoutButton.selected = NO; [UIView animateWithDuration:.2 animations:^{ [self.view setNeedsLayout]; [self.view layoutIfNeeded]; } completion:^(BOOL finished) { self.layoutType = LKS_PerspectiveLayoutFullScreen; }]; } } } } - (void)_handleTwoFingersGestureRecognizer:(UIPanGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateBegan) { if (recognizer.numberOfTouches != 2) { NSAssert(NO, @""); return; } CGPoint initialLoc0 = [recognizer locationOfTouch:0 inView:self.view]; CGPoint initialLoc1 = [recognizer locationOfTouch:1 inView:self.view]; [recognizer lookin_bindPoint:initialLoc0 forKey:@"initialLoc0"]; [recognizer lookin_bindPoint:initialLoc1 forKey:@"initialLoc1"]; [recognizer lookin_bindPoint:self.translation forKey:@"initialTranslation"]; [recognizer lookin_bindDouble:self.scale forKey:@"initialScale"]; } else if (recognizer.state == UIGestureRecognizerStateChanged) { if ([recognizer numberOfTouches] != 2) { return; } CGPoint initialLoc0 = [recognizer lookin_getBindPointForKey:@"initialLoc0"]; CGPoint initialLoc1 = [recognizer lookin_getBindPointForKey:@"initialLoc1"]; CGPoint initialCenter = CGPointMake((initialLoc1.x - initialLoc0.x) / 2.0 + initialLoc0.x, (initialLoc1.y - initialLoc0.y) / 2.0 + initialLoc0.y); CGPoint currentLoc0 = [recognizer locationOfTouch:0 inView:self.view]; CGPoint currentLoc1 = [recognizer locationOfTouch:1 inView:self.view]; CGPoint currentCenter = CGPointMake((currentLoc1.x - currentLoc0.x) / 2.0 + currentLoc0.x, (currentLoc1.y - currentLoc0.y) / 2.0 + currentLoc0.y); CGPoint initialTranslation = [recognizer lookin_getBindPointForKey:@"initialTranslation"]; CGPoint translationOffset = CGPointMake(currentCenter.x - initialCenter.x, currentCenter.y - initialCenter.y); [self setTranslation:CGPointMake(initialTranslation.x + translationOffset.x, initialTranslation.y + translationOffset.y) animated:NO]; CGFloat initialTouchesDistance = hypot(ABS(initialLoc1.x - initialLoc0.x), ABS(initialLoc1.y - initialLoc0.y)); CGFloat currentTouchesDistance = hypot(ABS(currentLoc1.x - currentLoc0.x), ABS(currentLoc1.y - currentLoc0.y)); CGFloat initialScale = [recognizer lookin_getBindDoubleForKey:@"initialScale"]; CGFloat currentScale = initialScale * (currentTouchesDistance / MAX(initialTouchesDistance, 1)); [self setScale:currentScale animated:NO]; } else { [recognizer lookin_clearBindForKey:@"initialLoc0"]; [recognizer lookin_clearBindForKey:@"initialLoc1"]; } } - (void)_handle2D { [self.dimensionButtonsView.button2D setBackgroundColor:_selectedButtonColor]; [self.dimensionButtonsView.button3D setBackgroundColor:nil]; self.previewLayer.dimension = LKS_PerspectiveDimension2D; } - (void)_handle3D { [self.dimensionButtonsView.button2D setBackgroundColor:nil]; [self.dimensionButtonsView.button3D setBackgroundColor:_selectedButtonColor]; self.previewLayer.dimension = LKS_PerspectiveDimension3D; } - (void)_handleVerticalLayout { if (self.layoutType == LKS_PerspectiveLayoutVertical) { return; } self.layoutType = LKS_PerspectiveLayoutVertical; self.previewAndHierarchySepPosition = self.view.bounds.size.height - 300; [self.view setNeedsLayout]; } - (void)_handleHorizontalLayout { if (self.layoutType == LKS_PerspectiveLayoutHorizontal) { return; } self.layoutType = LKS_PerspectiveLayoutHorizontal; self.previewAndHierarchySepPosition = 200; [self.view setNeedsLayout]; } - (void)setLayoutType:(LKS_PerspectiveLayout)layoutType { _layoutType = layoutType; if (layoutType == LKS_PerspectiveLayoutHorizontal) { self.hierarchyView.isHorizontalLayout = YES; } else { self.hierarchyView.isHorizontalLayout = NO; } [self.layoutButtonsView.verticalLayoutButton setBackgroundColor:nil]; [self.layoutButtonsView.horizontalLayoutButton setBackgroundColor:nil]; if (layoutType == LKS_PerspectiveLayoutHorizontal) { [self.layoutButtonsView.horizontalLayoutButton setBackgroundColor:_selectedButtonColor]; } else if (layoutType == LKS_PerspectiveLayoutVertical) { [self.layoutButtonsView.verticalLayoutButton setBackgroundColor:_selectedButtonColor]; } } #pragma mark - - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { if (gestureRecognizer == self.rotationGestureRecognizer) { BOOL canRotate = (self.previewLayer.dimension == LKS_PerspectiveDimension3D); return canRotate; } else if (gestureRecognizer == self.hierarchyDragGestureRecognizer) { if (self.layoutType == LKS_PerspectiveLayoutHorizontal) { CGFloat touchX = [touch locationInView:self.view].x; if (touchX > self.previewAndHierarchySepPosition) { return NO; } } else if (self.layoutType == LKS_PerspectiveLayoutVertical) { CGFloat touchY = [touch locationInView:self.view].y; if (touchY < self.previewAndHierarchySepPosition) { return NO; } } } return YES; } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer == self.hierarchyDragGestureRecognizer) { if (self.layoutType == LKS_PerspectiveLayoutFullScreen) { CGPoint translation = [self.hierarchyDragGestureRecognizer translationInView:self.view]; if (ABS(translation.x) >= 1) { return NO; } } } else if (gestureRecognizer == self.twoFingersGestureRecognizer) { if (gestureRecognizer.numberOfTouches != 2) { return NO; } } else if (gestureRecognizer == self.tapGestureRecognizer) { CGPoint location = [self.tapGestureRecognizer locationInView:self.view]; if (CGRectContainsPoint(self.hierarchyView.frame, location)) { return NO; } } return YES; } #pragma mark - Others - (void)_startEnterAnim { [self _handle3D]; [self.previewLayer setRotation:0 animated:NO completion:nil]; self.closeButton.alpha = 0; self.dimensionButtonsView.alpha = 0; self.layoutButtonsView.alpha = 0; #if TARGET_OS_TV BOOL isLandScape = YES; #else BOOL isLandScape = UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]); #endif if (isLandScape) { self.layoutType = LKS_PerspectiveLayoutHorizontal; self.previewAndHierarchySepPosition = 0; } else { self.layoutType = LKS_PerspectiveLayoutVertical; self.previewAndHierarchySepPosition = self.view.bounds.size.height; } [self.view setNeedsLayout]; [self.view layoutIfNeeded]; [self setScale:.6 animated:YES]; [self setTranslation:CGPointMake(0, -60) animated:YES]; [UIView animateWithDuration:.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ self.closeButton.alpha = 1; self.dimensionButtonsView.alpha = 1; self.layoutButtonsView.alpha = 1; if (isLandScape) { self.previewAndHierarchySepPosition = 200; } else { self.previewAndHierarchySepPosition = self.view.bounds.size.height - 130; } [self.view setNeedsLayout]; [self.view layoutIfNeeded]; } completion:^(BOOL finished) { [self.previewLayer setRotation:.5 animated:YES completion:nil]; }]; } - (void)setScale:(CGFloat)scale animated:(BOOL)animated { _scale = scale; [self _updateTransformAnimated:animated]; } - (void)setTranslation:(CGPoint)translation animated:(BOOL)animated { _translation = translation; [self _updateTransformAnimated:animated]; } - (void)_updateTransformAnimated:(BOOL)animated { CATransform3D transform = CATransform3DTranslate(CATransform3DIdentity, self.translation.x, self.translation.y, 0); transform = CATransform3DScale(transform, self.scale, self.scale, 1); [CATransaction begin]; [CATransaction setDisableActions:!animated]; self.previewLayer.transform = transform; [CATransaction commit]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Category/CALayer+Lookin.h ================================================ // // CALayer+Lookin.h // Lookin // // Created by Li Kai on 2018/8/4. // https://lookin.work // #import "LookinDefines.h" #import @interface CALayer (Lookin) - (void)lookin_removeImplicitAnimations; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Category/CALayer+Lookin.m ================================================ // // CALayer+Lookin.m // Lookin // // Created by Li Kai on 2018/8/4. // https://lookin.work // #import "CALayer+Lookin.h" @implementation CALayer (Lookin) - (void)lookin_removeImplicitAnimations { NSMutableDictionary> *actions = @{NSStringFromSelector(@selector(bounds)): [NSNull null], NSStringFromSelector(@selector(position)): [NSNull null], NSStringFromSelector(@selector(zPosition)): [NSNull null], NSStringFromSelector(@selector(anchorPoint)): [NSNull null], NSStringFromSelector(@selector(anchorPointZ)): [NSNull null], NSStringFromSelector(@selector(transform)): [NSNull null], NSStringFromSelector(@selector(sublayerTransform)): [NSNull null], NSStringFromSelector(@selector(masksToBounds)): [NSNull null], NSStringFromSelector(@selector(contents)): [NSNull null], NSStringFromSelector(@selector(contentsRect)): [NSNull null], NSStringFromSelector(@selector(contentsScale)): [NSNull null], NSStringFromSelector(@selector(contentsCenter)): [NSNull null], NSStringFromSelector(@selector(minificationFilterBias)): [NSNull null], NSStringFromSelector(@selector(backgroundColor)): [NSNull null], NSStringFromSelector(@selector(cornerRadius)): [NSNull null], NSStringFromSelector(@selector(borderWidth)): [NSNull null], NSStringFromSelector(@selector(borderColor)): [NSNull null], NSStringFromSelector(@selector(opacity)): [NSNull null], NSStringFromSelector(@selector(compositingFilter)): [NSNull null], NSStringFromSelector(@selector(filters)): [NSNull null], NSStringFromSelector(@selector(backgroundFilters)): [NSNull null], NSStringFromSelector(@selector(shouldRasterize)): [NSNull null], NSStringFromSelector(@selector(rasterizationScale)): [NSNull null], NSStringFromSelector(@selector(shadowColor)): [NSNull null], NSStringFromSelector(@selector(shadowOpacity)): [NSNull null], NSStringFromSelector(@selector(shadowOffset)): [NSNull null], NSStringFromSelector(@selector(shadowRadius)): [NSNull null], NSStringFromSelector(@selector(shadowPath)): [NSNull null]}.mutableCopy; if ([self isKindOfClass:[CAShapeLayer class]]) { [actions addEntriesFromDictionary:@{NSStringFromSelector(@selector(path)): [NSNull null], NSStringFromSelector(@selector(fillColor)): [NSNull null], NSStringFromSelector(@selector(strokeColor)): [NSNull null], NSStringFromSelector(@selector(strokeStart)): [NSNull null], NSStringFromSelector(@selector(strokeEnd)): [NSNull null], NSStringFromSelector(@selector(lineWidth)): [NSNull null], NSStringFromSelector(@selector(miterLimit)): [NSNull null], NSStringFromSelector(@selector(lineDashPhase)): [NSNull null]}]; } if ([self isKindOfClass:[CAGradientLayer class]]) { [actions addEntriesFromDictionary:@{NSStringFromSelector(@selector(colors)): [NSNull null], NSStringFromSelector(@selector(locations)): [NSNull null], NSStringFromSelector(@selector(startPoint)): [NSNull null], NSStringFromSelector(@selector(endPoint)): [NSNull null]}]; } self.actions = actions; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Category/NSArray+Lookin.h ================================================ // // NSArray+Lookin.h // Lookin // // Created by Li Kai on 2018/9/3. // https://lookin.work // #import "LookinDefines.h" #import #import @interface NSArray<__covariant ValueType> (Lookin) /** 初始化一个新的 NSArray 并返回,新数组的长度为 count,如果当前数组长度比 count 小则会补充新元素(被补充的元素由 addBlock 返回),如果当前数组长度比 count 大则会舍弃多余的元素,被舍弃的元素会作为参数传入 removeBlock。最终,新数组的所有元素均会作为参数被传入 doBlock。 */ - (NSArray *)lookin_resizeWithCount:(NSUInteger)count add:(ValueType (^)(NSUInteger idx))addBlock remove:(void (^)(NSUInteger idx, ValueType obj))removeBlock doNext:(void (^)(NSUInteger idx, ValueType obj))doBlock __attribute__((warn_unused_result)); + (NSArray *)lookin_arrayWithCount:(NSUInteger)count block:(id (^)(NSUInteger idx))block; /** 检查 index 位置是否有元素存在 */ - (BOOL)lookin_hasIndex:(NSInteger)index; - (NSArray *)lookin_map:(id (^)(NSUInteger idx, ValueType value))block; - (NSArray *)lookin_filter:(BOOL (^)( ValueType obj))block; - (ValueType)lookin_firstFiltered:(BOOL (^)(ValueType obj))block; /// 返回最后一个 block 返回 YES 的元素 - (ValueType)lookin_lastFiltered:(BOOL (^)(ValueType obj))block; - (id)lookin_reduce:(id (^)(id accumulator, NSUInteger idx, ValueType obj))block; - (CGFloat)lookin_reduceCGFloat:(CGFloat (^)(CGFloat accumulator, NSUInteger idx, ValueType obj))block initialAccumlator:(CGFloat)initialAccumlator; - (NSInteger)lookin_reduceInteger:(NSInteger (^)(NSInteger accumulator, NSUInteger idx, ValueType obj))block initialAccumlator:(NSInteger)initialAccumlator; - (BOOL)lookin_all:(BOOL (^)(ValueType obj))block; - (BOOL)lookin_any:(BOOL (^)(ValueType obj))block; - (NSArray *)lookin_arrayByRemovingObject:(ValueType)obj; - (NSArray *)lookin_nonredundantArray; - (ValueType)lookin_safeObjectAtIndex:(NSInteger)idx; /// 字符串长度从短到长,即 length 小的字符串的 idx 更小 - (NSArray *)lookin_sortedArrayByStringLength; @end @interface NSMutableArray (Lookin) /** 如果当前数组长度比 count 小则会补充新元素(被补充的元素由 addBlock 返回),如果当前数组长度比 count 大则多余的元素会被作为参数传入 notDequeued。然后从 idx 为 0 算起,前 count 个元素会被作为参数传入 doBlock */ - (void)lookin_dequeueWithCount:(NSUInteger)count add:(ValueType (^)(NSUInteger idx))addBlock notDequeued:(void (^)(NSUInteger idx, ValueType obj))notDequeuedBlock doNext:(void (^)(NSUInteger idx, ValueType obj))doBlock; - (void)lookin_removeObjectsPassingTest:(BOOL (^)(NSUInteger idx, ValueType obj))block; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Category/NSArray+Lookin.m ================================================ // // NSArray+Lookin.m // Lookin // // Created by Li Kai on 2018/9/3. // https://lookin.work // #import "NSArray+Lookin.h" @implementation NSArray (Lookin) - (NSArray *)lookin_resizeWithCount:(NSUInteger)count add:(id (^)(NSUInteger idx))addBlock remove:(void (^)(NSUInteger idx, id obj))removeBlock doNext:(void (^)(NSUInteger idx, id obj))doBlock { NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:count]; for (NSUInteger i = 0; i < count; i++) { if (self.count > i) { id obj = [self objectAtIndex:i]; [resultArray addObject:obj]; if (doBlock) { doBlock(i, obj); } } else { if (addBlock) { id newObj = addBlock(i); if (newObj) { [resultArray addObject:newObj]; if (doBlock) { doBlock(i, newObj); } } else { NSAssert(NO, @""); } } else { NSAssert(NO, @""); } } } if (removeBlock) { if (self.count > count) { for (NSUInteger i = count; i < self.count; i++) { id obj = [self objectAtIndex:i]; removeBlock(i, obj); } } } return [resultArray copy]; } + (NSArray *)lookin_arrayWithCount:(NSUInteger)count block:(id (^)(NSUInteger idx))block { NSMutableArray *array = [NSMutableArray arrayWithCapacity:count]; for (NSUInteger i = 0; i < count; i++) { id obj = block(i); if (obj) { [array addObject:obj]; } } return [array copy]; } - (BOOL)lookin_hasIndex:(NSInteger)index { if (index == NSNotFound || index < 0) { return NO; } return self.count > index; } - (NSArray *)lookin_map:(id (^)(NSUInteger , id))block { if (!block) { NSAssert(NO, @""); return nil; } NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:self.count]; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { id newObj = block(idx, obj); if (newObj) { [array addObject:newObj]; } }]; return [array copy]; } - (NSArray *)lookin_filter:(BOOL (^)(id obj))block { if (!block) { NSAssert(NO, @""); return nil; } NSMutableArray *mArray = [NSMutableArray array]; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (block(obj)) { [mArray addObject:obj]; } }]; return [mArray copy]; } - (id)lookin_firstFiltered:(BOOL (^)(id obj))block { if (!block) { NSAssert(NO, @""); return nil; } __block id targetObj = nil; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (block(obj)) { targetObj = obj; *stop = YES; } }]; return targetObj; } - (id)lookin_lastFiltered:(BOOL (^)(id obj))block { if (!block) { NSAssert(NO, @""); return nil; } __block id targetObj = nil; [self enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (block(obj)) { targetObj = obj; *stop = YES; } }]; return targetObj; } - (id)lookin_reduce:(id (^)(id accumulator, NSUInteger idx, id obj))block { if (!block) { NSAssert(NO, @""); return nil; } __block id accumulator = nil; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { accumulator = block(accumulator, idx, obj); }]; return accumulator; } - (CGFloat)lookin_reduceCGFloat:(CGFloat (^)(CGFloat accumulator, NSUInteger idx, id obj))block initialAccumlator:(CGFloat)initialAccumlator { if (!block) { NSAssert(NO, @""); return initialAccumlator; } __block CGFloat accumulator = initialAccumlator; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { accumulator = block(accumulator, idx, obj); }]; return accumulator; } - (NSInteger)lookin_reduceInteger:(NSInteger (^)(NSInteger, NSUInteger, id))block initialAccumlator:(NSInteger)initialAccumlator { if (!block) { NSAssert(NO, @""); return initialAccumlator; } __block NSInteger accumulator = initialAccumlator; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { accumulator = block(accumulator, idx, obj); }]; return accumulator; } - (BOOL)lookin_all:(BOOL (^)(id obj))block { if (!block) { NSAssert(NO, @""); return NO; } __block BOOL allPass = YES; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { BOOL boolValue = block(obj); if (!boolValue) { allPass = NO; *stop = YES; } }]; return allPass; } - (BOOL)lookin_any:(BOOL (^)(id obj))block { if (!block) { NSAssert(NO, @""); return NO; } __block BOOL anyPass = NO; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { BOOL boolValue = block(obj); if (boolValue) { anyPass = YES; *stop = YES; } }]; return anyPass; } - (NSArray *)lookin_arrayByRemovingObject:(id)obj { if (!obj || ![self containsObject:obj]) { return self; } NSMutableArray *mutableArray = self.mutableCopy; [mutableArray removeObject:obj]; return mutableArray.copy; } - (NSArray *)lookin_nonredundantArray { NSSet *set = [NSSet setWithArray:self]; NSArray *newArray = [set allObjects]; return newArray; } - (id)lookin_safeObjectAtIndex:(NSInteger)idx { if (idx == NSNotFound || idx < 0) { return nil; } if (self.count <= idx) { return nil; } return [self objectAtIndex:idx]; } - (NSArray *)lookin_sortedArrayByStringLength { NSArray *sortedArray = [self sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) { if (obj1.length > obj2.length) { return NSOrderedDescending; } else if (obj1.length == obj2.length) { return NSOrderedSame; } else { return NSOrderedAscending; } }]; return sortedArray; } @end @implementation NSMutableArray (Lookin) - (void)lookin_dequeueWithCount:(NSUInteger)count add:(id (^)(NSUInteger idx))addBlock notDequeued:(void (^)(NSUInteger idx, id obj))notDequeuedBlock doNext:(void (^)(NSUInteger idx, id obj))doBlock { for (NSUInteger i = 0; i < count; i++) { if ([self lookin_hasIndex:i]) { id obj = [self objectAtIndex:i]; if (doBlock) { doBlock(i, obj); } } else { if (addBlock) { id newObj = addBlock(i); if (newObj) { [self addObject:newObj]; if (doBlock) { doBlock(i, newObj); } } else { NSAssert(NO, @""); } } else { NSAssert(NO, @""); } } } if (notDequeuedBlock) { if (self.count > count) { for (NSUInteger i = count; i < self.count; i++) { id obj = [self objectAtIndex:i]; notDequeuedBlock(i, obj); } } } } - (void)lookin_removeObjectsPassingTest:(BOOL (^)(NSUInteger idx, id obj))block { if (!block) { return; } NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; [self enumerateObjectsUsingBlock:^(id _Nonnull currentObj, NSUInteger idx, BOOL * _Nonnull stop) { BOOL boolValue = block(idx, currentObj); if (boolValue) { [indexSet addIndex:idx]; } }]; [self removeObjectsAtIndexes:indexSet]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Category/NSObject+Lookin.h ================================================ // // NSObject+Lookin.h // Lookin // // Created by Li Kai on 2018/12/22. // https://lookin.work // #import "LookinDefines.h" #import #import "LookinCodingValueType.h" @interface NSObject (Lookin) #pragma mark - Data Bind /** 给对象绑定上另一个对象以供后续取出使用,如果 object 传入 nil 则会清除该 key 之前绑定的对象 @attention 被绑定的对象会被 strong 强引用 @note 内部是使用 objc_setAssociatedObject / objc_getAssociatedObject 来实现 @code - (UITableViewCell *)cellForIndexPath:(NSIndexPath *)indexPath { // 1)在这里给 button 绑定上 indexPath 对象 [cell lookin_bindObject:indexPath forKey:@"indexPath"]; } - (void)didTapButton:(UIButton *)button { // 2)在这里取出被点击的 button 的 indexPath 对象 NSIndexPath *indexPathTapped = [button lookin_getBindObjectForKey:@"indexPath"]; } @endcode */ - (void)lookin_bindObject:(id)object forKey:(NSString *)key; /** 给对象绑定上另一个对象以供后续取出使用,但相比于 lookin_bindObject:forKey:,该方法不会 strong 强引用传入的 object */ - (void)lookin_bindObjectWeakly:(id)object forKey:(NSString *)key; /** 取出之前使用 bind 方法绑定的对象 */ - (id)lookin_getBindObjectForKey:(NSString *)key; /** 给对象绑定上一个 double 值以供后续取出使用 */ - (void)lookin_bindDouble:(double)doubleValue forKey:(NSString *)key; /** 取出之前用 lookin_bindDouble:forKey: 绑定的值 */ - (double)lookin_getBindDoubleForKey:(NSString *)key; /** 给对象绑定上一个 BOOL 值以供后续取出使用 */ - (void)lookin_bindBOOL:(BOOL)boolValue forKey:(NSString *)key; /** 取出之前用 lookin_bindBOOL:forKey: 绑定的值 */ - (BOOL)lookin_getBindBOOLForKey:(NSString *)key; /** 给对象绑定上一个 long 值以供后续取出使用 */ - (void)lookin_bindLong:(long)longValue forKey:(NSString *)key; /** 取出之前用 lookin_bindLong:forKey: 绑定的值 */ - (long)lookin_getBindLongForKey:(NSString *)key; /** 给对象绑定上一个 CGPoint 值以供后续取出使用 */ - (void)lookin_bindPoint:(CGPoint)pointValue forKey:(NSString *)key; /** 取出之前用 lookin_bindPoint:forKey: 绑定的值 */ - (CGPoint)lookin_getBindPointForKey:(NSString *)key; /** 移除之前使用 bind 方法绑定的对象 */ - (void)lookin_clearBindForKey:(NSString *)key; @end @interface NSObject (Lookin_Coding) /// 会把 NSImage/UIImage 转换为 NSData,把 NSColor/UIColor 转换回 NSNumber 数组(rgba) - (id)lookin_encodedObjectWithType:(LookinCodingValueType)type; /// 会把 NSData 转换回 NSImage/UIImage,把 NSNumber 数组(rgba) 转换为 NSColor/UIColor - (id)lookin_decodedObjectWithType:(LookinCodingValueType)type; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Category/NSObject+Lookin.m ================================================ // // NSObject+Lookin.m // Lookin // // Created by Li Kai on 2018/12/22. // https://lookin.work // #import "NSObject+Lookin.h" #import #import "TargetConditionals.h" #import "LookinWeakContainer.h" @implementation NSObject (Lookin) #pragma mark - Data Bind static char kAssociatedObjectKey_LookinAllBindObjects; - (NSMutableDictionary *)lookin_allBindObjects { NSMutableDictionary *dict = objc_getAssociatedObject(self, &kAssociatedObjectKey_LookinAllBindObjects); if (!dict) { dict = [NSMutableDictionary dictionary]; objc_setAssociatedObject(self, &kAssociatedObjectKey_LookinAllBindObjects, dict, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return dict; } - (void)lookin_bindObject:(id)object forKey:(NSString *)key { if (!key.length) { NSAssert(NO, @""); return; } @synchronized (self) { if (object) { [[self lookin_allBindObjects] setObject:object forKey:key]; } else { [[self lookin_allBindObjects] removeObjectForKey:key]; } } } - (id)lookin_getBindObjectForKey:(NSString *)key { if (!key.length) { NSAssert(NO, @""); return nil; } @synchronized (self) { id storedObj = [[self lookin_allBindObjects] objectForKey:key]; if ([storedObj isKindOfClass:[LookinWeakContainer class]]) { storedObj = [(LookinWeakContainer *)storedObj object]; } return storedObj; } } - (void)lookin_bindObjectWeakly:(id)object forKey:(NSString *)key { if (!key.length) { NSAssert(NO, @""); return; } if (object) { LookinWeakContainer *container = [[LookinWeakContainer alloc] init]; container.object = object; [self lookin_bindObject:container forKey:key]; } else { [self lookin_bindObject:nil forKey:key]; } } - (void)lookin_bindDouble:(double)doubleValue forKey:(NSString *)key { [self lookin_bindObject:@(doubleValue) forKey:key]; } - (double)lookin_getBindDoubleForKey:(NSString *)key { id object = [self lookin_getBindObjectForKey:key]; if ([object isKindOfClass:[NSNumber class]]) { double doubleValue = [(NSNumber *)object doubleValue]; return doubleValue; } else { return 0.0; } } - (void)lookin_bindBOOL:(BOOL)boolValue forKey:(NSString *)key { [self lookin_bindObject:@(boolValue) forKey:key]; } - (BOOL)lookin_getBindBOOLForKey:(NSString *)key { id object = [self lookin_getBindObjectForKey:key]; if ([object isKindOfClass:[NSNumber class]]) { BOOL boolValue = [(NSNumber *)object boolValue]; return boolValue; } else { return NO; } } - (void)lookin_bindLong:(long)longValue forKey:(NSString *)key { [self lookin_bindObject:@(longValue) forKey:key]; } - (long)lookin_getBindLongForKey:(NSString *)key { id object = [self lookin_getBindObjectForKey:key]; if ([object isKindOfClass:[NSNumber class]]) { long longValue = [(NSNumber *)object longValue]; return longValue; } else { return 0; } } - (void)lookin_bindPoint:(CGPoint)pointValue forKey:(NSString *)key { #if TARGET_OS_IPHONE [self lookin_bindObject:[NSValue valueWithCGPoint:pointValue] forKey:key]; #elif TARGET_OS_MAC NSPoint nsPoint = NSMakePoint(pointValue.x, pointValue.y); [self lookin_bindObject:[NSValue valueWithPoint:nsPoint] forKey:key]; #endif } - (CGPoint)lookin_getBindPointForKey:(NSString *)key { id object = [self lookin_getBindObjectForKey:key]; if ([object isKindOfClass:[NSValue class]]) { #if TARGET_OS_IPHONE CGPoint pointValue = [(NSValue *)object CGPointValue]; #elif TARGET_OS_MAC NSPoint nsPointValue = [(NSValue *)object pointValue]; CGPoint pointValue = CGPointMake(nsPointValue.x, nsPointValue.y); #endif return pointValue; } else { return CGPointZero; } } - (void)lookin_clearBindForKey:(NSString *)key { [self lookin_bindObject:nil forKey:key]; } @end @implementation NSObject (Lookin_Coding) - (id)lookin_encodedObjectWithType:(LookinCodingValueType)type { if (type == LookinCodingValueTypeColor) { if ([self isKindOfClass:[LookinColor class]]) { CGFloat r, g, b, a; #if TARGET_OS_IPHONE CGFloat white; if ([(UIColor *)self getRed:&r green:&g blue:&b alpha:&a]) { // valid } else if ([(UIColor *)self getWhite:&white alpha:&a]) { r = white; g = white; b = white; } else { NSAssert(NO, @""); r = 0; g = 0; b = 0; a = 0; } #elif TARGET_OS_MAC NSColor *color = [((NSColor *)self) colorUsingColorSpace:NSColorSpace.sRGBColorSpace]; [color getRed:&r green:&g blue:&b alpha:&a]; #endif NSArray *rgba = @[@(r), @(g), @(b), @(a)]; return rgba; } else { NSAssert(NO, @""); return nil; } } else if (type == LookinCodingValueTypeImage) { #if TARGET_OS_IPHONE if ([self isKindOfClass:[UIImage class]]) { UIImage *image = (UIImage *)self; return UIImagePNGRepresentation(image); } else { NSAssert(NO, @""); return nil; } #elif TARGET_OS_MAC if ([self isKindOfClass:[NSImage class]]) { NSImage *image = (NSImage *)self; return [image TIFFRepresentation]; } else { NSAssert(NO, @""); return nil; } #endif } else { return self; } } - (id)lookin_decodedObjectWithType:(LookinCodingValueType)type { if (type == LookinCodingValueTypeColor) { if ([self isKindOfClass:[NSArray class]]) { NSArray *rgba = (NSArray *)self; CGFloat r = [rgba[0] doubleValue]; CGFloat g = [rgba[1] doubleValue]; CGFloat b = [rgba[2] doubleValue]; CGFloat a = [rgba[3] doubleValue]; LookinColor *color = [LookinColor colorWithRed:r green:g blue:b alpha:a]; return color; } else { NSAssert(NO, @""); return nil; } } else if (type == LookinCodingValueTypeImage) { if ([self isKindOfClass:[NSData class]]) { LookinImage *image = [[LookinImage alloc] initWithData:(NSData *)self]; return image; } else { NSAssert(NO, @""); return nil; } } else { return self; } } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Category/NSSet+Lookin.h ================================================ // // NSSet+Lookin.h // Lookin // // Created by Li Kai on 2019/1/13. // https://lookin.work // #import "LookinDefines.h" #import "TargetConditionals.h" #if TARGET_OS_IPHONE #import #elif TARGET_OS_MAC #import #endif @interface NSSet<__covariant ValueType> (Lookin) - (NSSet *)lookin_map:(id (^)(ValueType obj))block; - (ValueType)lookin_firstFiltered:(BOOL (^)(ValueType obj))block; - (NSSet *)lookin_filter:(BOOL (^)(ValueType obj))block; /** 是否有任何一个元素满足某条件 @note 元素将被依次传入 block 里,如果任何一个 block 返回 YES,则该方法返回 YES。如果所有 block 均返回 NO,则该方法返回 NO。 */ - (BOOL)lookin_any:(BOOL (^)(ValueType obj))block; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Category/NSSet+Lookin.m ================================================ // // NSSet+Lookin.m // Lookin // // Created by Li Kai on 2019/1/13. // https://lookin.work // #import "NSSet+Lookin.h" @implementation NSSet (Lookin) - (NSSet *)lookin_map:(id (^)(id obj))block { if (!block) { NSAssert(NO, @""); return nil; } NSMutableSet *newSet = [NSMutableSet setWithCapacity:self.count]; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) { id newObj = block(obj); if (newObj) { [newSet addObject:newObj]; } }]; return [newSet copy]; } - (id)lookin_firstFiltered:(BOOL (^)(id obj))block { if (!block) { NSAssert(NO, @""); return nil; } __block id targetObj = nil; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) { if (block(obj)) { targetObj = obj; *stop = YES; } }]; return targetObj; } - (NSSet *)lookin_filter:(BOOL (^)(id obj))block { if (!block) { NSAssert(NO, @""); return nil; } NSMutableSet *mSet = [NSMutableSet set]; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) { if (block(obj)) { [mSet addObject:obj]; } }]; return [mSet copy]; } - (BOOL)lookin_any:(BOOL (^)(id obj))block { if (!block) { NSAssert(NO, @""); return NO; } __block BOOL boolValue = NO; [self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) { if (block(obj)) { boolValue = YES; *stop = YES; } }]; return boolValue; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Category/NSString+Lookin.h ================================================ // // NSString+Lookin.h // Lookin // // Created by Li Kai on 2019/5/11. // https://lookin.work // #import "LookinDefines.h" #import @interface NSString (Lookin) /** 把 CGFloat 转成字符串,最多保留 3 位小数,转换后末尾的 0 会被删除 如:1.2341 => @"1.234", 2.1002 => @"2.1", 3.000 => @"3" */ + (NSString *)lookin_stringFromDouble:(double)doubleValue decimal:(NSUInteger)decimal; + (NSString *)lookin_stringFromRect:(CGRect)rect; + (NSString *)lookin_stringFromInset:(LookinInsets)insets; + (NSString *)lookin_stringFromSize:(CGSize)size; + (NSString *)lookin_stringFromPoint:(CGPoint)point; + (NSString *)lookin_rgbaStringFromColor:(LookinColor *)color; - (NSString *)lookin_safeInitWithUTF8String:(const char *)string; /// 在 swift 中,类名可能会被加前缀,比如 MyApp.MyView 或 _TtC5MyApp8TestView 这种,该方法会返回简化后的末尾的类名,比如 MyView - (NSString *)lookin_shortClassNameString; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Category/NSString+Lookin.m ================================================ // // NSString+Lookin.m // Lookin // // Created by Li Kai on 2019/5/11. // https://lookin.work // #import "NSString+Lookin.h" @implementation NSString (Lookin) + (NSString *)lookin_stringFromDouble:(double)doubleValue decimal:(NSUInteger)decimal { NSString *formatString = [NSString stringWithFormat:@"%%.%@f", @(decimal)]; NSString *string = [NSString stringWithFormat:formatString, doubleValue]; for (int i = 0; i < decimal; i++) { if ([[string substringFromIndex:string.length - 1] isEqualToString:@"0"]) { string = [string substringToIndex:string.length - 1]; } } if ([[string substringFromIndex:string.length - 1] isEqualToString:@"."]) { string = [string substringToIndex:string.length - 1]; } return string; } + (NSString *)lookin_stringFromInset:(LookinInsets)insets { return [NSString stringWithFormat:@"{%@, %@, %@, %@}", [NSString lookin_stringFromDouble:insets.top decimal:2], [NSString lookin_stringFromDouble:insets.left decimal:2], [NSString lookin_stringFromDouble:insets.bottom decimal:2], [NSString lookin_stringFromDouble:insets.right decimal:2]]; } + (NSString *)lookin_stringFromSize:(CGSize)size { return [NSString stringWithFormat:@"{%@, %@}", [NSString lookin_stringFromDouble:size.width decimal:2], [NSString lookin_stringFromDouble:size.height decimal:2]]; } + (NSString *)lookin_stringFromPoint:(CGPoint)point { return [NSString stringWithFormat:@"{%@, %@}", [NSString lookin_stringFromDouble:point.x decimal:2], [NSString lookin_stringFromDouble:point.y decimal:2]]; } + (NSString *)lookin_stringFromRect:(CGRect)rect { return [NSString stringWithFormat:@"{%@, %@, %@, %@}", [NSString lookin_stringFromDouble:rect.origin.x decimal:2], [NSString lookin_stringFromDouble:rect.origin.y decimal:2], [NSString lookin_stringFromDouble:rect.size.width decimal:2], [NSString lookin_stringFromDouble:rect.size.height decimal:2]]; } + (NSString *)lookin_rgbaStringFromColor:(LookinColor *)color { if (!color) { return @"nil"; } #if TARGET_OS_IPHONE UIColor *rgbColor = color; #elif TARGET_OS_MAC NSColor *rgbColor = [color colorUsingColorSpace:NSColorSpace.sRGBColorSpace]; #endif CGFloat r, g, b, a; [rgbColor getRed:&r green:&g blue:&b alpha:&a]; NSString *colorDesc; if (a >= 1) { colorDesc = [NSString stringWithFormat:@"(%.0f, %.0f, %.0f)", r * 255, g * 255, b * 255]; } else { colorDesc = [NSString stringWithFormat:@"(%.0f, %.0f, %.0f, %@)", r * 255, g * 255, b * 255, [NSString lookin_stringFromDouble:a decimal:2]]; } return colorDesc; } - (NSString *)lookin_safeInitWithUTF8String:(const char *)string { if (NULL != string) { return [self initWithUTF8String:string]; } return nil; } - (NSString *)lookin_shortClassNameString { if ([self containsString:@"."]) { // Swift 中,class 可能会被加前缀,比如 Weread.AvatarView,此时返回后半部分即可 NSString *shortName = [self componentsSeparatedByString:@"."].lastObject; return shortName; } if ([self hasPrefix:@"_Tt"]) { // Swift 中,private class 可能会被加前缀,比如 MyApp 里一个叫 TestView 的 private class,这里获取到的可能是:_TtC5MyAppP33_6BC07D230B8F25989003315CA1D8100B8TestView // 这里规则比较复杂,可以参考这里的讨论:https://stackoverflow.com/questions/24062957/swift-objective-c-runtime-class-naming // _Tt 开头则认为是这种类型 for (NSInteger i = self.length - 1; i >= 0; i--) { // 比如上面的注释里的例子,末尾的 TestView 即我们想截取的字符串,而它前面的那个数字 8 则是分隔符,8 在这里表示 TestView 字符串的长度是 8 // 这里是倒序从最后一个字母向前遍历,i 为当前被遍历到的字符串的 idx,比如遍历到 "_" 时则 i 为 0 // 当前已经被遍历到的末尾字符串的数量,比如第八次执行该循环时,enumeratedStringLength 为 8 NSInteger enumeratedStringLength = self.length - i; // 我们此刻检查下一次将会被遍历到的那一个(或两个)字符串是否是对应的数字 8,如果是,则认为当前被遍历过的字符串是我们要找的类名 // 要检查接下来多少位的字符串是否是数字,比如 enumeratedStringLength 为 8 时则检查接下来的 1 位字符串是否是 8,如果 enumeratedStringLength 为 12 则检查接下来的 2 位数字是否是 12 NSInteger numberLengthToCheck = 1; if (enumeratedStringLength >= 10) { numberLengthToCheck = 2; } else if (enumeratedStringLength >= 100) { numberLengthToCheck = 3; } if (i < numberLengthToCheck) { // 全部检查完了,失败,直接返回自身吧 return self; } NSRange checkRange = NSMakeRange(i - numberLengthToCheck, numberLengthToCheck); NSString *stringToCheck = [self substringWithRange:checkRange]; NSInteger scannedNumber = [stringToCheck integerValue]; if (scannedNumber == enumeratedStringLength) { // 比如我们已经遍历到了 “TextView”,而下一个字母确实就是数字 8,则这里我们认为找到了目标字符串 NSString *targetString = [self substringFromIndex:i]; return targetString; } // 继续遍历 } } return self; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAppInfo.h ================================================ // // LookinAppInfo.h // qmuidemo // // Created by Li Kai on 2018/11/3. // Copyright © 2018 QMUI Team. All rights reserved. // #import "LookinDefines.h" typedef NS_ENUM(NSInteger, LookinAppInfoDevice) { LookinAppInfoDeviceSimulator, // 模拟器 LookinAppInfoDeviceIPad, // iPad 真机 LookinAppInfoDeviceOthers // 应该视为 iPhone 真机 }; @interface LookinAppInfo : NSObject /// 每次启动 app 时都会随机生成一个 appInfoIdentifier 直到 app 被 kill 掉 @property(nonatomic, assign) NSUInteger appInfoIdentifier; /// mac 端应该先读取该属性,如果为 YES 则表示应该使用之前保存的旧 appInfo 对象即可 @property(nonatomic, assign) BOOL shouldUseCache; /// LookinServer 的版本 @property(nonatomic, assign) int serverVersion; /// app 的当前截图 @property(nonatomic, strong) LookinImage *screenshot; /// 可能为 nil,比如新建的 iOS 空项目 @property(nonatomic, strong) LookinImage *appIcon; /// @"微信读书" @property(nonatomic, copy) NSString *appName; /// hughkli.lookin @property(nonatomic, copy) NSString *appBundleIdentifier; /// @"iPhone X" @property(nonatomic, copy) NSString *deviceDescription; /// @"12.1" @property(nonatomic, copy) NSString *osDescription; /// 返回 os 的主版本号,比如 iOS 12.1 的设备将返回 12,iOS 13.2.1 的设备将返回 13 @property(nonatomic, assign) NSUInteger osMainVersion; /// 设备类型 @property(nonatomic, assign) LookinAppInfoDevice deviceType; /// 屏幕的宽度 @property(nonatomic, assign) double screenWidth; /// 屏幕的高度 @property(nonatomic, assign) double screenHeight; /// 是几倍的屏幕 @property(nonatomic, assign) double screenScale; - (BOOL)isEqualToAppInfo:(LookinAppInfo *)info; #if TARGET_OS_IPHONE + (LookinAppInfo *)currentInfoWithScreenshot:(BOOL)hasScreenshot icon:(BOOL)hasIcon localIdentifiers:(NSArray *)localIdentifiers; #else @property(nonatomic, assign) NSTimeInterval cachedTimestamp; #endif @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAppInfo.m ================================================ // // LookinAppInfo.m // qmuidemo // // Created by Li Kai on 2018/11/3. // Copyright © 2018 QMUI Team. All rights reserved. // #import "LookinAppInfo.h" static NSString * const CodingKey_AppIcon = @"1"; static NSString * const CodingKey_Screenshot = @"2"; static NSString * const CodingKey_DeviceDescription = @"3"; static NSString * const CodingKey_OsDescription = @"4"; static NSString * const CodingKey_AppName = @"5"; static NSString * const CodingKey_ScreenWidth = @"6"; static NSString * const CodingKey_ScreenHeight = @"7"; static NSString * const CodingKey_DeviceType = @"8"; @implementation LookinAppInfo - (id)copyWithZone:(NSZone *)zone { LookinAppInfo *newAppInfo = [[LookinAppInfo allocWithZone:zone] init]; newAppInfo.appIcon = self.appIcon; newAppInfo.appName = self.appName; newAppInfo.deviceDescription = self.deviceDescription; newAppInfo.osDescription = self.osDescription; newAppInfo.osMainVersion = self.osMainVersion; newAppInfo.deviceType = self.deviceType; newAppInfo.screenWidth = self.screenWidth; newAppInfo.screenHeight = self.screenHeight; newAppInfo.screenScale = self.screenScale; newAppInfo.appInfoIdentifier = self.appInfoIdentifier; return newAppInfo; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.serverVersion = [aDecoder decodeIntForKey:@"serverVersion"]; NSData *screenshotData = [aDecoder decodeObjectForKey:CodingKey_Screenshot]; self.screenshot = [[LookinImage alloc] initWithData:screenshotData]; NSData *appIconData = [aDecoder decodeObjectForKey:CodingKey_AppIcon]; self.appIcon = [[LookinImage alloc] initWithData:appIconData]; self.appName = [aDecoder decodeObjectForKey:CodingKey_AppName]; self.appBundleIdentifier = [aDecoder decodeObjectForKey:@"appBundleIdentifier"]; self.deviceDescription = [aDecoder decodeObjectForKey:CodingKey_DeviceDescription]; self.osDescription = [aDecoder decodeObjectForKey:CodingKey_OsDescription]; self.osMainVersion = [aDecoder decodeIntegerForKey:@"osMainVersion"]; self.deviceType = [aDecoder decodeIntegerForKey:CodingKey_DeviceType]; self.screenWidth = [aDecoder decodeDoubleForKey:CodingKey_ScreenWidth]; self.screenHeight = [aDecoder decodeDoubleForKey:CodingKey_ScreenHeight]; self.screenScale = [aDecoder decodeDoubleForKey:@"screenScale"]; self.appInfoIdentifier = [aDecoder decodeIntegerForKey:@"appInfoIdentifier"]; self.shouldUseCache = [aDecoder decodeBoolForKey:@"shouldUseCache"]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeInt:self.serverVersion forKey:@"serverVersion"]; #if TARGET_OS_IPHONE NSData *screenshotData = UIImagePNGRepresentation(self.screenshot); [aCoder encodeObject:screenshotData forKey:CodingKey_Screenshot]; NSData *appIconData = UIImagePNGRepresentation(self.appIcon); [aCoder encodeObject:appIconData forKey:CodingKey_AppIcon]; #elif TARGET_OS_MAC NSData *screenshotData = [self.screenshot TIFFRepresentation]; [aCoder encodeObject:screenshotData forKey:CodingKey_Screenshot]; NSData *appIconData = [self.appIcon TIFFRepresentation]; [aCoder encodeObject:appIconData forKey:CodingKey_AppIcon]; #endif [aCoder encodeObject:self.appName forKey:CodingKey_AppName]; [aCoder encodeObject:self.appBundleIdentifier forKey:@"appBundleIdentifier"]; [aCoder encodeObject:self.deviceDescription forKey:CodingKey_DeviceDescription]; [aCoder encodeObject:self.osDescription forKey:CodingKey_OsDescription]; [aCoder encodeInteger:self.osMainVersion forKey:@"osMainVersion"]; [aCoder encodeInteger:self.deviceType forKey:CodingKey_DeviceType]; [aCoder encodeDouble:self.screenWidth forKey:CodingKey_ScreenWidth]; [aCoder encodeDouble:self.screenHeight forKey:CodingKey_ScreenHeight]; [aCoder encodeDouble:self.screenScale forKey:@"screenScale"]; [aCoder encodeInteger:self.appInfoIdentifier forKey:@"appInfoIdentifier"]; [aCoder encodeBool:self.shouldUseCache forKey:@"shouldUseCache"]; } + (BOOL)supportsSecureCoding { return YES; } - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[LookinAppInfo class]]) { return NO; } if ([self isEqualToAppInfo:object]) { return YES; } return NO; } - (NSUInteger)hash { return self.appName.hash ^ self.deviceDescription.hash ^ self.osDescription.hash ^ self.deviceType; } - (BOOL)isEqualToAppInfo:(LookinAppInfo *)info { if (!info) { return NO; } if ([self.appName isEqualToString:info.appName] && [self.deviceDescription isEqualToString:info.deviceDescription] && [self.osDescription isEqualToString:info.osDescription] && self.deviceType == info.deviceType) { return YES; } return NO; } #if TARGET_OS_IPHONE + (LookinAppInfo *)currentInfoWithScreenshot:(BOOL)hasScreenshot icon:(BOOL)hasIcon localIdentifiers:(NSArray *)localIdentifiers { NSInteger selfIdentifier = [self getAppInfoIdentifier]; if ([localIdentifiers containsObject:@(selfIdentifier)]) { LookinAppInfo *info = [LookinAppInfo new]; info.appInfoIdentifier = selfIdentifier; info.shouldUseCache = YES; return info; } LookinAppInfo *info = [[LookinAppInfo alloc] init]; info.appInfoIdentifier = selfIdentifier; info.appName = [self appName]; info.deviceDescription = [UIDevice currentDevice].name; info.appBundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; if ([self isSimulator]) { info.deviceType = LookinAppInfoDeviceSimulator; } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { info.deviceType = LookinAppInfoDeviceIPad; } else { info.deviceType = LookinAppInfoDeviceOthers; } info.osDescription = [UIDevice currentDevice].systemVersion; NSString *mainVersionStr = [[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:@"."].firstObject; info.osMainVersion = [mainVersionStr integerValue]; CGSize screenSize = [UIScreen mainScreen].bounds.size; info.screenWidth = screenSize.width; info.screenHeight = screenSize.height; info.screenScale = [UIScreen mainScreen].scale; if (hasScreenshot) { info.screenshot = [self screenshotImage]; } if (hasIcon) { info.appIcon = [self appIcon]; } return info; } + (NSString *)appName { NSDictionary *info = [[NSBundle mainBundle] infoDictionary]; NSString *displayName = [info objectForKey:@"CFBundleDisplayName"]; NSString *name = [info objectForKey:@"CFBundleName"]; return displayName.length ? displayName : name; } + (UIImage *)appIcon { #if TARGET_OS_TV return nil; #else NSString *imageName = [[[[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIcons"] objectForKey:@"CFBundlePrimaryIcon"] objectForKey:@"CFBundleIconFiles"] lastObject]; if (!imageName.length) { // 正常情况下拿到的 name 可能比如 “AppIcon60x60”。但某些情况可能为 nil,此时直接 return 否则 [UIImage imageNamed:nil] 可能导致 console 报 "CUICatalog: Invalid asset name supplied: '(null)'" 的错误信息 return nil; } return [UIImage imageNamed:imageName]; #endif } + (UIImage *)screenshotImage { UIWindow *window = [UIApplication sharedApplication].keyWindow; if (!window) { return nil; } UIGraphicsBeginImageContextWithOptions(window.bounds.size, YES, 0.4); [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } + (BOOL)isSimulator { if (TARGET_OS_SIMULATOR) { return YES; } return NO; } #endif + (NSInteger)getAppInfoIdentifier { static dispatch_once_t onceToken; static NSInteger identifier = 0; dispatch_once(&onceToken,^{ identifier = [[NSDate date] timeIntervalSince1970]; }); return identifier; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttrIdentifiers.h ================================================ // // LookinAttrIdentifiers.h // Lookin // // Created by Li Kai on 2019/9/18. // https://lookin.work // #import #pragma mark - Group typedef NSString * LookinAttrGroupIdentifier; extern LookinAttrGroupIdentifier const LookinAttrGroup_None; extern LookinAttrGroupIdentifier const LookinAttrGroup_Class; extern LookinAttrGroupIdentifier const LookinAttrGroup_Relation; extern LookinAttrGroupIdentifier const LookinAttrGroup_Layout; extern LookinAttrGroupIdentifier const LookinAttrGroup_AutoLayout; extern LookinAttrGroupIdentifier const LookinAttrGroup_ViewLayer; extern LookinAttrGroupIdentifier const LookinAttrGroup_UIImageView; extern LookinAttrGroupIdentifier const LookinAttrGroup_UILabel; extern LookinAttrGroupIdentifier const LookinAttrGroup_UIControl; extern LookinAttrGroupIdentifier const LookinAttrGroup_UIButton; extern LookinAttrGroupIdentifier const LookinAttrGroup_UIScrollView; extern LookinAttrGroupIdentifier const LookinAttrGroup_UITableView; extern LookinAttrGroupIdentifier const LookinAttrGroup_UITextView; extern LookinAttrGroupIdentifier const LookinAttrGroup_UITextField; extern LookinAttrGroupIdentifier const LookinAttrGroup_UIVisualEffectView; #pragma mark - Section typedef NSString * LookinAttrSectionIdentifier; extern LookinAttrSectionIdentifier const LookinAttrSec_None; extern LookinAttrSectionIdentifier const LookinAttrSec_Class_Class; extern LookinAttrSectionIdentifier const LookinAttrSec_Relation_Relation; extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_Frame; extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_Bounds; extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_SafeArea; extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_Position; extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_AnchorPoint; extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Hugging; extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Resistance; extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Constraints; extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_IntrinsicSize; extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Visibility; extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_InterationAndMasks; extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Corner; extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_BgColor; extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Border; extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Shadow; extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_ContentMode; extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_TintColor; extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Tag; extern LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Name; extern LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Open; extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Text; extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Font; extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_NumberOfLines; extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_TextColor; extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_BreakMode; extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Alignment; extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_CanAdjustFont; extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_EnabledSelected; extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_VerAlignment; extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_HorAlignment; extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_QMUIOutsideEdge; extern LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ContentInsets; extern LookinAttrSectionIdentifier const LookinAttrSec_UIButton_TitleInsets; extern LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ImageInsets; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentInset; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_AdjustedInset; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_IndicatorInset; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Offset; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentSize; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Behavior; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ShowsIndicator; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Bounce; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ScrollPaging; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentTouches; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Zoom; extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_QMUIInitialInset; extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_Style; extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SectionsNumber; extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_RowsNumber; extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorStyle; extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorColor; extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorInset; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Basic; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Text; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Font; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_TextColor; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Alignment; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_ContainerInset; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Text; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Placeholder; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Font; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_TextColor; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Alignment; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Clears; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_CanAdjustFont; extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_ClearButtonMode; extern LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_Style; extern LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_QMUIForegroundColor; #pragma mark - Attr typedef NSString * LookinAttrIdentifier; extern LookinAttrIdentifier const LookinAttr_None; extern LookinAttrIdentifier const LookinAttr_Class_Class_Class; extern LookinAttrIdentifier const LookinAttr_Relation_Relation_Relation; extern LookinAttrIdentifier const LookinAttr_Layout_Frame_Frame; extern LookinAttrIdentifier const LookinAttr_Layout_Bounds_Bounds; extern LookinAttrIdentifier const LookinAttr_Layout_SafeArea_SafeArea; extern LookinAttrIdentifier const LookinAttr_Layout_Position_Position; extern LookinAttrIdentifier const LookinAttr_Layout_AnchorPoint_AnchorPoint; extern LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Hor; extern LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Ver; extern LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Hor; extern LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Ver; extern LookinAttrIdentifier const LookinAttr_AutoLayout_Constraints_Constraints; extern LookinAttrIdentifier const LookinAttr_AutoLayout_IntrinsicSize_Size; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Hidden; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Opacity; extern LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_Interaction; extern LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_MasksToBounds; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Corner_Radius; extern LookinAttrIdentifier const LookinAttr_ViewLayer_BgColor_BgColor; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Color; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Width; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Color; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Opacity; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Radius; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetW; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetH; extern LookinAttrIdentifier const LookinAttr_ViewLayer_ContentMode_Mode; extern LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Color; extern LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Mode; extern LookinAttrIdentifier const LookinAttr_ViewLayer_Tag_Tag; extern LookinAttrIdentifier const LookinAttr_UIImageView_Name_Name; extern LookinAttrIdentifier const LookinAttr_UIImageView_Open_Open; extern LookinAttrIdentifier const LookinAttr_UILabel_Text_Text; extern LookinAttrIdentifier const LookinAttr_UILabel_Font_Name; extern LookinAttrIdentifier const LookinAttr_UILabel_Font_Size; extern LookinAttrIdentifier const LookinAttr_UILabel_NumberOfLines_NumberOfLines; extern LookinAttrIdentifier const LookinAttr_UILabel_TextColor_Color; extern LookinAttrIdentifier const LookinAttr_UILabel_Alignment_Alignment; extern LookinAttrIdentifier const LookinAttr_UILabel_BreakMode_Mode; extern LookinAttrIdentifier const LookinAttr_UILabel_CanAdjustFont_CanAdjustFont; extern LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Enabled; extern LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Selected; extern LookinAttrIdentifier const LookinAttr_UIControl_VerAlignment_Alignment; extern LookinAttrIdentifier const LookinAttr_UIControl_HorAlignment_Alignment; extern LookinAttrIdentifier const LookinAttr_UIControl_QMUIOutsideEdge_Edge; extern LookinAttrIdentifier const LookinAttr_UIButton_ContentInsets_Insets; extern LookinAttrIdentifier const LookinAttr_UIButton_TitleInsets_Insets; extern LookinAttrIdentifier const LookinAttr_UIButton_ImageInsets_Insets; extern LookinAttrIdentifier const LookinAttr_UIScrollView_Offset_Offset; extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentSize_Size; extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentInset_Inset; extern LookinAttrIdentifier const LookinAttr_UIScrollView_AdjustedInset_Inset; extern LookinAttrIdentifier const LookinAttr_UIScrollView_Behavior_Behavior; extern LookinAttrIdentifier const LookinAttr_UIScrollView_IndicatorInset_Inset; extern LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_ScrollEnabled; extern LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_PagingEnabled; extern LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Ver; extern LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Hor; extern LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Hor; extern LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Ver; extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_Delay; extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_CanCancel; extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MinScale; extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MaxScale; extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Scale; extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Bounce; extern LookinAttrIdentifier const LookinAttr_UIScrollView_QMUIInitialInset_Inset; extern LookinAttrIdentifier const LookinAttr_UITableView_Style_Style; extern LookinAttrIdentifier const LookinAttr_UITableView_SectionsNumber_Number; extern LookinAttrIdentifier const LookinAttr_UITableView_RowsNumber_Number; extern LookinAttrIdentifier const LookinAttr_UITableView_SeparatorInset_Inset; extern LookinAttrIdentifier const LookinAttr_UITableView_SeparatorColor_Color; extern LookinAttrIdentifier const LookinAttr_UITableView_SeparatorStyle_Style; extern LookinAttrIdentifier const LookinAttr_UITextView_Font_Name; extern LookinAttrIdentifier const LookinAttr_UITextView_Font_Size; extern LookinAttrIdentifier const LookinAttr_UITextView_Basic_Editable; extern LookinAttrIdentifier const LookinAttr_UITextView_Basic_Selectable; extern LookinAttrIdentifier const LookinAttr_UITextView_Text_Text; extern LookinAttrIdentifier const LookinAttr_UITextView_TextColor_Color; extern LookinAttrIdentifier const LookinAttr_UITextView_Alignment_Alignment; extern LookinAttrIdentifier const LookinAttr_UITextView_ContainerInset_Inset; extern LookinAttrIdentifier const LookinAttr_UITextField_Text_Text; extern LookinAttrIdentifier const LookinAttr_UITextField_Placeholder_Placeholder; extern LookinAttrIdentifier const LookinAttr_UITextField_Font_Name; extern LookinAttrIdentifier const LookinAttr_UITextField_Font_Size; extern LookinAttrIdentifier const LookinAttr_UITextField_TextColor_Color; extern LookinAttrIdentifier const LookinAttr_UITextField_Alignment_Alignment; extern LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnBeginEditing; extern LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnInsertion; extern LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_CanAdjustFont; extern LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_MinSize; extern LookinAttrIdentifier const LookinAttr_UITextField_ClearButtonMode_Mode; extern LookinAttrIdentifier const LookinAttr_UIVisualEffectView_Style_Style; extern LookinAttrIdentifier const LookinAttr_UIVisualEffectView_QMUIForegroundColor_Color; ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttrIdentifiers.m ================================================ // // LookinAttrIdentifiers.m // Lookin // // Created by Li Kai on 2019/9/18. // https://lookin.work // #import "LookinAttrIdentifiers.h" // value 不能重复(AppDelegate 里的 runTests 有相关 test) // 如果要去掉某一项可以考虑注释掉而非直接删除,以防止新项和旧项的 value 相同而引发 preference 错乱(这些 value 会被存储到 userDefaults 里) #pragma mark - Group LookinAttrGroupIdentifier const LookinAttrGroup_None = @"n"; LookinAttrGroupIdentifier const LookinAttrGroup_Class = @"c"; LookinAttrGroupIdentifier const LookinAttrGroup_Relation = @"r"; LookinAttrGroupIdentifier const LookinAttrGroup_Layout = @"l"; LookinAttrGroupIdentifier const LookinAttrGroup_AutoLayout = @"a"; LookinAttrGroupIdentifier const LookinAttrGroup_ViewLayer = @"vl"; LookinAttrGroupIdentifier const LookinAttrGroup_UIImageView = @"i"; LookinAttrGroupIdentifier const LookinAttrGroup_UILabel = @"la"; LookinAttrGroupIdentifier const LookinAttrGroup_UIControl = @"co"; LookinAttrGroupIdentifier const LookinAttrGroup_UIButton = @"b"; LookinAttrGroupIdentifier const LookinAttrGroup_UIScrollView = @"s"; LookinAttrGroupIdentifier const LookinAttrGroup_UITableView = @"ta"; LookinAttrGroupIdentifier const LookinAttrGroup_UITextView = @"te"; LookinAttrGroupIdentifier const LookinAttrGroup_UITextField = @"tf"; LookinAttrGroupIdentifier const LookinAttrGroup_UIVisualEffectView = @"ve"; #pragma mark - Section LookinAttrSectionIdentifier const LookinAttrSec_None = @"n"; LookinAttrSectionIdentifier const LookinAttrSec_Class_Class = @"cl_c"; LookinAttrSectionIdentifier const LookinAttrSec_Relation_Relation = @"r_r"; LookinAttrSectionIdentifier const LookinAttrSec_Layout_Frame = @"l_f"; LookinAttrSectionIdentifier const LookinAttrSec_Layout_Bounds = @"l_b"; LookinAttrSectionIdentifier const LookinAttrSec_Layout_SafeArea = @"l_s"; LookinAttrSectionIdentifier const LookinAttrSec_Layout_Position = @"l_p"; LookinAttrSectionIdentifier const LookinAttrSec_Layout_AnchorPoint = @"l_a"; LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Hugging = @"a_h"; LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Resistance = @"a_r"; LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Constraints = @"a_c"; LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_IntrinsicSize = @"a_i"; LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Visibility = @"v_v"; LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_InterationAndMasks = @"v_i"; LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Corner = @"v_c"; LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_BgColor = @"v_b"; LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Border = @"v_bo"; LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Shadow = @"v_s"; LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_ContentMode = @"v_co"; LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_TintColor = @"v_t"; LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Tag = @"v_ta"; LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Name = @"i_n"; LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Open = @"i_o"; LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Text = @"lb_t"; LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Font = @"lb_f"; LookinAttrSectionIdentifier const LookinAttrSec_UILabel_NumberOfLines = @"lb_n"; LookinAttrSectionIdentifier const LookinAttrSec_UILabel_TextColor = @"lb_tc"; LookinAttrSectionIdentifier const LookinAttrSec_UILabel_BreakMode = @"lb_b"; LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Alignment = @"lb_a"; LookinAttrSectionIdentifier const LookinAttrSec_UILabel_CanAdjustFont = @"lb_c"; LookinAttrSectionIdentifier const LookinAttrSec_UIControl_EnabledSelected = @"c_e"; LookinAttrSectionIdentifier const LookinAttrSec_UIControl_VerAlignment = @"c_v"; LookinAttrSectionIdentifier const LookinAttrSec_UIControl_HorAlignment = @"c_h"; LookinAttrSectionIdentifier const LookinAttrSec_UIControl_QMUIOutsideEdge = @"c_o"; LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ContentInsets = @"b_c"; LookinAttrSectionIdentifier const LookinAttrSec_UIButton_TitleInsets = @"b_t"; LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ImageInsets = @"b_i"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentInset = @"s_c"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_AdjustedInset = @"s_a"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_IndicatorInset = @"s_i"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Offset = @"s_o"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentSize = @"s_cs"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Behavior = @"s_b"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ShowsIndicator = @"s_si"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Bounce = @"s_bo"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ScrollPaging = @"s_s"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentTouches = @"s_ct"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Zoom = @"s_z"; LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_QMUIInitialInset = @"s_ii"; LookinAttrSectionIdentifier const LookinAttrSec_UITableView_Style = @"t_s"; LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SectionsNumber = @"t_sn"; LookinAttrSectionIdentifier const LookinAttrSec_UITableView_RowsNumber = @"t_r"; LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorStyle = @"t_ss"; LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorColor = @"t_sc"; LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorInset = @"t_si"; LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Basic = @"tv_b"; LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Text = @"tv_t"; LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Font = @"tv_f"; LookinAttrSectionIdentifier const LookinAttrSec_UITextView_TextColor = @"tv_tc"; LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Alignment = @"tv_a"; LookinAttrSectionIdentifier const LookinAttrSec_UITextView_ContainerInset = @"tv_c"; LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Text = @"tf_t"; LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Placeholder = @"tf_p"; LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Font = @"tf_f"; LookinAttrSectionIdentifier const LookinAttrSec_UITextField_TextColor = @"tf_tc"; LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Alignment = @"tf_a"; LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Clears = @"tf_c"; LookinAttrSectionIdentifier const LookinAttrSec_UITextField_CanAdjustFont = @"tf_ca"; LookinAttrSectionIdentifier const LookinAttrSec_UITextField_ClearButtonMode = @"tf_cb"; LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_Style = @"ve_s"; LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_QMUIForegroundColor = @"ve_f"; #pragma mark - Attr LookinAttrIdentifier const LookinAttr_None = @"n"; LookinAttrIdentifier const LookinAttr_Class_Class_Class = @"c_c_c"; LookinAttrIdentifier const LookinAttr_Relation_Relation_Relation = @"r_r_r"; LookinAttrIdentifier const LookinAttr_Layout_Frame_Frame = @"l_f_f"; LookinAttrIdentifier const LookinAttr_Layout_Bounds_Bounds = @"l_b_b"; LookinAttrIdentifier const LookinAttr_Layout_SafeArea_SafeArea = @"l_s_s"; LookinAttrIdentifier const LookinAttr_Layout_Position_Position = @"l_p_p"; LookinAttrIdentifier const LookinAttr_Layout_AnchorPoint_AnchorPoint = @"l_a_a"; LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Hor = @"al_h_h"; LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Ver = @"al_h_v"; LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Hor = @"al_r_h"; LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Ver = @"al_r_v"; LookinAttrIdentifier const LookinAttr_AutoLayout_Constraints_Constraints = @"al_c_c"; LookinAttrIdentifier const LookinAttr_AutoLayout_IntrinsicSize_Size = @"cl_i_s"; LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Hidden = @"vl_v_h"; LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Opacity = @"vl_v_o"; LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_Interaction = @"vl_i_i"; LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_MasksToBounds = @"vl_i_m"; LookinAttrIdentifier const LookinAttr_ViewLayer_Corner_Radius = @"vl_c_r"; LookinAttrIdentifier const LookinAttr_ViewLayer_BgColor_BgColor = @"vl_b_b"; LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Color = @"vl_b_c"; LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Width = @"vl_b_w"; LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Color = @"vl_s_c"; LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Opacity = @"vl_s_o"; LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Radius = @"vl_s_r"; LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetW = @"vl_s_ow"; LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetH = @"vl_s_oh"; LookinAttrIdentifier const LookinAttr_ViewLayer_ContentMode_Mode = @"vl_c_m"; LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Color = @"vl_t_c"; LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Mode = @"vl_t_m"; LookinAttrIdentifier const LookinAttr_ViewLayer_Tag_Tag = @"vl_t_t"; LookinAttrIdentifier const LookinAttr_UIImageView_Name_Name = @"iv_n_n"; LookinAttrIdentifier const LookinAttr_UIImageView_Open_Open = @"iv_o_o"; LookinAttrIdentifier const LookinAttr_UILabel_Text_Text = @"lb_t_t"; LookinAttrIdentifier const LookinAttr_UILabel_Font_Name = @"lb_f_n"; LookinAttrIdentifier const LookinAttr_UILabel_Font_Size = @"lb_f_s"; LookinAttrIdentifier const LookinAttr_UILabel_NumberOfLines_NumberOfLines = @"lb_n_n"; LookinAttrIdentifier const LookinAttr_UILabel_TextColor_Color = @"lb_t_c"; LookinAttrIdentifier const LookinAttr_UILabel_Alignment_Alignment = @"lb_a_a"; LookinAttrIdentifier const LookinAttr_UILabel_BreakMode_Mode = @"lb_b_m"; LookinAttrIdentifier const LookinAttr_UILabel_CanAdjustFont_CanAdjustFont = @"lb_c_c"; LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Enabled = @"ct_e_e"; LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Selected = @"ct_e_s"; LookinAttrIdentifier const LookinAttr_UIControl_VerAlignment_Alignment = @"ct_v_a"; LookinAttrIdentifier const LookinAttr_UIControl_HorAlignment_Alignment = @"ct_h_a"; LookinAttrIdentifier const LookinAttr_UIControl_QMUIOutsideEdge_Edge = @"ct_o_e"; LookinAttrIdentifier const LookinAttr_UIButton_ContentInsets_Insets = @"bt_c_i"; LookinAttrIdentifier const LookinAttr_UIButton_TitleInsets_Insets = @"bt_t_i"; LookinAttrIdentifier const LookinAttr_UIButton_ImageInsets_Insets = @"bt_i_i"; LookinAttrIdentifier const LookinAttr_UIScrollView_Offset_Offset = @"sv_o_o"; LookinAttrIdentifier const LookinAttr_UIScrollView_ContentSize_Size = @"sv_c_s"; LookinAttrIdentifier const LookinAttr_UIScrollView_ContentInset_Inset = @"sv_c_i"; LookinAttrIdentifier const LookinAttr_UIScrollView_AdjustedInset_Inset = @"sv_a_i"; LookinAttrIdentifier const LookinAttr_UIScrollView_Behavior_Behavior = @"sv_b_b"; LookinAttrIdentifier const LookinAttr_UIScrollView_IndicatorInset_Inset = @"sv_i_i"; LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_ScrollEnabled = @"sv_s_s"; LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_PagingEnabled = @"sv_s_p"; LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Ver = @"sv_b_v"; LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Hor = @"sv_b_h"; LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Hor = @"sv_h_h"; LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Ver = @"sv_s_v"; LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_Delay = @"sv_c_d"; LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_CanCancel = @"sv_c_c"; LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MinScale = @"sv_z_mi"; LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MaxScale = @"sv_z_ma"; LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Scale = @"sv_z_s"; LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Bounce = @"sv_z_b"; LookinAttrIdentifier const LookinAttr_UIScrollView_QMUIInitialInset_Inset = @"sv_qi_i"; LookinAttrIdentifier const LookinAttr_UITableView_Style_Style = @"tv_s_s"; LookinAttrIdentifier const LookinAttr_UITableView_SectionsNumber_Number = @"tv_s_n"; LookinAttrIdentifier const LookinAttr_UITableView_RowsNumber_Number = @"tv_r_n"; LookinAttrIdentifier const LookinAttr_UITableView_SeparatorInset_Inset = @"tv_s_i"; LookinAttrIdentifier const LookinAttr_UITableView_SeparatorColor_Color = @"tv_s_c"; LookinAttrIdentifier const LookinAttr_UITableView_SeparatorStyle_Style = @"tv_ss_s"; LookinAttrIdentifier const LookinAttr_UITextView_Font_Name = @"te_f_n"; LookinAttrIdentifier const LookinAttr_UITextView_Font_Size = @"te_f_s"; LookinAttrIdentifier const LookinAttr_UITextView_Basic_Editable = @"te_b_e"; LookinAttrIdentifier const LookinAttr_UITextView_Basic_Selectable = @"te_b_s"; LookinAttrIdentifier const LookinAttr_UITextView_Text_Text = @"te_t_t"; LookinAttrIdentifier const LookinAttr_UITextView_TextColor_Color = @"te_t_c"; LookinAttrIdentifier const LookinAttr_UITextView_Alignment_Alignment = @"te_a_a"; LookinAttrIdentifier const LookinAttr_UITextView_ContainerInset_Inset = @"te_c_i"; LookinAttrIdentifier const LookinAttr_UITextField_Text_Text = @"tf_t_t"; LookinAttrIdentifier const LookinAttr_UITextField_Placeholder_Placeholder = @"tf_p_p"; LookinAttrIdentifier const LookinAttr_UITextField_Font_Name = @"tf_f_n"; LookinAttrIdentifier const LookinAttr_UITextField_Font_Size = @"tf_f_s"; LookinAttrIdentifier const LookinAttr_UITextField_TextColor_Color = @"tf_t_c"; LookinAttrIdentifier const LookinAttr_UITextField_Alignment_Alignment = @"tf_a_a"; LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnBeginEditing = @"tf_c_c"; LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnInsertion = @"tf_c_co"; LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_CanAdjustFont = @"tf_c_ca"; LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_MinSize = @"tf_c_m"; LookinAttrIdentifier const LookinAttr_UITextField_ClearButtonMode_Mode = @"tf_cb_m"; LookinAttrIdentifier const LookinAttr_UIVisualEffectView_Style_Style = @"ve_s_s"; LookinAttrIdentifier const LookinAttr_UIVisualEffectView_QMUIForegroundColor_Color = @"ve_f_c"; ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttrType.h ================================================ // // LookinAttrIdentifiers.h // Lookin // // Created by Li Kai on 2018/12/1. // https://lookin.work // typedef NS_ENUM(NSInteger, LookinAttrType) { LookinAttrTypeNone, LookinAttrTypeVoid, LookinAttrTypeChar, LookinAttrTypeInt, LookinAttrTypeShort, LookinAttrTypeLong, LookinAttrTypeLongLong, LookinAttrTypeUnsignedChar, LookinAttrTypeUnsignedInt, LookinAttrTypeUnsignedShort, LookinAttrTypeUnsignedLong, LookinAttrTypeUnsignedLongLong, LookinAttrTypeFloat, LookinAttrTypeDouble, LookinAttrTypeBOOL, LookinAttrTypeSel, LookinAttrTypeClass, LookinAttrTypeCGPoint, LookinAttrTypeCGVector, LookinAttrTypeCGSize, LookinAttrTypeCGRect, LookinAttrTypeCGAffineTransform, LookinAttrTypeUIEdgeInsets, LookinAttrTypeUIOffset, LookinAttrTypeNSString, LookinAttrTypeEnumInt, LookinAttrTypeEnumLong, /// value 实际为 RGBA 数组,即 @[NSNumber, NSNumber, NSNumber, NSNumber],NSNumber 范围是 0 ~ 1 LookinAttrTypeUIColor, /// 业务需要根据具体的 AttrIdentifier 来解析 LookinAttrTypeCustomObj, }; ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttribute.h ================================================ // // LookinAttribute.h // qmuidemo // // Created by Li Kai on 2018/11/17. // Copyright © 2018 QMUI Team. All rights reserved. // #import "LookinAttrIdentifiers.h" #import "LookinCodingValueType.h" #import "LookinAttrType.h" @class LookinDisplayItem; @interface LookinAttribute : NSObject @property(nonatomic, copy) LookinAttrIdentifier identifier; /// 具体的值,需配合 attrType 属性来解析它 @property(nonatomic, strong) id value; /// 标识 value 的具体类型(如 double / NSString /...) @property(nonatomic, assign) LookinAttrType attrType; #pragma mark - 以下属性不会参与 encode/decode /// 标识该 LookinAttribute 对象隶属于哪一个 LookinDisplayItem @property(nonatomic, weak) LookinDisplayItem *targetDisplayItem; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttribute.m ================================================ // // LookinAttribute.m // qmuidemo // // Created by Li Kai on 2018/11/17. // Copyright © 2018 QMUI Team. All rights reserved. // #import "LookinAttribute.h" #import "LookinDisplayItem.h" @implementation LookinAttribute #pragma mark - - (id)copyWithZone:(NSZone *)zone { LookinAttribute *newAttr = [[LookinAttribute allocWithZone:zone] init]; newAttr.identifier = self.identifier; newAttr.value = self.value; newAttr.attrType = self.attrType; return newAttr; } #pragma mark - - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.identifier forKey:@"identifier"]; [aCoder encodeInteger:self.attrType forKey:@"attrType"]; [aCoder encodeObject:self.value forKey:@"value"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.identifier = [aDecoder decodeObjectForKey:@"identifier"]; self.attrType = [aDecoder decodeIntegerForKey:@"attrType"]; self.value = [aDecoder decodeObjectForKey:@"value"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttributeModification.h ================================================ // // LookinAttributeModification.h // Lookin // // Created by Li Kai on 2018/11/20. // https://lookin.work // #import #import "LookinAttrType.h" @interface LookinAttributeModification : NSObject @property(nonatomic, assign) unsigned long targetOid; @property(nonatomic, assign) SEL setterSelector; @property(nonatomic, assign) SEL getterSelector; @property(nonatomic, assign) LookinAttrType attrType; @property(nonatomic, strong) id value; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttributeModification.m ================================================ // // LookinAttributeModification.m // Lookin // // Created by Li Kai on 2018/11/20. // https://lookin.work // #import "LookinAttributeModification.h" @implementation LookinAttributeModification - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:@(self.targetOid) forKey:@"targetOid"]; [aCoder encodeObject:NSStringFromSelector(self.setterSelector) forKey:@"setterSelector"]; [aCoder encodeInteger:self.attrType forKey:@"attrType"]; [aCoder encodeObject:self.value forKey:@"value"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.targetOid = [[aDecoder decodeObjectForKey:@"targetOid"] unsignedLongValue]; self.setterSelector = NSSelectorFromString([aDecoder decodeObjectForKey:@"setterSelector"]); self.attrType = [aDecoder decodeIntegerForKey:@"attrType"]; self.value = [aDecoder decodeObjectForKey:@"value"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttributesGroup.h ================================================ // // LookinAttributesGroup.h // Lookin // // Created by Li Kai on 2018/11/19. // https://lookin.work // #import #import "LookinAttrIdentifiers.h" @class LookinAttributesSection; /** 在 Lookin 中,一个 LookinAttributesGroup 会被对应渲染为一张卡片 如果两个 attrGroup 有相同的 LookinAttrGroupIdentifier,则 isEqual: 返回 YES */ @interface LookinAttributesGroup : NSObject @property(nonatomic, copy) LookinAttrGroupIdentifier identifier; @property(nonatomic, copy) NSArray *attrSections; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttributesGroup.m ================================================ // // LookinAttributesGroup.m // Lookin // // Created by Li Kai on 2018/11/19. // https://lookin.work // #import "LookinAttributesGroup.h" #import "LookinAttribute.h" #import "LookinAttributesSection.h" #import "NSArray+Lookin.h" @implementation LookinAttributesGroup #pragma mark - - (id)copyWithZone:(NSZone *)zone { LookinAttributesGroup *newGroup = [[LookinAttributesGroup allocWithZone:zone] init]; newGroup.identifier = self.identifier; newGroup.attrSections = [self.attrSections lookin_map:^id(NSUInteger idx, LookinAttributesSection *value) { return value.copy; }]; return newGroup; } #pragma mark - - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.identifier forKey:@"identifier"]; [aCoder encodeObject:self.attrSections forKey:@"attrSections"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.identifier = [aDecoder decodeObjectForKey:@"identifier"]; self.attrSections = [aDecoder decodeObjectForKey:@"attrSections"]; } return self; } - (NSUInteger)hash { return self.identifier.hash; } - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[LookinAttributesGroup class]]) { return NO; } if (self.identifier == ((LookinAttributesGroup *)object).identifier) { return YES; } return NO; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttributesSection.h ================================================ // // LookinAttributesSection.h // Lookin // // Created by Li Kai on 2019/3/2. // https://lookin.work // #import #import "LookinAttrIdentifiers.h" @class LookinAttribute; typedef NS_ENUM (NSInteger, LookinAttributesSectionStyle) { LookinAttributesSectionStyleDefault, // 每个 attr 独占一行 LookinAttributesSectionStyle0, // frame 等卡片使用,前 4 个 attr 每行两个,之后每个 attr 在同一排,每个宽度为 1/4 LookinAttributesSectionStyle1, // 第一个 attr 在第一排靠左,第二个 attr 在第一排靠右,之后的 attr 每个独占一行 LookinAttributesSectionStyle2 // 第一排独占一行,剩下的在同一行且均分宽度 }; @interface LookinAttributesSection : NSObject @property(nonatomic, copy) LookinAttrSectionIdentifier identifier; @property(nonatomic, copy) NSArray *attributes; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAttributesSection.m ================================================ // // LookinAttributesSection.m // Lookin // // Created by Li Kai on 2019/3/2. // https://lookin.work // #import "LookinAttributesSection.h" #import "LookinAttribute.h" #import "NSArray+Lookin.h" @implementation LookinAttributesSection #pragma mark - - (id)copyWithZone:(NSZone *)zone { LookinAttributesSection *newSection = [[LookinAttributesSection allocWithZone:zone] init]; newSection.identifier = self.identifier; newSection.attributes = [self.attributes lookin_map:^id(NSUInteger idx, LookinAttribute *value) { return value.copy; }]; return newSection; } #pragma mark - - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.identifier forKey:@"identifier"]; [aCoder encodeObject:self.attributes forKey:@"attributes"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.identifier = [aDecoder decodeObjectForKey:@"identifier"]; self.attributes = [aDecoder decodeObjectForKey:@"attributes"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAutoLayoutConstraint.h ================================================ // // LookinAutoLayoutConstraint.h // Lookin // // Created by Li Kai on 2019/9/28. // https://lookin.work // #import "LookinDefines.h" @class LookinObject; typedef NS_ENUM(NSInteger, LookinConstraintItemType) { LookinConstraintItemTypeUnknown, LookinConstraintItemTypeNil, LookinConstraintItemTypeView, LookinConstraintItemTypeSelf, LookinConstraintItemTypeSuper, LookinConstraintItemTypeLayoutGuide }; @interface LookinAutoLayoutConstraint : NSObject #if TARGET_OS_IPHONE + (instancetype)instanceFromNSConstraint:(NSLayoutConstraint *)constraint isEffective:(BOOL)isEffective firstItemType:(LookinConstraintItemType)firstItemType secondItemType:(LookinConstraintItemType)secondItemType; #endif @property(nonatomic, assign) BOOL effective; @property(nonatomic, assign) BOOL active; @property(nonatomic, assign) BOOL shouldBeArchived; @property(nonatomic, strong) LookinObject *firstItem; @property(nonatomic, assign) LookinConstraintItemType firstItemType; @property(nonatomic, assign) NSLayoutAttribute firstAttribute; @property(nonatomic, assign) NSLayoutRelation relation; @property(nonatomic, strong) LookinObject *secondItem; @property(nonatomic, assign) LookinConstraintItemType secondItemType; @property(nonatomic, assign) NSLayoutAttribute secondAttribute; @property(nonatomic, assign) CGFloat multiplier; @property(nonatomic, assign) CGFloat constant; @property(nonatomic, assign) CGFloat priority; @property(nonatomic, copy) NSString *identifier; + (NSString *)descriptionWithItemObject:(LookinObject *)object type:(LookinConstraintItemType)type detailed:(BOOL)detailed; + (NSString *)descriptionWithAttribute:(NSLayoutAttribute)attribute; + (NSString *)symbolWithRelation:(NSLayoutRelation)relation; + (NSString *)descriptionWithRelation:(NSLayoutRelation)relation; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinAutoLayoutConstraint.m ================================================ // // LookinAutoLayoutConstraint.m // Lookin // // Created by Li Kai on 2019/9/28. // https://lookin.work // #import "LookinAutoLayoutConstraint.h" #import "LookinObject.h" @implementation LookinAutoLayoutConstraint #if TARGET_OS_IPHONE + (instancetype)instanceFromNSConstraint:(NSLayoutConstraint *)constraint isEffective:(BOOL)isEffective firstItemType:(LookinConstraintItemType)firstItemType secondItemType:(LookinConstraintItemType)secondItemType { LookinAutoLayoutConstraint *instance = [LookinAutoLayoutConstraint new]; instance.effective = isEffective; instance.active = constraint.active; instance.shouldBeArchived = constraint.shouldBeArchived; instance.firstItem = [LookinObject instanceWithObject:constraint.firstItem]; instance.firstItemType = firstItemType; instance.firstAttribute = constraint.firstAttribute; instance.relation = constraint.relation; instance.secondItem = [LookinObject instanceWithObject:constraint.secondItem]; instance.secondItemType = secondItemType; instance.secondAttribute = constraint.secondAttribute; instance.multiplier = constraint.multiplier; instance.constant = constraint.constant; instance.priority = constraint.priority; instance.identifier = constraint.identifier; return instance; } - (void)setFirstAttribute:(NSLayoutAttribute)firstAttribute { _firstAttribute = firstAttribute; [self _assertUnknownAttribute:firstAttribute]; } - (void)setSecondAttribute:(NSLayoutAttribute)secondAttribute { _secondAttribute = secondAttribute; [self _assertUnknownAttribute:secondAttribute]; } - (void)_assertUnknownAttribute:(NSLayoutAttribute)attribute { // 以下几个 assert 用来帮助发现那些系统私有的定义,正式发布时应该去掉这几个 assert if (attribute > 21 && attribute < 32) { NSAssert(NO, nil); } if (attribute > 37) { NSAssert(NO, nil); } } #endif + (NSString *)descriptionWithItemObject:(LookinObject *)object type:(LookinConstraintItemType)type detailed:(BOOL)detailed { switch (type) { case LookinConstraintItemTypeNil: return detailed ? @"Nil" : @"nil"; case LookinConstraintItemTypeSelf: return detailed ? @"Self" : @"self"; case LookinConstraintItemTypeSuper: return detailed ? @"Superview" : @"super"; case LookinConstraintItemTypeView: case LookinConstraintItemTypeLayoutGuide: return detailed ? [NSString stringWithFormat:@"<%@: %@>", object.shortSelfClassName, object.memoryAddress] : [NSString stringWithFormat:@"(%@*)", object.shortSelfClassName]; default: NSAssert(NO, @""); return detailed ? [NSString stringWithFormat:@"<%@: %@>", object.shortSelfClassName, object.memoryAddress] : [NSString stringWithFormat:@"(%@*)", object.shortSelfClassName]; } } + (NSString *)descriptionWithAttribute:(NSLayoutAttribute)attribute { switch (attribute) { case 0 : // 在某些业务里确实会出现这种情况,在 Reveal 和 UI Debugger 里也是这么显示的 return @"notAnAttribute"; case 1: return @"left"; case 2: return @"right"; case 3: return @"top"; case 4: return @"bottom"; case 5: return @"leading"; case 6: return @"trailing"; case 7: return @"width"; case 8: return @"height"; case 9: return @"centerX"; case 10: return @"centerY"; case 11: return @"lastBaseline"; case 12: return @"baseline"; case 13: return @"firstBaseline"; case 14: return @"leftMargin"; case 15: return @"rightMargin"; case 16: return @"topMargin"; case 17: return @"bottomMargin"; case 18: return @"leadingMargin"; case 19: return @"trailingMargin"; case 20: return @"centerXWithinMargins"; case 21: return @"centerYWithinMargins"; // 以下都是和 AutoResizingMask 有关的,这里的定义是从系统 UI Debugger 里抄过来的,暂时没在官方文档里发现它们的公开定义 case 32: return @"minX"; case 33: return @"minY"; case 34: return @"midX"; case 35: return @"midY"; case 36: return @"maxX"; case 37: return @"maxY"; default: NSAssert(NO, @""); return [NSString stringWithFormat:@"unknownAttr(%@)", @(attribute)]; } } + (NSString *)symbolWithRelation:(NSLayoutRelation)relation { switch (relation) { case -1: return @"<="; case 0: return @"="; case 1: return @">="; default: NSAssert(NO, @""); return @"?"; } } + (NSString *)descriptionWithRelation:(NSLayoutRelation)relation { switch (relation) { case -1: return @"LessThanOrEqual"; case 0: return @"Equal"; case 1: return @"GreaterThanOrEqual"; default: NSAssert(NO, @""); return @"?"; } } #pragma mark - + (BOOL)supportsSecureCoding { return YES; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeBool:self.effective forKey:@"effective"]; [aCoder encodeBool:self.active forKey:@"active"]; [aCoder encodeBool:self.shouldBeArchived forKey:@"shouldBeArchived"]; [aCoder encodeObject:self.firstItem forKey:@"firstItem"]; [aCoder encodeInteger:self.firstItemType forKey:@"firstItemType"]; [aCoder encodeInteger:self.firstAttribute forKey:@"firstAttribute"]; [aCoder encodeInteger:self.relation forKey:@"relation"]; [aCoder encodeObject:self.secondItem forKey:@"secondItem"]; [aCoder encodeInteger:self.secondItemType forKey:@"secondItemType"]; [aCoder encodeInteger:self.secondAttribute forKey:@"secondAttribute"]; [aCoder encodeDouble:self.multiplier forKey:@"multiplier"]; [aCoder encodeDouble:self.constant forKey:@"constant"]; [aCoder encodeDouble:self.priority forKey:@"priority"]; [aCoder encodeObject:self.identifier forKey:@"identifier"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.effective = [aDecoder decodeBoolForKey:@"effective"]; self.active = [aDecoder decodeBoolForKey:@"active"]; self.shouldBeArchived = [aDecoder decodeBoolForKey:@"shouldBeArchived"]; self.firstItem = [aDecoder decodeObjectForKey:@"firstItem"]; self.firstItemType = [aDecoder decodeIntegerForKey:@"firstItemType"]; self.firstAttribute = [aDecoder decodeIntegerForKey:@"firstAttribute"]; self.relation = [aDecoder decodeIntegerForKey:@"relation"]; self.secondItem = [aDecoder decodeObjectForKey:@"secondItem"]; self.secondItemType = [aDecoder decodeIntegerForKey:@"secondItemType"]; self.secondAttribute = [aDecoder decodeIntegerForKey:@"secondAttribute"]; self.multiplier = [aDecoder decodeDoubleForKey:@"multiplier"]; self.constant = [aDecoder decodeDoubleForKey:@"constant"]; self.priority = [aDecoder decodeDoubleForKey:@"priority"]; self.identifier = [aDecoder decodeObjectForKey:@"identifier"]; } return self; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinCodingValueType.h ================================================ // // LookinCodingValueType.h // Lookin // // Created by Li Kai on 2019/2/13. // https://lookin.work // typedef NS_ENUM(NSInteger, LookinCodingValueType) { LookinCodingValueTypeUnknown, LookinCodingValueTypeChar, LookinCodingValueTypeDouble, LookinCodingValueTypeFloat, LookinCodingValueTypeLongLong, // LookinCodingValueTypePoint, // LookinCodingValueTypeString, // LookinCodingValueTypeStringArray, // LookinCodingValueTypeEdgeInsets, // LookinCodingValueTypeRect, LookinCodingValueTypeBOOL, // LookinCodingValueTypeSize, LookinCodingValueTypeColor, LookinCodingValueTypeEnum, // LookinCodingValueTypeRange, LookinCodingValueTypeImage }; ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinConnectionAttachment.h ================================================ // // LookinConnectionAttachment.h // Lookin // // Created by Li Kai on 2019/2/15. // https://lookin.work // #import #import "LookinCodingValueType.h" @interface LookinConnectionAttachment : NSObject @property(nonatomic, assign) LookinCodingValueType dataType; @property(nonatomic, strong) id data; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinConnectionAttachment.m ================================================ // // LookinConnectionAttachment.m // Lookin // // Created by Li Kai on 2019/2/15. // https://lookin.work // #import "LookinConnectionAttachment.h" #import "LookinDefines.h" #import "NSObject+Lookin.h" static NSString * const Key_Data = @"0"; static NSString * const Key_DataType = @"1"; @interface LookinConnectionAttachment () @end @implementation LookinConnectionAttachment - (instancetype)init { if (self = [super init]) { } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:[self.data lookin_encodedObjectWithType:self.dataType] forKey:Key_Data]; [aCoder encodeInteger:self.dataType forKey:Key_DataType]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.dataType = [aDecoder decodeIntegerForKey:Key_DataType]; self.data = [[aDecoder decodeObjectForKey:Key_Data] lookin_decodedObjectWithType:self.dataType]; } return self; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinConnectionResponseAttachment.h ================================================ // // LookinConnectionResponse.h // Lookin // // Created by Li Kai on 2019/1/15. // https://lookin.work // #import #import "LookinConnectionAttachment.h" @interface LookinConnectionResponseAttachment : LookinConnectionAttachment + (instancetype)attachmentWithError:(NSError *)error; @property(nonatomic, assign) int lookinServerVersion; @property(nonatomic, assign) BOOL lookinServerIsExprimental; @property(nonatomic, strong) NSError *error; /// 如果为 YES,则表示 app 正处于后台模式,默认为 NO @property(nonatomic, assign) BOOL appIsInBackground; /** dataTotalCount 为 0 时表示仅有这一个 response,默认为 0 dataTotalCount 大于 0 时表示可能有多个 response,当所有 response 的 currentDataCount 的总和大于 dataTotalCount 即表示所有 response 已接收完毕 */ @property(nonatomic, assign) NSUInteger dataTotalCount; @property(nonatomic, assign) NSUInteger currentDataCount; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinConnectionResponseAttachment.m ================================================ // // LookinConnectionResponse.m // Lookin // // Created by Li Kai on 2019/1/15. // https://lookin.work // #import "LookinConnectionResponseAttachment.h" #import "LookinDefines.h" @interface LookinConnectionResponseAttachment () @end @implementation LookinConnectionResponseAttachment - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; [aCoder encodeInt:self.lookinServerVersion forKey:@"lookinServerVersion"]; [aCoder encodeObject:self.error forKey:@"error"]; [aCoder encodeObject:@(self.dataTotalCount) forKey:@"dataTotalCount"]; [aCoder encodeObject:@(self.currentDataCount) forKey:@"currentDataCount"]; [aCoder encodeBool:self.lookinServerIsExprimental forKey:@"lookinServerIsExprimental"]; [aCoder encodeBool:self.appIsInBackground forKey:@"appIsInBackground"]; } - (instancetype)init { if (self = [super init]) { self.lookinServerVersion = LOOKIN_SERVER_VERSION; self.dataTotalCount = 0; self.lookinServerIsExprimental = LOOKIN_SERVER_IS_EXPERIMENTAL; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { self.lookinServerVersion = [aDecoder decodeIntForKey:@"lookinServerVersion"]; self.lookinServerIsExprimental = [aDecoder decodeBoolForKey:@"lookinServerIsExprimental"]; self.error = [aDecoder decodeObjectForKey:@"error"]; self.dataTotalCount = [[aDecoder decodeObjectForKey:@"dataTotalCount"] unsignedIntegerValue]; self.currentDataCount = [[aDecoder decodeObjectForKey:@"currentDataCount"] unsignedIntegerValue]; self.appIsInBackground = [aDecoder decodeBoolForKey:@"appIsInBackground"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } + (instancetype)attachmentWithError:(NSError *)error { LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new]; attachment.error = error; return attachment; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinDashboardBlueprint.h ================================================ // // LookinDashboardBlueprint.h // Lookin // // Created by Li Kai on 2019/6/5. // https://lookin.work // #import #import "LookinAttrIdentifiers.h" #import "LookinAttrType.h" /** 该对象定义了: - 每一个 Attr 的信息 - 哪些 GroupID, SectionID, AttrID 是合法的 - 这些 ID 的父子顺序,比如 LookinAttrGroup_Frame 包含哪些 Section - 这些 ID 展示顺序(比如哪个 Group 在前、哪个 Group 在后) */ @interface LookinDashboardBlueprint : NSObject + (NSArray *)groupIDs; + (NSArray *)sectionIDsForGroupID:(LookinAttrGroupIdentifier)groupID; + (NSArray *)attrIDsForSectionID:(LookinAttrSectionIdentifier)sectionID; /// 返回包含目标 attr 的 groupID 和 sectionID + (void)getHostGroupID:(inout LookinAttrGroupIdentifier *)groupID sectionID:(inout LookinAttrSectionIdentifier *)sectionID fromAttrID:(LookinAttrIdentifier)attrID; /// 返回某个 group 的标题 + (NSString *)groupTitleWithGroupID:(LookinAttrGroupIdentifier)groupID; /// 返回某个 section 的标题,nil 则表示不显示标题 + (NSString *)sectionTitleWithSectionID:(LookinAttrSectionIdentifier)secID; /// 当某个 LookinAttribute 确定是 NSObject 类型时,该方法返回它具体是什么对象,比如 UIColor 等 + (LookinAttrType)objectAttrTypeWithAttrID:(LookinAttrIdentifier)attrID; /// 返回某个 LookinAttribute 代表的属性是哪一个类拥有的,比如 LookinAttrSec_UILabel_TextColor 是 UILabel 才有的 + (NSString *)classNameWithAttrID:(LookinAttrIdentifier)attrID; /// 一个 attr 要么属于 UIView 要么属于 CALayer,如果它属于 UIView 那么该方法返回 YES + (BOOL)isUIViewPropertyWithAttrID:(LookinAttrIdentifier)attrID; /// 如果某个 attribute 是 enum,则这里会返回相应的 enum 的名称(如 @"NSTextAlignment"),进而可通过这个名称查询可用的枚举值列表 + (NSString *)enumListNameWithAttrID:(LookinAttrIdentifier)attrID; /// 如果返回 YES,则说明用户在 Lookin 里修改了该 Attribute 的值后,应该重新拉取和更新相关图层的位置、截图等信息 + (BOOL)needPatchAfterModificationWithAttrID:(LookinAttrIdentifier)attrID; /// 完整的名字 + (NSString *)fullTitleWithAttrID:(LookinAttrIdentifier)attrID; /// 在某些 textField 和 checkbox 里会显示这里返回的 title + (NSString *)briefTitleWithAttrID:(LookinAttrIdentifier)attrID; /// 获取 getter 方法 + (SEL)getterWithAttrID:(LookinAttrIdentifier)attrID; /// 获取 setter 方法 + (SEL)setterWithAttrID:(LookinAttrIdentifier)attrID; /// 获取 “hideIfNil” 的值。如果为 YES,则当读取 getter 获取的 value 为 nil 时,Lookin 不会传输该 attr /// 如果为 NO,则即使 value 为 nil 也会传输(比如 label 的 text 属性,即使它是 nil 我们也要显示,所以它的 hideIfNil 应该为 NO) + (BOOL)hideIfNilWithAttrID:(LookinAttrIdentifier)attrID; /// 该属性需要的最低的 iOS 版本,比如 safeAreaInsets 从 iOS 11.0 开始出现,则该方法返回 11,如果返回 0 则表示不限制 iOS 版本(注意 Lookin 项目仅支持 iOS 8.0+) + (NSInteger)minAvailableOSVersionWithAttrID:(LookinAttrIdentifier)attrID; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinDashboardBlueprint.m ================================================ // // LookinDashboardBlueprint.m // Lookin // // Created by Li Kai on 2019/6/5. // https://lookin.work // #import "LookinDashboardBlueprint.h" @implementation LookinDashboardBlueprint + (NSArray *)groupIDs { static NSArray *array; static dispatch_once_t onceToken; dispatch_once(&onceToken,^{ array = @[ LookinAttrGroup_Class, LookinAttrGroup_Relation, LookinAttrGroup_Layout, LookinAttrGroup_AutoLayout, LookinAttrGroup_ViewLayer, LookinAttrGroup_UIVisualEffectView, LookinAttrGroup_UIImageView, LookinAttrGroup_UILabel, LookinAttrGroup_UIControl, LookinAttrGroup_UIButton, LookinAttrGroup_UIScrollView, LookinAttrGroup_UITableView, LookinAttrGroup_UITextView, LookinAttrGroup_UITextField ]; }); return array; } + (NSArray *)sectionIDsForGroupID:(LookinAttrGroupIdentifier)groupID { static NSDictionary *> *dict; static dispatch_once_t onceToken; dispatch_once(&onceToken,^{ dict = @{ LookinAttrGroup_Class: @[LookinAttrSec_Class_Class], LookinAttrGroup_Relation: @[LookinAttrSec_Relation_Relation], LookinAttrGroup_Layout: @[LookinAttrSec_Layout_Frame, LookinAttrSec_Layout_Bounds, LookinAttrSec_Layout_SafeArea, LookinAttrSec_Layout_Position, LookinAttrSec_Layout_AnchorPoint], LookinAttrGroup_AutoLayout: @[LookinAttrSec_AutoLayout_Constraints, LookinAttrSec_AutoLayout_IntrinsicSize, LookinAttrSec_AutoLayout_Hugging, LookinAttrSec_AutoLayout_Resistance], LookinAttrGroup_ViewLayer: @[ LookinAttrSec_ViewLayer_Visibility, LookinAttrSec_ViewLayer_InterationAndMasks, LookinAttrSec_ViewLayer_BgColor, LookinAttrSec_ViewLayer_Border, LookinAttrSec_ViewLayer_Corner, LookinAttrSec_ViewLayer_Shadow, LookinAttrSec_ViewLayer_ContentMode, LookinAttrSec_ViewLayer_TintColor, LookinAttrSec_ViewLayer_Tag ], LookinAttrGroup_UIVisualEffectView: @[ LookinAttrSec_UIVisualEffectView_Style, LookinAttrSec_UIVisualEffectView_QMUIForegroundColor ], LookinAttrGroup_UIImageView: @[LookinAttrSec_UIImageView_Name, LookinAttrSec_UIImageView_Open], LookinAttrGroup_UILabel: @[ LookinAttrSec_UILabel_Text, LookinAttrSec_UILabel_Font, LookinAttrSec_UILabel_NumberOfLines, LookinAttrSec_UILabel_TextColor, LookinAttrSec_UILabel_BreakMode, LookinAttrSec_UILabel_Alignment, LookinAttrSec_UILabel_CanAdjustFont], LookinAttrGroup_UIControl: @[LookinAttrSec_UIControl_EnabledSelected, LookinAttrSec_UIControl_QMUIOutsideEdge, LookinAttrSec_UIControl_VerAlignment, LookinAttrSec_UIControl_HorAlignment], LookinAttrGroup_UIButton: @[LookinAttrSec_UIButton_ContentInsets, LookinAttrSec_UIButton_TitleInsets, LookinAttrSec_UIButton_ImageInsets], LookinAttrGroup_UIScrollView: @[LookinAttrSec_UIScrollView_ContentInset, LookinAttrSec_UIScrollView_AdjustedInset, LookinAttrSec_UIScrollView_QMUIInitialInset, LookinAttrSec_UIScrollView_IndicatorInset, LookinAttrSec_UIScrollView_Offset, LookinAttrSec_UIScrollView_ContentSize, LookinAttrSec_UIScrollView_Behavior, LookinAttrSec_UIScrollView_ShowsIndicator, LookinAttrSec_UIScrollView_Bounce, LookinAttrSec_UIScrollView_ScrollPaging, LookinAttrSec_UIScrollView_ContentTouches, LookinAttrSec_UIScrollView_Zoom], LookinAttrGroup_UITableView: @[LookinAttrSec_UITableView_Style, LookinAttrSec_UITableView_SectionsNumber, LookinAttrSec_UITableView_RowsNumber, LookinAttrSec_UITableView_SeparatorStyle, LookinAttrSec_UITableView_SeparatorColor, LookinAttrSec_UITableView_SeparatorInset], LookinAttrGroup_UITextView: @[LookinAttrSec_UITextView_Basic, LookinAttrSec_UITextView_Text, LookinAttrSec_UITextView_Font, LookinAttrSec_UITextView_TextColor, LookinAttrSec_UITextView_Alignment, LookinAttrSec_UITextView_ContainerInset], LookinAttrGroup_UITextField: @[LookinAttrSec_UITextField_Text, LookinAttrSec_UITextField_Placeholder, LookinAttrSec_UITextField_Font, LookinAttrSec_UITextField_TextColor, LookinAttrSec_UITextField_Alignment, LookinAttrSec_UITextField_Clears, LookinAttrSec_UITextField_CanAdjustFont, LookinAttrSec_UITextField_ClearButtonMode], }; }); return dict[groupID]; } + (NSArray *)attrIDsForSectionID:(LookinAttrSectionIdentifier)sectionID { static NSDictionary *> *dict; static dispatch_once_t onceToken; dispatch_once(&onceToken,^{ dict = @{ LookinAttrSec_Class_Class: @[LookinAttr_Class_Class_Class], LookinAttrSec_Relation_Relation: @[LookinAttr_Relation_Relation_Relation], LookinAttrSec_Layout_Frame: @[LookinAttr_Layout_Frame_Frame], LookinAttrSec_Layout_Bounds: @[LookinAttr_Layout_Bounds_Bounds], LookinAttrSec_Layout_SafeArea: @[LookinAttr_Layout_SafeArea_SafeArea], LookinAttrSec_Layout_Position: @[LookinAttr_Layout_Position_Position], LookinAttrSec_Layout_AnchorPoint: @[LookinAttr_Layout_AnchorPoint_AnchorPoint], LookinAttrSec_AutoLayout_Hugging: @[LookinAttr_AutoLayout_Hugging_Hor, LookinAttr_AutoLayout_Hugging_Ver], LookinAttrSec_AutoLayout_Resistance: @[LookinAttr_AutoLayout_Resistance_Hor, LookinAttr_AutoLayout_Resistance_Ver], LookinAttrSec_AutoLayout_Constraints: @[LookinAttr_AutoLayout_Constraints_Constraints], LookinAttrSec_AutoLayout_IntrinsicSize: @[LookinAttr_AutoLayout_IntrinsicSize_Size], LookinAttrSec_ViewLayer_Visibility: @[LookinAttr_ViewLayer_Visibility_Hidden, LookinAttr_ViewLayer_Visibility_Opacity], LookinAttrSec_ViewLayer_InterationAndMasks: @[LookinAttr_ViewLayer_InterationAndMasks_Interaction, LookinAttr_ViewLayer_InterationAndMasks_MasksToBounds], LookinAttrSec_ViewLayer_Corner: @[LookinAttr_ViewLayer_Corner_Radius], LookinAttrSec_ViewLayer_BgColor: @[LookinAttr_ViewLayer_BgColor_BgColor], LookinAttrSec_ViewLayer_Border: @[LookinAttr_ViewLayer_Border_Color, LookinAttr_ViewLayer_Border_Width], LookinAttrSec_ViewLayer_Shadow: @[LookinAttr_ViewLayer_Shadow_Color, LookinAttr_ViewLayer_Shadow_Opacity, LookinAttr_ViewLayer_Shadow_Radius, LookinAttr_ViewLayer_Shadow_OffsetW, LookinAttr_ViewLayer_Shadow_OffsetH], LookinAttrSec_ViewLayer_ContentMode: @[LookinAttr_ViewLayer_ContentMode_Mode], LookinAttrSec_ViewLayer_TintColor: @[LookinAttr_ViewLayer_TintColor_Color, LookinAttr_ViewLayer_TintColor_Mode], LookinAttrSec_ViewLayer_Tag: @[LookinAttr_ViewLayer_Tag_Tag], LookinAttrSec_UIVisualEffectView_Style: @[LookinAttr_UIVisualEffectView_Style_Style], LookinAttrSec_UIVisualEffectView_QMUIForegroundColor: @[LookinAttr_UIVisualEffectView_QMUIForegroundColor_Color], LookinAttrSec_UIImageView_Name: @[LookinAttr_UIImageView_Name_Name], LookinAttrSec_UIImageView_Open: @[LookinAttr_UIImageView_Open_Open], LookinAttrSec_UILabel_Font: @[LookinAttr_UILabel_Font_Name, LookinAttr_UILabel_Font_Size], LookinAttrSec_UILabel_NumberOfLines: @[LookinAttr_UILabel_NumberOfLines_NumberOfLines], LookinAttrSec_UILabel_Text: @[LookinAttr_UILabel_Text_Text], LookinAttrSec_UILabel_TextColor: @[LookinAttr_UILabel_TextColor_Color], LookinAttrSec_UILabel_BreakMode: @[LookinAttr_UILabel_BreakMode_Mode], LookinAttrSec_UILabel_Alignment: @[LookinAttr_UILabel_Alignment_Alignment], LookinAttrSec_UILabel_CanAdjustFont: @[LookinAttr_UILabel_CanAdjustFont_CanAdjustFont], LookinAttrSec_UIControl_EnabledSelected: @[LookinAttr_UIControl_EnabledSelected_Enabled, LookinAttr_UIControl_EnabledSelected_Selected], LookinAttrSec_UIControl_QMUIOutsideEdge: @[LookinAttr_UIControl_QMUIOutsideEdge_Edge], LookinAttrSec_UIControl_VerAlignment: @[LookinAttr_UIControl_VerAlignment_Alignment], LookinAttrSec_UIControl_HorAlignment: @[LookinAttr_UIControl_HorAlignment_Alignment], LookinAttrSec_UIButton_ContentInsets: @[LookinAttr_UIButton_ContentInsets_Insets], LookinAttrSec_UIButton_TitleInsets: @[LookinAttr_UIButton_TitleInsets_Insets], LookinAttrSec_UIButton_ImageInsets: @[LookinAttr_UIButton_ImageInsets_Insets], LookinAttrSec_UIScrollView_ContentInset: @[LookinAttr_UIScrollView_ContentInset_Inset], LookinAttrSec_UIScrollView_AdjustedInset: @[LookinAttr_UIScrollView_AdjustedInset_Inset], LookinAttrSec_UIScrollView_QMUIInitialInset: @[LookinAttr_UIScrollView_QMUIInitialInset_Inset], LookinAttrSec_UIScrollView_IndicatorInset: @[LookinAttr_UIScrollView_IndicatorInset_Inset], LookinAttrSec_UIScrollView_Offset: @[LookinAttr_UIScrollView_Offset_Offset], LookinAttrSec_UIScrollView_ContentSize: @[LookinAttr_UIScrollView_ContentSize_Size], LookinAttrSec_UIScrollView_Behavior: @[LookinAttr_UIScrollView_Behavior_Behavior], LookinAttrSec_UIScrollView_ShowsIndicator: @[LookinAttr_UIScrollView_ShowsIndicator_Hor, LookinAttr_UIScrollView_ShowsIndicator_Ver], LookinAttrSec_UIScrollView_Bounce: @[LookinAttr_UIScrollView_Bounce_Hor, LookinAttr_UIScrollView_Bounce_Ver], LookinAttrSec_UIScrollView_ScrollPaging: @[LookinAttr_UIScrollView_ScrollPaging_ScrollEnabled, LookinAttr_UIScrollView_ScrollPaging_PagingEnabled], LookinAttrSec_UIScrollView_ContentTouches: @[LookinAttr_UIScrollView_ContentTouches_Delay, LookinAttr_UIScrollView_ContentTouches_CanCancel], LookinAttrSec_UIScrollView_Zoom: @[LookinAttr_UIScrollView_Zoom_Bounce, LookinAttr_UIScrollView_Zoom_Scale, LookinAttr_UIScrollView_Zoom_MinScale, LookinAttr_UIScrollView_Zoom_MaxScale], LookinAttrSec_UITableView_Style: @[LookinAttr_UITableView_Style_Style], LookinAttrSec_UITableView_SectionsNumber: @[LookinAttr_UITableView_SectionsNumber_Number], LookinAttrSec_UITableView_RowsNumber: @[LookinAttr_UITableView_RowsNumber_Number], LookinAttrSec_UITableView_SeparatorInset: @[LookinAttr_UITableView_SeparatorInset_Inset], LookinAttrSec_UITableView_SeparatorColor: @[LookinAttr_UITableView_SeparatorColor_Color], LookinAttrSec_UITableView_SeparatorStyle: @[LookinAttr_UITableView_SeparatorStyle_Style], LookinAttrSec_UITextView_Basic: @[LookinAttr_UITextView_Basic_Editable, LookinAttr_UITextView_Basic_Selectable], LookinAttrSec_UITextView_Text: @[LookinAttr_UITextView_Text_Text], LookinAttrSec_UITextView_Font: @[LookinAttr_UITextView_Font_Name, LookinAttr_UITextView_Font_Size], LookinAttrSec_UITextView_TextColor: @[LookinAttr_UITextView_TextColor_Color], LookinAttrSec_UITextView_Alignment: @[LookinAttr_UITextView_Alignment_Alignment], LookinAttrSec_UITextView_ContainerInset: @[LookinAttr_UITextView_ContainerInset_Inset], LookinAttrSec_UITextField_Text: @[LookinAttr_UITextField_Text_Text], LookinAttrSec_UITextField_Placeholder: @[LookinAttr_UITextField_Placeholder_Placeholder], LookinAttrSec_UITextField_Font: @[LookinAttr_UITextField_Font_Name, LookinAttr_UITextField_Font_Size], LookinAttrSec_UITextField_TextColor: @[LookinAttr_UITextField_TextColor_Color], LookinAttrSec_UITextField_Alignment: @[LookinAttr_UITextField_Alignment_Alignment], LookinAttrSec_UITextField_Clears: @[LookinAttr_UITextField_Clears_ClearsOnBeginEditing, LookinAttr_UITextField_Clears_ClearsOnInsertion], LookinAttrSec_UITextField_CanAdjustFont: @[LookinAttr_UITextField_CanAdjustFont_CanAdjustFont, LookinAttr_UITextField_CanAdjustFont_MinSize], LookinAttrSec_UITextField_ClearButtonMode: @[LookinAttr_UITextField_ClearButtonMode_Mode] }; }); return dict[sectionID]; } + (void)getHostGroupID:(inout LookinAttrGroupIdentifier *)groupID_inout sectionID:(inout LookinAttrSectionIdentifier *)sectionID_inout fromAttrID:(LookinAttrIdentifier)targetAttrID { __block LookinAttrGroupIdentifier targetGroupID = nil; __block LookinAttrSectionIdentifier targetSecID = nil; [[self groupIDs] enumerateObjectsUsingBlock:^(LookinAttrGroupIdentifier _Nonnull groupID, NSUInteger idx, BOOL * _Nonnull stop0) { [[self sectionIDsForGroupID:groupID] enumerateObjectsUsingBlock:^(LookinAttrSectionIdentifier _Nonnull secID, NSUInteger idx, BOOL * _Nonnull stop1) { [[self attrIDsForSectionID:secID] enumerateObjectsUsingBlock:^(LookinAttrIdentifier _Nonnull attrID, NSUInteger idx, BOOL * _Nonnull stop2) { if ([attrID isEqualToString:targetAttrID]) { targetGroupID = groupID; targetSecID = secID; *stop0 = YES; *stop1 = YES; *stop2 = YES; } }]; }]; }]; if (groupID_inout && targetGroupID) { *groupID_inout = targetGroupID; } if (sectionID_inout && targetSecID) { *sectionID_inout = targetSecID; } } + (NSString *)groupTitleWithGroupID:(LookinAttrGroupIdentifier)groupID { static dispatch_once_t onceToken; static NSDictionary *rawInfo = nil; dispatch_once(&onceToken,^{ rawInfo = @{ LookinAttrGroup_Class: @"Class", LookinAttrGroup_Relation: @"Relation", LookinAttrGroup_Layout: @"Layout", LookinAttrGroup_AutoLayout: @"AutoLayout", LookinAttrGroup_ViewLayer: @"CALayer / UIView", LookinAttrGroup_UIImageView: @"UIImageView", LookinAttrGroup_UILabel: @"UILabel", LookinAttrGroup_UIControl: @"UIControl", LookinAttrGroup_UIButton: @"UIButton", LookinAttrGroup_UIScrollView: @"UIScrollView", LookinAttrGroup_UITableView: @"UITableView", LookinAttrGroup_UITextView: @"UITextView", LookinAttrGroup_UITextField: @"UITextField", LookinAttrGroup_UIVisualEffectView: @"UIVisualEffectView" }; }); NSString *title = rawInfo[groupID]; NSAssert(title.length, @""); return title; } + (NSString *)sectionTitleWithSectionID:(LookinAttrSectionIdentifier)secID { static dispatch_once_t onceToken; static NSDictionary *rawInfo = nil; dispatch_once(&onceToken,^{ rawInfo = @{ LookinAttrSec_Layout_Frame: @"Frame", LookinAttrSec_Layout_Bounds: @"Bounds", LookinAttrSec_Layout_SafeArea: @"SafeArea", LookinAttrSec_Layout_Position: @"Position", LookinAttrSec_Layout_AnchorPoint: @"AnchorPoint", LookinAttrSec_AutoLayout_Hugging: @"HuggingPriority", LookinAttrSec_AutoLayout_Resistance: @"ResistancePriority", LookinAttrSec_AutoLayout_IntrinsicSize: @"IntrinsicSize", LookinAttrSec_ViewLayer_Corner: @"CornerRadius", LookinAttrSec_ViewLayer_BgColor: @"BackgroundColor", LookinAttrSec_ViewLayer_Border: @"Border", LookinAttrSec_ViewLayer_Shadow: @"Shadow", LookinAttrSec_ViewLayer_ContentMode: @"ContentMode", LookinAttrSec_ViewLayer_TintColor: @"TintColor", LookinAttrSec_ViewLayer_Tag: @"Tag", LookinAttrSec_UIVisualEffectView_Style: @"Style", LookinAttrSec_UIVisualEffectView_QMUIForegroundColor: @"ForegroundColor", LookinAttrSec_UIImageView_Name: @"ImageName", LookinAttrSec_UILabel_TextColor: @"TextColor", LookinAttrSec_UITextView_TextColor: @"TextColor", LookinAttrSec_UITextField_TextColor: @"TextColor", LookinAttrSec_UILabel_BreakMode: @"LineBreakMode", LookinAttrSec_UILabel_NumberOfLines: @"NumberOfLines", LookinAttrSec_UILabel_Text: @"Text", LookinAttrSec_UITextView_Text: @"Text", LookinAttrSec_UITextField_Text: @"Text", LookinAttrSec_UITextField_Placeholder: @"Placeholder", LookinAttrSec_UILabel_Alignment: @"TextAlignment", LookinAttrSec_UITextView_Alignment: @"TextAlignment", LookinAttrSec_UITextField_Alignment: @"TextAlignment", LookinAttrSec_UIControl_HorAlignment: @"HorizontalAlignment", LookinAttrSec_UIControl_VerAlignment: @"VerticalAlignment", LookinAttrSec_UIControl_QMUIOutsideEdge: @"QMUI_outsideEdge", LookinAttrSec_UIButton_ContentInsets: @"ContentInsets", LookinAttrSec_UIButton_TitleInsets: @"TitleInsets", LookinAttrSec_UIButton_ImageInsets: @"ImageInsets", LookinAttrSec_UIScrollView_QMUIInitialInset: @"QMUI_initialContentInset", LookinAttrSec_UIScrollView_ContentInset: @"ContentInset", LookinAttrSec_UIScrollView_AdjustedInset: @"AdjustedContentInset", LookinAttrSec_UIScrollView_IndicatorInset: @"ScrollIndicatorInsets", LookinAttrSec_UIScrollView_Offset: @"ContentOffset", LookinAttrSec_UIScrollView_ContentSize: @"ContentSize", LookinAttrSec_UIScrollView_Behavior: @"InsetAdjustmentBehavior", LookinAttrSec_UIScrollView_ShowsIndicator: @"ShowsScrollIndicator", LookinAttrSec_UIScrollView_Bounce: @"AlwaysBounce", LookinAttrSec_UIScrollView_Zoom: @"Zoom", LookinAttrSec_UITableView_Style: @"Style", LookinAttrSec_UITableView_SectionsNumber: @"NumberOfSections", LookinAttrSec_UITableView_RowsNumber: @"NumberOfRows", LookinAttrSec_UITableView_SeparatorColor: @"SeparatorColor", LookinAttrSec_UITableView_SeparatorInset: @"SeparatorInset", LookinAttrSec_UITableView_SeparatorStyle: @"SeparatorStyle", LookinAttrSec_UILabel_Font: @"Font", LookinAttrSec_UITextField_Font: @"Font", LookinAttrSec_UITextView_Font: @"Font", LookinAttrSec_UITextView_ContainerInset: @"ContainerInset", LookinAttrSec_UITextField_ClearButtonMode: @"ClearButtonMode", }; }); return rawInfo[secID]; } /** className: 必填项,标识该属性是哪一个类拥有的 fullTitle: 完整的名字,将作为搜索的 keywords,也会展示在搜索结果中,如果为 nil 则不会被搜索到 briefTitle:简略的名字,仅 checkbox 和那种自带标题的 input 才需要这个属性,如果需要该属性但该属性又为空,则会读取 fullTitle setterString:用户试图修改属性值时会用到,若该字段为空字符串(即 @“”)则该属性不可修改,若该字段为 nil 则会在 fullTitle 的基础上自动生成(自动改首字母大小写、加前缀后缀,比如 alpha 会被转换为 setAlpha:) getterString:必填项,业务中读取属性值时会用到。如果该字段为 nil ,则会在 fullTitle 的基础上自动生成(自动把 fullTitle 的第一个字母改成小写,比如 Alpha 会被转换为 alpha)。如果该字段为空字符串(比如 image_open_open)则属性值会被固定为 nil,attrType 会被指为 LookinAttrTypeCustomObj typeIfObj:当某个 LookinAttribute 确定是 NSObject 类型时,该方法返回它具体是什么对象,比如 UIColor、NSString enumList:如果某个 attribute 是 enum,则这里标识了相应的 enum 的名称(如 "NSTextAlignment"),业务可通过这个名称进而查询可用的枚举值列表 patch:如果为 YES,则用户修改了该 Attribute 的值后,Lookin 会重新拉取和更新相关图层的位置、截图等信息,如果为 nil 则默认是 NO hideIfNil:如果为 YES,则当获取的 value 为 nil 时,Lookin 不会传输该 attr。如果为 NO,则即使 value 为 nil 也会传输(比如 label 的 text 属性,即使它是 nil 我们也要显示,所以它的 hideIfNil 应该为 NO)。如果该字段为 nil 则默认是 NO osVersion: 该属性需要的最低的 iOS 版本,比如 safeAreaInsets 从 iOS 11.0 开始出现,则该属性应该为 @11,如果为 nil 则表示不限制 iOS 版本(注意 Lookin 项目仅支持 iOS 8.0+) */ + (NSDictionary *)_infoForAttrID:(LookinAttrIdentifier)attrID { static NSDictionary *> *dict; static dispatch_once_t onceToken; dispatch_once(&onceToken,^{ dict = @{ LookinAttr_Class_Class_Class: @{ @"className": @"CALayer", @"getterString": @"lks_relatedClassChainList", @"setterString": @"", @"typeIfObj": @(LookinAttrTypeCustomObj) }, LookinAttr_Relation_Relation_Relation: @{ @"className": @"CALayer", @"getterString": @"lks_selfRelation", @"setterString": @"", @"typeIfObj": @(LookinAttrTypeCustomObj), @"hideIfNil": @(YES) }, LookinAttr_Layout_Frame_Frame: @{ @"className": @"CALayer", @"fullTitle": @"Frame", @"patch": @(YES) }, LookinAttr_Layout_Bounds_Bounds: @{ @"className": @"CALayer", @"fullTitle": @"Bounds", @"patch": @(YES) }, LookinAttr_Layout_SafeArea_SafeArea: @{ @"className": @"UIView", @"fullTitle": @"SafeAreaInsets", @"setterString": @"", @"osVersion": @(11) }, LookinAttr_Layout_Position_Position: @{ @"className": @"CALayer", @"fullTitle": @"Position", @"patch": @(YES) }, LookinAttr_Layout_AnchorPoint_AnchorPoint: @{ @"className": @"CALayer", @"fullTitle": @"AnchorPoint", @"patch": @(YES) }, LookinAttr_AutoLayout_Hugging_Hor: @{ @"className": @"UIView", @"fullTitle": @"ContentHuggingPriority(Horizontal)", @"getterString": @"lks_horizontalContentHuggingPriority", @"setterString": @"setLks_horizontalContentHuggingPriority:", @"briefTitle": @"H", @"patch": @(YES) }, LookinAttr_AutoLayout_Hugging_Ver: @{ @"className": @"UIView", @"fullTitle": @"ContentHuggingPriority(Vertical)", @"getterString": @"lks_verticalContentHuggingPriority", @"setterString": @"setLks_verticalContentHuggingPriority:", @"briefTitle": @"V", @"patch": @(YES) }, LookinAttr_AutoLayout_Resistance_Hor: @{ @"className": @"UIView", @"fullTitle": @"ContentCompressionResistancePriority(Horizontal)", @"getterString": @"lks_horizontalContentCompressionResistancePriority", @"setterString": @"setLks_horizontalContentCompressionResistancePriority:", @"briefTitle": @"H", @"patch": @(YES) }, LookinAttr_AutoLayout_Resistance_Ver: @{ @"className": @"UIView", @"fullTitle": @"ContentCompressionResistancePriority(Vertical)", @"getterString": @"lks_verticalContentCompressionResistancePriority", @"setterString": @"setLks_verticalContentCompressionResistancePriority:", @"briefTitle": @"V", @"patch": @(YES) }, LookinAttr_AutoLayout_Constraints_Constraints: @{ @"className": @"UIView", @"getterString": @"lks_constraints", @"setterString": @"", @"typeIfObj": @(LookinAttrTypeCustomObj), @"hideIfNil": @(YES) }, LookinAttr_AutoLayout_IntrinsicSize_Size: @{ @"className": @"UIView", @"fullTitle": @"IntrinsicContentSize", @"setterString": @"" }, LookinAttr_ViewLayer_Visibility_Hidden: @{ @"className": @"CALayer", @"fullTitle": @"Hidden", @"getterString": @"isHidden", @"patch": @(YES) }, LookinAttr_ViewLayer_Visibility_Opacity: @{ @"className": @"CALayer", @"fullTitle": @"Opacity / Alpha", @"setterString": @"setOpacity:", @"getterString": @"opacity", @"patch": @(YES) }, LookinAttr_ViewLayer_InterationAndMasks_Interaction: @{ @"className": @"UIView", @"fullTitle": @"UserInteractionEnabled", @"getterString": @"isUserInteractionEnabled", @"patch": @(NO) }, LookinAttr_ViewLayer_InterationAndMasks_MasksToBounds: @{ @"className": @"CALayer", @"fullTitle": @"MasksToBounds / ClipsToBounds", @"briefTitle": @"MasksToBounds", @"setterString": @"setMasksToBounds:", @"getterString": @"masksToBounds", @"patch": @(YES) }, LookinAttr_ViewLayer_Corner_Radius: @{ @"className": @"CALayer", @"fullTitle": @"CornerRadius", @"briefTitle": @"", @"patch": @(YES) }, LookinAttr_ViewLayer_BgColor_BgColor: @{ @"className": @"CALayer", @"fullTitle": @"BackgroundColor", @"setterString": @"setLks_backgroundColor:", @"getterString": @"lks_backgroundColor", @"typeIfObj": @(LookinAttrTypeUIColor), @"patch": @(YES) }, LookinAttr_ViewLayer_Border_Color: @{ @"className": @"CALayer", @"fullTitle": @"BorderColor", @"setterString": @"setLks_borderColor:", @"getterString": @"lks_borderColor", @"typeIfObj": @(LookinAttrTypeUIColor), @"patch": @(YES) }, LookinAttr_ViewLayer_Border_Width: @{ @"className": @"CALayer", @"fullTitle": @"BorderWidth", @"patch": @(YES) }, LookinAttr_ViewLayer_Shadow_Color: @{ @"className": @"CALayer", @"fullTitle": @"ShadowColor", @"setterString": @"setLks_shadowColor:", @"getterString": @"lks_shadowColor", @"typeIfObj": @(LookinAttrTypeUIColor), @"patch": @(YES) }, LookinAttr_ViewLayer_Shadow_Opacity: @{ @"className": @"CALayer", @"fullTitle": @"ShadowOpacity", @"briefTitle": @"Opacity", @"patch": @(YES) }, LookinAttr_ViewLayer_Shadow_Radius: @{ @"className": @"CALayer", @"fullTitle": @"ShadowRadius", @"briefTitle": @"Radius", @"patch": @(YES) }, LookinAttr_ViewLayer_Shadow_OffsetW: @{ @"className": @"CALayer", @"fullTitle": @"ShadowOffsetWidth", @"briefTitle": @"OffsetW", @"setterString": @"setLks_shadowOffsetWidth:", @"getterString": @"lks_shadowOffsetWidth", @"patch": @(YES) }, LookinAttr_ViewLayer_Shadow_OffsetH: @{ @"className": @"CALayer", @"fullTitle": @"ShadowOffsetHeight", @"briefTitle": @"OffsetH", @"setterString": @"setLks_shadowOffsetHeight:", @"getterString": @"lks_shadowOffsetHeight", @"patch": @(YES) }, LookinAttr_ViewLayer_ContentMode_Mode: @{ @"className": @"UIView", @"fullTitle": @"ContentMode", @"enumList": @"UIViewContentMode", @"patch": @(YES) }, LookinAttr_ViewLayer_TintColor_Color: @{ @"className": @"UIView", @"fullTitle": @"TintColor", @"typeIfObj": @(LookinAttrTypeUIColor), @"patch": @(YES) }, LookinAttr_ViewLayer_TintColor_Mode: @{ @"className": @"UIView", @"fullTitle": @"TintAdjustmentMode", @"enumList": @"UIViewTintAdjustmentMode", @"patch": @(YES) }, LookinAttr_ViewLayer_Tag_Tag: @{ @"className": @"UIView", @"fullTitle": @"Tag", @"briefTitle": @"", @"patch": @(NO) }, LookinAttr_UIVisualEffectView_Style_Style: @{ @"className": @"UIVisualEffectView", @"setterString": @"setLks_blurEffectStyleNumber:", @"getterString": @"lks_blurEffectStyleNumber", @"enumList": @"UIBlurEffectStyle", @"typeIfObj": @(LookinAttrTypeCustomObj), @"patch": @(YES), @"hideIfNil": @(YES) }, LookinAttr_UIVisualEffectView_QMUIForegroundColor_Color: @{ @"className": @"QMUIVisualEffectView", @"fullTitle": @"ForegroundColor", @"typeIfObj": @(LookinAttrTypeUIColor), @"patch": @(YES), }, LookinAttr_UIImageView_Name_Name: @{ @"className": @"UIImageView", @"fullTitle": @"ImageName", @"setterString": @"", @"getterString": @"lks_imageSourceName", @"typeIfObj": @(LookinAttrTypeNSString), @"hideIfNil": @(YES) }, LookinAttr_UIImageView_Open_Open: @{ @"className": @"UIImageView", @"setterString": @"", @"getterString": @"lks_imageViewOidIfHasImage", @"typeIfObj": @(LookinAttrTypeCustomObj), @"hideIfNil": @(YES) }, LookinAttr_UILabel_Text_Text: @{ @"className": @"UILabel", @"fullTitle": @"Text", @"typeIfObj": @(LookinAttrTypeNSString), @"patch": @(YES) }, LookinAttr_UILabel_NumberOfLines_NumberOfLines: @{ @"className": @"UILabel", @"fullTitle": @"NumberOfLines", @"briefTitle": @"", @"patch": @(YES) }, LookinAttr_UILabel_Font_Size: @{ @"className": @"UILabel", @"fullTitle": @"FontSize", @"briefTitle": @"FontSize", @"setterString": @"setLks_fontSize:", @"getterString": @"lks_fontSize", @"patch": @(YES) }, LookinAttr_UILabel_Font_Name: @{ @"className": @"UILabel", @"fullTitle": @"FontName", @"setterString": @"", @"getterString": @"lks_fontName", @"typeIfObj": @(LookinAttrTypeNSString), @"patch": @(NO) }, LookinAttr_UILabel_TextColor_Color: @{ @"className": @"UILabel", @"fullTitle": @"TextColor", @"typeIfObj": @(LookinAttrTypeUIColor), @"patch": @(YES) }, LookinAttr_UILabel_Alignment_Alignment: @{ @"className": @"UILabel", @"fullTitle": @"TextAlignment", @"enumList": @"NSTextAlignment", @"patch": @(YES) }, LookinAttr_UILabel_BreakMode_Mode: @{ @"className": @"UILabel", @"fullTitle": @"LineBreakMode", @"enumList": @"NSLineBreakMode", @"patch": @(YES) }, LookinAttr_UILabel_CanAdjustFont_CanAdjustFont: @{ @"className": @"UILabel", @"fullTitle": @"AdjustsFontSizeToFitWidth", @"patch": @(YES) }, LookinAttr_UIControl_EnabledSelected_Enabled: @{ @"className": @"UIControl", @"fullTitle": @"Enabled", @"getterString": @"isEnabled", @"patch": @(NO) }, LookinAttr_UIControl_EnabledSelected_Selected: @{ @"className": @"UIControl", @"fullTitle": @"Selected", @"getterString": @"isSelected", @"patch": @(YES) }, LookinAttr_UIControl_VerAlignment_Alignment: @{ @"className": @"UIControl", @"fullTitle": @"ContentVerticalAlignment", @"enumList": @"UIControlContentVerticalAlignment", @"patch": @(YES) }, LookinAttr_UIControl_HorAlignment_Alignment: @{ @"className": @"UIControl", @"fullTitle": @"ContentHorizontalAlignment", @"enumList": @"UIControlContentHorizontalAlignment", @"patch": @(YES) }, LookinAttr_UIControl_QMUIOutsideEdge_Edge: @{ @"className": @"UIControl", @"fullTitle": @"qmui_outsideEdge" }, LookinAttr_UIButton_ContentInsets_Insets: @{ @"className": @"UIButton", @"fullTitle": @"ContentEdgeInsets", @"patch": @(YES) }, LookinAttr_UIButton_TitleInsets_Insets: @{ @"className": @"UIButton", @"fullTitle": @"TitleEdgeInsets", @"patch": @(YES) }, LookinAttr_UIButton_ImageInsets_Insets: @{ @"className": @"UIButton", @"fullTitle": @"ImageEdgeInsets", @"patch": @(YES) }, LookinAttr_UIScrollView_Offset_Offset: @{ @"className": @"UIScrollView", @"fullTitle": @"ContentOffset", @"patch": @(YES) }, LookinAttr_UIScrollView_ContentSize_Size: @{ @"className": @"UIScrollView", @"fullTitle": @"ContentSize", @"patch": @(YES) }, LookinAttr_UIScrollView_ContentInset_Inset: @{ @"className": @"UIScrollView", @"fullTitle": @"ContentInset", @"patch": @(YES) }, LookinAttr_UIScrollView_QMUIInitialInset_Inset: @{ @"className": @"UIScrollView", @"fullTitle": @"qmui_initialContentInset", @"patch": @(YES) }, LookinAttr_UIScrollView_AdjustedInset_Inset: @{ @"className": @"UIScrollView", @"fullTitle": @"AdjustedContentInset", @"setterString": @"", @"osVersion": @(11) }, LookinAttr_UIScrollView_Behavior_Behavior: @{ @"className": @"UIScrollView", @"fullTitle": @"ContentInsetAdjustmentBehavior", @"enumList": @"UIScrollViewContentInsetAdjustmentBehavior", @"patch": @(YES), @"osVersion": @(11) }, LookinAttr_UIScrollView_IndicatorInset_Inset: @{ @"className": @"UIScrollView", @"fullTitle": @"ScrollIndicatorInsets", @"patch": @(NO) }, LookinAttr_UIScrollView_ScrollPaging_ScrollEnabled: @{ @"className": @"UIScrollView", @"fullTitle": @"ScrollEnabled", @"getterString": @"isScrollEnabled", @"patch": @(NO) }, LookinAttr_UIScrollView_ScrollPaging_PagingEnabled: @{ @"className": @"UIScrollView", @"fullTitle": @"PagingEnabled", @"getterString": @"isPagingEnabled", @"patch": @(NO) }, LookinAttr_UIScrollView_Bounce_Ver: @{ @"className": @"UIScrollView", @"fullTitle": @"AlwaysBounceVertical", @"briefTitle": @"Vertical", @"patch": @(NO) }, LookinAttr_UIScrollView_Bounce_Hor: @{ @"className": @"UIScrollView", @"fullTitle": @"AlwaysBounceHorizontal", @"briefTitle": @"Horizontal", @"patch": @(NO) }, LookinAttr_UIScrollView_ShowsIndicator_Hor: @{ @"className": @"UIScrollView", @"fullTitle": @"ShowsHorizontalScrollIndicator", @"briefTitle": @"Horizontal", @"patch": @(NO) }, LookinAttr_UIScrollView_ShowsIndicator_Ver: @{ @"className": @"UIScrollView", @"fullTitle": @"ShowsVerticalScrollIndicator", @"briefTitle": @"Vertical", @"patch": @(NO) }, LookinAttr_UIScrollView_ContentTouches_Delay: @{ @"className": @"UIScrollView", @"fullTitle": @"DelaysContentTouches", @"patch": @(NO) }, LookinAttr_UIScrollView_ContentTouches_CanCancel: @{ @"className": @"UIScrollView", @"fullTitle": @"CanCancelContentTouches", @"patch": @(NO) }, LookinAttr_UIScrollView_Zoom_MinScale: @{ @"className": @"UIScrollView", @"fullTitle": @"MinimumZoomScale", @"briefTitle": @"MinScale", @"patch": @(NO) }, LookinAttr_UIScrollView_Zoom_MaxScale: @{ @"className": @"UIScrollView", @"fullTitle": @"MaximumZoomScale", @"briefTitle": @"MaxScale", @"patch": @(NO) }, LookinAttr_UIScrollView_Zoom_Scale: @{ @"className": @"UIScrollView", @"fullTitle": @"ZoomScale", @"briefTitle": @"Scale", @"patch": @(YES) }, LookinAttr_UIScrollView_Zoom_Bounce: @{ @"className": @"UIScrollView", @"fullTitle": @"BouncesZoom", @"patch": @(NO) }, LookinAttr_UITableView_Style_Style: @{ @"className": @"UITableView", @"fullTitle": @"Style", @"setterString": @"", @"enumList": @"UITableViewStyle", @"patch": @(YES) }, LookinAttr_UITableView_SectionsNumber_Number: @{ @"className": @"UITableView", @"fullTitle": @"NumberOfSections", @"setterString": @"", @"patch": @(YES) }, LookinAttr_UITableView_RowsNumber_Number: @{ @"className": @"UITableView", @"setterString": @"", @"getterString": @"lks_numberOfRows", @"typeIfObj": @(LookinAttrTypeCustomObj) }, LookinAttr_UITableView_SeparatorInset_Inset: @{ @"className": @"UITableView", @"fullTitle": @"SeparatorInset", @"patch": @(NO) }, LookinAttr_UITableView_SeparatorColor_Color: @{ @"className": @"UITableView", @"fullTitle": @"SeparatorColor", @"typeIfObj": @(LookinAttrTypeUIColor), @"patch": @(YES) }, LookinAttr_UITableView_SeparatorStyle_Style: @{ @"className": @"UITableView", @"fullTitle": @"SeparatorStyle", @"enumList": @"UITableViewCellSeparatorStyle", @"patch": @(YES) }, LookinAttr_UITextView_Text_Text: @{ @"className": @"UITextView", @"fullTitle": @"Text", @"typeIfObj": @(LookinAttrTypeNSString), @"patch": @(YES) }, LookinAttr_UITextView_Font_Name: @{ @"className": @"UITextView", @"fullTitle": @"FontName", @"setterString": @"", @"getterString": @"lks_fontName", @"typeIfObj": @(LookinAttrTypeNSString), @"patch": @(NO) }, LookinAttr_UITextView_Font_Size: @{ @"className": @"UITextView", @"fullTitle": @"FontSize", @"setterString": @"setLks_fontSize:", @"getterString": @"lks_fontSize", @"patch": @(YES) }, LookinAttr_UITextView_Basic_Editable: @{ @"className": @"UITextView", @"fullTitle": @"Editable", @"getterString": @"isEditable", @"patch": @(NO) }, LookinAttr_UITextView_Basic_Selectable: @{ @"className": @"UITextView", @"fullTitle": @"Selectable", @"getterString": @"isSelectable", @"patch": @(NO) }, LookinAttr_UITextView_TextColor_Color: @{ @"className": @"UITextView", @"fullTitle": @"TextColor", @"typeIfObj": @(LookinAttrTypeUIColor), @"patch": @(YES) }, LookinAttr_UITextView_Alignment_Alignment: @{ @"className": @"UITextView", @"fullTitle": @"TextAlignment", @"enumList": @"NSTextAlignment", @"patch": @(YES) }, LookinAttr_UITextView_ContainerInset_Inset: @{ @"className": @"UITextView", @"fullTitle": @"TextContainerInset", @"patch": @(YES) }, LookinAttr_UITextField_Font_Name: @{ @"className": @"UITextField", @"fullTitle": @"FontName", @"setterString": @"", @"getterString": @"lks_fontName", @"typeIfObj": @(LookinAttrTypeNSString), @"patch": @(NO) }, LookinAttr_UITextField_Font_Size: @{ @"className": @"UITextField", @"fullTitle": @"FontSize", @"setterString": @"setLks_fontSize:", @"getterString": @"lks_fontSize", @"patch": @(YES) }, LookinAttr_UITextField_TextColor_Color: @{ @"className": @"UITextField", @"fullTitle": @"TextColor", @"typeIfObj": @(LookinAttrTypeUIColor), @"patch": @(YES) }, LookinAttr_UITextField_Alignment_Alignment: @{ @"className": @"UITextField", @"fullTitle": @"TextAlignment", @"enumList": @"NSTextAlignment", @"patch": @(YES) }, LookinAttr_UITextField_Text_Text: @{ @"className": @"UITextField", @"fullTitle": @"Text", @"typeIfObj": @(LookinAttrTypeNSString), @"patch": @(YES) }, LookinAttr_UITextField_Placeholder_Placeholder: @{ @"className": @"UITextField", @"fullTitle": @"Placeholder", @"typeIfObj": @(LookinAttrTypeNSString), @"patch": @(YES) }, LookinAttr_UITextField_Clears_ClearsOnBeginEditing: @{ @"className": @"UITextField", @"fullTitle": @"ClearsOnBeginEditing", @"patch": @(NO) }, LookinAttr_UITextField_Clears_ClearsOnInsertion: @{ @"className": @"UITextField", @"fullTitle": @"ClearsOnInsertion", @"patch": @(NO) }, LookinAttr_UITextField_CanAdjustFont_CanAdjustFont: @{ @"className": @"UITextField", @"fullTitle": @"AdjustsFontSizeToFitWidth", @"patch": @(YES) }, LookinAttr_UITextField_CanAdjustFont_MinSize: @{ @"className": @"UITextField", @"fullTitle": @"MinimumFontSize", @"patch": @(YES) }, LookinAttr_UITextField_ClearButtonMode_Mode: @{ @"className": @"UITextField", @"fullTitle": @"ClearButtonMode", @"enumList": @"UITextFieldViewMode", @"patch": @(NO) }, }; }); NSDictionary *targetInfo = dict[attrID]; return targetInfo; } + (LookinAttrType)objectAttrTypeWithAttrID:(LookinAttrIdentifier)attrID { NSDictionary *attrInfo = [self _infoForAttrID:attrID]; NSNumber *typeIfObj = attrInfo[@"typeIfObj"]; return [typeIfObj integerValue]; } + (NSString *)classNameWithAttrID:(LookinAttrIdentifier)attrID { NSDictionary *attrInfo = [self _infoForAttrID:attrID]; NSString *className = attrInfo[@"className"]; NSAssert(className.length > 0, @""); return className; } + (BOOL)isUIViewPropertyWithAttrID:(LookinAttrIdentifier)attrID { NSString *className = [self classNameWithAttrID:attrID]; if ([className isEqualToString:@"CALayer"]) { return NO; } return YES; } + (NSString *)enumListNameWithAttrID:(LookinAttrIdentifier)attrID { NSDictionary *attrInfo = [self _infoForAttrID:attrID]; NSString *name = attrInfo[@"enumList"]; return name; } + (BOOL)needPatchAfterModificationWithAttrID:(LookinAttrIdentifier)attrID { NSDictionary *attrInfo = [self _infoForAttrID:attrID]; NSNumber *needPatch = attrInfo[@"patch"]; return [needPatch boolValue]; } + (NSString *)fullTitleWithAttrID:(LookinAttrIdentifier)attrID { NSDictionary *attrInfo = [self _infoForAttrID:attrID]; NSString *fullTitle = attrInfo[@"fullTitle"]; return fullTitle; } + (NSString *)briefTitleWithAttrID:(LookinAttrIdentifier)attrID { NSDictionary *attrInfo = [self _infoForAttrID:attrID]; NSString *briefTitle = attrInfo[@"briefTitle"]; if (!briefTitle) { briefTitle = attrInfo[@"fullTitle"]; } return briefTitle; } + (SEL)getterWithAttrID:(LookinAttrIdentifier)attrID { NSDictionary *attrInfo = [self _infoForAttrID:attrID]; NSString *getterString = attrInfo[@"getterString"]; if (getterString && getterString.length == 0) { // 空字符串,比如 image_open_open return nil; } if (!getterString) { NSString *fullTitle = attrInfo[@"fullTitle"]; NSAssert(fullTitle.length > 0, @""); getterString = [NSString stringWithFormat:@"%@%@", [fullTitle substringToIndex:1].lowercaseString, [fullTitle substringFromIndex:1]].copy; } return NSSelectorFromString(getterString); } + (SEL)setterWithAttrID:(LookinAttrIdentifier)attrID { NSDictionary *attrInfo = [self _infoForAttrID:attrID]; NSString *setterString = attrInfo[@"setterString"]; if ([setterString isEqualToString:@""]) { // 该属性不可在 Lookin 客户端中被修改 return nil; } if (!setterString) { NSString *fullTitle = attrInfo[@"fullTitle"]; NSAssert(fullTitle.length > 0, @""); setterString = [NSString stringWithFormat:@"set%@%@:", [fullTitle substringToIndex:1].uppercaseString, [fullTitle substringFromIndex:1]]; } return NSSelectorFromString(setterString); } + (BOOL)hideIfNilWithAttrID:(LookinAttrIdentifier)attrID { NSDictionary *attrInfo = [self _infoForAttrID:attrID]; NSNumber *boolValue = attrInfo[@"hideIfNil"]; return boolValue.boolValue; } + (NSInteger)minAvailableOSVersionWithAttrID:(LookinAttrIdentifier)attrID { NSDictionary *attrInfo = [self _infoForAttrID:attrID]; NSNumber *minVerNum = attrInfo[@"osVersion"]; NSInteger minVer = [minVerNum integerValue]; return minVer; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinDefines.h ================================================ // // LookinMessageProtocol.h // Lookin // // Created by Li Kai on 2018/8/6. // https://lookin.work // #import "TargetConditionals.h" #if TARGET_OS_IPHONE #import #elif TARGET_OS_MAC #import #endif #include #pragma mark - Version /// 当前 LookinServer 的版本 static const int LOOKIN_SERVER_VERSION = 7; static NSString * const LOOKIN_SERVER_READABLE_VERSION = @"1.0.5"; /// 当前 LookinClient 的版本 static const int LOOKIN_CLIENT_VERSION = 7; /// 当前 LookinServer 和 LookinClient 是否是内部实验版本 static BOOL LOOKIN_SERVER_IS_EXPERIMENTAL = NO; static BOOL LOOKIN_CLIENT_IS_EXPERIMENTAL = NO; /// 当前 Lookin 客户端所支持的 LookinServer 的最低版本 static const int LOOKIN_SUPPORTED_SERVER_MIN = 7; /// 当前 Lookin 客户端所支持的 LookinServer 的最高版本 static const int LOOKIN_SUPPORTED_SERVER_MAX = 7; /// 标记该 LookinServer 是通过什么方式安装的,0:未知,1:CocoaPods,2:手动,3:源代码,4:断点 static const int LOOKIN_SERVER_SETUP_TYPE = 1; #pragma mark - Links static NSString * const LOOKIN_SERVER_FRAMEWORK_URL = @"https://lookin.work/download/framework/LookinServer-1-0-0.zip"; #pragma mark - Connection /// LookinServer 在真机上会依次尝试监听 47175 ~ 47179 这几个端口 static const int LookinUSBDeviceIPv4PortNumberStart = 47175; static const int LookinUSBDeviceIPv4PortNumberEnd = 47179; /// LookinServer 在模拟器中会依次尝试监听 47164 ~ 47169 这几个端口 static const int LookinSimulatorIPv4PortNumberStart = 47164; static const int LookinSimulatorIPv4PortNumberEnd = 47169; enum { /// 确认两端是否可以响应通讯 LookinRequestTypePing = 200, /// 请求 App 的截图、设备型号等信息 LookinRequestTypeApp = 201, /// 请求 Hierarchy 信息 LookinRequestTypeHierarchy = 202, /// 请求 screenshots 和 attrGroups 信息 LookinRequestTypeHierarchyDetails = 203, /// 请求修改某个 Attribute 的值 LookinRequestTypeModification = 204, /// 修改某个 attr 后,请求一系列最新的 Screenshots、属性值等信息 LookinRequestTypeAttrModificationPatch = 205, /// 执行某个方法 LookinRequestTypeInvokeMethod = 206, /** @request: @{@"oid":} @response: LookinObject * */ LookinRequestTypeFetchObject = 207, LookinRequestTypeFetchImageViewImage = 208, LookinRequestTypeModifyRecognizerEnable = 209, /// 请求 attribute group list LookinRequestTypeAllAttrGroups = 210, /// 请求 iOS App 里所有的 class 名字列表和监听中的方法列表 LookinRequestTypeClassesAndMethodTraceLit = 212, /// 请求 iOS App 里某个 class 的所有 selector 名字列表(包括 superclass) LookinRequestTypeAllSelectorNames = 213, // 增加 methodTrace LookinRequestTypeAddMethodTrace = 214, // 删除 methodTrace LookinRequestTypeDeleteMethodTrace = 215, LookinPush_BringForwardScreenshotTask = 303, // 用户在 Lookin 客户端取消了之前 HierarchyDetails 的拉取 LookinPush_CanceHierarchyDetails = 304, /// iOS 端推送 method trace 信息 LookinPush_MethodTraceRecord = 403 }; static NSString * const LookinParam_ViewLayerTag = @"tag"; static NSString * const LookinParam_SelectorName = @"sn"; static NSString * const LookinParam_MethodType = @"mt"; static NSString * const LookinParam_SelectorClassName = @"scn"; static NSString * const LookinStringFlag_VoidReturn = @"LOOKIN_TAG_RETURN_VALUE_VOID"; #pragma mark - Error static NSString * const LookinErrorDomain = @"LookinError"; enum { LookinErrCode_Default = -400, /// Lookin 内部业务逻辑错误 LookinErrCode_Inner = -401, /// PeerTalk 内部错误 LookinErrCode_PeerTalk = -402, /// 连接不存在或已断开 LookinErrCode_NoConnect = -403, /// ping 失败了,原因是 ping 请求超时 LookinErrCode_PingFailForTimeout = -404, /// 请求超时未返回 LookinErrCode_Timeout = -405, /// 有相同 Type 的新请求被发出,因此旧请求被丢弃 LookinErrCode_Discard = -406, /// ping 失败了,原因是 app 主动报告自身正处于后台模式 LookinErrCode_PingFailForBackgroundState = -407, /// 没有找到对应的对象,可能已被释放 LookinErrCode_ObjectNotFound = -500, /// 不支持修改当前类型的 LookinCodingValueType LookinErrCode_ModifyValueTypeInvalid = -501, LookinErrCode_Exception = -502, // LookinServer 版本过高,要升级 client LookinErrCode_ServerVersionTooHigh = -600, // LookinServer 版本过低,要升级 server LookinErrCode_ServerVersionTooLow = -601, // LookinServer 是私有版本,但 client 是现网版本 LookinErrCode_ServerIsPrivate = -602, // LookinServer 是现网版本,但 client 是私有版本 LookinErrCode_ClientIsPrivate = - 603, // 不支持的文件类型 LookinErrCode_UnsupportedFileType = -700, }; #define LookinErr_ObjNotFound [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_ObjectNotFound userInfo:@{NSLocalizedDescriptionKey:NSLocalizedString(@"Failed to get target object in iOS app", nil), NSLocalizedRecoverySuggestionErrorKey:NSLocalizedString(@"Perhaps the related object was deallocated. You can reload Lookin to get newest data.", nil)}] #define LookinErr_NoConnect [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_NoConnect userInfo:@{NSLocalizedDescriptionKey:NSLocalizedString(@"The operation failed due to disconnection with the iOS app.", nil)}] #define LookinErr_Inner [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_Inner userInfo:@{NSLocalizedDescriptionKey:NSLocalizedString(@"The operation failed due to an inner error.", nil)}] #define LookinErrorMake(errorTitle, errorDetail) [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_Default userInfo:@{NSLocalizedDescriptionKey:errorTitle, NSLocalizedRecoverySuggestionErrorKey:errorDetail}] #define LookinErrorText_Timeout NSLocalizedString(@"Perhaps your iOS app is paused with breakpoint in Xcode, blocked by other tasks in main thread, or moved to background state.", nil) #pragma mark - Colors #if TARGET_OS_IPHONE #define LookinColor UIColor #define LookinInsets UIEdgeInsets #define LookinImage UIImage #elif TARGET_OS_MAC #define LookinColor NSColor #define LookinInsets NSEdgeInsets #define LookinImage NSImage #endif #define LookinColorRGBAMake(r, g, b, a) [LookinColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a] #define LookinColorMake(r, g, b) [LookinColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1] #pragma mark - Preview /// SCNNode 所允许的图片的最大的长和宽,单位是 px,这个值是 Scenekit 自身指定的 static const double LookinNodeImageMaxLengthInPx = 16384; typedef NS_OPTIONS(NSUInteger, LookinPreviewBitMask) { LookinPreviewBitMask_None = 0, LookinPreviewBitMask_Selectable = 1 << 1, LookinPreviewBitMask_Unselectable = 1 << 2, LookinPreviewBitMask_HasLight = 1 << 3, LookinPreviewBitMask_NoLight = 1 << 4 }; ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinDisplayItem.h ================================================ // // LookinDisplayItem.h // qmuidemo // // Created by Li Kai on 2018/11/15. // Copyright © 2018 QMUI Team. All rights reserved. // #import "TargetConditionals.h" #import "LookinObject.h" #import "LookinDefines.h" #if TARGET_OS_IPHONE #import #elif TARGET_OS_MAC #import #endif @class LookinAttributesGroup, LookinIvarTrace, LookinPreviewItemLayer, LookinEventHandler, LookinDisplayItemNode, LookinDisplayItem; typedef NS_ENUM(NSUInteger, LookinDisplayItemImageEncodeType) { LookinDisplayItemImageEncodeTypeNone, // 不进行 encode LookinDisplayItemImageEncodeTypeNSData, // 转换为 NSData LookinDisplayItemImageEncodeTypeImage // 使用 NSImage / UIImage 自身的 encode 方法 }; typedef NS_ENUM(NSUInteger, LookinDisplayItemProperty) { // 当初次设置 delegate 对象时,会立即以该值触发一次 displayItem:propertyDidChange: LookinDisplayItemProperty_None, LookinDisplayItemProperty_FrameToRoot, LookinDisplayItemProperty_DisplayingInHierarchy, LookinDisplayItemProperty_InHiddenHierarchy, LookinDisplayItemProperty_IsExpandable, LookinDisplayItemProperty_IsExpanded, LookinDisplayItemProperty_SoloScreenshot, LookinDisplayItemProperty_GroupScreenshot, LookinDisplayItemProperty_IsSelected, LookinDisplayItemProperty_IsHovered, LookinDisplayItemProperty_AvoidSyncScreenshot, LookinDisplayItemProperty_InNoPreviewHierarchy, LookinDisplayItemProperty_IsInSearch, LookinDisplayItemProperty_HighlightedSearchString, }; @protocol LookinDisplayItemDelegate - (void)displayItem:(LookinDisplayItem *)displayItem propertyDidChange:(LookinDisplayItemProperty)property; @end @interface LookinDisplayItem : NSObject @property(nonatomic, copy) NSArray *subitems; @property(nonatomic, assign) BOOL isHidden; @property(nonatomic, assign) float alpha; @property(nonatomic, assign) CGRect frame; @property(nonatomic, assign) CGRect bounds; /// 不存在 subitems 时,该属性的值为 nil @property(nonatomic, strong) LookinImage *soloScreenshot; /// 无论是否存在 subitems,该属性始终存在 @property(nonatomic, strong) LookinImage *groupScreenshot; @property(nonatomic, strong) LookinObject *viewObject; @property(nonatomic, strong) LookinObject *layerObject; @property(nonatomic, strong) LookinObject *hostViewControllerObject; /// attrGroups 列表 @property(nonatomic, copy) NSArray *attributesGroupList; @property(nonatomic, copy) NSArray *eventHandlers; // 如果当前 item 代表 UIWindow 且是 keyWindow,则该属性为 YES @property(nonatomic, assign) BOOL representedAsKeyWindow; @property(nonatomic, weak) id previewItemDelegate; @property(nonatomic, weak) id rowViewDelegate; /// view 或 layer 的 backgroundColor,利用该属性来提前渲染 node 的背景色,使得用户感觉加载的快一点 /// 注意有一个缺点是,理论上应该像 screenshot 一样拆成 soloBackgroundColor 和 groupBackgroundColor,这里的 backgroundColor 实际上是 soloBackgroundColor,因此某些场景的显示会有瑕疵 @property(nonatomic, strong) LookinColor *backgroundColor; #pragma mark - No Encode/Decode /// 父节点 @property(nonatomic, weak) LookinDisplayItem *superItem; /// 如果存在 viewObject 则返回 viewObject,否则返回 layerObject - (LookinObject *)displayingObject; /// 该 item 在左侧 hierarchy 中显示的字符串,现在则返回其 ShortClassName @property(nonatomic, copy, readonly) NSString *title; @property(nonatomic, copy, readonly) NSString *subtitle; /// 在 hierarchy 中的层级,比如顶层的 UIWindow.indentLevel 为 0,UIWindow 的 subitem 的 indentLevel 为 1 - (NSInteger)indentLevel; /// className 以 “UI”、“CA” 等开头时认为是系统类,该属性将返回 YES @property(nonatomic, assign, readonly) BOOL representedForSystemClass; /** 该项是否被展开 @note 假如自己没有被折叠,但是 superItem 被折叠了,则自己仍然不会被看到,但是 self.isExpanded 值仍然为 NO @note 如果 item 没有 subitems,则该值没有意义 */ @property(nonatomic, assign) BOOL isExpanded; /// 如果有 subitems,则该属性返回 YES,否则返回 NO @property(nonatomic, assign, readonly) BOOL isExpandable; /** 是否能在 hierarchy panel 上被看到,假如有任意一层的父级元素的 isExpanded 为 NO,则 displayingInHierarchy 为 NO。如果所有父级元素的 isExpanded 均为 YES,则 displayingInHierarchy 为 YES */ @property(nonatomic, assign, readonly) BOOL displayingInHierarchy; /** 如果自身或任意一个上层元素的 isHidden 为 YES 或 alpha 为 0,则该属性返回 YES */ @property(nonatomic, assign, readonly) BOOL inHiddenHierarchy; /// 返回 soloScreenshot 或 groupScreenshot - (LookinImage *)appropriateScreenshot; @property(nonatomic, assign) LookinDisplayItemImageEncodeType screenshotEncodeType; /// 如果该属性为 YES 则不会自动同步该 item 的图像(比如该 item 尺寸过大) @property(nonatomic, assign) BOOL avoidSyncScreenshot; @property(nonatomic, weak) LookinPreviewItemLayer *previewLayer; @property(nonatomic, weak) LookinDisplayItemNode *previewNode; /// 如果该值为 YES,则该 item 及所有子 item 均不会在 preview 中被显示出来,只能在 hierarchy 中选择。默认为 NO @property(nonatomic, assign) BOOL noPreview; /// 如果自身或某个上级元素的 noPreview 值为 YES,则该方法返回 YES @property(nonatomic, assign, readonly) BOOL inNoPreviewHierarchy; /// 当小于 0 时表示未被设置 @property(nonatomic, assign) NSInteger previewZIndex; /** 在当前 hierarchy 的最顶层的 item 的坐标系中,该 item 的 frame 值 */ @property(nonatomic, assign, readonly) CGRect frameToRoot; /// 遍历自身和所有上级元素 - (void)enumerateSelfAndAncestors:(void (^)(LookinDisplayItem *item, BOOL *stop))block; - (void)enumerateAncestors:(void (^)(LookinDisplayItem *item, BOOL *stop))block; /// 遍历自身后所有下级元素 - (void)enumerateSelfAndChildren:(void (^)(LookinDisplayItem *item))block; @property(nonatomic, assign) BOOL preferToBeCollapsed; @property(nonatomic, assign) BOOL isSelected; @property(nonatomic, assign) BOOL isHovered; - (BOOL)itemIsKindOfClassWithName:(NSString *)className; - (BOOL)itemIsKindOfClassesWithNames:(NSSet *)classNames; /// 根据 subItems 属性将 items 打平为一维数组 + (NSArray *)flatItemsFromHierarchicalItems:(NSArray *)items; @property(nonatomic, assign) BOOL hasDeterminedExpansion; /// 在 string 这个搜索词下,如果该 displayItem 应该被搜索到,则该方法返回 YES。 /// string 字段不能为 nil 或空字符串 - (BOOL)isMatchedWithSearchString:(NSString *)string; /// 设置当前是否处于搜索状态 @property(nonatomic, assign) BOOL isInSearch; /// 因为搜索而应该被高亮的字符串 @property(nonatomic, copy) NSString *highlightedSearchString; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinDisplayItem.m ================================================ // // LookinDisplayItem.m // qmuidemo // // Created by Li Kai on 2018/11/15. // Copyright © 2018 QMUI Team. All rights reserved. // #import "LookinDisplayItem.h" #import "LookinAttributesGroup.h" #import "LookinAttributesSection.h" #import "LookinAttribute.h" #import "LookinEventHandler.h" #import "LookinIvarTrace.h" #import "NSArray+Lookin.h" #import "NSObject+Lookin.h" #if TARGET_OS_IPHONE #import "UIColor+LookinServer.h" #import "UIImage+LookinServer.h" #elif TARGET_OS_MAC #ifdef DEBUG #import "LKEfficiencyMonitor.h" #endif #endif @interface LookinDisplayItem () @property(nonatomic, assign, readwrite) CGRect frameToRoot; @property(nonatomic, assign, readwrite) BOOL inNoPreviewHierarchy; @property(nonatomic, assign) NSInteger indentLevel; @property(nonatomic, assign, readwrite) BOOL isExpandable; @property(nonatomic, assign, readwrite) BOOL inHiddenHierarchy; @property(nonatomic, assign, readwrite) BOOL displayingInHierarchy; @end @implementation LookinDisplayItem #pragma mark - - (id)copyWithZone:(NSZone *)zone { LookinDisplayItem *newDisplayItem = [[LookinDisplayItem allocWithZone:zone] init]; newDisplayItem.subitems = [self.subitems lookin_map:^id(NSUInteger idx, LookinDisplayItem *value) { return value.copy; }]; newDisplayItem.isHidden = self.isHidden; newDisplayItem.alpha = self.alpha; newDisplayItem.frame = self.frame; newDisplayItem.bounds = self.bounds; newDisplayItem.soloScreenshot = self.soloScreenshot; newDisplayItem.groupScreenshot = self.groupScreenshot; newDisplayItem.viewObject = self.viewObject.copy; newDisplayItem.layerObject = self.layerObject.copy; newDisplayItem.hostViewControllerObject = self.hostViewControllerObject.copy; newDisplayItem.attributesGroupList = [self.attributesGroupList lookin_map:^id(NSUInteger idx, LookinAttributesGroup *value) { return value.copy; }]; newDisplayItem.eventHandlers = [self.eventHandlers lookin_map:^id(NSUInteger idx, LookinEventHandler *value) { return value.copy; }]; newDisplayItem.representedAsKeyWindow = self.representedAsKeyWindow; [newDisplayItem _updateDisplayingInHierarchyProperty]; return newDisplayItem; } #pragma mark - - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.subitems forKey:@"subitems"]; [aCoder encodeBool:self.isHidden forKey:@"hidden"]; [aCoder encodeFloat:self.alpha forKey:@"alpha"]; [aCoder encodeObject:self.viewObject forKey:@"viewObject"]; [aCoder encodeObject:self.layerObject forKey:@"layerObject"]; [aCoder encodeObject:self.hostViewControllerObject forKey:@"hostViewControllerObject"]; [aCoder encodeObject:self.attributesGroupList forKey:@"attributesGroupList"]; [aCoder encodeBool:self.representedAsKeyWindow forKey:@"representedAsKeyWindow"]; [aCoder encodeObject:self.eventHandlers forKey:@"eventHandlers"]; if (self.screenshotEncodeType == LookinDisplayItemImageEncodeTypeNSData) { [aCoder encodeObject:[self.soloScreenshot lookin_encodedObjectWithType:LookinCodingValueTypeImage] forKey:@"soloScreenshot"]; [aCoder encodeObject:[self.groupScreenshot lookin_encodedObjectWithType:LookinCodingValueTypeImage] forKey:@"groupScreenshot"]; } else if (self.screenshotEncodeType == LookinDisplayItemImageEncodeTypeImage) { [aCoder encodeObject:self.soloScreenshot forKey:@"soloScreenshot"]; [aCoder encodeObject:self.groupScreenshot forKey:@"groupScreenshot"]; } #if TARGET_OS_IPHONE [aCoder encodeCGRect:self.frame forKey:@"frame"]; [aCoder encodeCGRect:self.bounds forKey:@"bounds"]; [aCoder encodeObject:self.backgroundColor.lks_rgbaComponents forKey:@"backgroundColor"]; #elif TARGET_OS_MAC [aCoder encodeRect:self.frame forKey:@"frame"]; [aCoder encodeRect:self.bounds forKey:@"bounds"]; [aCoder encodeObject:self.backgroundColor.lk_rgbaComponents forKey:@"backgroundColor"]; #endif } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.subitems = [aDecoder decodeObjectForKey:@"subitems"]; self.isHidden = [aDecoder decodeBoolForKey:@"hidden"]; self.alpha = [aDecoder decodeFloatForKey:@"alpha"]; self.viewObject = [aDecoder decodeObjectForKey:@"viewObject"]; self.layerObject = [aDecoder decodeObjectForKey:@"layerObject"]; self.hostViewControllerObject = [aDecoder decodeObjectForKey:@"hostViewControllerObject"]; self.attributesGroupList = [aDecoder decodeObjectForKey:@"attributesGroupList"]; self.representedAsKeyWindow = [aDecoder decodeBoolForKey:@"representedAsKeyWindow"]; id soloScreenshotObj = [aDecoder decodeObjectForKey:@"soloScreenshot"]; if (soloScreenshotObj) { if ([soloScreenshotObj isKindOfClass:[NSData class]]) { self.soloScreenshot = [soloScreenshotObj lookin_decodedObjectWithType:LookinCodingValueTypeImage]; } else if ([soloScreenshotObj isKindOfClass:[LookinImage class]]) { self.soloScreenshot = soloScreenshotObj; } else { NSAssert(NO, @""); } } id groupScreenshotObj = [aDecoder decodeObjectForKey:@"groupScreenshot"]; if (groupScreenshotObj) { if ([groupScreenshotObj isKindOfClass:[NSData class]]) { self.groupScreenshot = [groupScreenshotObj lookin_decodedObjectWithType:LookinCodingValueTypeImage]; } else if ([groupScreenshotObj isKindOfClass:[LookinImage class]]) { self.groupScreenshot = groupScreenshotObj; } else { NSAssert(NO, @""); } } self.eventHandlers = [aDecoder decodeObjectForKey:@"eventHandlers"]; #if TARGET_OS_IPHONE self.frame = [aDecoder decodeCGRectForKey:@"frame"]; self.bounds = [aDecoder decodeCGRectForKey:@"bounds"]; self.backgroundColor = [UIColor lks_colorFromRGBAComponents:[aDecoder decodeObjectForKey:@"backgroundColor"]]; #elif TARGET_OS_MAC self.frame = [aDecoder decodeRectForKey:@"frame"]; self.bounds = [aDecoder decodeRectForKey:@"bounds"]; self.backgroundColor = [NSColor lk_colorFromRGBAComponents:[aDecoder decodeObjectForKey:@"backgroundColor"]]; #ifdef DEBUG [[LKEfficiencyMonitor sharedInstance] displayItemDidInit]; #endif #endif [self _updateDisplayingInHierarchyProperty]; } return self; } + (BOOL)supportsSecureCoding { return YES; } - (instancetype)init { if (self = [super init]) { /// 在手机端,displayItem 被创建时会调用这个方法 [self _updateDisplayingInHierarchyProperty]; #ifdef DEBUG #if TARGET_OS_IPHONE #elif TARGET_OS_MAC [[LKEfficiencyMonitor sharedInstance] displayItemDidInit]; #endif #endif } return self; } - (LookinObject *)displayingObject { return self.viewObject ? : self.layerObject; } - (void)setAttributesGroupList:(NSSet *)attributesGroupList { _attributesGroupList = [attributesGroupList copy]; [attributesGroupList enumerateObjectsUsingBlock:^(LookinAttributesGroup * _Nonnull group, BOOL * _Nonnull stop) { [group.attrSections enumerateObjectsUsingBlock:^(LookinAttributesSection * _Nonnull section, NSUInteger idx, BOOL * _Nonnull stop) { [section.attributes enumerateObjectsUsingBlock:^(LookinAttribute * _Nonnull attr, NSUInteger idx, BOOL * _Nonnull stop) { attr.targetDisplayItem = self; }]; }]; }]; } - (BOOL)representedForSystemClass { return [self.title hasPrefix:@"UI"] || [self.title hasPrefix:@"CA"] || [self.title hasPrefix:@"_"]; } - (BOOL)itemIsKindOfClassWithName:(NSString *)className { if (!className) { NSAssert(NO, @""); return NO; } return [self itemIsKindOfClassesWithNames:[NSSet setWithObject:className]]; } - (BOOL)itemIsKindOfClassesWithNames:(NSSet *)targetClassNames { if (!targetClassNames.count) { return NO; } LookinObject *selfObj = self.viewObject ? : self.layerObject; if (!selfObj) { return NO; } __block BOOL boolValue = NO; [targetClassNames enumerateObjectsUsingBlock:^(NSString * _Nonnull targetClassName, BOOL * _Nonnull stop_outer) { [selfObj.classChainList enumerateObjectsUsingBlock:^(NSString * _Nonnull selfClass, NSUInteger idx, BOOL * _Nonnull stop_inner) { NSString *nonPrefixSelfClass = [selfClass componentsSeparatedByString:@"."].lastObject; if ([nonPrefixSelfClass isEqualToString:targetClassName]) { boolValue = YES; *stop_inner = YES; } }]; if (boolValue) { *stop_outer = YES; } }]; return boolValue; } - (void)setSubitems:(NSArray *)subitems { [_subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.superItem = nil; }]; _subitems = subitems; self.isExpandable = (subitems.count > 0); [subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSAssert(!obj.superItem, @""); obj.superItem = self; [obj _updateInHiddenHierarchyProperty]; [obj _updateDisplayingInHierarchyProperty]; [obj _updateFrameToRoot]; }]; } - (void)setIsExpandable:(BOOL)isExpandable { if (_isExpandable == isExpandable) { return; } _isExpandable = isExpandable; [self _notifyDelegatesWith:LookinDisplayItemProperty_IsExpandable]; } - (void)setIsExpanded:(BOOL)isExpanded { if (_isExpanded == isExpanded) { return; } _isExpanded = isExpanded; [self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [obj _updateDisplayingInHierarchyProperty]; }]; [self _notifyDelegatesWith:LookinDisplayItemProperty_IsExpanded]; } - (void)setSoloScreenshot:(LookinImage *)soloScreenshot { if (_soloScreenshot == soloScreenshot) { return; } _soloScreenshot = soloScreenshot; [self _notifyDelegatesWith:LookinDisplayItemProperty_SoloScreenshot]; } - (void)setIsSelected:(BOOL)isSelected { if (_isSelected == isSelected) { return; } _isSelected = isSelected; [self _notifyDelegatesWith:LookinDisplayItemProperty_IsSelected]; } - (void)setAvoidSyncScreenshot:(BOOL)avoidSyncScreenshot { if (_avoidSyncScreenshot == avoidSyncScreenshot) { return; } _avoidSyncScreenshot = avoidSyncScreenshot; [self _notifyDelegatesWith:LookinDisplayItemProperty_AvoidSyncScreenshot]; } - (void)setIsHovered:(BOOL)isHovered { if (_isHovered == isHovered) { return; } _isHovered = isHovered; [self _notifyDelegatesWith:LookinDisplayItemProperty_IsHovered]; } - (void)setGroupScreenshot:(LookinImage *)groupScreenshot { if (_groupScreenshot == groupScreenshot) { return; } _groupScreenshot = groupScreenshot; [self _notifyDelegatesWith:LookinDisplayItemProperty_GroupScreenshot]; } - (void)setDisplayingInHierarchy:(BOOL)displayingInHierarchy { if (_displayingInHierarchy == displayingInHierarchy) { return; } _displayingInHierarchy = displayingInHierarchy; [self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [obj _updateDisplayingInHierarchyProperty]; }]; [self _notifyDelegatesWith:LookinDisplayItemProperty_DisplayingInHierarchy]; } - (void)_updateDisplayingInHierarchyProperty { if (self.superItem && (!self.superItem.displayingInHierarchy || !self.superItem.isExpanded)) { self.displayingInHierarchy = NO; } else { self.displayingInHierarchy = YES; } } - (void)setIsHidden:(BOOL)isHidden { _isHidden = isHidden; [self _updateInHiddenHierarchyProperty]; } - (void)setAlpha:(float)alpha { _alpha = alpha; [self _updateInHiddenHierarchyProperty]; } - (void)setInHiddenHierarchy:(BOOL)inHiddenHierarchy { if (_inHiddenHierarchy == inHiddenHierarchy) { return; } _inHiddenHierarchy = inHiddenHierarchy; [self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [obj _updateInHiddenHierarchyProperty]; }]; [self _notifyDelegatesWith:LookinDisplayItemProperty_InHiddenHierarchy]; } - (void)_updateInHiddenHierarchyProperty { if (self.superItem.inHiddenHierarchy || self.isHidden || self.alpha <= 0) { self.inHiddenHierarchy = YES; } else { self.inHiddenHierarchy = NO; } } - (void)enumerateSelfAndAncestors:(void (^)(LookinDisplayItem *, BOOL *))block { if (!block) { return; } LookinDisplayItem *item = self; while (item) { BOOL shouldStop = NO; block(item, &shouldStop); if (shouldStop) { break; } item = item.superItem; } } - (void)enumerateAncestors:(void (^)(LookinDisplayItem *, BOOL *))block { [self.superItem enumerateSelfAndAncestors:block]; } - (void)enumerateSelfAndChildren:(void (^)(LookinDisplayItem *item))block { if (!block) { return; } block(self); [self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull subitem, NSUInteger idx, BOOL * _Nonnull stop) { [subitem enumerateSelfAndChildren:block]; }]; } + (NSArray *)flatItemsFromHierarchicalItems:(NSArray *)items { NSMutableArray *resultArray = [NSMutableArray array]; [items enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (obj.superItem) { obj.indentLevel = obj.superItem.indentLevel + 1; } [resultArray addObject:obj]; if (obj.subitems.count) { [resultArray addObjectsFromArray:[self flatItemsFromHierarchicalItems:obj.subitems]]; } }]; return resultArray; } - (NSString *)description { return [NSString stringWithFormat:@"%@", self.title]; } - (void)setFrameToRoot:(CGRect)frameToRoot { if (CGRectEqualToRect(_frameToRoot, frameToRoot)) { return; } _frameToRoot = frameToRoot; [(NSArray *)self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [obj _updateFrameToRoot]; [obj _updateInNoPreviewHierarchy]; }]; [self _notifyDelegatesWith:LookinDisplayItemProperty_FrameToRoot]; } - (void)setPreviewItemDelegate:(id)previewItemDelegate { _previewItemDelegate = previewItemDelegate; if (![previewItemDelegate respondsToSelector:@selector(displayItem:propertyDidChange:)]) { NSAssert(NO, @""); _previewItemDelegate = nil; return; } [self.previewItemDelegate displayItem:self propertyDidChange:LookinDisplayItemProperty_None]; } - (void)setRowViewDelegate:(id)rowViewDelegate { if (_rowViewDelegate == rowViewDelegate) { return; } _rowViewDelegate = rowViewDelegate; if (![rowViewDelegate respondsToSelector:@selector(displayItem:propertyDidChange:)]) { NSAssert(NO, @""); _rowViewDelegate = nil; return; } [self.rowViewDelegate displayItem:self propertyDidChange:LookinDisplayItemProperty_None]; } - (void)setFrame:(CGRect)frame { _frame = frame; [self _updateFrameToRoot]; } - (void)setBounds:(CGRect)bounds { _bounds = bounds; [(NSArray *)self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [obj _updateFrameToRoot]; }]; } - (void)_updateFrameToRoot { if (!self.superItem) { self.frameToRoot = self.frame; return; } CGRect superFrameToRoot = self.superItem.frameToRoot; CGRect superBounds = self.superItem.bounds; CGRect selfFrame = self.frame; CGFloat x = selfFrame.origin.x - superBounds.origin.x + superFrameToRoot.origin.x; CGFloat y = selfFrame.origin.y - superBounds.origin.y + superFrameToRoot.origin.y; CGFloat width = selfFrame.size.width; CGFloat height = selfFrame.size.height; self.frameToRoot = CGRectMake(x, y, width, height); } - (void)setInNoPreviewHierarchy:(BOOL)inNoPreviewHierarchy { if (_inNoPreviewHierarchy == inNoPreviewHierarchy) { return; } _inNoPreviewHierarchy = inNoPreviewHierarchy; [self.subitems enumerateObjectsUsingBlock:^(LookinDisplayItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [obj _updateInNoPreviewHierarchy]; }]; [self _notifyDelegatesWith:LookinDisplayItemProperty_InNoPreviewHierarchy]; } - (void)setNoPreview:(BOOL)noPreview { _noPreview = noPreview; [self _updateInNoPreviewHierarchy]; } - (void)_updateInNoPreviewHierarchy { if (self.superItem.inNoPreviewHierarchy || self.noPreview) { self.inNoPreviewHierarchy = YES; } else { self.inNoPreviewHierarchy = NO; } } - (LookinImage *)appropriateScreenshot { if (self.isExpandable && self.isExpanded) { return self.soloScreenshot; } return self.groupScreenshot; } - (void)_notifyDelegatesWith:(LookinDisplayItemProperty)property { [self.previewItemDelegate displayItem:self propertyDidChange:property]; [self.rowViewDelegate displayItem:self propertyDidChange:property]; } - (BOOL)isMatchedWithSearchString:(NSString *)string { if (string.length == 0) { NSAssert(NO, @""); return NO; } if ([self.title.lowercaseString containsString:string.lowercaseString]) { return YES; } if ([self.subtitle.lowercaseString containsString:string.lowercaseString]) { return YES; } return NO; } - (void)setIsInSearch:(BOOL)isInSearch { _isInSearch = isInSearch; [self _notifyDelegatesWith:LookinDisplayItemProperty_IsInSearch]; } - (void)setHighlightedSearchString:(NSString *)highlightedSearchString { _highlightedSearchString = highlightedSearchString; [self _notifyDelegatesWith:LookinDisplayItemProperty_HighlightedSearchString]; } - (void)setHostViewControllerObject:(LookinObject *)hostViewControllerObject { _hostViewControllerObject = hostViewControllerObject; [self _updateSubtitleProperty]; } - (void)setViewObject:(LookinObject *)viewObject { _viewObject = viewObject; [self _updateSubtitleProperty]; [self _updateTitleProperty]; } - (void)setLayerObject:(LookinObject *)layerObject { _layerObject = layerObject; [self _updateSubtitleProperty]; [self _updateTitleProperty]; } - (void)_updateTitleProperty { if (self.viewObject) { _title = self.viewObject.shortSelfClassName; } else if (self.layerObject) { _title = self.layerObject.shortSelfClassName; } else { _title = nil; } } - (void)_updateSubtitleProperty { NSString *subtitle = @""; if (self.hostViewControllerObject.shortSelfClassName.length) { subtitle = [NSString stringWithFormat:@"%@.view", self.hostViewControllerObject.shortSelfClassName]; } else { LookinObject *representedObject = self.viewObject ? : self.layerObject; if (representedObject.specialTrace.length) { subtitle = representedObject.specialTrace; } else if (representedObject.ivarTraces.count) { NSArray *ivarNameList = [representedObject.ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *value) { return value.ivarName; }]; subtitle = [[[NSSet setWithArray:ivarNameList] allObjects] componentsJoinedByString:@" "]; } } _subtitle = subtitle; } - (void)dealloc { #if TARGET_OS_IPHONE #elif TARGET_OS_MAC #ifdef DEBUG [[LKEfficiencyMonitor sharedInstance] displayItemDidDealloc]; #endif #endif } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinDisplayItemDetail.h ================================================ // // LookinDisplayItemDetail.h // Lookin // // Created by Li Kai on 2019/2/19. // https://lookin.work // #import "LookinDefines.h" @class LookinAttributesGroup; @interface LookinDisplayItemDetail : NSObject @property(nonatomic, assign) unsigned long displayItemOid; @property(nonatomic, strong) LookinImage *groupScreenshot; @property(nonatomic, strong) LookinImage *soloScreenshot; @property(nonatomic, strong) NSValue *frameValue; @property(nonatomic, strong) NSValue *boundsValue; @property(nonatomic, strong) NSNumber *hiddenValue; @property(nonatomic, strong) NSNumber *alphaValue; @property(nonatomic, copy) NSArray *attributesGroupList; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinDisplayItemDetail.m ================================================ // // LookinDisplayItemDetail.m // Lookin // // Created by Li Kai on 2019/2/19. // https://lookin.work // #import "LookinDisplayItemDetail.h" #if TARGET_OS_IPHONE #import "UIImage+LookinServer.h" #endif @implementation LookinDisplayItemDetail - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:@(self.displayItemOid) forKey:@"displayItemOid"]; [aCoder encodeObject:self.groupScreenshot.lookin_data forKey:@"groupScreenshot"]; [aCoder encodeObject:self.soloScreenshot.lookin_data forKey:@"soloScreenshot"]; [aCoder encodeObject:self.frameValue forKey:@"frameValue"]; [aCoder encodeObject:self.boundsValue forKey:@"boundsValue"]; [aCoder encodeObject:self.hiddenValue forKey:@"hiddenValue"]; [aCoder encodeObject:self.alphaValue forKey:@"alphaValue"]; [aCoder encodeObject:self.attributesGroupList forKey:@"attributesGroupList"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.displayItemOid = [[aDecoder decodeObjectForKey:@"displayItemOid"] unsignedLongValue]; self.groupScreenshot = [[LookinImage alloc] initWithData:[aDecoder decodeObjectForKey:@"groupScreenshot"]]; self.soloScreenshot = [[LookinImage alloc] initWithData:[aDecoder decodeObjectForKey:@"soloScreenshot"]]; self.frameValue = [aDecoder decodeObjectForKey:@"frameValue"]; self.boundsValue = [aDecoder decodeObjectForKey:@"boundsValue"]; self.hiddenValue = [aDecoder decodeObjectForKey:@"hiddenValue"]; self.alphaValue = [aDecoder decodeObjectForKey:@"alphaValue"]; self.attributesGroupList = [aDecoder decodeObjectForKey:@"attributesGroupList"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinEventHandler.h ================================================ // // LookinEventHandler.h // Lookin // // Created by Li Kai on 2019/8/7. // https://lookin.work // #import @class LookinObject, LookinIvarTrace, LookinStringTwoTuple; typedef NS_ENUM(NSInteger, LookinEventHandlerType) { LookinEventHandlerTypeTargetAction, LookinEventHandlerTypeGesture }; @interface LookinEventHandler : NSObject @property(nonatomic, assign) LookinEventHandlerType handlerType; /// 比如 "UIControlEventTouchUpInside", "UITapGestureRecognizer" @property(nonatomic, copy) NSString *eventName; /// tuple.first => @"",tuple.second => @"handleTap" @property(nonatomic, copy) NSArray *targetActions; /// 返回当前 recognizer 是继承自哪一个基本款 recognizer。 /// 基本款 recognizer 指的是 TapRecognizer, PinchRecognizer 之类的常见 recognizer /// 如果当前 recognizer 本身就是基本款 recognizer,则该属性为 nil @property(nonatomic, copy) NSString *inheritedRecognizerName; @property(nonatomic, assign) BOOL gestureRecognizerIsEnabled; @property(nonatomic, copy) NSString *gestureRecognizerDelegator; @property(nonatomic, copy) NSArray *recognizerIvarTraces; /// recognizer 对象 @property(nonatomic, assign) unsigned long long recognizerOid; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinEventHandler.m ================================================ // // LookinEventHandler.m // Lookin // // Created by Li Kai on 2019/8/7. // https://lookin.work // #import "LookinEventHandler.h" #import "LookinObject.h" #import "LookinTuple.h" #import "NSArray+Lookin.h" @implementation LookinEventHandler #pragma mark - - (id)copyWithZone:(NSZone *)zone { LookinEventHandler *newHandler = [[LookinEventHandler allocWithZone:zone] init]; newHandler.handlerType = self.handlerType; newHandler.eventName = self.eventName; newHandler.targetActions = [self.targetActions lookin_map:^id(NSUInteger idx, LookinStringTwoTuple *value) { return value.copy; }]; newHandler.gestureRecognizerIsEnabled = self.gestureRecognizerIsEnabled; newHandler.gestureRecognizerDelegator = self.gestureRecognizerDelegator; newHandler.inheritedRecognizerName = self.inheritedRecognizerName; newHandler.recognizerIvarTraces = self.recognizerIvarTraces.copy; newHandler.recognizerOid = self.recognizerOid; return newHandler; } #pragma mark - - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeInteger:self.handlerType forKey:@"handlerType"]; [aCoder encodeBool:self.gestureRecognizerIsEnabled forKey:@"gestureRecognizerIsEnabled"]; [aCoder encodeObject:self.eventName forKey:@"eventName"]; [aCoder encodeObject:self.gestureRecognizerDelegator forKey:@"gestureRecognizerDelegator"]; [aCoder encodeObject:self.targetActions forKey:@"targetActions"]; [aCoder encodeObject:self.inheritedRecognizerName forKey:@"inheritedRecognizerName"]; [aCoder encodeObject:self.recognizerIvarTraces forKey:@"recognizerIvarTraces"]; [aCoder encodeObject:@(self.recognizerOid) forKey:@"recognizerOid"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.handlerType = [aDecoder decodeIntegerForKey:@"handlerType"]; self.gestureRecognizerIsEnabled = [aDecoder decodeBoolForKey:@"gestureRecognizerIsEnabled"]; self.eventName = [aDecoder decodeObjectForKey:@"eventName"]; self.gestureRecognizerDelegator = [aDecoder decodeObjectForKey:@"gestureRecognizerDelegator"]; self.targetActions = [aDecoder decodeObjectForKey:@"targetActions"]; self.inheritedRecognizerName = [aDecoder decodeObjectForKey:@"inheritedRecognizerName"]; self.recognizerIvarTraces = [aDecoder decodeObjectForKey:@"recognizerIvarTraces"]; self.recognizerOid = ((NSNumber *)[aDecoder decodeObjectForKey:@"recognizerOid"]).unsignedLongValue; } return self; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinHierarchyFile.h ================================================ // // LookinHierarchyFile.h // Lookin // // Created by Li Kai on 2019/5/12. // https://lookin.work // #import @class LookinHierarchyInfo; @interface LookinHierarchyFile : NSObject /// 记录创建该文件的 LookinServer 的版本 @property(nonatomic, assign) int serverVersion; @property(nonatomic, strong) LookinHierarchyInfo *hierarchyInfo; @property(nonatomic, copy) NSDictionary *soloScreenshots; @property(nonatomic, copy) NSDictionary *groupScreenshots; /// 验证 file 的版本之类的是否和当前 Lookin 客户端匹配,如果没有问题则返回 nil,如果有问题则返回 error + (NSError *)verifyHierarchyFile:(LookinHierarchyFile *)file; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinHierarchyFile.m ================================================ // // LookinHierarchyFile.m // Lookin // // Created by Li Kai on 2019/5/12. // https://lookin.work // #import "LookinHierarchyFile.h" #import "NSArray+Lookin.h" @implementation LookinHierarchyFile - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeInt:self.serverVersion forKey:@"serverVersion"]; [aCoder encodeObject:self.hierarchyInfo forKey:@"hierarchyInfo"]; [aCoder encodeObject:self.soloScreenshots forKey:@"soloScreenshots"]; [aCoder encodeObject:self.groupScreenshots forKey:@"groupScreenshots"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.serverVersion = [aDecoder decodeIntForKey:@"serverVersion"]; self.hierarchyInfo = [aDecoder decodeObjectForKey:@"hierarchyInfo"]; self.soloScreenshots = [aDecoder decodeObjectForKey:@"soloScreenshots"]; self.groupScreenshots = [aDecoder decodeObjectForKey:@"groupScreenshots"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } + (NSError *)verifyHierarchyFile:(LookinHierarchyFile *)hierarchyFile { if (![hierarchyFile isKindOfClass:[LookinHierarchyFile class]]) { return LookinErr_Inner; } if (hierarchyFile.serverVersion < LOOKIN_SUPPORTED_SERVER_MIN) { // 文件版本太旧 // 如果不存在 serverVersion 这个字段,说明版本是 6 int fileVersion = hierarchyFile.serverVersion ? : 6; NSString *detail = [NSString stringWithFormat:NSLocalizedString(@"The document was created by a Lookin app with too old version. Current Lookin app version is %@, but the document version is %@.", nil), @(LOOKIN_CLIENT_VERSION), @(fileVersion)]; return [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_ServerVersionTooLow userInfo:@{NSLocalizedDescriptionKey:NSLocalizedString(@"Failed to open the document.", nil), NSLocalizedRecoverySuggestionErrorKey:detail}]; } if (hierarchyFile.serverVersion > LOOKIN_SUPPORTED_SERVER_MAX) { // 文件版本太新 NSString *detail = [NSString stringWithFormat:NSLocalizedString(@"Current Lookin app is too old to open this document. Current Lookin app version is %@, but the document version is %@.", nil), @(LOOKIN_CLIENT_VERSION), @(hierarchyFile.serverVersion)]; return [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_ServerVersionTooHigh userInfo:@{NSLocalizedDescriptionKey:NSLocalizedString(@"Failed to open the document.", nil), NSLocalizedRecoverySuggestionErrorKey:detail}]; } return nil; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinHierarchyInfo.h ================================================ // // LookinDisplayInfo.h // WeRead // // Created by Li Kai on 2018/10/22. // Copyright © 2018年 tencent. All rights reserved. // #import "LookinDefines.h" #import "TargetConditionals.h" #if TARGET_OS_IPHONE #import #elif TARGET_OS_MAC #import #endif @class LookinDisplayItem, LookinAttributesGroup, LookinAppInfo; @interface LookinHierarchyInfo : NSObject #if TARGET_OS_IPHONE + (instancetype)staticInfo; + (instancetype)exportedInfo; + (instancetype)perspectiveInfoWithIncludedWindows:(NSArray *)includedWindows excludedWindows:(NSArray *)excludedWindows; + (NSArray *)collapsedClassList; #endif /// 这里其实就是顶端的那几个 UIWindow @property(nonatomic, copy) NSArray *displayItems; @property(nonatomic, copy) NSDictionary *colorAlias; @property(nonatomic, copy) NSArray *collapsedClassList; @property(nonatomic, strong) LookinAppInfo *appInfo; /// 标记该 LookinServer 是通过什么方式安装的,0:未知,1:CocoaPods,2:手动,3:源代码,4:断点 @property(nonatomic, assign) int serverVersion; @property(nonatomic, assign) int serverSetupType; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinHierarchyInfo.m ================================================ // // LookinDisplayInfo.m // WeRead // // Created by Li Kai on 2018/10/22. // Copyright © 2018年 tencent. All rights reserved. // #import #import "LookinHierarchyInfo.h" #import "LookinAttributesGroup.h" #import "LookinDisplayItem.h" #import "LookinAppInfo.h" #import "NSArray+Lookin.h" #if TARGET_OS_IPHONE #import "LKS_HierarchyDisplayItemsMaker.h" #endif @implementation LookinHierarchyInfo #if TARGET_OS_IPHONE + (Class)configClass { static dispatch_once_t onceToken; static Class configClass = NULL; dispatch_once(&onceToken,^{ NSString *rawName = @"LookinConfig"; configClass = NSClassFromString(rawName); if (!configClass) { int numberOfClasses = objc_getClassList(NULL, 0); if (numberOfClasses > 0) { Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numberOfClasses); numberOfClasses = objc_getClassList(classes, numberOfClasses); for (int i = 0; i < numberOfClasses; i++) { Class class = classes[i]; if ([NSStringFromClass(class) hasSuffix:@"LookinConfig"]) { configClass = class; break; } } free(classes); } } }); return configClass; } + (instancetype)staticInfo { LookinHierarchyInfo *info = [LookinHierarchyInfo new]; info.serverVersion = LOOKIN_SERVER_VERSION; info.displayItems = [LKS_HierarchyDisplayItemsMaker itemsWithScreenshots:NO attrList:NO lowImageQuality:NO includedWindows:nil excludedWindows:nil]; info.appInfo = [LookinAppInfo currentInfoWithScreenshot:NO icon:YES localIdentifiers:nil]; info.collapsedClassList = [self collapsedClassList]; info.colorAlias = [self colorAlias]; info.serverSetupType = LOOKIN_SERVER_SETUP_TYPE; return info; } + (instancetype)exportedInfo { LookinHierarchyInfo *info = [LookinHierarchyInfo new]; info.serverVersion = LOOKIN_SERVER_VERSION; info.displayItems = [LKS_HierarchyDisplayItemsMaker itemsWithScreenshots:YES attrList:YES lowImageQuality:YES includedWindows:nil excludedWindows:nil]; info.appInfo = [LookinAppInfo currentInfoWithScreenshot:NO icon:YES localIdentifiers:nil]; info.collapsedClassList = [self collapsedClassList]; info.colorAlias = [self colorAlias]; info.serverSetupType = LOOKIN_SERVER_SETUP_TYPE; return info; } + (instancetype)perspectiveInfoWithIncludedWindows:(NSArray *)includedWindows excludedWindows:(NSArray *)excludedWindows { LookinHierarchyInfo *info = [LookinHierarchyInfo new]; info.serverVersion = LOOKIN_SERVER_VERSION; info.displayItems = [LKS_HierarchyDisplayItemsMaker itemsWithScreenshots:YES attrList:YES lowImageQuality:NO includedWindows:includedWindows excludedWindows:excludedWindows]; info.appInfo = [LookinAppInfo currentInfoWithScreenshot:NO icon:YES localIdentifiers:nil]; info.collapsedClassList = [self collapsedClassList]; info.colorAlias = [self colorAlias]; info.serverSetupType = LOOKIN_SERVER_SETUP_TYPE; return info; } + (NSArray *)collapsedClassList { Class configClass = [self configClass]; if (!configClass) { return nil; } SEL selector = NSSelectorFromString(@"collapsedClasses"); if (![configClass respondsToSelector:selector]) { return nil; } NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[configClass methodSignatureForSelector:selector]]; [invocation setTarget:configClass]; [invocation setSelector:selector]; [invocation invoke]; void *arrayValue; [invocation getReturnValue:&arrayValue]; id classList = (__bridge id)(arrayValue); if ([classList isKindOfClass:[NSArray class]]) { NSArray *validClassList = [((NSArray *)classList) lookin_filter:^BOOL(id obj) { return [obj isKindOfClass:[NSString class]]; }]; return [validClassList copy]; } return nil; } + (NSDictionary *)colorAlias { Class configClass = [self configClass]; if (!configClass) { return nil; } SEL selector = NSSelectorFromString(@"colors"); if (![configClass respondsToSelector:selector]) { return nil; } NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[configClass methodSignatureForSelector:selector]]; [invocation setTarget:configClass]; [invocation setSelector:selector]; [invocation invoke]; void *dictValue; [invocation getReturnValue:&dictValue]; id colorAlias = (__bridge id)(dictValue); if ([colorAlias isKindOfClass:[NSDictionary class]]) { NSMutableDictionary *validDictionary = [NSMutableDictionary dictionary]; [(NSDictionary *)colorAlias enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { if ([key isKindOfClass:[NSString class]]) { if ([obj isKindOfClass:[UIColor class]]) { [validDictionary setObject:obj forKey:key]; } else if ([obj isKindOfClass:[NSDictionary class]]) { __block BOOL isValidSubDict = YES; [((NSDictionary *)obj) enumerateKeysAndObjectsUsingBlock:^(id _Nonnull subKey, id _Nonnull subObj, BOOL * _Nonnull stop) { if (![subKey isKindOfClass:[NSString class]] || ![subObj isKindOfClass:[UIColor class]]) { isValidSubDict = NO; *stop = YES; } }]; if (isValidSubDict) { [validDictionary setObject:obj forKey:key]; } } } }]; return [validDictionary copy]; } return nil; } #endif #pragma mark - static NSString * const LookinHierarchyInfoCodingKey_DisplayItems = @"1"; static NSString * const LookinHierarchyInfoCodingKey_AppInfo = @"2"; static NSString * const LookinHierarchyInfoCodingKey_ColorAlias = @"3"; static NSString * const LookinHierarchyInfoCodingKey_CollapsedClassList = @"4"; - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.displayItems forKey:LookinHierarchyInfoCodingKey_DisplayItems]; [aCoder encodeObject:self.colorAlias forKey:LookinHierarchyInfoCodingKey_ColorAlias]; [aCoder encodeObject:self.collapsedClassList forKey:LookinHierarchyInfoCodingKey_CollapsedClassList]; [aCoder encodeObject:self.appInfo forKey:LookinHierarchyInfoCodingKey_AppInfo]; [aCoder encodeInt:self.serverVersion forKey:@"serverVersion"]; [aCoder encodeInt:self.serverSetupType forKey:@"serverSetupType"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.displayItems = [aDecoder decodeObjectForKey:LookinHierarchyInfoCodingKey_DisplayItems]; self.colorAlias = [aDecoder decodeObjectForKey:LookinHierarchyInfoCodingKey_ColorAlias]; self.collapsedClassList = [aDecoder decodeObjectForKey:LookinHierarchyInfoCodingKey_CollapsedClassList]; self.appInfo = [aDecoder decodeObjectForKey:LookinHierarchyInfoCodingKey_AppInfo]; self.serverVersion = [aDecoder decodeIntForKey:@"serverVersion"]; self.serverSetupType = [aDecoder decodeIntForKey:@"serverSetupType"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } #pragma mark - - (id)copyWithZone:(NSZone *)zone { LookinHierarchyInfo *newAppInfo = [[LookinHierarchyInfo allocWithZone:zone] init]; newAppInfo.serverVersion = self.serverVersion; newAppInfo.appInfo = self.appInfo.copy; newAppInfo.collapsedClassList = self.collapsedClassList; newAppInfo.colorAlias = self.colorAlias; newAppInfo.serverSetupType = self.serverSetupType; newAppInfo.displayItems = [self.displayItems lookin_map:^id(NSUInteger idx, LookinDisplayItem *oldItem) { return oldItem.copy; }]; return newAppInfo; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinIvarTrace.h ================================================ // // LookinIvarTrace.h // Lookin // // Created by Li Kai on 2019/4/30. // https://lookin.work // #import extern NSString *const LookinIvarTraceRelationValue_Self; /// 如果 hostClassName 和 ivarName 均 equal,则认为两个 LookinIvarTrace 对象彼此 equal /// 比如 A 是 B 的 superview,且 A 的 "_stageView" 指向 B,则 B 会有一个 LookinIvarTrace:hostType 为 “superview”,hostClassName 为 A 的 class,ivarName 为 “_stageView” @interface LookinIvarTrace : NSObject /// 该值可能是 "superview"、"superlayer"、“self” 或 nil @property(nonatomic, copy) NSString *relation; @property(nonatomic, copy) NSString *hostClassName; @property(nonatomic, copy) NSString *ivarName; #pragma mark - No Coding #if TARGET_OS_IPHONE @property(nonatomic, weak) id hostObject; #endif @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinIvarTrace.m ================================================ // // LookinIvarTrace.m // Lookin // // Created by Li Kai on 2019/4/30. // https://lookin.work // #import "LookinIvarTrace.h" NSString *const LookinIvarTraceRelationValue_Self = @"self"; @implementation LookinIvarTrace #pragma mark - Equal - (NSUInteger)hash { return self.hostClassName.hash ^ self.ivarName.hash; } - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[LookinIvarTrace class]]) { return NO; } LookinIvarTrace *comparedObj = object; if ([self.hostClassName isEqualToString:comparedObj.hostClassName] && [self.ivarName isEqualToString:comparedObj.ivarName]) { return YES; } return NO; } #pragma mark - - (id)copyWithZone:(NSZone *)zone { LookinIvarTrace *newTrace = [[LookinIvarTrace allocWithZone:zone] init]; newTrace.relation = self.relation; newTrace.hostClassName = self.hostClassName; newTrace.ivarName = self.ivarName; return newTrace; } #pragma mark - - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.relation forKey:@"relation"]; [aCoder encodeObject:self.hostClassName forKey:@"hostClassName"]; [aCoder encodeObject:self.ivarName forKey:@"ivarName"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.relation = [aDecoder decodeObjectForKey:@"relation"]; self.hostClassName = [aDecoder decodeObjectForKey:@"hostClassName"]; self.ivarName = [aDecoder decodeObjectForKey:@"ivarName"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinMethodTraceRecord.h ================================================ // // LookinMethodTraceRecord.h // Lookin // // Created by Li Kai on 2019/5/27. // https://lookin.work // #import @interface LookinMethodTraceRecordStackItem : NSObject @property(nonatomic, assign) NSUInteger idx; @property(nonatomic, copy) NSString *category; @property(nonatomic, copy) NSString *detail; @property(nonatomic, assign) BOOL isSystemSeriesEnding; @property(nonatomic, assign) BOOL isSystemItem; @end @interface LookinMethodTraceRecord : NSObject @property(nonatomic, copy) NSString *targetAddress; @property(nonatomic, copy) NSString *selClassName; @property(nonatomic, copy) NSString *selName; @property(nonatomic, copy) NSArray *args; @property(nonatomic, copy) NSArray *callStacks; @property(nonatomic, strong) NSDate *date; #pragma mark - Non Coding @property(nonatomic, copy, readonly) NSString *combinedTitle; - (NSArray *)briefFormattedCallStacks; - (NSArray *)completeFormattedCallStacks; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinMethodTraceRecord.m ================================================ // // LookinMethodTraceRecord.m // Lookin // // Created by Li Kai on 2019/5/27. // https://lookin.work // #import "LookinMethodTraceRecord.h" #import "NSArray+Lookin.h" @implementation LookinMethodTraceRecordStackItem @end @implementation LookinMethodTraceRecord - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.targetAddress forKey:@"targetAddress"]; [aCoder encodeObject:self.selClassName forKey:@"selClassName"]; [aCoder encodeObject:self.selName forKey:@"selName"]; [aCoder encodeObject:self.args forKey:@"args"]; [aCoder encodeObject:self.callStacks forKey:@"callStacks"]; [aCoder encodeObject:self.date forKey:@"date"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.targetAddress = [aDecoder decodeObjectForKey:@"targetAddress"]; self.selClassName = [aDecoder decodeObjectForKey:@"selClassName"]; self.selName = [aDecoder decodeObjectForKey:@"selName"]; self.args = [aDecoder decodeObjectForKey:@"args"]; self.callStacks = [aDecoder decodeObjectForKey:@"callStacks"]; self.date = [aDecoder decodeObjectForKey:@"date"]; _combinedTitle = [self _makeCombinedTitle]; } return self; } + (BOOL)supportsSecureCoding { return YES; } - (NSArray *)briefFormattedCallStacks { return [self _formattedStacksFromRawStacks:self.callStacks brief:YES]; } - (NSArray *)completeFormattedCallStacks { return [self _formattedStacksFromRawStacks:self.callStacks brief:NO]; } - (NSString *)_makeCombinedTitle { NSString *selString; if (self.args.count) { NSArray *selParts = [[self.selName componentsSeparatedByString:@":"] lookin_filter:^BOOL(NSString *obj) { return obj.length > 0; }]; NSMutableString *mutableSelString = [NSMutableString string]; [selParts enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [mutableSelString appendString:obj]; NSString *arg = [self.args lookin_safeObjectAtIndex:idx]; if (arg) { [mutableSelString appendFormat:@":%@", arg]; } else { [mutableSelString appendString:@":?"]; } if (idx < selParts.count - 1) { [mutableSelString appendString:@" "]; } }]; selString = mutableSelString.copy; } else { selString = self.selName; } NSString *combinedTitle = [NSString stringWithFormat:@"[(%@ *)%@ %@]", self.selClassName, self.targetAddress, selString]; return combinedTitle; } - (NSArray *)_formattedStacksFromRawStacks:(NSArray *)strings brief:(BOOL)brief { NSMutableArray *items = [NSMutableArray array]; [items addObject:({ LookinMethodTraceRecordStackItem *item = [LookinMethodTraceRecordStackItem new]; item.idx = 0; item.detail = [NSString stringWithFormat:@"-[%@ %@]", self.selClassName, self.selName]; item; })]; [items addObjectsFromArray:[strings lookin_map:^id(NSUInteger idx, NSString *value) { if (idx <= 2) { // 过滤掉 Lookin 相关 return nil; } LookinMethodTraceRecordStackItem *item = [self _formattedStackItemFromRawString:value]; item.idx = idx - 2; return item; }]]; if (!brief) { return items.copy; } NSMutableArray *briefItems = [NSMutableArray array]; [items enumerateObjectsUsingBlock:^(LookinMethodTraceRecordStackItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj.category isEqualToString:@"???"]) { return; } if (!obj.isSystemItem) { [briefItems addObject:obj]; return; } if (!briefItems.lastObject.isSystemItem) { [briefItems addObject:obj]; return; } LookinMethodTraceRecordStackItem *nextItem = [items lookin_safeObjectAtIndex:idx + 1]; if (!nextItem || !nextItem.isSystemItem) { [briefItems addObject:obj]; LookinMethodTraceRecordStackItem *prevItem = [items lookin_safeObjectAtIndex:idx - 1]; LookinMethodTraceRecordStackItem *prevPrevItem = [items lookin_safeObjectAtIndex:idx - 2]; if (prevItem && prevPrevItem && prevItem.isSystemItem && prevPrevItem.isSystemItem) { obj.isSystemSeriesEnding = YES; } } }]; return briefItems.copy; } - (LookinMethodTraceRecordStackItem *)_formattedStackItemFromRawString:(NSString *)string { LookinMethodTraceRecordStackItem *item = [LookinMethodTraceRecordStackItem new]; item.category = ({ NSArray *strs = [[string componentsSeparatedByString:@" "] lookin_filter:^BOOL(NSString *obj) { return obj.length > 0; }]; strs[1]; }); item.detail = ({ NSString *tmpStr = [[string componentsSeparatedByString:@" "].lastObject stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; NSUInteger loc0 = [tmpStr rangeOfString:@" "].location; NSUInteger loc1 = [tmpStr rangeOfString:@" + "].location; NSString *str = [tmpStr substringWithRange:NSMakeRange(loc0 + 1, loc1 - loc0)]; str; }); if ([item.category isEqualToString:@"UIKitCore"] || [item.category isEqualToString:@"libdyld.dylib"] || [item.category isEqualToString:@"CoreFoundation"]) { item.isSystemItem = YES; } return item; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinObject.h ================================================ // // LookinObject.h // Lookin // // Created by Li Kai on 2019/4/20. // https://lookin.work // #import @class LookinObjectIvar, LookinIvarTrace; @interface LookinObject : NSObject #if TARGET_OS_IPHONE + (instancetype)instanceWithObject:(NSObject *)object; #endif @property(nonatomic, assign) unsigned long oid; @property(nonatomic, copy) NSString *memoryAddress; /** 比如有一个 UILabel 对象,则它的 classChainList 为 @[@"UILabel", @"UIView", @"UIResponder", @"NSObject"],而它的 ivarList 长度为 4,idx 从小到大分别是 UILabel 层级的 ivars, UIView 层级的 ivars..... */ @property(nonatomic, copy) NSArray *classChainList; @property(nonatomic, copy) NSString *specialTrace; @property(nonatomic, copy) NSArray *ivarTraces; #pragma mark - Non Coding /// 在 OC 中,completedSelfClassName 和 shortSelfClassName 返回值一样。在 Swift 中,completedSelfClassName 返回的是带命名空间的(比如 LBFM_Swift.LBFMHomeController 或 _TtC 11TestRuntime14ViewController),而 shortSelfClassName 返回的是没有命名空间的(比如 LBFMHomeController) @property(nonatomic, copy, readonly) NSString *completedSelfClassName; @property(nonatomic, copy, readonly) NSString *shortSelfClassName; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinObject.m ================================================ // // LookinObject.m // Lookin // // Created by Li Kai on 2019/4/20. // https://lookin.work // #import "LookinObject.h" #import "LookinIvarTrace.h" #import "NSArray+Lookin.h" #import "NSString+Lookin.h" #import "NSObject+LookinServer.h" @implementation LookinObject #if TARGET_OS_IPHONE + (instancetype)instanceWithObject:(NSObject *)object { LookinObject *lookinObj = [LookinObject new]; lookinObj.oid = [object lks_registerOid]; lookinObj.memoryAddress = [NSString stringWithFormat:@"%p", object]; lookinObj.classChainList = [object lks_classChainListWithSwiftPrefix:YES]; lookinObj.specialTrace = object.lks_specialTrace; lookinObj.ivarTraces = object.lks_ivarTraces; return lookinObj; } #endif #pragma mark - - (id)copyWithZone:(NSZone *)zone { LookinObject *newObject = [[LookinObject allocWithZone:zone] init]; newObject.oid = self.oid; newObject.memoryAddress = self.memoryAddress; newObject.classChainList = self.classChainList; newObject.specialTrace = self.specialTrace; newObject.ivarTraces = [self.ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *value) { return value.copy; }]; return newObject; } #pragma mark - - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:@(self.oid) forKey:@"oid"]; [aCoder encodeObject:self.memoryAddress forKey:@"memoryAddress"]; [aCoder encodeObject:self.classChainList forKey:@"classChainList"]; [aCoder encodeObject:self.specialTrace forKey:@"specialTrace"]; [aCoder encodeObject:self.ivarTraces forKey:@"ivarTraces"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.oid = [(NSNumber *)[aDecoder decodeObjectForKey:@"oid"] unsignedLongValue]; self.memoryAddress = [aDecoder decodeObjectForKey:@"memoryAddress"]; self.classChainList = [aDecoder decodeObjectForKey:@"classChainList"]; self.specialTrace = [aDecoder decodeObjectForKey:@"specialTrace"]; self.ivarTraces = [aDecoder decodeObjectForKey:@"ivarTraces"]; } return self; } - (void)setClassChainList:(NSArray *)classChainList { _classChainList = classChainList; } + (BOOL)supportsSecureCoding { return YES; } - (NSString *)completedSelfClassName { return self.classChainList.firstObject; } - (NSString *)shortSelfClassName { return [[self completedSelfClassName] lookin_shortClassNameString]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinScreenshotFetchManager.h ================================================ // // LKImageFetchManager.h // Lookin_macOS // // Created by 李凯 on 2019/1/15. // Copyright © 2019 hughkli. All rights reserved. // #import @class LookinDisplayItem; @interface LkScreenshotFetchManager : NSObject + (instancetype)sharedInstance; - (void)prepare; /// 拉取 item 的所有 ancestor 的 groupScreenshot - (void)fetchGroupScreenshotForAncestorsOfItem:(LookinDisplayItem *)item; /// 拉取 item 的 soloScreenshot - (void)fetchSoloScreenshotForItem:(LookinDisplayItem *)item; /// 拉取 item 的 groupScreenshot - (void)fetchGroupScreenshotForItem:(LookinDisplayItem *)item; - (void)submitTasks; /// 拉取 items 的 soloScreenshot 和 groupScreenshot - (void)fetchAndUpdateScreenshotsForItems:(NSArray *)items; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinStaticAsyncUpdateTask.h ================================================ // // LookinStaticAsyncUpdateTask.h // Lookin // // Created by Li Kai on 2019/6/21. // https://lookin.work // #import "LookinDefines.h" typedef NS_ENUM(NSInteger, LookinStaticAsyncUpdateTaskType) { LookinStaticAsyncUpdateTaskTypeNoScreenshot, LookinStaticAsyncUpdateTaskTypeSoloScreenshot, LookinStaticAsyncUpdateTaskTypeGroupScreenshot }; /// 如果两个 Task 对象的 oid 和 taskType 均相同,则二者 equal @interface LookinStaticAsyncUpdateTask : NSObject @property(nonatomic, assign) unsigned long oid; @property(nonatomic, assign) LookinStaticAsyncUpdateTaskType taskType; #pragma mark - Non Coding @property(nonatomic, assign) CGSize frameSize; @end @interface LookinStaticAsyncUpdateTasksPackage : NSObject @property(nonatomic, copy) NSArray *tasks; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinStaticAsyncUpdateTask.m ================================================ // // LookinStaticAsyncUpdateTask.m // Lookin // // Created by Li Kai on 2019/6/21. // https://lookin.work // #import "LookinStaticAsyncUpdateTask.h" @implementation LookinStaticAsyncUpdateTask - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:@(self.oid) forKey:@"oid"]; [aCoder encodeInteger:self.taskType forKey:@"taskType"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.oid = [[aDecoder decodeObjectForKey:@"oid"] unsignedLongValue]; self.taskType = [aDecoder decodeIntegerForKey:@"taskType"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } - (NSUInteger)hash { return self.oid ^ self.taskType; } - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[LookinStaticAsyncUpdateTask class]]) { return NO; } LookinStaticAsyncUpdateTask *targetTask = object; if (self.oid == targetTask.oid && self.taskType == targetTask.taskType) { return YES; } return NO; } @end @implementation LookinStaticAsyncUpdateTasksPackage - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.tasks forKey:@"tasks"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.tasks = [aDecoder decodeObjectForKey:@"tasks"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinTuple.h ================================================ // // LookinTuples.h // Lookin // // Created by Li Kai on 2019/8/14. // https://lookin.work // #import @interface LookinTwoTuple : NSObject @property(nonatomic, strong) NSObject *first; @property(nonatomic, strong) NSObject *second; @end @interface LookinStringTwoTuple : NSObject + (instancetype)tupleWithFirst:(NSString *)firstString second:(NSString *)secondString; @property(nonatomic, copy) NSString *first; @property(nonatomic, copy) NSString *second; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinTuple.m ================================================ // // LookinTuples.m // Lookin // // Created by Li Kai on 2019/8/14. // https://lookin.work // #import "LookinTuple.h" @implementation LookinTwoTuple - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.first forKey:@"first"]; [aCoder encodeObject:self.second forKey:@"second"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.first = [aDecoder decodeObjectForKey:@"first"]; self.second = [aDecoder decodeObjectForKey:@"second"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } - (NSUInteger)hash { return self.first.hash ^ self.second.hash; } - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[LookinTwoTuple class]]) { return NO; } LookinTwoTuple *comparedObj = object; if ([self.first isEqual:comparedObj.first] && [self.second isEqual:comparedObj.second]) { return YES; } return NO; } @end @implementation LookinStringTwoTuple + (instancetype)tupleWithFirst:(NSString *)firstString second:(NSString *)secondString { LookinStringTwoTuple *tuple = [LookinStringTwoTuple new]; tuple.first = firstString; tuple.second = secondString; return tuple; } #pragma mark - - (id)copyWithZone:(NSZone *)zone { LookinStringTwoTuple *newTuple = [[LookinStringTwoTuple allocWithZone:zone] init]; newTuple.first = self.first; newTuple.second = self.second; return newTuple; } #pragma mark - - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.first forKey:@"first"]; [aCoder encodeObject:self.second forKey:@"second"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.first = [aDecoder decodeObjectForKey:@"first"]; self.second = [aDecoder decodeObjectForKey:@"second"]; } return self; } + (BOOL)supportsSecureCoding { return YES; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinWeakContainer.h ================================================ // // LookinWeakContainer.h // Lookin // // Created by Li Kai on 2019/8/14. // https://lookin.work // #import @interface LookinWeakContainer : NSObject + (instancetype)containerWithObject:(id)object; @property (nonatomic, weak) id object; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/LookinWeakContainer.m ================================================ // // LookinWeakContainer.m // Lookin // // Created by Li Kai on 2019/8/14. // https://lookin.work // #import "LookinWeakContainer.h" @implementation LookinWeakContainer + (instancetype)containerWithObject:(id)object { LookinWeakContainer *container = [LookinWeakContainer new]; container.object = object; return container; } - (NSUInteger)hash { return [self.object hash]; } - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[LookinWeakContainer class]]) { return NO; } LookinWeakContainer *comparedObj = object; if ([self.object isEqual:comparedObj.object]) { return YES; } return NO; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Message/LookinMsgAttribute.h ================================================ // // LookinMsgAttribute.h // Lookin // // Created by Li Kai on 2019/8/19. // https://lookin.work // #import "LookinDefines.h" @interface LookinMsgActionParams : NSObject @property(nonatomic, strong) id value; @property(nonatomic, assign) double doubleValue; @property(nonatomic, assign) NSInteger integerValue; @property(nonatomic, assign) BOOL boolValue; @property(nonatomic, weak) id relatedObject; @property(nonatomic, strong) id userInfo; @end @interface LookinMsgAttribute : NSObject /// 创建一个示例并给予一个初始值 + (instancetype)attributeWithValue:(id)value; /// 当前的值 @property(nonatomic, strong, readonly) id currentValue; /** 使用 value 作为 currentValue 属性的值 调用该方法后,所有此前通过 subscribe:action: 相关方法注册的 subscriber 的 action 都会被调用,参数是一个 LookinMsgActionParams 对象 如果传入了 ignoreSubscriber,则 ignoreSubscriber 这个对象不会收到这次通知 如果传入的 value 和之前已有的 value 是 equal 的,则该方法不会产生任何效果 传入的 userInfo 对象可在 LookinMsgActionParams 中被读取 */ - (void)setValue:(id)value ignoreSubscriber:(id)ignoreSubscriber userInfo:(id)userInfo; /// target, relatedObject 均不会被强引用,action 方法的参数是一个 LookinMsgActionParams /// 即使多次调用该方法添加同一个 target,target 也只会收到一次通知 - (void)subscribe:(id)target action:(SEL)action relatedObject:(id)relatedObject; /// 如果 sendAtOnce 为 YES,则在该方法调用后,会立即收到一条消息 - (void)subscribe:(id)target action:(SEL)action relatedObject:(id)relatedObject sendAtOnce:(BOOL)sendAtOnce; @end @interface LookinDoubleMsgAttribute : LookinMsgAttribute @property(nonatomic, assign, readonly) double currentDoubleValue; + (instancetype)attributeWithDouble:(double)value; - (void)setDoubleValue:(double)doubleValue ignoreSubscriber:(id)ignoreSubscriber; @end @interface LookinIntegerMsgAttribute : LookinMsgAttribute @property(nonatomic, assign, readonly) NSInteger currentIntegerValue; + (instancetype)attributeWithInteger:(NSInteger)value; - (void)setIntegerValue:(NSInteger)integerValue ignoreSubscriber:(id)ignoreSubscriber; @end @interface LookinBOOLMsgAttribute : LookinMsgAttribute @property(nonatomic, assign, readonly) BOOL currentBOOLValue; + (instancetype)attributeWithBOOL:(BOOL)value; - (void)setBOOLValue:(BOOL)BOOLValue ignoreSubscriber:(id)ignoreSubscriber; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Message/LookinMsgAttribute.m ================================================ // // LookinMsgAttribute.m // Lookin // // Created by Li Kai on 2019/8/19. // https://lookin.work // #import "LookinMsgAttribute.h" #import "LookinMsgTargetAction.h" @implementation LookinMsgActionParams - (double)doubleValue { if (![self.value isKindOfClass:[NSNumber class]]) { NSAssert(NO, @""); return 0; } return ((NSNumber *)self.value).doubleValue; } - (NSInteger)integerValue { if (![self.value isKindOfClass:[NSNumber class]]) { NSAssert(NO, @""); return 0; } return ((NSNumber *)self.value).integerValue; } - (BOOL)boolValue { if (![self.value isKindOfClass:[NSNumber class]]) { NSAssert(NO, @""); return 0; } return ((NSNumber *)self.value).boolValue; } @end @interface LookinMsgAttribute () @property(nonatomic, strong, readwrite) id currentValue; @property(nonatomic, strong) NSMutableArray *subscribers; @end @implementation LookinMsgAttribute + (instancetype)attributeWithValue:(id)value { LookinMsgAttribute *attr = [LookinMsgAttribute new]; attr.currentValue = value; return attr; } - (instancetype)init { if (self = [super init]) { self.subscribers = [NSMutableArray array]; } return self; } - (void)setValue:(id)value ignoreSubscriber:(id)ignoreSubscriber userInfo:(id)userInfo { if ([self.currentValue isEqual:value]) { // value 相等的话直接拒绝 return; } self.currentValue = value; __block NSMutableIndexSet *invalidIdxs = nil; [self.subscribers enumerateObjectsUsingBlock:^(LookinMsgTargetAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { id subscriberTarget = obj.target; SEL subscriberAction = obj.action; if (!subscriberTarget) { // target 已被释放,删掉这组 target-action 吧 if (!invalidIdxs) { invalidIdxs = [NSMutableIndexSet indexSet]; } [invalidIdxs addIndex:idx]; return; } if (subscriberTarget == ignoreSubscriber) { // 不要通知 ignoreSubscriber return; } LookinMsgActionParams *params = [LookinMsgActionParams new]; params.userInfo = userInfo; params.value = value; params.relatedObject = obj.relatedObject; #if TARGET_OS_IPHONE [[UIApplication sharedApplication] sendAction:subscriberAction to:subscriberTarget from:params forEvent:nil]; #elif TARGET_OS_MAC [NSApp sendAction:subscriberAction to:subscriberTarget from:params]; #endif }]; if (invalidIdxs.count) { [self.subscribers removeObjectsAtIndexes:invalidIdxs]; } } - (void)subscribe:(id)target action:(SEL)action relatedObject:(id)relatedObject { [self subscribe:target action:action relatedObject:relatedObject sendAtOnce:NO]; } - (void)subscribe:(id)target action:(SEL)action relatedObject:(id)relatedObject sendAtOnce:(BOOL)sendAtOnce { if (!target || !action) { return; } LookinMsgTargetAction *newOne = [LookinMsgTargetAction new]; newOne.target = target; newOne.action = action; newOne.relatedObject = relatedObject; if ([self.subscribers containsObject:newOne]) { return; } [self.subscribers addObject:newOne]; if (sendAtOnce) { LookinMsgActionParams *params = [LookinMsgActionParams new]; params.value = self.currentValue; params.relatedObject = relatedObject; #if TARGET_OS_IPHONE [[UIApplication sharedApplication] sendAction:action to:target from:params forEvent:nil]; #elif TARGET_OS_MAC [NSApp sendAction:action to:target from:params]; #endif } } @end @implementation LookinDoubleMsgAttribute + (instancetype)attributeWithDouble:(double)value { LookinDoubleMsgAttribute *attr = [LookinDoubleMsgAttribute new]; attr.currentValue = @(value); return attr; } - (void)setDoubleValue:(double)doubleValue ignoreSubscriber:(id)ignoreSubscriber { [self setValue:@(doubleValue) ignoreSubscriber:ignoreSubscriber userInfo:nil]; } - (double)currentDoubleValue { return ((NSNumber *)self.currentValue).doubleValue; } @end @implementation LookinIntegerMsgAttribute + (instancetype)attributeWithInteger:(NSInteger)value { LookinIntegerMsgAttribute *attr = [LookinIntegerMsgAttribute new]; attr.currentValue = @(value); return attr; } - (void)setIntegerValue:(NSInteger)integerValue ignoreSubscriber:(id)ignoreSubscriber { [self setValue:@(integerValue) ignoreSubscriber:ignoreSubscriber userInfo:nil]; } - (NSInteger)currentIntegerValue { return ((NSNumber *)self.currentValue).integerValue; } @end @implementation LookinBOOLMsgAttribute + (instancetype)attributeWithBOOL:(BOOL)value { LookinBOOLMsgAttribute *attr = [LookinBOOLMsgAttribute new]; attr.currentValue = @(value); return attr; } - (void)setBOOLValue:(BOOL)BOOLValue ignoreSubscriber:(id)ignoreSubscriber { [self setValue:@(BOOLValue) ignoreSubscriber:ignoreSubscriber userInfo:nil]; } - (BOOL)currentBOOLValue { return ((NSNumber *)self.currentValue).boolValue; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Message/LookinMsgTargetAction.h ================================================ // // LookinMsgTargetAction.h // Lookin // // Created by Li Kai on 2019/8/19. // https://lookin.work // #import /// target 和 relatedObject 相等,action 名字相同则认为 equal @interface LookinMsgTargetAction : NSObject @property(nonatomic, weak) id target; @property(nonatomic, assign) SEL action; @property(nonatomic, weak) id relatedObject; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Message/LookinMsgTargetAction.m ================================================ // // LookinMsgTargetAction.m // Lookin // // Created by Li Kai on 2019/8/19. // https://lookin.work // #import "LookinMsgTargetAction.h" @implementation LookinMsgTargetAction - (NSUInteger)hash { return [self.target hash] ^ NSStringFromSelector(self.action).hash ^ [self.relatedObject hash]; } - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (![object isKindOfClass:[LookinMsgTargetAction class]]) { return NO; } LookinMsgTargetAction *comparedObj = object; if (self.target == comparedObj.target && [NSStringFromSelector(self.action) isEqual:NSStringFromSelector(comparedObj.action)] && self.relatedObject == comparedObj.relatedObject) { return YES; } return NO; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Peertalk/Lookin_PTChannel.h ================================================ // // Represents a communication channel between two endpoints talking the same // Lookin_PTProtocol. // #import #import #import #import #import "Lookin_PTProtocol.h" #import "Lookin_PTUSBHub.h" @class Lookin_PTData, Lookin_PTAddress; @protocol Lookin_PTChannelDelegate; @interface Lookin_PTChannel : NSObject // Delegate @property (strong) id delegate; // Communication protocol. Must not be nil. @property Lookin_PTProtocol *protocol; // YES if this channel is a listening server @property (readonly) BOOL isListening; // YES if this channel is a connected peer @property (readonly) BOOL isConnected; // Arbitrary attachment. Note that if you set this, the object will grow by // 8 bytes (64 bits). @property (strong) id userInfo; // Create a new channel using the shared Lookin_PTProtocol for the current dispatch // queue, with *delegate*. + (Lookin_PTChannel*)channelWithDelegate:(id)delegate; // Initialize a new frame channel, configuring it to use the calling queue's // protocol instance (as returned by [Lookin_PTProtocol sharedProtocolForQueue: // dispatch_get_current_queue()]) - (id)init; // Initialize a new frame channel with a specific protocol. - (id)initWithProtocol:(Lookin_PTProtocol*)protocol; // Initialize a new frame channel with a specific protocol and delegate. - (id)initWithProtocol:(Lookin_PTProtocol*)protocol delegate:(id)delegate; // Connect to a TCP port on a device connected over USB - (void)connectToPort:(int)port overUSBHub:(Lookin_PTUSBHub*)usbHub deviceID:(NSNumber*)deviceID callback:(void(^)(NSError *error))callback; // Connect to a TCP port at IPv4 address. Provided port must NOT be in network // byte order. Provided in_addr_t must NOT be in network byte order. A value returned // from inet_aton() will be in network byte order. You can use a value of inet_aton() // as the address parameter here, but you must flip the byte order before passing the // in_addr_t to this function. - (void)connectToPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error, Lookin_PTAddress *address))callback; // Listen for connections on port and address, effectively starting a socket // server. Provided port must NOT be in network byte order. Provided in_addr_t // must NOT be in network byte order. // For this to make sense, you should provide a onAccept block handler // or a delegate implementing ioFrameChannel:didAcceptConnection:. - (void)listenOnPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error))callback; // Send a frame with an optional payload and optional callback. // If *callback* is not NULL, the block is invoked when either an error occured // or when the frame (and payload, if any) has been completely sent. - (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload callback:(void(^)(NSError *error))callback; // Lower-level method to assign a connected dispatch IO channel to this channel - (BOOL)startReadingFromConnectedChannel:(dispatch_io_t)channel error:(__autoreleasing NSError**)error; // Close the channel, preventing further reading and writing. Any ongoing and // queued reads and writes will be aborted. - (void)close; // "graceful" close -- any ongoing and queued reads and writes will complete // before the channel ends. - (void)cancel; @end // Wraps a mapped dispatch_data_t object. The memory pointed to by *data* is // valid until *dispatchData* is deallocated (normally when the receiver is // deallocated). @interface Lookin_PTData : NSObject @property (readonly) dispatch_data_t dispatchData; @property (readonly) void *data; @property (readonly) size_t length; @end // Represents a peer's address @interface Lookin_PTAddress : NSObject // For network addresses, this is the IP address in textual format @property (readonly) NSString *name; // For network addresses, this is the port number. Otherwise 0 (zero). @property (readonly) NSInteger port; @end // Protocol for Lookin_PTChannel delegates @protocol Lookin_PTChannelDelegate @required // Invoked when a new frame has arrived on a channel. - (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload; @optional // Invoked to accept an incoming frame on a channel. Reply NO ignore the // incoming frame. If not implemented by the delegate, all frames are accepted. - (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize; // Invoked when the channel closed. If it closed because of an error, *error* is // a non-nil NSError object. - (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error; // For listening channels, this method is invoked when a new connection has been // accepted. - (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Peertalk/Lookin_PTChannel.m ================================================ #import "Lookin_PTChannel.h" #import "Lookin_PTPrivate.h" #include #include #include #include #include #import // Read member of sockaddr_in without knowing the family #define PT_SOCKADDR_ACCESS(ss, member4, member6) \ (((ss)->ss_family == AF_INET) ? ( \ ((const struct sockaddr_in *)(ss))->member4 \ ) : ( \ ((const struct sockaddr_in6 *)(ss))->member6 \ )) // Connection state (storage: uint8_t) #define kConnStateNone 0 #define kConnStateConnecting 1 #define kConnStateConnected 2 #define kConnStateListening 3 // Delegate support optimization (storage: uint8_t) #define kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize 1 #define kDelegateFlagImplements_ioFrameChannel_didEndWithError 2 #define kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress 4 #pragma mark - // Note: We are careful about the size of this struct as each connected peer // implies one allocation of this struct. @interface Lookin_PTChannel () { dispatch_io_t dispatchObj_channel_; dispatch_source_t dispatchObj_source_; NSError *endError_; // 64 bit @public // here be hacks id delegate_; // 64 bit uint8_t delegateFlags_; // 8 bit @private uint8_t connState_; // 8 bit //char padding_[6]; // 48 bit -- only if allocation speed is important } - (id)initWithProtocol:(Lookin_PTProtocol*)protocol delegate:(id)delegate; - (BOOL)acceptIncomingConnection:(dispatch_fd_t)serverSocketFD; @end static const uint8_t kUserInfoKey; #pragma mark - @interface Lookin_PTData () - (id)initWithMappedDispatchData:(dispatch_data_t)mappedContiguousData data:(void*)data length:(size_t)length; @end #pragma mark - @interface Lookin_PTAddress () { struct sockaddr_storage sockaddr_; } - (id)initWithSockaddr:(const struct sockaddr_storage*)addr; @end #pragma mark - @implementation Lookin_PTChannel @synthesize protocol = protocol_; + (Lookin_PTChannel*)channelWithDelegate:(id)delegate { return [[Lookin_PTChannel alloc] initWithProtocol:[Lookin_PTProtocol sharedProtocolForQueue:dispatch_get_main_queue()] delegate:delegate]; } - (id)initWithProtocol:(Lookin_PTProtocol*)protocol delegate:(id)delegate { if (!(self = [super init])) return nil; protocol_ = protocol; self.delegate = delegate; return self; } - (id)initWithProtocol:(Lookin_PTProtocol*)protocol { if (!(self = [super init])) return nil; protocol_ = protocol; return self; } - (id)init { return [self initWithProtocol:[Lookin_PTProtocol sharedProtocolForQueue:dispatch_get_main_queue()]]; } - (void)dealloc { #if PT_DISPATCH_RETAIN_RELEASE if (dispatchObj_channel_) dispatch_release(dispatchObj_channel_); else if (dispatchObj_source_) dispatch_release(dispatchObj_source_); #endif } - (BOOL)isConnected { return connState_ == kConnStateConnecting || connState_ == kConnStateConnected; } - (BOOL)isListening { return connState_ == kConnStateListening; } - (id)userInfo { return objc_getAssociatedObject(self, (void*)&kUserInfoKey); } - (void)setUserInfo:(id)userInfo { objc_setAssociatedObject(self, (const void*)&kUserInfoKey, userInfo, OBJC_ASSOCIATION_RETAIN); } - (void)setConnState:(char)connState { connState_ = connState; } - (void)setDispatchChannel:(dispatch_io_t)channel { assert(connState_ == kConnStateConnecting || connState_ == kConnStateConnected || connState_ == kConnStateNone); dispatch_io_t prevChannel = dispatchObj_channel_; if (prevChannel != channel) { dispatchObj_channel_ = channel; #if PT_DISPATCH_RETAIN_RELEASE if (dispatchObj_channel_) dispatch_retain(dispatchObj_channel_); if (prevChannel) dispatch_release(prevChannel); #endif if (!dispatchObj_channel_ && !dispatchObj_source_) { connState_ = kConnStateNone; } } } - (void)setDispatchSource:(dispatch_source_t)source { assert(connState_ == kConnStateListening || connState_ == kConnStateNone); dispatch_source_t prevSource = dispatchObj_source_; if (prevSource != source) { dispatchObj_source_ = source; #if PT_DISPATCH_RETAIN_RELEASE if (dispatchObj_source_) dispatch_retain(dispatchObj_source_); if (prevSource) dispatch_release(prevSource); #endif if (!dispatchObj_channel_ && !dispatchObj_source_) { connState_ = kConnStateNone; } } } - (id)delegate { return delegate_; } - (void)setDelegate:(id)delegate { delegate_ = delegate; delegateFlags_ = 0; if (!delegate_) { return; } if ([delegate respondsToSelector:@selector(ioFrameChannel:shouldAcceptFrameOfType:tag:payloadSize:)]) { delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize; } if (delegate_ && [delegate respondsToSelector:@selector(ioFrameChannel:didEndWithError:)]) { delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_didEndWithError; } if (delegate_ && [delegate respondsToSelector:@selector(ioFrameChannel:didAcceptConnection:fromAddress:)]) { delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress; } } //- (void)setFileDescriptor:(dispatch_fd_t)fd { // [self setDispatchChannel:dispatch_io_create(DISPATCH_IO_STREAM, fd, protocol_.queue, ^(int error) { // close(fd); // })]; //} #pragma mark - Connecting - (void)connectToPort:(int)port overUSBHub:(Lookin_PTUSBHub*)usbHub deviceID:(NSNumber*)deviceID callback:(void(^)(NSError *error))callback { assert(protocol_ != NULL); if (connState_ != kConnStateNone) { if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]); return; } connState_ = kConnStateConnecting; [usbHub connectToDevice:deviceID port:port onStart:^(NSError *err, dispatch_io_t dispatchChannel) { NSError *error = err; if (!error) { [self startReadingFromConnectedChannel:dispatchChannel error:&error]; } else { self->connState_ = kConnStateNone; } if (callback) callback(error); } onEnd:^(NSError *error) { if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { [self->delegate_ ioFrameChannel:self didEndWithError:error]; } self->endError_ = nil; }]; } - (void)connectToPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error, Lookin_PTAddress *address))callback { assert(protocol_ != NULL); if (connState_ != kConnStateNone) { if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil], nil); return; } connState_ = kConnStateConnecting; int error = 0; // Create socket dispatch_fd_t fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { perror("socket(AF_INET, SOCK_STREAM, 0) failed"); error = errno; if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil], nil); return; } // Connect socket struct sockaddr_in addr; bzero((char *)&addr, sizeof(addr)); addr.sin_len = sizeof(addr); addr.sin_family = AF_INET; addr.sin_port = htons(port); //addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); //addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_addr.s_addr = htonl(address); // prevent SIGPIPE int on = 1; setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); // int socket, const struct sockaddr *address, socklen_t address_len if (connect(fd, (const struct sockaddr *)&addr, addr.sin_len) == -1) { //perror("connect"); error = errno; close(fd); if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil); return; } // get actual address //if (getsockname(fd, (struct sockaddr*)&addr, (socklen_t*)&addr.sin_len) == -1) { // error = errno; // close(fd); // if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil); // return; //} dispatch_io_t dispatchChannel = dispatch_io_create(DISPATCH_IO_STREAM, fd, protocol_.queue, ^(int error) { close(fd); if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { NSError *err = error == 0 ? self->endError_ : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]; [self->delegate_ ioFrameChannel:self didEndWithError:err]; self->endError_ = nil; } }); if (!dispatchChannel) { close(fd); if (callback) callback([[NSError alloc] initWithDomain:@"PTError" code:0 userInfo:nil], nil); return; } // Success NSError *err = nil; Lookin_PTAddress *ptAddr = [[Lookin_PTAddress alloc] initWithSockaddr:(struct sockaddr_storage*)&addr]; [self startReadingFromConnectedChannel:dispatchChannel error:&err]; if (callback) callback(err, ptAddr); } #pragma mark - Listening and serving - (void)listenOnPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error))callback { assert(dispatchObj_source_ == nil); // Create socket dispatch_fd_t fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; } // Connect socket struct sockaddr_in addr; bzero((char *)&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); //addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); //addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_addr.s_addr = htonl(address); socklen_t socklen = sizeof(addr); int on = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { close(fd); if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; } if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { close(fd); if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; } if (bind(fd, (struct sockaddr*)&addr, socklen) != 0) { close(fd); if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; } if (listen(fd, 512) != 0) { close(fd); if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; } [self setDispatchSource:dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, protocol_.queue)]; dispatch_source_set_event_handler(dispatchObj_source_, ^{ unsigned long nconns = dispatch_source_get_data(self->dispatchObj_source_); while ([self acceptIncomingConnection:fd] && --nconns); }); dispatch_source_set_cancel_handler(dispatchObj_source_, ^{ // Captures *self*, effectively holding a reference to *self* until cancelled. self->dispatchObj_source_ = nil; close(fd); if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { [self->delegate_ ioFrameChannel:self didEndWithError:self->endError_]; self->endError_ = nil; } }); dispatch_resume(dispatchObj_source_); //NSLog(@"%@ opened on fd #%d", self, fd); connState_ = kConnStateListening; if (callback) callback(nil); } - (BOOL)acceptIncomingConnection:(dispatch_fd_t)serverSocketFD { struct sockaddr_in addr; socklen_t addrLen = sizeof(addr); dispatch_fd_t clientSocketFD = accept(serverSocketFD, (struct sockaddr*)&addr, &addrLen); if (clientSocketFD == -1) { perror("accept()"); return NO; } // prevent SIGPIPE int on = 1; setsockopt(clientSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); if (fcntl(clientSocketFD, F_SETFL, O_NONBLOCK) == -1) { perror("fcntl(.. O_NONBLOCK)"); close(clientSocketFD); return NO; } if (delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress) { Lookin_PTChannel *peerChannel = [[Lookin_PTChannel alloc] initWithProtocol:protocol_ delegate:delegate_]; __block Lookin_PTChannel *localChannelRef = self; dispatch_io_t dispatchChannel = dispatch_io_create(DISPATCH_IO_STREAM, clientSocketFD, protocol_.queue, ^(int error) { // Important note: This block captures *self*, thus a reference is held to // *self* until the fd is truly closed. localChannelRef = nil; close(clientSocketFD); if (peerChannel->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { NSError *err = error == 0 ? peerChannel->endError_ : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]; [peerChannel->delegate_ ioFrameChannel:peerChannel didEndWithError:err]; peerChannel->endError_ = nil; } }); [peerChannel setConnState:kConnStateConnected]; [peerChannel setDispatchChannel:dispatchChannel]; assert(((struct sockaddr_storage*)&addr)->ss_len == addrLen); Lookin_PTAddress *address = [[Lookin_PTAddress alloc] initWithSockaddr:(struct sockaddr_storage*)&addr]; [delegate_ ioFrameChannel:self didAcceptConnection:peerChannel fromAddress:address]; NSError *err = nil; if (![peerChannel startReadingFromConnectedChannel:dispatchChannel error:&err]) { NSLog(@"startReadingFromConnectedChannel failed in accept: %@", err); } } else { close(clientSocketFD); } return YES; } #pragma mark - Closing the channel - (void)close { if ((connState_ == kConnStateConnecting || connState_ == kConnStateConnected) && dispatchObj_channel_) { dispatch_io_close(dispatchObj_channel_, DISPATCH_IO_STOP); [self setDispatchChannel:NULL]; } else if (connState_ == kConnStateListening && dispatchObj_source_) { dispatch_source_cancel(dispatchObj_source_); } } - (void)cancel { if ((connState_ == kConnStateConnecting || connState_ == kConnStateConnected) && dispatchObj_channel_) { dispatch_io_close(dispatchObj_channel_, 0); [self setDispatchChannel:NULL]; } else if (connState_ == kConnStateListening && dispatchObj_source_) { dispatch_source_cancel(dispatchObj_source_); } } #pragma mark - Reading - (BOOL)startReadingFromConnectedChannel:(dispatch_io_t)channel error:(__autoreleasing NSError**)error { if (connState_ != kConnStateNone && connState_ != kConnStateConnecting && connState_ != kConnStateConnected) { if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]; return NO; } if (dispatchObj_channel_ != channel) { [self close]; [self setDispatchChannel:channel]; } connState_ = kConnStateConnected; // helper BOOL(^handleError)(NSError*,BOOL) = ^BOOL(NSError *error, BOOL isEOS) { if (error) { //NSLog(@"Error while communicating: %@", error); self->endError_ = error; [self close]; return YES; } else if (isEOS) { [self cancel]; return YES; } return NO; }; [protocol_ readFramesOverChannel:channel onFrame:^(NSError *error, uint32_t type, uint32_t tag, uint32_t payloadSize, dispatch_block_t resumeReadingFrames) { if (handleError(error, type == PTFrameTypeEndOfStream)) { return; } BOOL accepted = (channel == self->dispatchObj_channel_); if (accepted && (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize)) { accepted = [self->delegate_ ioFrameChannel:self shouldAcceptFrameOfType:type tag:tag payloadSize:payloadSize]; } if (payloadSize == 0) { if (accepted && self->delegate_) { [self->delegate_ ioFrameChannel:self didReceiveFrameOfType:type tag:tag payload:nil]; } else { // simply ignore the frame } resumeReadingFrames(); } else { // has payload if (!accepted) { // Read and discard payload, ignoring frame [self->protocol_ readAndDiscardDataOfSize:payloadSize overChannel:channel callback:^(NSError *error, BOOL endOfStream) { if (!handleError(error, endOfStream)) { resumeReadingFrames(); } }]; } else { [self->protocol_ readPayloadOfSize:payloadSize overChannel:channel callback:^(NSError *error, dispatch_data_t contiguousData, const uint8_t *buffer, size_t bufferSize) { if (handleError(error, bufferSize == 0)) { return; } if (self->delegate_) { Lookin_PTData *payload = [[Lookin_PTData alloc] initWithMappedDispatchData:contiguousData data:(void*)buffer length:bufferSize]; [self->delegate_ ioFrameChannel:self didReceiveFrameOfType:type tag:tag payload:payload]; } resumeReadingFrames(); }]; } } }]; return YES; } #pragma mark - Sending - (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload callback:(void(^)(NSError *error))callback { if (connState_ == kConnStateConnecting || connState_ == kConnStateConnected) { [protocol_ sendFrameOfType:frameType tag:tag withPayload:payload overChannel:dispatchObj_channel_ callback:callback]; } else if (callback) { callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]); } } #pragma mark - NSObject - (NSString*)description { id userInfo = objc_getAssociatedObject(self, (void*)&kUserInfoKey); return [NSString stringWithFormat:@"", self, ( connState_ == kConnStateConnecting ? @"connecting" : connState_ == kConnStateConnected ? @"connected" : connState_ == kConnStateListening ? @"listening" : @"closed"), userInfo ? " " : "", userInfo ? userInfo : @""]; } @end #pragma mark - @implementation Lookin_PTAddress - (id)initWithSockaddr:(const struct sockaddr_storage*)addr { if (!(self = [super init])) return nil; assert(addr); memcpy((void*)&sockaddr_, (const void*)addr, addr->ss_len); return self; } - (NSString*)name { if (sockaddr_.ss_len) { const void *sin_addr = NULL; size_t bufsize = 0; if (sockaddr_.ss_family == AF_INET6) { bufsize = INET6_ADDRSTRLEN; sin_addr = (const void *)&((const struct sockaddr_in6*)&sockaddr_)->sin6_addr; } else { bufsize = INET_ADDRSTRLEN; sin_addr = (const void *)&((const struct sockaddr_in*)&sockaddr_)->sin_addr; } char *buf = CFAllocatorAllocate(kCFAllocatorDefault, bufsize+1, 0); if (inet_ntop(sockaddr_.ss_family, sin_addr, buf, (unsigned int)bufsize-1) == NULL) { CFAllocatorDeallocate(kCFAllocatorDefault, buf); return nil; } return [[NSString alloc] initWithBytesNoCopy:(void*)buf length:strlen(buf) encoding:NSUTF8StringEncoding freeWhenDone:YES]; } else { return nil; } } - (NSInteger)port { if (sockaddr_.ss_len) { return ntohs(PT_SOCKADDR_ACCESS(&sockaddr_, sin_port, sin6_port)); } else { return 0; } } - (NSString*)description { if (sockaddr_.ss_len) { return [NSString stringWithFormat:@"%@:%u", self.name, (unsigned)self.port]; } else { return @"(?)"; } } @end #pragma mark - @implementation Lookin_PTData @synthesize dispatchData = dispatchData_; @synthesize data = data_; @synthesize length = length_; - (id)initWithMappedDispatchData:(dispatch_data_t)mappedContiguousData data:(void*)data length:(size_t)length { if (!(self = [super init])) return nil; dispatchData_ = mappedContiguousData; #if PT_DISPATCH_RETAIN_RELEASE if (dispatchData_) dispatch_retain(dispatchData_); #endif data_ = data; length_ = length; return self; } - (void)dealloc { #if PT_DISPATCH_RETAIN_RELEASE if (dispatchData_) dispatch_release(dispatchData_); #endif data_ = NULL; length_ = 0; } #pragma mark - NSObject - (NSString*)description { return [NSString stringWithFormat:@"", self, length_]; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Peertalk/Lookin_PTPrivate.h ================================================ #if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && (!defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0)) || \ (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && (!defined(__MAC_10_8) || __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_8)) #define PT_DISPATCH_RETAIN_RELEASE 1 #else #define PT_DISPATCH_RETAIN_RELEASE 0 #endif #if PT_DISPATCH_RETAIN_RELEASE #define PT_PRECISE_LIFETIME #define PT_PRECISE_LIFETIME_UNUSED #else #define PT_PRECISE_LIFETIME __attribute__((objc_precise_lifetime)) #define PT_PRECISE_LIFETIME_UNUSED __attribute__((objc_precise_lifetime, unused)) #endif ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Peertalk/Lookin_PTProtocol.h ================================================ // // A universal frame-based communication protocol which can be used to exchange // arbitrary structured data. // // In short: // // - Each transmission is comprised by one fixed-size frame. // - Each frame contains a protocol version number. // - Each frame contains an application frame type. // - Each frame can contain an identifying tag. // - Each frame can have application-specific data of up to UINT32_MAX size. // - Transactions style messaging can be modeled on top using frame tags. // - Lightweight API on top of libdispatch (aka GCD) -- close to the metal. // #include #import // Special frame tag that signifies "no tag". Your implementation should never // create a reply for a frame with this tag. static const uint32_t PTFrameNoTag = 0; // Special frame type that signifies that the stream has ended. static const uint32_t PTFrameTypeEndOfStream = 0; // NSError domain FOUNDATION_EXPORT NSString * const Lookin_PTProtocolErrorDomain; @interface Lookin_PTProtocol : NSObject // Queue on which to run data processing blocks. @property dispatch_queue_t queue; // Get the shared protocol object for *queue* + (Lookin_PTProtocol*)sharedProtocolForQueue:(dispatch_queue_t)queue; // Initialize a new protocol object to use a specific queue. - (id)initWithDispatchQueue:(dispatch_queue_t)queue; // Initialize a new protocol object to use the current calling queue. - (id)init; #pragma mark Sending frames // Generate a new tag that is unique within this protocol object. - (uint32_t)newTag; // Send a frame over *channel* with an optional payload and optional callback. // If *callback* is not NULL, the block is invoked when either an error occured // or when the frame (and payload, if any) has been completely sent. - (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload overChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error))callback; #pragma mark Receiving frames // Read frames over *channel* as they arrive. // The onFrame handler is responsible for reading (or discarding) any payload // and call *resumeReadingFrames* afterwards to resume reading frames. // To stop reading frames, simply do not invoke *resumeReadingFrames*. // When the stream ends, a frame of type PTFrameTypeEndOfStream is received. - (void)readFramesOverChannel:(dispatch_io_t)channel onFrame:(void(^)(NSError *error, uint32_t type, uint32_t tag, uint32_t payloadSize, dispatch_block_t resumeReadingFrames))onFrame; // Read a single frame over *channel*. A frame of type PTFrameTypeEndOfStream // denotes the stream has ended. - (void)readFrameOverChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, uint32_t frameType, uint32_t frameTag, uint32_t payloadSize))callback; #pragma mark Receiving frame payloads // Read a complete payload. It's the callers responsibility to make sure // payloadSize is not too large since memory will be automatically allocated // where only payloadSize is the limit. // The returned dispatch_data_t object owns *buffer* and thus you need to call // dispatch_retain on *contiguousData* if you plan to keep *buffer* around after // returning from the callback. - (void)readPayloadOfSize:(size_t)payloadSize overChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, dispatch_data_t contiguousData, const uint8_t *buffer, size_t bufferSize))callback; // Discard data of *size* waiting on *channel*. *callback* can be NULL. - (void)readAndDiscardDataOfSize:(size_t)size overChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, BOOL endOfStream))callback; @end @interface NSData (Lookin_PTProtocol) // Creates a new dispatch_data_t object which references the receiver and uses // the receivers bytes as its backing data. The returned dispatch_data_t object // holds a reference to the recevier. It's the callers responsibility to call // dispatch_release on the returned object when done. - (dispatch_data_t)createReferencingDispatchData; + (NSData *)dataWithContentsOfDispatchData:(dispatch_data_t)data; @end @interface NSDictionary (Lookin_PTProtocol) // See description of -[NSData(Lookin_PTProtocol) createReferencingDispatchData] - (dispatch_data_t)createReferencingDispatchData; // Decode *data* as a peroperty list-encoded dictionary. Returns nil on failure. + (NSDictionary*)dictionaryWithContentsOfDispatchData:(dispatch_data_t)data; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Peertalk/Lookin_PTProtocol.m ================================================ #import "Lookin_PTProtocol.h" #import "Lookin_PTPrivate.h" #import static const uint32_t PTProtocolVersion1 = 1; NSString * const Lookin_PTProtocolErrorDomain = @"PTProtocolError"; // This is what we send as the header for each frame. typedef struct _PTFrame { // The version of the frame and protocol. uint32_t version; // Type of frame uint32_t type; // Unless zero, a tag is retained in frames that are responses to previous // frames. Applications can use this to build transactions or request-response // logic. uint32_t tag; // If payloadSize is larger than zero, *payloadSize* number of bytes are // following, constituting application-specific data. uint32_t payloadSize; } PTFrame; @interface Lookin_PTProtocol () { uint32_t nextFrameTag_; @public dispatch_queue_t queue_; } - (dispatch_data_t)createDispatchDataWithFrameOfType:(uint32_t)type frameTag:(uint32_t)frameTag payload:(dispatch_data_t)payload; @end static void _release_queue_local_protocol(void *objcobj) { if (objcobj) { Lookin_PTProtocol *protocol = (__bridge_transfer id)objcobj; protocol->queue_ = NULL; } } @interface Lookin_RQueueLocalIOFrameProtocol : Lookin_PTProtocol @end @implementation Lookin_RQueueLocalIOFrameProtocol - (void)setQueue:(dispatch_queue_t)queue { } @end @implementation Lookin_PTProtocol + (Lookin_PTProtocol*)sharedProtocolForQueue:(dispatch_queue_t)queue { static const char currentQueueFrameProtocolKey; //dispatch_queue_t queue = dispatch_get_current_queue(); Lookin_PTProtocol *currentQueueFrameProtocol = (__bridge Lookin_PTProtocol*)dispatch_queue_get_specific(queue, ¤tQueueFrameProtocolKey); if (!currentQueueFrameProtocol) { currentQueueFrameProtocol = [[Lookin_RQueueLocalIOFrameProtocol alloc] initWithDispatchQueue:NULL]; currentQueueFrameProtocol->queue_ = queue; // reference, no retain, since we would create cyclic references dispatch_queue_set_specific(queue, ¤tQueueFrameProtocolKey, (__bridge_retained void*)currentQueueFrameProtocol, &_release_queue_local_protocol); return (__bridge Lookin_PTProtocol*)dispatch_queue_get_specific(queue, ¤tQueueFrameProtocolKey); // to avoid race conds } else { return currentQueueFrameProtocol; } } - (id)initWithDispatchQueue:(dispatch_queue_t)queue { if (!(self = [super init])) return nil; queue_ = queue; #if PT_DISPATCH_RETAIN_RELEASE if (queue_) dispatch_retain(queue_); #endif return self; } - (id)init { return [self initWithDispatchQueue:dispatch_get_main_queue()]; } - (void)dealloc { if (queue_) { #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(queue_); #endif } } - (dispatch_queue_t)queue { return queue_; } - (void)setQueue:(dispatch_queue_t)queue { #if PT_DISPATCH_RETAIN_RELEASE dispatch_queue_t prev_queue = queue_; queue_ = queue; if (queue_) dispatch_retain(queue_); if (prev_queue) dispatch_release(prev_queue); #else queue_ = queue; #endif } - (uint32_t)newTag { return ++nextFrameTag_; } #pragma mark - #pragma mark Creating frames - (dispatch_data_t)createDispatchDataWithFrameOfType:(uint32_t)type frameTag:(uint32_t)frameTag payload:(dispatch_data_t)payload { PTFrame *frame = CFAllocatorAllocate(kCFAllocatorDefault, sizeof(PTFrame), 0); frame->version = htonl(PTProtocolVersion1); frame->type = htonl(type); frame->tag = htonl(frameTag); if (payload) { size_t payloadSize = dispatch_data_get_size(payload); assert(payloadSize <= UINT32_MAX); frame->payloadSize = htonl((uint32_t)payloadSize); } else { frame->payloadSize = 0; } dispatch_data_t frameData = dispatch_data_create((const void*)frame, sizeof(PTFrame), queue_, ^{ CFAllocatorDeallocate(kCFAllocatorDefault, (void*)frame); }); if (payload && frame->payloadSize != 0) { // chain frame + payload dispatch_data_t data = dispatch_data_create_concat(frameData, payload); #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(frameData); #endif frameData = data; } return frameData; } #pragma mark - #pragma mark Sending frames - (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload overChannel:(dispatch_io_t)channel callback:(void(^)(NSError*))callback { dispatch_data_t frame = [self createDispatchDataWithFrameOfType:frameType frameTag:tag payload:payload]; dispatch_io_write(channel, 0, frame, queue_, ^(bool done, dispatch_data_t data, int _errno) { if (done && callback) { callback(_errno == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil]); } }); #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(frame); #endif } #pragma mark - #pragma mark Receiving frames - (void)readFrameOverChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, uint32_t frameType, uint32_t frameTag, uint32_t payloadSize))callback { __block dispatch_data_t allData = NULL; dispatch_io_read(channel, 0, sizeof(PTFrame), queue_, ^(bool done, dispatch_data_t data, int error) { //NSLog(@"dispatch_io_read: done=%d data=%p error=%d", done, data, error); size_t dataSize = data ? dispatch_data_get_size(data) : 0; if (dataSize) { if (!allData) { allData = data; #if PT_DISPATCH_RETAIN_RELEASE dispatch_retain(allData); #endif } else { #if PT_DISPATCH_RETAIN_RELEASE dispatch_data_t allDataPrev = allData; allData = dispatch_data_create_concat(allData, data); dispatch_release(allDataPrev); #else allData = dispatch_data_create_concat(allData, data); #endif } } if (done) { if (error != 0) { callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], 0, 0, 0); return; } if (dataSize == 0) { callback(nil, PTFrameTypeEndOfStream, 0, 0); return; } if (!allData || dispatch_data_get_size(allData) < sizeof(PTFrame)) { #if PT_DISPATCH_RETAIN_RELEASE if (allData) dispatch_release(allData); #endif callback([[NSError alloc] initWithDomain:Lookin_PTProtocolErrorDomain code:0 userInfo:nil], 0, 0, 0); return; } PTFrame *frame = NULL; size_t size = 0; PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(allData, (const void **)&frame, &size); // precise lifetime guarantees bytes in frame will stay valid till the end of scope #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(allData); #endif if (!contiguousData) { callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil], 0, 0, 0); return; } frame->version = ntohl(frame->version); if (frame->version != PTProtocolVersion1) { callback([[NSError alloc] initWithDomain:Lookin_PTProtocolErrorDomain code:0 userInfo:nil], 0, 0, 0); } else { frame->type = ntohl(frame->type); frame->tag = ntohl(frame->tag); frame->payloadSize = ntohl(frame->payloadSize); callback(nil, frame->type, frame->tag, frame->payloadSize); } #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(contiguousData); #endif } }); } - (void)readPayloadOfSize:(size_t)payloadSize overChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, dispatch_data_t contiguousData, const uint8_t *buffer, size_t bufferSize))callback { __block dispatch_data_t allData = NULL; dispatch_io_read(channel, 0, payloadSize, queue_, ^(bool done, dispatch_data_t data, int error) { //NSLog(@"dispatch_io_read: done=%d data=%p error=%d", done, data, error); size_t dataSize = dispatch_data_get_size(data); if (dataSize) { if (!allData) { allData = data; #if PT_DISPATCH_RETAIN_RELEASE dispatch_retain(allData); #endif } else { #if PT_DISPATCH_RETAIN_RELEASE dispatch_data_t allDataPrev = allData; allData = dispatch_data_create_concat(allData, data); dispatch_release(allDataPrev); #else allData = dispatch_data_create_concat(allData, data); #endif } } if (done) { if (error != 0) { #if PT_DISPATCH_RETAIN_RELEASE if (allData) dispatch_release(allData); #endif callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], NULL, NULL, 0); return; } if (dataSize == 0) { #if PT_DISPATCH_RETAIN_RELEASE if (allData) dispatch_release(allData); #endif callback(nil, NULL, NULL, 0); return; } uint8_t *buffer = NULL; size_t bufferSize = 0; PT_PRECISE_LIFETIME dispatch_data_t contiguousData = NULL; if (allData) { contiguousData = dispatch_data_create_map(allData, (const void **)&buffer, &bufferSize); #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(allData); allData = NULL; #endif if (!contiguousData) { callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil], NULL, NULL, 0); return; } } callback(nil, contiguousData, buffer, bufferSize); #if PT_DISPATCH_RETAIN_RELEASE if (contiguousData) dispatch_release(contiguousData); #endif } }); } - (void)readAndDiscardDataOfSize:(size_t)size overChannel:(dispatch_io_t)channel callback:(void(^)(NSError*, BOOL))callback { dispatch_io_read(channel, 0, size, queue_, ^(bool done, dispatch_data_t data, int error) { if (done && callback) { size_t dataSize = data ? dispatch_data_get_size(data) : 0; callback(error == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], dataSize == 0); } }); } - (void)readFramesOverChannel:(dispatch_io_t)channel onFrame:(void(^)(NSError*, uint32_t, uint32_t, uint32_t, dispatch_block_t))onFrame { [self readFrameOverChannel:channel callback:^(NSError *error, uint32_t type, uint32_t tag, uint32_t payloadSize) { onFrame(error, type, tag, payloadSize, ^{ if (type != PTFrameTypeEndOfStream) { [self readFramesOverChannel:channel onFrame:onFrame]; } }); }]; } @end @interface Lookin_PTDispatchData : NSObject { dispatch_data_t dispatchData_; } @end @implementation Lookin_PTDispatchData - (id)initWithDispatchData:(dispatch_data_t)dispatchData { if (!(self = [super init])) return nil; dispatchData_ = dispatchData; #if PT_DISPATCH_RETAIN_RELEASE dispatch_retain(dispatchData_); #endif return self; } - (void)dealloc { #if PT_DISPATCH_RETAIN_RELEASE if (dispatchData_) dispatch_release(dispatchData_); #endif } @end @implementation NSData (Lookin_PTProtocol) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-getter-return-value" - (dispatch_data_t)createReferencingDispatchData { // Note: The queue is used to submit the destructor. Since we only perform an // atomic release of self, it doesn't really matter which queue is used, thus // we use the current calling queue. return dispatch_data_create((const void*)self.bytes, self.length, dispatch_get_main_queue(), ^{ // trick to have the block capture the data, thus retain/releasing [self length]; }); } #pragma clang diagnostic pop + (NSData *)dataWithContentsOfDispatchData:(dispatch_data_t)data { if (!data) { return nil; } uint8_t *buffer = NULL; size_t bufferSize = 0; PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(data, (const void **)&buffer, &bufferSize); if (!contiguousData) { return nil; } Lookin_PTDispatchData *dispatchDataRef = [[Lookin_PTDispatchData alloc] initWithDispatchData:contiguousData]; NSData *newData = [NSData dataWithBytesNoCopy:(void*)buffer length:bufferSize freeWhenDone:NO]; #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(contiguousData); #endif static const bool kDispatchDataRefKey; objc_setAssociatedObject(newData, (const void*)kDispatchDataRefKey, dispatchDataRef, OBJC_ASSOCIATION_RETAIN); return newData; } @end @implementation NSDictionary (Lookin_PTProtocol) - (dispatch_data_t)createReferencingDispatchData { NSError *error = nil; NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:self format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error]; if (!plistData) { NSLog(@"Failed to serialize property list: %@", error); return nil; } else { return [plistData createReferencingDispatchData]; } } // Decode *data* as a peroperty list-encoded dictionary. Returns nil on failure. + (NSDictionary*)dictionaryWithContentsOfDispatchData:(dispatch_data_t)data { if (!data) { return nil; } uint8_t *buffer = NULL; size_t bufferSize = 0; PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(data, (const void **)&buffer, &bufferSize); if (!contiguousData) { return nil; } NSDictionary *dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:(void*)buffer length:bufferSize freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:nil]; #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(contiguousData); #endif return dict; } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Peertalk/Lookin_PTUSBHub.h ================================================ #include #import // Lookin_PTUSBDeviceDidAttachNotification // Posted when a device has been attached. Also posted for each device that is // already attached when the Lookin_PTUSBHub starts listening. // // .userInfo = { // DeviceID = 3; // MessageType = Attached; // Properties = { // ConnectionSpeed = 480000000; // ConnectionType = USB; // DeviceID = 3; // LocationID = 1234567890; // ProductID = 1234; // SerialNumber = 0123456789abcdef0123456789abcdef01234567; // }; // } // FOUNDATION_EXPORT NSString * const Lookin_PTUSBDeviceDidAttachNotification; // Lookin_PTUSBDeviceDidDetachNotification // Posted when a device has been detached. // // .userInfo = { // DeviceID = 3; // MessageType = Detached; // } // FOUNDATION_EXPORT NSString * const Lookin_PTUSBDeviceDidDetachNotification; // NSError domain FOUNDATION_EXPORT NSString * const Lookin_PTUSBHubErrorDomain; // Error codes returned with NSError.code for NSError domain Lookin_PTUSBHubErrorDomain typedef enum { PTUSBHubErrorBadDevice = 2, PTUSBHubErrorConnectionRefused = 3, } PTUSBHubError; @interface Lookin_PTUSBHub : NSObject // Shared, implicitly opened hub. + (Lookin_PTUSBHub*)sharedHub; // Connect to a TCP *port* on a device, while the actual transport is over USB. // Upon success, *error* is nil and *channel* is a duplex I/O channel. // You can retrieve the underlying file descriptor using // dispatch_io_get_descriptor(channel). The dispatch_io_t channel behaves just // like any stream type dispatch_io_t, making it possible to use the same logic // for both USB bridged connections and e.g. ethernet-based connections. // // *onStart* is called either when a connection failed, in which case the error // argument is non-nil, or when the connection was successfully established (the // error argument is nil). Must not be NULL. // // *onEnd* is called when a connection was open and just did close. If the error // argument is non-nil, the channel closed because of an error. Pass NULL for no // callback. // - (void)connectToDevice:(NSNumber*)deviceID port:(int)port onStart:(void(^)(NSError *error, dispatch_io_t channel))onStart onEnd:(void(^)(NSError *error))onEnd; // Start listening for devices. You only need to invoke this method on custom // instances to start receiving notifications. The shared instance returned from // +sharedHub is always in listening mode. // // *onStart* is called either when the system failed to start listening, in // which case the error argument is non-nil, or when the receiver is listening. // Pass NULL for no callback. // // *onEnd* is called when listening stopped. If the error argument is non-nil, // listening stopped because of an error. Pass NULL for no callback. // - (void)listenOnQueue:(dispatch_queue_t)queue onStart:(void(^)(NSError*))onStart onEnd:(void(^)(NSError*))onEnd; @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Peertalk/Lookin_PTUSBHub.m ================================================ #import "Lookin_PTUSBHub.h" #import "Lookin_PTPrivate.h" #include #include #include #include #include NSString * const Lookin_PTUSBHubErrorDomain = @"PTUSBHubError"; typedef uint32_t USBMuxPacketType; enum { USBMuxPacketTypeResult = 1, USBMuxPacketTypeConnect = 2, USBMuxPacketTypeListen = 3, USBMuxPacketTypeDeviceAdd = 4, USBMuxPacketTypeDeviceRemove = 5, // ? = 6, // ? = 7, USBMuxPacketTypePlistPayload = 8, }; typedef uint32_t USBMuxPacketProtocol; enum { USBMuxPacketProtocolBinary = 0, USBMuxPacketProtocolPlist = 1, }; typedef uint32_t USBMuxReplyCode; enum { USBMuxReplyCodeOK = 0, USBMuxReplyCodeBadCommand = 1, USBMuxReplyCodeBadDevice = 2, USBMuxReplyCodeConnectionRefused = 3, // ? = 4, // ? = 5, USBMuxReplyCodeBadVersion = 6, }; typedef struct usbmux_packet { uint32_t size; USBMuxPacketProtocol protocol; USBMuxPacketType type; uint32_t tag; char data[0]; } __attribute__((__packed__)) usbmux_packet_t; static const uint32_t kUsbmuxPacketMaxPayloadSize = UINT32_MAX - (uint32_t)sizeof(usbmux_packet_t); static uint32_t usbmux_packet_payload_size(usbmux_packet_t *upacket) { return upacket->size - sizeof(usbmux_packet_t); } static void *usbmux_packet_payload(usbmux_packet_t *upacket) { return (void*)upacket->data; } static void usbmux_packet_set_payload(usbmux_packet_t *upacket, const void *payload, uint32_t payloadLength) { memcpy(usbmux_packet_payload(upacket), payload, payloadLength); } static usbmux_packet_t *usbmux_packet_alloc(uint32_t payloadSize) { assert(payloadSize <= kUsbmuxPacketMaxPayloadSize); uint32_t upacketSize = sizeof(usbmux_packet_t) + payloadSize; usbmux_packet_t *upacket = CFAllocatorAllocate(kCFAllocatorDefault, upacketSize, 0); memset(upacket, 0, sizeof(usbmux_packet_t)); upacket->size = upacketSize; return upacket; } static usbmux_packet_t *usbmux_packet_create(USBMuxPacketProtocol protocol, USBMuxPacketType type, uint32_t tag, const void *payload, uint32_t payloadSize) { usbmux_packet_t *upacket = usbmux_packet_alloc(payloadSize); if (!upacket) { return NULL; } upacket->protocol = protocol; upacket->type = type; upacket->tag = tag; if (payload && payloadSize) { usbmux_packet_set_payload(upacket, payload, (uint32_t)payloadSize); } return upacket; } static void usbmux_packet_free(usbmux_packet_t *upacket) { CFAllocatorDeallocate(kCFAllocatorDefault, upacket); } NSString * const Lookin_PTUSBDeviceDidAttachNotification = @"Lookin_PTUSBDeviceDidAttachNotification"; NSString * const Lookin_PTUSBDeviceDidDetachNotification = @"Lookin_PTUSBDeviceDidDetachNotification"; static NSString *kPlistPacketTypeListen = @"Listen"; static NSString *kPlistPacketTypeConnect = @"Connect"; // Represents a channel of communication between the host process and a remote // (device) system. In practice, a Lookin_PTUSBChannel is connected to a usbmuxd // endpoint which is configured to either listen for device changes (the // PTUSBHub's channel is usually configured as a device notification listener) or // configured as a TCP bridge (e.g. channels returned from PTUSBHub's // connectToDevice:port:callback:). You should not create channels yourself, but // let Lookin_PTUSBHub provide you with already configured channels. @interface Lookin_PTUSBChannel : NSObject { dispatch_io_t channel_; dispatch_queue_t queue_; uint32_t nextPacketTag_; NSMutableDictionary *responseQueue_; BOOL autoReadPackets_; BOOL isReadingPackets_; } // The underlying dispatch I/O channel. This is handy if you want to handle your // own I/O logic without Lookin_PTUSBChannel. Remember to dispatch_retain() the channel // if you plan on using it as it might be released from the Lookin_PTUSBChannel at any // point in time. @property (readonly) dispatch_io_t dispatchChannel; // The underlying file descriptor. @property (readonly) dispatch_fd_t fileDescriptor; // Send data - (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callback; - (void)sendData:(NSData*)data callback:(void(^)(NSError*))callback; // Read data - (void)readFromOffset:(off_t)offset length:(size_t)length callback:(void(^)(NSError *error, dispatch_data_t data))callback; // Close the channel, preventing further reads and writes, but letting currently // queued reads and writes finish. - (void)cancel; // Close the channel, preventing further reads and writes, immediately // terminating any ongoing reads and writes. - (void)stop; @end @interface Lookin_PTUSBChannel (Private) + (NSDictionary*)packetDictionaryWithPacketType:(NSString*)messageType payload:(NSDictionary*)payload; - (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError *error))onEnd; - (void)listenWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler callback:(void(^)(NSError*))callback; - (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error; - (uint32_t)nextPacketTag; - (void)sendPacketOfType:(USBMuxPacketType)type overProtocol:(USBMuxPacketProtocol)protocol tag:(uint32_t)tag payload:(NSData*)payload callback:(void(^)(NSError*))callback; - (void)sendPacket:(NSDictionary*)packet tag:(uint32_t)tag callback:(void(^)(NSError *error))callback; - (void)sendRequest:(NSDictionary*)packet callback:(void(^)(NSError *error, NSDictionary *responsePacket))callback; - (void)scheduleReadPacketWithCallback:(void(^)(NSError *error, NSDictionary *packet, uint32_t packetTag))callback; - (void)scheduleReadPacketWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler; - (void)setNeedsReadingPacket; @end @interface Lookin_PTUSBHub () { Lookin_PTUSBChannel *channel_; } - (void)handleBroadcastPacket:(NSDictionary*)packet; @end @implementation Lookin_PTUSBHub + (Lookin_PTUSBHub*)sharedHub { static Lookin_PTUSBHub *gSharedHub; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ gSharedHub = [Lookin_PTUSBHub new]; [gSharedHub listenOnQueue:dispatch_get_main_queue() onStart:^(NSError *error) { if (error) { NSLog(@"Lookin_PTUSBHub failed to initialize: %@", error); } } onEnd:nil]; }); return gSharedHub; } - (id)init { if (!(self = [super init])) return nil; return self; } - (void)listenOnQueue:(dispatch_queue_t)queue onStart:(void(^)(NSError*))onStart onEnd:(void(^)(NSError*))onEnd { if (channel_) { if (onStart) onStart(nil); return; } channel_ = [Lookin_PTUSBChannel new]; NSError *error = nil; if ([channel_ openOnQueue:queue error:&error onEnd:onEnd]) { [channel_ listenWithBroadcastHandler:^(NSDictionary *packet) { [self handleBroadcastPacket:packet]; } callback:onStart]; } else if (onStart) { onStart(error); } } - (void)connectToDevice:(NSNumber*)deviceID port:(int)port onStart:(void(^)(NSError*, dispatch_io_t))onStart onEnd:(void(^)(NSError*))onEnd { Lookin_PTUSBChannel *channel = [Lookin_PTUSBChannel new]; NSError *error = nil; if (![channel openOnQueue:dispatch_get_main_queue() error:&error onEnd:onEnd]) { onStart(error, nil); return; } port = ((port<<8) & 0xFF00) | (port>>8); // limit NSDictionary *packet = [Lookin_PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeConnect payload:[NSDictionary dictionaryWithObjectsAndKeys: deviceID, @"DeviceID", [NSNumber numberWithInt:port], @"PortNumber", nil]]; [channel sendRequest:packet callback:^(NSError *error_, NSDictionary *responsePacket) { NSError *error = error_; [channel errorFromPlistResponse:responsePacket error:&error]; onStart(error, (error ? nil : channel.dispatchChannel) ); }]; } - (void)handleBroadcastPacket:(NSDictionary*)packet { NSString *messageType = [packet objectForKey:@"MessageType"]; if ([@"Attached" isEqualToString:messageType]) { [[NSNotificationCenter defaultCenter] postNotificationName:Lookin_PTUSBDeviceDidAttachNotification object:self userInfo:packet]; } else if ([@"Detached" isEqualToString:messageType]) { [[NSNotificationCenter defaultCenter] postNotificationName:Lookin_PTUSBDeviceDidDetachNotification object:self userInfo:packet]; } else { NSLog(@"Warning: Unhandled broadcast message: %@", packet); } } @end #pragma mark - @implementation Lookin_PTUSBChannel + (NSDictionary*)packetDictionaryWithPacketType:(NSString*)messageType payload:(NSDictionary*)payload { NSDictionary *packet = nil; static NSString *bundleName = nil; static NSString *bundleVersion = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSDictionary *infoDict = [NSBundle mainBundle].infoDictionary; if (infoDict) { bundleName = [infoDict objectForKey:@"CFBundleName"]; bundleVersion = [[infoDict objectForKey:@"CFBundleVersion"] description]; } }); if (bundleName) { packet = [NSDictionary dictionaryWithObjectsAndKeys: messageType, @"MessageType", bundleName, @"ProgName", bundleVersion, @"ClientVersionString", nil]; } else { packet = [NSDictionary dictionaryWithObjectsAndKeys:messageType, @"MessageType", nil]; } if (payload) { NSMutableDictionary *mpacket = [NSMutableDictionary dictionaryWithDictionary:payload]; [mpacket addEntriesFromDictionary:packet]; packet = mpacket; } return packet; } - (id)init { if (!(self = [super init])) return nil; return self; } - (void)dealloc { //NSLog(@"dealloc %@", self); if (channel_) { #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(channel_); #endif channel_ = nil; } } - (BOOL)valid { return !!channel_; } - (dispatch_io_t)dispatchChannel { return channel_; } - (dispatch_fd_t)fileDescriptor { return dispatch_io_get_descriptor(channel_); } - (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError*))onEnd { assert(queue != nil); assert(channel_ == nil); queue_ = queue; // Create socket dispatch_fd_t fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { if (error) *error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; return NO; } // prevent SIGPIPE int on = 1; setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); // Connect socket struct sockaddr_un addr; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, "/var/run/usbmuxd"); socklen_t socklen = sizeof(addr); if (connect(fd, (struct sockaddr*)&addr, socklen) == -1) { if (error) *error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; return NO; } channel_ = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue_, ^(int error) { close(fd); if (onEnd) { onEnd(error == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]); } }); return YES; } - (void)listenWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler callback:(void(^)(NSError*))callback { autoReadPackets_ = YES; [self scheduleReadPacketWithBroadcastHandler:broadcastHandler]; NSDictionary *packet = [Lookin_PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeListen payload:nil]; [self sendRequest:packet callback:^(NSError *error_, NSDictionary *responsePacket) { if (!callback) return; NSError *error = error_; [self errorFromPlistResponse:responsePacket error:&error]; callback(error); }]; } - (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error { if (!*error) { NSNumber *n = [packet objectForKey:@"Number"]; if (!n) { *error = [NSError errorWithDomain:Lookin_PTUSBHubErrorDomain code:(n ? n.integerValue : 0) userInfo:nil]; return NO; } USBMuxReplyCode replyCode = (USBMuxReplyCode)n.integerValue; if (replyCode != 0) { NSString *errmessage = @"Unspecified error"; switch (replyCode) { case USBMuxReplyCodeBadCommand: errmessage = @"illegal command"; break; case USBMuxReplyCodeBadDevice: errmessage = @"unknown device"; break; case USBMuxReplyCodeConnectionRefused: errmessage = @"connection refused"; break; case USBMuxReplyCodeBadVersion: errmessage = @"invalid version"; break; default: break; } *error = [NSError errorWithDomain:Lookin_PTUSBHubErrorDomain code:replyCode userInfo:[NSDictionary dictionaryWithObject:errmessage forKey:NSLocalizedDescriptionKey]]; return NO; } } return YES; } - (uint32_t)nextPacketTag { return ++nextPacketTag_; } - (void)sendRequest:(NSDictionary*)packet callback:(void(^)(NSError*, NSDictionary*))callback { uint32_t tag = [self nextPacketTag]; [self sendPacket:packet tag:tag callback:^(NSError *error) { if (error) { callback(error, nil); return; } // TODO: timeout un-triggered callbacks in responseQueue_ if (!self->responseQueue_) self->responseQueue_ = [NSMutableDictionary new]; [self->responseQueue_ setObject:callback forKey:[NSNumber numberWithUnsignedInt:tag]]; }]; // We are awaiting a response [self setNeedsReadingPacket]; } - (void)setNeedsReadingPacket { if (!isReadingPackets_) { [self scheduleReadPacketWithBroadcastHandler:nil]; } } - (void)scheduleReadPacketWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler { assert(isReadingPackets_ == NO); [self scheduleReadPacketWithCallback:^(NSError *error, NSDictionary *packet, uint32_t packetTag) { // Interpret the package we just received if (packetTag == 0) { // Broadcast message //NSLog(@"Received broadcast: %@", packet); if (broadcastHandler) broadcastHandler(packet); } else if (self->responseQueue_) { // Reply NSNumber *key = [NSNumber numberWithUnsignedInt:packetTag]; void(^requestCallback)(NSError*,NSDictionary*) = [self->responseQueue_ objectForKey:key]; if (requestCallback) { [self->responseQueue_ removeObjectForKey:key]; requestCallback(error, packet); } else { NSLog(@"Warning: Ignoring reply packet for which there is no registered callback. Packet => %@", packet); } } // Schedule reading another incoming package if (self->autoReadPackets_) { [self scheduleReadPacketWithBroadcastHandler:broadcastHandler]; } }]; } - (void)scheduleReadPacketWithCallback:(void(^)(NSError*, NSDictionary*, uint32_t))callback { static usbmux_packet_t ref_upacket; isReadingPackets_ = YES; // Read the first `sizeof(ref_upacket.size)` bytes off the channel_ dispatch_io_read(channel_, 0, sizeof(ref_upacket.size), queue_, ^(bool done, dispatch_data_t data, int error) { //NSLog(@"dispatch_io_read 0,4: done=%d data=%p error=%d", done, data, error); if (!done) return; if (error) { self->isReadingPackets_ = NO; callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil, 0); return; } // Read size of incoming usbmux_packet_t uint32_t upacket_len = 0; char *buffer = NULL; size_t buffer_size = 0; PT_PRECISE_LIFETIME_UNUSED dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size); // objc_precise_lifetime guarantees 'map_data' isn't released before memcpy has a chance to do its thing assert(buffer_size == sizeof(ref_upacket.size)); assert(sizeof(upacket_len) == sizeof(ref_upacket.size)); memcpy((void *)&(upacket_len), (const void *)buffer, buffer_size); #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(map_data); #endif // Allocate a new usbmux_packet_t for the expected size uint32_t payloadLength = upacket_len - (uint32_t)sizeof(usbmux_packet_t); usbmux_packet_t *upacket = usbmux_packet_alloc(payloadLength); // Read rest of the incoming usbmux_packet_t off_t offset = sizeof(ref_upacket.size); dispatch_io_read(self->channel_, offset, (size_t)(upacket->size - offset), self->queue_, ^(bool done, dispatch_data_t data, int error) { //NSLog(@"dispatch_io_read X,Y: done=%d data=%p error=%d", done, data, error); if (!done) { return; } self->isReadingPackets_ = NO; if (error) { callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil, 0); usbmux_packet_free(upacket); return; } if (upacket_len > kUsbmuxPacketMaxPayloadSize) { callback( [[NSError alloc] initWithDomain:Lookin_PTUSBHubErrorDomain code:1 userInfo:@{ NSLocalizedDescriptionKey:@"Received a packet that is too large"}], nil, 0 ); usbmux_packet_free(upacket); return; } // Copy read bytes onto our usbmux_packet_t char *buffer = NULL; size_t buffer_size = 0; PT_PRECISE_LIFETIME_UNUSED dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size); assert(buffer_size == upacket->size - offset); memcpy(((void *)(upacket))+offset, (const void *)buffer, buffer_size); #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(map_data); #endif // We only support plist protocol if (upacket->protocol != USBMuxPacketProtocolPlist) { callback([[NSError alloc] initWithDomain:Lookin_PTUSBHubErrorDomain code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unexpected package protocol" forKey:NSLocalizedDescriptionKey]], nil, upacket->tag); usbmux_packet_free(upacket); return; } // Only one type of packet in the plist protocol if (upacket->type != USBMuxPacketTypePlistPayload) { callback([[NSError alloc] initWithDomain:Lookin_PTUSBHubErrorDomain code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unexpected package type" forKey:NSLocalizedDescriptionKey]], nil, upacket->tag); usbmux_packet_free(upacket); return; } // Try to decode any payload as plist NSError *err = nil; NSDictionary *dict = nil; if (usbmux_packet_payload_size(upacket)) { dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:usbmux_packet_payload(upacket) length:usbmux_packet_payload_size(upacket) freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:&err]; } // Invoke callback callback(err, dict, upacket->tag); usbmux_packet_free(upacket); }); }); } - (void)sendPacketOfType:(USBMuxPacketType)type overProtocol:(USBMuxPacketProtocol)protocol tag:(uint32_t)tag payload:(NSData*)payload callback:(void(^)(NSError*))callback { assert(payload.length <= kUsbmuxPacketMaxPayloadSize); usbmux_packet_t *upacket = usbmux_packet_create( protocol, type, tag, payload ? payload.bytes : nil, (uint32_t)(payload ? payload.length : 0) ); dispatch_data_t data = dispatch_data_create((const void*)upacket, upacket->size, queue_, ^{ // Free packet when data is freed usbmux_packet_free(upacket); }); //NSData *data1 = [NSData dataWithBytesNoCopy:(void*)upacket length:upacket->size freeWhenDone:NO]; //[data1 writeToFile:[NSString stringWithFormat:@"/Users/rsms/c-packet-%u.data", tag] atomically:NO]; [self sendDispatchData:data callback:callback]; } - (void)sendPacket:(NSDictionary*)packet tag:(uint32_t)tag callback:(void(^)(NSError*))callback { NSError *error = nil; // NSPropertyListBinaryFormat_v1_0 NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:packet format:NSPropertyListXMLFormat_v1_0 options:0 error:&error]; if (!plistData) { callback(error); } else { [self sendPacketOfType:USBMuxPacketTypePlistPayload overProtocol:USBMuxPacketProtocolPlist tag:tag payload:plistData callback:callback]; } } - (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callback { off_t offset = 0; dispatch_io_write(channel_, offset, data, queue_, ^(bool done, dispatch_data_t data, int _errno) { //NSLog(@"dispatch_io_write: done=%d data=%p error=%d", done, data, error); if (!done) return; if (callback) { NSError *err = nil; if (_errno) err = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil]; callback(err); } }); #if PT_DISPATCH_RETAIN_RELEASE dispatch_release(data); // Release our ref. A ref is still held by dispatch_io_write #endif } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-getter-return-value" - (void)sendData:(NSData*)data callback:(void(^)(NSError*))callback { dispatch_data_t ddata = dispatch_data_create((const void*)data.bytes, data.length, queue_, ^{ // trick to have the block capture and retain the data [data length]; }); [self sendDispatchData:ddata callback:callback]; } #pragma clang diagnostic pop - (void)readFromOffset:(off_t)offset length:(size_t)length callback:(void(^)(NSError *error, dispatch_data_t data))callback { dispatch_io_read(channel_, offset, length, queue_, ^(bool done, dispatch_data_t data, int _errno) { if (!done) return; NSError *error = nil; if (_errno != 0) { error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil]; } callback(error, data); }); } - (void)cancel { if (channel_) { dispatch_io_close(channel_, 0); } } - (void)stop { if (channel_) { dispatch_io_close(channel_, DISPATCH_IO_STOP); } } @end ================================================ FILE: JetChat/Pods/LookinServer/Src/Shared/Peertalk/Peertalk.h ================================================ // // Peertalk.h // Peertalk // // Created by Marek Cirkos on 12/04/2016. // // #import //! Project version number for Peertalk. FOUNDATION_EXPORT double PeertalkVersionNumber; //! Project version string for Peertalk. FOUNDATION_EXPORT const unsigned char PeertalkVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import #import "Lookin_PTChannel.h" #import "Lookin_PTProtocol.h" #import "Lookin_PTUSBHub.h" ================================================ FILE: JetChat/Pods/LookinServer.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 002AFEB451777DB9A3DC1418A64FDAEB /* UIImageView+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = BBB9FB08942E7D1C225B340C732C8423 /* UIImageView+LookinServer.m */; }; 00A917F15A02C36E85016A1EC5151F48 /* LKS_PerspectiveHierarchyCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E9E73467EA12C21865FE8795FBCCC7B /* LKS_PerspectiveHierarchyCell.m */; }; 03BF733EE0CCA34067BE750A86CB4FE3 /* UIView+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 56E60FA565B2F3D48818431F6A85B480 /* UIView+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 05DE3FCE3B92E2C903FA0A27C2949E5C /* NSString+Lookin.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BE23A84D94F58A983283437C76B01EA /* NSString+Lookin.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0A5D6BE168F45534389D58B0E5C402F2 /* LKS_RequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 70A99B3B51A5DA2F5F3FFA7A193677A0 /* LKS_RequestHandler.m */; }; 0B19793D921F29CA064CF7CFEC4DACFF /* LookinAttrIdentifiers.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F0724A757C4E810ADCA40BF3CE53E06 /* LookinAttrIdentifiers.m */; }; 0B38FBE4A49B85218C8BB54696D057F7 /* UIView+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F2894DDD0E7CDB23764E9BACDF09E09 /* UIView+LookinServer.m */; }; 0ED01054EF8DFC123A9AD872AFC9CD15 /* UILabel+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E191CE81EFBC8BCBEB3FB1BA5BA26C5 /* UILabel+LookinServer.m */; }; 0F44787643816923BAC087056C4A2080 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529308949481ABD4AB88E1E73E3E257A /* Foundation.framework */; }; 162B974D5CFAA781469962B3E3452586 /* LookinServerDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 6152D80B4CE36E418A4B43B6BC0659F1 /* LookinServerDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1A07C183B19438380EC391F638EAB6C4 /* LKS_TraceManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 941BD4B62238E984825C8E89087A91F4 /* LKS_TraceManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1C33FC41BC4C4A2BF858360886B333F4 /* LookinEventHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E6212748693A9421890641FD76646BA /* LookinEventHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1DF8E0315478D3C7360E2E6A1175A234 /* LKS_LocalInspectPanelLabelView.h in Headers */ = {isa = PBXBuildFile; fileRef = 022AE5FFEC8ED71480F4495D0C937D05 /* LKS_LocalInspectPanelLabelView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1FAE39D41FE1F9741E04003A6C903582 /* CALayer+Lookin.m in Sources */ = {isa = PBXBuildFile; fileRef = C7D818A123DBDDE9CD05E9C12841308A /* CALayer+Lookin.m */; }; 226CDFCBA1E5B2AFC1C73CEEBF4C3B9D /* NSObject+Lookin.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A048573D79E53B8ED42A1FE066C1EA2 /* NSObject+Lookin.m */; }; 243F6946BE59EA71989BE9CC0E4395CE /* UILabel+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = DF9096D9A2FD6A999E61346368CAA7ED /* UILabel+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 244E750327B0F3772822E822AE74C4B1 /* LKS_RequestHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 42A2DA1C4547F338D0840E159B27BBC1 /* LKS_RequestHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24788392D96B78DACD52C327D7C81588 /* LookinScreenshotFetchManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 465E6E3CA198A46580FAB98EDF3A4650 /* LookinScreenshotFetchManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25D304D758958B0D9C27B8FC8EB0ACBB /* UITableView+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = F29C8C8AF671DD8AD6A6F71861EA1879 /* UITableView+LookinServer.m */; }; 26C1AA2A72BA4911E95AEC4B218892F4 /* UIBlurEffect+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 29885A010A1ED17B3073AC278B9CD638 /* UIBlurEffect+LookinServer.m */; }; 282C55A16088154CC5A1D5DEDA5496BD /* LookinAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = C20376EF9715E89F9FAB805E395FC794 /* LookinAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; }; 28D119755232554E19AF0D22A47E9A6D /* LookinServer-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = B18EB5F410154612C3DBD3105D53A343 /* LookinServer-dummy.m */; }; 29BEE3561388B64AF95D3514D5D06A04 /* LKS_PerspectiveToolbarButtons.h in Headers */ = {isa = PBXBuildFile; fileRef = AC84153F310DFC708D8164A5A577F77B /* LKS_PerspectiveToolbarButtons.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AC92219E8826061D9670134CF8D9834 /* UIGestureRecognizer+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = E27C154D81D31A232159AB57767C52AE /* UIGestureRecognizer+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2CF42A33A3EFB7E758A00EAE88CA48A6 /* LookinDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 3909A6FD0C3D8CEB54CAAEB9D79DC25C /* LookinDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2D76B474884CE186A953AB361C3A0754 /* LookinHierarchyInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = FEC7FD91328A4C8DC0E6D7BCE863BF81 /* LookinHierarchyInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2F15C8851FC095F0E86485957577397D /* LKS_PerspectiveManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 515D40D758457E6B5B1173030C01612F /* LKS_PerspectiveManager.m */; }; 2FE5520BE07DDA2734D019A4CB043F70 /* LKS_PerspectiveViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 637189BE5F1131ED2B6238784DCD7D27 /* LKS_PerspectiveViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 323CCED73BD97CE6A7061AB9DCF29ABD /* NSArray+Lookin.h in Headers */ = {isa = PBXBuildFile; fileRef = 40557903743DD96F426AD2B887B19435 /* NSArray+Lookin.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3387D7AE3F4445B00FBC375F19A1C1E3 /* LKS_PerspectiveItemLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = F77E54FD0F6D7095EE92036D9B822191 /* LKS_PerspectiveItemLayer.m */; }; 37773027F74EDBAF1EBEA0F49D0736B8 /* LookinMsgTargetAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EC9DA8B816F7ACB2A377E14F2B2742E /* LookinMsgTargetAction.h */; settings = {ATTRIBUTES = (Public, ); }; }; 38584E6F140A852D0FC933C6453DC727 /* LKS_HierarchyDisplayItemsMaker.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A57D122B6D3D68B27A0801624E11D79 /* LKS_HierarchyDisplayItemsMaker.h */; settings = {ATTRIBUTES = (Public, ); }; }; 395FC03D4A5C78FEBF6B71C65915FE25 /* UITableView+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 92F6252A0756D75A03CE9423F3290EE9 /* UITableView+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3AE087196930BBC5B29288CC6FC012A8 /* LKS_LocalInspectPanelLabelView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E443763A7FABE91F240735CAA10A8EC /* LKS_LocalInspectPanelLabelView.m */; }; 3B3AEA75418D1246AA27FAD925A55D9F /* UITextView+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = B034E538DC0BB5FECD524A9C5C4D13D7 /* UITextView+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3BEC9A7C9CC95CAF7EA103FC4ABD6958 /* LKS_MethodTraceManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B08F267BE32A0116787F7BB24B80E175 /* LKS_MethodTraceManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4062F9C27D84C7947F3ECE1EF54F8105 /* Peertalk.h in Headers */ = {isa = PBXBuildFile; fileRef = 224D485BBB4E6CC4AAF6953AEEBE11A2 /* Peertalk.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4101D5C79944A7EE20D39C305E3925E7 /* LookinAttributesSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 0FE713547D1B7A6F064D3C459B11643D /* LookinAttributesSection.m */; }; 41E1BB7BA9DD30BEA8B2CA1878BD08E8 /* LKS_HierarchyDetailsHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 21F08BBDCB1DE1C436221C396C470AD9 /* LKS_HierarchyDetailsHandler.m */; }; 45E3043648A92DBD685AE0C864652A67 /* LKS_PerspectiveItemLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = EF7E557AED816E41355C82D9B01503E8 /* LKS_PerspectiveItemLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4CA8C1C4D57F45A59342E14A03B8F05B /* LookinServer-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = EDA3ADE695F2893B80C54B136A905F4E /* LookinServer-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4FC57AC9601D64119D70C2D753D9C2AE /* Lookin_PTChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 21F862FE51621788FE3B5B10B9658E14 /* Lookin_PTChannel.m */; }; 5220A06D70DA6E2E50C4A6947923C228 /* LKS_ExportManager.h in Headers */ = {isa = PBXBuildFile; fileRef = A8B0FC1B422235EFC24A5C70F1D78072 /* LKS_ExportManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 52AD271308B525C3896B492C90B04041 /* LookinAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F20D43D8013D763C2F1557F458A5878 /* LookinAttribute.m */; }; 5388ABE101FDE4B892036F8CDF6D244F /* LKS_AttrModificationPatchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 51D4F1461926A74C5F2355B790ED8C4C /* LKS_AttrModificationPatchHandler.m */; }; 5392E9741A719C21EC773F166E4CC320 /* LookinEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 551E524A4439B10891291D09502E75F9 /* LookinEventHandler.m */; }; 5415C8359379460B1C0973DFFAA43BC1 /* LKS_PerspectiveLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = F25DC14B557F5D850EB4B9EC29125151 /* LKS_PerspectiveLayer.m */; }; 58483A17890415B06F360903F67C0A9B /* LookinHierarchyFile.m in Sources */ = {isa = PBXBuildFile; fileRef = CB13006AA2FDB3AE3E4B22D3D9F5489D /* LookinHierarchyFile.m */; }; 588F4168AD89A508BB0443FC637EA740 /* LKS_ConnectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = A7DDA8A26682379CB80FC5F701737A35 /* LKS_ConnectionManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 59D9096FFE602883B44BE1490A401BA3 /* LKS_ObjectRegistry.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A84F074D462823887DC12C48CF1A23D /* LKS_ObjectRegistry.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5A4663B94CB417B32F89C525235FA8B6 /* UIColor+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = ECABCAA9FECE7257B4267ADB213E3283 /* UIColor+LookinServer.m */; }; 5C9E1A1CBF7201E4AAE584DD0D9303D0 /* LookinAttributeModification.h in Headers */ = {isa = PBXBuildFile; fileRef = F860090A54E794DBDDE6230A75E4739F /* LookinAttributeModification.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5EA56E8AABB0D5BD07ED6A782DA8B1AC /* UIViewController+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = DBBB0BD8EE0599FB15FBA352E0A04D30 /* UIViewController+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5F1D8D32B4FF5122EBDF3C8DA4F29C16 /* LKS_PerspectiveDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B106512A34844A36D1126D046386C11 /* LKS_PerspectiveDataSource.m */; }; 61A82B81A37C4EF0DE9245DF03BCC0B2 /* LKS_AttrModificationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 7C2456EC8212C2D2646BF28F0307667A /* LKS_AttrModificationHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 637D22A34CDB36C98E7251295D61F38D /* UIBlurEffect+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 40E4E28CD8268379C395F944C80E5D24 /* UIBlurEffect+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 66ACB4C67161227890538AB1D296E2D9 /* LookinConnectionAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 2232E67A42835952B7296F474DBE7E5C /* LookinConnectionAttachment.m */; }; 676EEFF596210E09477FD56A7A00A413 /* LKS_LocalInspectManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D43309255489FF7F10A373E13EBAECCB /* LKS_LocalInspectManager.m */; }; 686B63A2DB1582BA86FD2474862A55A2 /* Lookin_PTProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 219AA6511B5F17D2215297AA9D40FA0F /* Lookin_PTProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7031600C6E773A7B7DD831B7936CBB38 /* LookinAppInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 4215DBA0FCC1E7DBED9F012AE258EEF7 /* LookinAppInfo.m */; }; 718515A9A80108D18EA957F3F1D2737C /* LKS_PerspectiveToolbarButtons.m in Sources */ = {isa = PBXBuildFile; fileRef = C6609C187DF26848E8C472E1D75F3C91 /* LKS_PerspectiveToolbarButtons.m */; }; 71B30263D74D639BF4E4691B8A13D8F1 /* LKS_PerspectiveHierarchyCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 2CBCB52C0DEB35E82AF29E260674BEC9 /* LKS_PerspectiveHierarchyCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; 73E919F3A51CE449FF8ADD8DE5F31166 /* NSSet+Lookin.h in Headers */ = {isa = PBXBuildFile; fileRef = AEC483F02167148745A30C09435FB5CF /* NSSet+Lookin.h */; settings = {ATTRIBUTES = (Public, ); }; }; 75AA7C8E605783943689788D6E0613DE /* NSString+Lookin.m in Sources */ = {isa = PBXBuildFile; fileRef = D83BF3E2881F2D2FFE4AFC22771FCE36 /* NSString+Lookin.m */; }; 75C4F601BA3E44DED010203F2FA45C2A /* UITextField+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8309B1276C55989BB2E4DFB8CECBEC85 /* UITextField+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7666D4A2B5B913494C898D0C480C1361 /* LKS_ConnectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 18267B21FB282FB0B18EC75340C86FA3 /* LKS_ConnectionManager.m */; }; 773059DF5A7F7830A39BDEC5F5E592EA /* LookinHierarchyInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = BB6373DF2DC64B584B8CF09C4150C433 /* LookinHierarchyInfo.m */; }; 7B9FEF91BA9C3F85EE09C3CC4E2A8704 /* LKS_HierarchyDetailsHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 173E783D805E674CA7CABEF6EB38D617 /* LKS_HierarchyDetailsHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7BC0882A3A100DB0ADDDEC34A7E9226A /* LKS_PerspectiveViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E3525E795CBB069BECBB66727A9040B7 /* LKS_PerspectiveViewController.m */; }; 7C294DC14AFC7D036D4A44F755187029 /* LookinHierarchyFile.h in Headers */ = {isa = PBXBuildFile; fileRef = ED0BE4AA53E1F0B4CEFB210EB072B5B8 /* LookinHierarchyFile.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7D796BD98330217929ED6786E25D328E /* LKS_EventHandlerMaker.h in Headers */ = {isa = PBXBuildFile; fileRef = 81F4FD3C4B4EA45A4F30C40F3439154D /* LKS_EventHandlerMaker.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7E6EB0135FFE02705D5953A6EDD53670 /* LookinAutoLayoutConstraint.h in Headers */ = {isa = PBXBuildFile; fileRef = 36B7DA9575C5ACE426132494D8083ED2 /* LookinAutoLayoutConstraint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7F37B6373DE062C9B0B24DD8E7AE11F5 /* LKS_HierarchyDisplayItemsMaker.m in Sources */ = {isa = PBXBuildFile; fileRef = 656101743950D6F3B29B041FABBABBBA /* LKS_HierarchyDisplayItemsMaker.m */; }; 7FD7C3C2E6628BD153FBE19EE0B862C6 /* LookinMsgAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BC258C8DE9140CD28149D90C8F /* LookinMsgAttribute.m */; }; 80B065E4294688AEE94640333908E2D5 /* UIImage+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 246618B47B4E7B3A98F764EE2AB3D655 /* UIImage+LookinServer.m */; }; 81031A88AF1A82C8CA05312E921AD20F /* LookinMsgTargetAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E62B8AF634F85D998245B5DD74B9A7B /* LookinMsgTargetAction.m */; }; 817F906E97C8BDC685735D7ACA16BF67 /* LookinObject.h in Headers */ = {isa = PBXBuildFile; fileRef = C407FEBCA96257041F34657CB4E402A6 /* LookinObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8287DC67E9027000D2FAF4BC8C47900F /* LookinDisplayItemDetail.m in Sources */ = {isa = PBXBuildFile; fileRef = F4EE9799E4B8DF680C384F179C7DEA96 /* LookinDisplayItemDetail.m */; }; 85A8B0F6DE99D2661790D9587DE88ACB /* LKS_PerspectiveManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E85CC932DE80B014D9094D2EAB34A2BB /* LKS_PerspectiveManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86260B5E519495CBA470ADD62F5F7EFE /* LookinCodingValueType.h in Headers */ = {isa = PBXBuildFile; fileRef = D9AB931C6A78ED2A1999B2FBFE1B2058 /* LookinCodingValueType.h */; settings = {ATTRIBUTES = (Public, ); }; }; 88303415F115915F79C2A03F5BE82076 /* LookinDisplayItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 8CD1D33AA0DDB360941DF2F6E823CAF5 /* LookinDisplayItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8852CB988A3DEC15C36D642391B16F79 /* UIImageView+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7469939E6B32E3BE277DF6CD1809B0C4 /* UIImageView+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 89A793565D3DA2487F0400F51E97E51A /* LKS_Helper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E7FEAAFC6B39DC7F79F3474722C57CD /* LKS_Helper.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8DA545D912C08215993B294F79A135D7 /* LookinWeakContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 78021CFD427249E79E99A44F636233A4 /* LookinWeakContainer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 90B9E88EB07332D674B580EA9523D778 /* LookinIvarTrace.h in Headers */ = {isa = PBXBuildFile; fileRef = C2242704AF70D5C582CC2B6198FD68E0 /* LookinIvarTrace.h */; settings = {ATTRIBUTES = (Public, ); }; }; 929990A7CD2FA0F831E363B1575D342D /* Lookin_PTUSBHub.m in Sources */ = {isa = PBXBuildFile; fileRef = CF2D7FC011FE161AC8F55805FF17FA56 /* Lookin_PTUSBHub.m */; }; 938AB95A69CB2DBC5586C7BB9A6FB3C4 /* LookinStaticAsyncUpdateTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EED9D3B1A085A02A5DA4405DC471C06 /* LookinStaticAsyncUpdateTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; 93CE3C05AC802BDD1E4DACC8AD1D04FB /* NSObject+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 15624D3BC8B0BBC6561F583620D5503A /* NSObject+LookinServer.m */; }; 96D3B5E4BF637A8C58E69ADD0F7466F7 /* LookinAttributesGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 87003642A2A1C6CC8D560F23C205ED96 /* LookinAttributesGroup.m */; }; 9723A5322855C2CE944B6ADA7144AF3D /* LookinAutoLayoutConstraint.m in Sources */ = {isa = PBXBuildFile; fileRef = E74AA900536DACC16713277804225CFF /* LookinAutoLayoutConstraint.m */; }; 9AE34167B727AB574413F989BBD79C55 /* UIVisualEffectView+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 49591CFFFE70FE73881333F76E3E0D63 /* UIVisualEffectView+LookinServer.m */; }; A0DE3EA47497800844AF034DB6F24252 /* LKS_PerspectiveHierarchyView.h in Headers */ = {isa = PBXBuildFile; fileRef = 430091E00751A8309CBEFCB975D628B2 /* LKS_PerspectiveHierarchyView.h */; settings = {ATTRIBUTES = (Public, ); }; }; A2C5F8D1319F000A4952F3752DD43A58 /* LKS_LocalInspectViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F2232DD63EB754EAC74ABEFFF76FE5A /* LKS_LocalInspectViewController.m */; }; A66B08B83E58ED25DE9F62F7860A0B83 /* LookinAttrType.h in Headers */ = {isa = PBXBuildFile; fileRef = EB6D809DF947CA72C9E61926B4EB3428 /* LookinAttrType.h */; settings = {ATTRIBUTES = (Public, ); }; }; A8203C32C9F49EAC14E3EF4F7AAA2F1B /* LookinDashboardBlueprint.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EC99F32DAB1371DC1BC2B4DE054EBB1 /* LookinDashboardBlueprint.h */; settings = {ATTRIBUTES = (Public, ); }; }; A8ABD68C212B85DBF0DB98B6BE93771E /* LookinConnectionResponseAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B8C4C1259B87ADA2BA1155FD8682977 /* LookinConnectionResponseAttachment.h */; settings = {ATTRIBUTES = (Public, ); }; }; AB72691991AB2233825ABF6562E75ABC /* LookinObject.m in Sources */ = {isa = PBXBuildFile; fileRef = B4807F2269356902D5229A64D3702476 /* LookinObject.m */; }; AD3B97F3C235643CC0BF3FAA024282DF /* LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 182681E841B47CA3389B60AB51ED6557 /* LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; AD8129C7689DB229DA465BD6C4100D21 /* LKS_AttrModificationPatchHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = D9AB342FF6C5A234675B5A531C1139CE /* LKS_AttrModificationPatchHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF2E535AFA72186438117A4E70BCFD50 /* LookinStaticAsyncUpdateTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D84A4451E510F809149A89ECD44325B /* LookinStaticAsyncUpdateTask.m */; }; AFE7D8A53068BF01D48B797654F8D432 /* LKS_PerspectiveHierarchyView.m in Sources */ = {isa = PBXBuildFile; fileRef = EE4C7F689F691D2932E6C966D2FF4D79 /* LKS_PerspectiveHierarchyView.m */; }; B1D0A281262122304BEB8EE4D8656B89 /* NSObject+Lookin.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F3B972C5B0BC04A7DD297F63757AE18 /* NSObject+Lookin.h */; settings = {ATTRIBUTES = (Public, ); }; }; B21BD55B46BD2A28CF186366F7B20E2E /* LKS_LocalInspectViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = CE764CE3893B37689C0BC44ECA3B4B91 /* LKS_LocalInspectViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; B29549D4F3B66555C98D8181395290C3 /* LookinAttributesSection.h in Headers */ = {isa = PBXBuildFile; fileRef = 7493D9CF640ACB6E2372474DA69C4DBE /* LookinAttributesSection.h */; settings = {ATTRIBUTES = (Public, ); }; }; B39AE573AC30734EC60348DD0B533DA4 /* NSArray+Lookin.m in Sources */ = {isa = PBXBuildFile; fileRef = 92BE346C95BF1CD82EE3A572BAE514F3 /* NSArray+Lookin.m */; }; B45B85409AE28EDE3532938ECB54301E /* LookinTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = 16CE2F8C86820C423CE6BE2AE6F52F46 /* LookinTuple.m */; }; B466251D9036F2EFC186A34E76524780 /* LookinAppInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 867C4CCA7408106ED81C200C0923172A /* LookinAppInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; B4BFA1A5E58FCDD2BA5D4261D71302A9 /* CALayer+Lookin.h in Headers */ = {isa = PBXBuildFile; fileRef = EA12ACB760FC94D0D4DA39BA6AF7E6B5 /* CALayer+Lookin.h */; settings = {ATTRIBUTES = (Public, ); }; }; B54C6FB5A76EDC47073B283362FB7469 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 070553B20C0DF9A38585E6C4EAD989AE /* UIKit.framework */; }; B94FD9A94D4B58CB9EC9C3E323E1B96C /* LookinTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = C530A07C10CC3C497B8AA6BD6DE8052D /* LookinTuple.h */; settings = {ATTRIBUTES = (Public, ); }; }; BBECE031D3606D9AEF902AC02B5D6B2A /* LookinConnectionResponseAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = E4F03438230841C57C8CCE5423D89E3E /* LookinConnectionResponseAttachment.m */; }; BDA4B3D5927885B71F129F17B4D68949 /* Lookin_PTPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3E97A80192FBDADDC55188CF6091D5E3 /* Lookin_PTPrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; BDC2179794DF128F9C6B76E79688ED7A /* LookinWeakContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9988BB7C7CD4DB52B5C26669B32EDEB6 /* LookinWeakContainer.m */; }; BE1853E860F777A2557B51C90A4B777E /* LookinAttrIdentifiers.h in Headers */ = {isa = PBXBuildFile; fileRef = 30A15219ABF51508E1B5E7F41021FCF3 /* LookinAttrIdentifiers.h */; settings = {ATTRIBUTES = (Public, ); }; }; BEE3F7679AECC5605C87AC2CC57F642B /* NSSet+Lookin.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B4800BE9AA6A7DBFDF9F166216FE328 /* NSSet+Lookin.m */; }; C1D8A55A4355FACAC236D48FBB55B106 /* UITextField+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3361441F9DE5F0C1FF71ED1246DE1347 /* UITextField+LookinServer.m */; }; C3090402C42FEE35E46BE9ACA62E59D5 /* LookinMethodTraceRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = BD6B5A8BA9AB2C7289B5C3AF5CE7637D /* LookinMethodTraceRecord.m */; }; C65099DD0C74F8C6DAEB416D5CB78ABF /* LKS_Helper.m in Sources */ = {isa = PBXBuildFile; fileRef = EA38C1286D76AB74A0A3F07249310FE4 /* LKS_Helper.m */; }; C6D113E45DF8A85A2417FBA19976723D /* LookinDisplayItemDetail.h in Headers */ = {isa = PBXBuildFile; fileRef = 7479CE957173AC886F78C2AC7C84AE5E /* LookinDisplayItemDetail.h */; settings = {ATTRIBUTES = (Public, ); }; }; C77C105596399C54E4ACDB7081069F09 /* LookinDisplayItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 46288120050C8C2D6841B9477D8CB3AC /* LookinDisplayItem.m */; }; CA8EA2FE75BC0DA7F5598CA0632CDFFE /* LKS_ObjectRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B8AD05BE1E641DD05378CDD1790D19A /* LKS_ObjectRegistry.m */; }; CDFC89C87D324B64B7499DD1604348AA /* LKS_PerspectiveDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A11CB612DBE8B848DB0A4FDE2FE0AC4 /* LKS_PerspectiveDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; CEF0CBC49993952ED1604A188D1563AB /* CALayer+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = DE9E6B913EEB5F5A1C9A7A85856175D1 /* CALayer+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; CF0933DBA480911BF4C1108EAC878BDA /* NSObject+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = E3B76BDD53364E83F98E635EB179731A /* NSObject+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; CFBCC88A4F3DEF0CE69E543801EBA607 /* LookinDashboardBlueprint.m in Sources */ = {isa = PBXBuildFile; fileRef = BDC348B013DE04566F37F77372AC0051 /* LookinDashboardBlueprint.m */; }; D4240909C3EB581AAD9D4F30A54D5060 /* LookinAttributeModification.m in Sources */ = {isa = PBXBuildFile; fileRef = FB6A511D0F970464D508C64B44400C49 /* LookinAttributeModification.m */; }; D4398BC5DF54688C6A49EA7644C2AA85 /* UIColor+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 2C0ACC09086E7B9ABB0D6E23122FBC38 /* UIColor+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; D835AB44CED4D20B062627054BBFCCBD /* LKS_TraceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AB4C8818C35EE45FF37BA06805CD7D8E /* LKS_TraceManager.m */; }; D8391668AAC27BE0089925F58E3D948F /* LKS_EventHandlerMaker.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC48F9F883D5F0A27890E9C02AF37B7 /* LKS_EventHandlerMaker.m */; }; D85F1AEDA79F234519A58954C3663A17 /* UIGestureRecognizer+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = F5AA9A2709B8E14E300CA2BC507F5C40 /* UIGestureRecognizer+LookinServer.m */; }; D8A570C0CFDCF761E580B74116D85154 /* LookinMethodTraceRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = 60445E6794C31E0A2C99993647C024EC /* LookinMethodTraceRecord.h */; settings = {ATTRIBUTES = (Public, ); }; }; E3EE06E9F651979DBE44DE18CBC537AA /* Lookin_PTProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = D8B765FF61800231762A18FFE0F6B382 /* Lookin_PTProtocol.m */; }; E432E79F5333083DA305D61EE51357C6 /* UITextView+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = C5A40322BA2B1C0B2B5F7CABD8A212F2 /* UITextView+LookinServer.m */; }; E4FDE57E1B38FCAE9B3884F4A4F863FC /* LKS_MethodTraceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FCAC034F412FE0127C5A680672CF74AE /* LKS_MethodTraceManager.m */; }; E81887F5984EF9D94B822A01D1D84B22 /* LookinAttributesGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = E3DEC557F4FBE7C86105D097E474E510 /* LookinAttributesGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; EAF37F0CADDFB6B59F3537F1C4345A41 /* LKS_AttrGroupsMaker.m in Sources */ = {isa = PBXBuildFile; fileRef = FF0F6F9E63946A3F858110F6F4A7960E /* LKS_AttrGroupsMaker.m */; }; EBDBD9E36412D0FA1E2780ED9765E6B6 /* LKS_PerspectiveLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A038A6A3CA099582D1CDBF695869B04 /* LKS_PerspectiveLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; ED22780431607AF0AC5178858EE9B47D /* Lookin_PTChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C5E5E31A936D294D1C60BCD4CB9F702 /* Lookin_PTChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; EDB75BEDB97A0DCA8AC5925B5A630763 /* Lookin_PTUSBHub.h in Headers */ = {isa = PBXBuildFile; fileRef = 179A2FE851754AACFEEDCF6AB4C759E7 /* Lookin_PTUSBHub.h */; settings = {ATTRIBUTES = (Public, ); }; }; EEF5224101D9FB9215C83192D9FFA475 /* CALayer+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 700AAE793D800E8DD0EB6BC4B0ADBBF7 /* CALayer+LookinServer.m */; }; EEF7475AA070A169E5479462C4D7E441 /* LKS_LocalInspectManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 2772D6A405FA998F1381C4FFC69BBCB6 /* LKS_LocalInspectManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; EF592766200A98B22B45A7FE854418CE /* LookinMsgAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = EABC1F238084414A9438F0EDD1898DB5 /* LookinMsgAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; }; F2809BD02FFD2D96AFF92F909DD11088 /* UIImage+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 64244C3B0B7A8712804C5B3E3B1E6739 /* UIImage+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; F2967247DC7D19DDCEBEE97D626741D8 /* LKS_AttrGroupsMaker.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BFF5A0B9A97313479A5ACC0713BBB88 /* LKS_AttrGroupsMaker.h */; settings = {ATTRIBUTES = (Public, ); }; }; F731001E2F0512B9FD9C46DA0D9B7CB3 /* LKS_AttrModificationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 991728F74F4232B04DA2F7EF601817A0 /* LKS_AttrModificationHandler.m */; }; FAACFEE16C085FB5F6D2F9D24C97D0F2 /* LookinConnectionAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DAB7747E848BF750F935DB1509AF889 /* LookinConnectionAttachment.h */; settings = {ATTRIBUTES = (Public, ); }; }; FADA811653EE71E91BB62922879788D5 /* LKS_ExportManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 87A73AAFE241AE1A9B0B1ECC3637BB09 /* LKS_ExportManager.m */; }; FBE0905907E8A2E96C60D614BE7AD96B /* UIVisualEffectView+LookinServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E6A258A86907EBBDB399A0A13A4BBE5 /* UIVisualEffectView+LookinServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; FECE5A4ACEDF90AC48C93974E4D14E98 /* UIViewController+LookinServer.m in Sources */ = {isa = PBXBuildFile; fileRef = B4F1E7E5E9079471F411ED35B385AF1C /* UIViewController+LookinServer.m */; }; FF00082EEA92F5B850C4EBAC6052065B /* LookinIvarTrace.m in Sources */ = {isa = PBXBuildFile; fileRef = CE8A797C14470C70649EE63152213162 /* LookinIvarTrace.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 022AE5FFEC8ED71480F4495D0C937D05 /* LKS_LocalInspectPanelLabelView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_LocalInspectPanelLabelView.h; path = Src/Server/Inspect/LKS_LocalInspectPanelLabelView.h; sourceTree = ""; }; 070553B20C0DF9A38585E6C4EAD989AE /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 0A57D122B6D3D68B27A0801624E11D79 /* LKS_HierarchyDisplayItemsMaker.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_HierarchyDisplayItemsMaker.h; path = Src/Server/Others/LKS_HierarchyDisplayItemsMaker.h; sourceTree = ""; }; 0BFF5A0B9A97313479A5ACC0713BBB88 /* LKS_AttrGroupsMaker.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_AttrGroupsMaker.h; path = Src/Server/Others/LKS_AttrGroupsMaker.h; sourceTree = ""; }; 0E7FEAAFC6B39DC7F79F3474722C57CD /* LKS_Helper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_Helper.h; path = Src/Server/Others/LKS_Helper.h; sourceTree = ""; }; 0EC99F32DAB1371DC1BC2B4DE054EBB1 /* LookinDashboardBlueprint.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinDashboardBlueprint.h; path = Src/Shared/LookinDashboardBlueprint.h; sourceTree = ""; }; 0EC9DA8B816F7ACB2A377E14F2B2742E /* LookinMsgTargetAction.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinMsgTargetAction.h; path = Src/Shared/Message/LookinMsgTargetAction.h; sourceTree = ""; }; 0F20D43D8013D763C2F1557F458A5878 /* LookinAttribute.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinAttribute.m; path = Src/Shared/LookinAttribute.m; sourceTree = ""; }; 0FE713547D1B7A6F064D3C459B11643D /* LookinAttributesSection.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinAttributesSection.m; path = Src/Shared/LookinAttributesSection.m; sourceTree = ""; }; 15624D3BC8B0BBC6561F583620D5503A /* NSObject+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSObject+LookinServer.m"; path = "Src/Server/Category/NSObject+LookinServer.m"; sourceTree = ""; }; 16CE2F8C86820C423CE6BE2AE6F52F46 /* LookinTuple.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinTuple.m; path = Src/Shared/LookinTuple.m; sourceTree = ""; }; 173E783D805E674CA7CABEF6EB38D617 /* LKS_HierarchyDetailsHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_HierarchyDetailsHandler.h; path = Src/Server/Connection/RequestHandler/LKS_HierarchyDetailsHandler.h; sourceTree = ""; }; 179A2FE851754AACFEEDCF6AB4C759E7 /* Lookin_PTUSBHub.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Lookin_PTUSBHub.h; path = Src/Shared/Peertalk/Lookin_PTUSBHub.h; sourceTree = ""; }; 18267B21FB282FB0B18EC75340C86FA3 /* LKS_ConnectionManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_ConnectionManager.m; path = Src/Server/Connection/LKS_ConnectionManager.m; sourceTree = ""; }; 182681E841B47CA3389B60AB51ED6557 /* LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinServer.h; path = Src/Server/LookinServer.h; sourceTree = ""; }; 1A038A6A3CA099582D1CDBF695869B04 /* LKS_PerspectiveLayer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_PerspectiveLayer.h; path = Src/Server/Perspective/LKS_PerspectiveLayer.h; sourceTree = ""; }; 1A11CB612DBE8B848DB0A4FDE2FE0AC4 /* LKS_PerspectiveDataSource.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_PerspectiveDataSource.h; path = Src/Server/Perspective/LKS_PerspectiveDataSource.h; sourceTree = ""; }; 1D84A4451E510F809149A89ECD44325B /* LookinStaticAsyncUpdateTask.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinStaticAsyncUpdateTask.m; path = Src/Shared/LookinStaticAsyncUpdateTask.m; sourceTree = ""; }; 1DAB7747E848BF750F935DB1509AF889 /* LookinConnectionAttachment.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinConnectionAttachment.h; path = Src/Shared/LookinConnectionAttachment.h; sourceTree = ""; }; 1EED9D3B1A085A02A5DA4405DC471C06 /* LookinStaticAsyncUpdateTask.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinStaticAsyncUpdateTask.h; path = Src/Shared/LookinStaticAsyncUpdateTask.h; sourceTree = ""; }; 219AA6511B5F17D2215297AA9D40FA0F /* Lookin_PTProtocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Lookin_PTProtocol.h; path = Src/Shared/Peertalk/Lookin_PTProtocol.h; sourceTree = ""; }; 21F08BBDCB1DE1C436221C396C470AD9 /* LKS_HierarchyDetailsHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_HierarchyDetailsHandler.m; path = Src/Server/Connection/RequestHandler/LKS_HierarchyDetailsHandler.m; sourceTree = ""; }; 21F862FE51621788FE3B5B10B9658E14 /* Lookin_PTChannel.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = Lookin_PTChannel.m; path = Src/Shared/Peertalk/Lookin_PTChannel.m; sourceTree = ""; }; 2232E67A42835952B7296F474DBE7E5C /* LookinConnectionAttachment.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinConnectionAttachment.m; path = Src/Shared/LookinConnectionAttachment.m; sourceTree = ""; }; 224D485BBB4E6CC4AAF6953AEEBE11A2 /* Peertalk.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Peertalk.h; path = Src/Shared/Peertalk/Peertalk.h; sourceTree = ""; }; 246618B47B4E7B3A98F764EE2AB3D655 /* UIImage+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+LookinServer.m"; path = "Src/Server/Category/UIImage+LookinServer.m"; sourceTree = ""; }; 2772D6A405FA998F1381C4FFC69BBCB6 /* LKS_LocalInspectManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_LocalInspectManager.h; path = Src/Server/Inspect/LKS_LocalInspectManager.h; sourceTree = ""; }; 29885A010A1ED17B3073AC278B9CD638 /* UIBlurEffect+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIBlurEffect+LookinServer.m"; path = "Src/Server/Category/UIBlurEffect+LookinServer.m"; sourceTree = ""; }; 2C0ACC09086E7B9ABB0D6E23122FBC38 /* UIColor+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIColor+LookinServer.h"; path = "Src/Server/Category/UIColor+LookinServer.h"; sourceTree = ""; }; 2CBCB52C0DEB35E82AF29E260674BEC9 /* LKS_PerspectiveHierarchyCell.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_PerspectiveHierarchyCell.h; path = Src/Server/Perspective/LKS_PerspectiveHierarchyCell.h; sourceTree = ""; }; 2D1FAE0CBF86AD8A024D1773DEFB7138 /* LookinServer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = LookinServer.debug.xcconfig; sourceTree = ""; }; 2F0724A757C4E810ADCA40BF3CE53E06 /* LookinAttrIdentifiers.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinAttrIdentifiers.m; path = Src/Shared/LookinAttrIdentifiers.m; sourceTree = ""; }; 30A15219ABF51508E1B5E7F41021FCF3 /* LookinAttrIdentifiers.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinAttrIdentifiers.h; path = Src/Shared/LookinAttrIdentifiers.h; sourceTree = ""; }; 3361441F9DE5F0C1FF71ED1246DE1347 /* UITextField+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UITextField+LookinServer.m"; path = "Src/Server/Category/UITextField+LookinServer.m"; sourceTree = ""; }; 36B7DA9575C5ACE426132494D8083ED2 /* LookinAutoLayoutConstraint.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinAutoLayoutConstraint.h; path = Src/Shared/LookinAutoLayoutConstraint.h; sourceTree = ""; }; 3909A6FD0C3D8CEB54CAAEB9D79DC25C /* LookinDefines.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinDefines.h; path = Src/Shared/LookinDefines.h; sourceTree = ""; }; 3E97A80192FBDADDC55188CF6091D5E3 /* Lookin_PTPrivate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Lookin_PTPrivate.h; path = Src/Shared/Peertalk/Lookin_PTPrivate.h; sourceTree = ""; }; 40557903743DD96F426AD2B887B19435 /* NSArray+Lookin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSArray+Lookin.h"; path = "Src/Shared/Category/NSArray+Lookin.h"; sourceTree = ""; }; 40E4E28CD8268379C395F944C80E5D24 /* UIBlurEffect+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIBlurEffect+LookinServer.h"; path = "Src/Server/Category/UIBlurEffect+LookinServer.h"; sourceTree = ""; }; 4215DBA0FCC1E7DBED9F012AE258EEF7 /* LookinAppInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinAppInfo.m; path = Src/Shared/LookinAppInfo.m; sourceTree = ""; }; 42A2DA1C4547F338D0840E159B27BBC1 /* LKS_RequestHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_RequestHandler.h; path = Src/Server/Connection/LKS_RequestHandler.h; sourceTree = ""; }; 430091E00751A8309CBEFCB975D628B2 /* LKS_PerspectiveHierarchyView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_PerspectiveHierarchyView.h; path = Src/Server/Perspective/LKS_PerspectiveHierarchyView.h; sourceTree = ""; }; 46288120050C8C2D6841B9477D8CB3AC /* LookinDisplayItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinDisplayItem.m; path = Src/Shared/LookinDisplayItem.m; sourceTree = ""; }; 465E6E3CA198A46580FAB98EDF3A4650 /* LookinScreenshotFetchManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinScreenshotFetchManager.h; path = Src/Shared/LookinScreenshotFetchManager.h; sourceTree = ""; }; 49591CFFFE70FE73881333F76E3E0D63 /* UIVisualEffectView+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIVisualEffectView+LookinServer.m"; path = "Src/Server/Category/UIVisualEffectView+LookinServer.m"; sourceTree = ""; }; 4C5E5E31A936D294D1C60BCD4CB9F702 /* Lookin_PTChannel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Lookin_PTChannel.h; path = Src/Shared/Peertalk/Lookin_PTChannel.h; sourceTree = ""; }; 515D40D758457E6B5B1173030C01612F /* LKS_PerspectiveManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_PerspectiveManager.m; path = Src/Server/Perspective/LKS_PerspectiveManager.m; sourceTree = ""; }; 51D4F1461926A74C5F2355B790ED8C4C /* LKS_AttrModificationPatchHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_AttrModificationPatchHandler.m; path = Src/Server/Connection/RequestHandler/LKS_AttrModificationPatchHandler.m; sourceTree = ""; }; 529308949481ABD4AB88E1E73E3E257A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 551E524A4439B10891291D09502E75F9 /* LookinEventHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinEventHandler.m; path = Src/Shared/LookinEventHandler.m; sourceTree = ""; }; 56E60FA565B2F3D48818431F6A85B480 /* UIView+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+LookinServer.h"; path = "Src/Server/Category/UIView+LookinServer.h"; sourceTree = ""; }; 57C8F91060EB0C2BE160F350E73A8D0C /* LookinServer-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "LookinServer-Info.plist"; sourceTree = ""; }; 5E62B8AF634F85D998245B5DD74B9A7B /* LookinMsgTargetAction.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinMsgTargetAction.m; path = Src/Shared/Message/LookinMsgTargetAction.m; sourceTree = ""; }; 5E9E73467EA12C21865FE8795FBCCC7B /* LKS_PerspectiveHierarchyCell.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_PerspectiveHierarchyCell.m; path = Src/Server/Perspective/LKS_PerspectiveHierarchyCell.m; sourceTree = ""; }; 60445E6794C31E0A2C99993647C024EC /* LookinMethodTraceRecord.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinMethodTraceRecord.h; path = Src/Shared/LookinMethodTraceRecord.h; sourceTree = ""; }; 6152D80B4CE36E418A4B43B6BC0659F1 /* LookinServerDefines.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinServerDefines.h; path = Src/Server/Others/LookinServerDefines.h; sourceTree = ""; }; 637189BE5F1131ED2B6238784DCD7D27 /* LKS_PerspectiveViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_PerspectiveViewController.h; path = Src/Server/Perspective/LKS_PerspectiveViewController.h; sourceTree = ""; }; 64244C3B0B7A8712804C5B3E3B1E6739 /* UIImage+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+LookinServer.h"; path = "Src/Server/Category/UIImage+LookinServer.h"; sourceTree = ""; }; 656101743950D6F3B29B041FABBABBBA /* LKS_HierarchyDisplayItemsMaker.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_HierarchyDisplayItemsMaker.m; path = Src/Server/Others/LKS_HierarchyDisplayItemsMaker.m; sourceTree = ""; }; 6B106512A34844A36D1126D046386C11 /* LKS_PerspectiveDataSource.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_PerspectiveDataSource.m; path = Src/Server/Perspective/LKS_PerspectiveDataSource.m; sourceTree = ""; }; 6B4800BE9AA6A7DBFDF9F166216FE328 /* NSSet+Lookin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSSet+Lookin.m"; path = "Src/Shared/Category/NSSet+Lookin.m"; sourceTree = ""; }; 6E191CE81EFBC8BCBEB3FB1BA5BA26C5 /* UILabel+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UILabel+LookinServer.m"; path = "Src/Server/Category/UILabel+LookinServer.m"; sourceTree = ""; }; 6F2232DD63EB754EAC74ABEFFF76FE5A /* LKS_LocalInspectViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_LocalInspectViewController.m; path = Src/Server/Inspect/LKS_LocalInspectViewController.m; sourceTree = ""; }; 6F3B972C5B0BC04A7DD297F63757AE18 /* NSObject+Lookin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSObject+Lookin.h"; path = "Src/Shared/Category/NSObject+Lookin.h"; sourceTree = ""; }; 700AAE793D800E8DD0EB6BC4B0ADBBF7 /* CALayer+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "CALayer+LookinServer.m"; path = "Src/Server/Category/CALayer+LookinServer.m"; sourceTree = ""; }; 70A99B3B51A5DA2F5F3FFA7A193677A0 /* LKS_RequestHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_RequestHandler.m; path = Src/Server/Connection/LKS_RequestHandler.m; sourceTree = ""; }; 71205DCD6C69B060581558DEB238117D /* LookinServer-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "LookinServer-prefix.pch"; sourceTree = ""; }; 7469939E6B32E3BE277DF6CD1809B0C4 /* UIImageView+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImageView+LookinServer.h"; path = "Src/Server/Category/UIImageView+LookinServer.h"; sourceTree = ""; }; 7479CE957173AC886F78C2AC7C84AE5E /* LookinDisplayItemDetail.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinDisplayItemDetail.h; path = Src/Shared/LookinDisplayItemDetail.h; sourceTree = ""; }; 7493D9CF640ACB6E2372474DA69C4DBE /* LookinAttributesSection.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinAttributesSection.h; path = Src/Shared/LookinAttributesSection.h; sourceTree = ""; }; 78021CFD427249E79E99A44F636233A4 /* LookinWeakContainer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinWeakContainer.h; path = Src/Shared/LookinWeakContainer.h; sourceTree = ""; }; 7C2456EC8212C2D2646BF28F0307667A /* LKS_AttrModificationHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_AttrModificationHandler.h; path = Src/Server/Connection/RequestHandler/LKS_AttrModificationHandler.h; sourceTree = ""; }; 7E443763A7FABE91F240735CAA10A8EC /* LKS_LocalInspectPanelLabelView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_LocalInspectPanelLabelView.m; path = Src/Server/Inspect/LKS_LocalInspectPanelLabelView.m; sourceTree = ""; }; 7E6212748693A9421890641FD76646BA /* LookinEventHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinEventHandler.h; path = Src/Shared/LookinEventHandler.h; sourceTree = ""; }; 7F2894DDD0E7CDB23764E9BACDF09E09 /* UIView+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+LookinServer.m"; path = "Src/Server/Category/UIView+LookinServer.m"; sourceTree = ""; }; 7FC48F9F883D5F0A27890E9C02AF37B7 /* LKS_EventHandlerMaker.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_EventHandlerMaker.m; path = Src/Server/Others/LKS_EventHandlerMaker.m; sourceTree = ""; }; 81F4FD3C4B4EA45A4F30C40F3439154D /* LKS_EventHandlerMaker.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_EventHandlerMaker.h; path = Src/Server/Others/LKS_EventHandlerMaker.h; sourceTree = ""; }; 8309B1276C55989BB2E4DFB8CECBEC85 /* UITextField+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UITextField+LookinServer.h"; path = "Src/Server/Category/UITextField+LookinServer.h"; sourceTree = ""; }; 867C4CCA7408106ED81C200C0923172A /* LookinAppInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinAppInfo.h; path = Src/Shared/LookinAppInfo.h; sourceTree = ""; }; 87003642A2A1C6CC8D560F23C205ED96 /* LookinAttributesGroup.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinAttributesGroup.m; path = Src/Shared/LookinAttributesGroup.m; sourceTree = ""; }; 87A73AAFE241AE1A9B0B1ECC3637BB09 /* LKS_ExportManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_ExportManager.m; path = Src/Server/Others/LKS_ExportManager.m; sourceTree = ""; }; 8A048573D79E53B8ED42A1FE066C1EA2 /* NSObject+Lookin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSObject+Lookin.m"; path = "Src/Shared/Category/NSObject+Lookin.m"; sourceTree = ""; }; 8B8C4C1259B87ADA2BA1155FD8682977 /* LookinConnectionResponseAttachment.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinConnectionResponseAttachment.h; path = Src/Shared/LookinConnectionResponseAttachment.h; sourceTree = ""; }; 8BE23A84D94F58A983283437C76B01EA /* NSString+Lookin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+Lookin.h"; path = "Src/Shared/Category/NSString+Lookin.h"; sourceTree = ""; }; 8CD1D33AA0DDB360941DF2F6E823CAF5 /* LookinDisplayItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinDisplayItem.h; path = Src/Shared/LookinDisplayItem.h; sourceTree = ""; }; 92BE346C95BF1CD82EE3A572BAE514F3 /* NSArray+Lookin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSArray+Lookin.m"; path = "Src/Shared/Category/NSArray+Lookin.m"; sourceTree = ""; }; 92F6252A0756D75A03CE9423F3290EE9 /* UITableView+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UITableView+LookinServer.h"; path = "Src/Server/Category/UITableView+LookinServer.h"; sourceTree = ""; }; 941BD4B62238E984825C8E89087A91F4 /* LKS_TraceManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_TraceManager.h; path = Src/Server/Others/LKS_TraceManager.h; sourceTree = ""; }; 991728F74F4232B04DA2F7EF601817A0 /* LKS_AttrModificationHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_AttrModificationHandler.m; path = Src/Server/Connection/RequestHandler/LKS_AttrModificationHandler.m; sourceTree = ""; }; 9988BB7C7CD4DB52B5C26669B32EDEB6 /* LookinWeakContainer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinWeakContainer.m; path = Src/Shared/LookinWeakContainer.m; sourceTree = ""; }; 9A84F074D462823887DC12C48CF1A23D /* LKS_ObjectRegistry.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_ObjectRegistry.h; path = Src/Server/Others/LKS_ObjectRegistry.h; sourceTree = ""; }; 9B8AD05BE1E641DD05378CDD1790D19A /* LKS_ObjectRegistry.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_ObjectRegistry.m; path = Src/Server/Others/LKS_ObjectRegistry.m; sourceTree = ""; }; 9DA9FD79B390D311AB61B8493E143F18 /* LookinServer */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = LookinServer; path = LookinServer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9E6A258A86907EBBDB399A0A13A4BBE5 /* UIVisualEffectView+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIVisualEffectView+LookinServer.h"; path = "Src/Server/Category/UIVisualEffectView+LookinServer.h"; sourceTree = ""; }; A7DDA8A26682379CB80FC5F701737A35 /* LKS_ConnectionManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_ConnectionManager.h; path = Src/Server/Connection/LKS_ConnectionManager.h; sourceTree = ""; }; A8B0FC1B422235EFC24A5C70F1D78072 /* LKS_ExportManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_ExportManager.h; path = Src/Server/Others/LKS_ExportManager.h; sourceTree = ""; }; AB4C8818C35EE45FF37BA06805CD7D8E /* LKS_TraceManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_TraceManager.m; path = Src/Server/Others/LKS_TraceManager.m; sourceTree = ""; }; AC84153F310DFC708D8164A5A577F77B /* LKS_PerspectiveToolbarButtons.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_PerspectiveToolbarButtons.h; path = Src/Server/Perspective/LKS_PerspectiveToolbarButtons.h; sourceTree = ""; }; AEC483F02167148745A30C09435FB5CF /* NSSet+Lookin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSSet+Lookin.h"; path = "Src/Shared/Category/NSSet+Lookin.h"; sourceTree = ""; }; B034E538DC0BB5FECD524A9C5C4D13D7 /* UITextView+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UITextView+LookinServer.h"; path = "Src/Server/Category/UITextView+LookinServer.h"; sourceTree = ""; }; B08F267BE32A0116787F7BB24B80E175 /* LKS_MethodTraceManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_MethodTraceManager.h; path = Src/Server/Others/LKS_MethodTraceManager.h; sourceTree = ""; }; B18EB5F410154612C3DBD3105D53A343 /* LookinServer-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "LookinServer-dummy.m"; sourceTree = ""; }; B4807F2269356902D5229A64D3702476 /* LookinObject.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinObject.m; path = Src/Shared/LookinObject.m; sourceTree = ""; }; B4F1E7E5E9079471F411ED35B385AF1C /* UIViewController+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+LookinServer.m"; path = "Src/Server/Category/UIViewController+LookinServer.m"; sourceTree = ""; }; BB6373DF2DC64B584B8CF09C4150C433 /* LookinHierarchyInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinHierarchyInfo.m; path = Src/Shared/LookinHierarchyInfo.m; sourceTree = ""; }; BBB9FB08942E7D1C225B340C732C8423 /* UIImageView+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImageView+LookinServer.m"; path = "Src/Server/Category/UIImageView+LookinServer.m"; sourceTree = ""; }; BD6B5A8BA9AB2C7289B5C3AF5CE7637D /* LookinMethodTraceRecord.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinMethodTraceRecord.m; path = Src/Shared/LookinMethodTraceRecord.m; sourceTree = ""; }; BDC348B013DE04566F37F77372AC0051 /* LookinDashboardBlueprint.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinDashboardBlueprint.m; path = Src/Shared/LookinDashboardBlueprint.m; sourceTree = ""; }; C20376EF9715E89F9FAB805E395FC794 /* LookinAttribute.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinAttribute.h; path = Src/Shared/LookinAttribute.h; sourceTree = ""; }; C2242704AF70D5C582CC2B6198FD68E0 /* LookinIvarTrace.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinIvarTrace.h; path = Src/Shared/LookinIvarTrace.h; sourceTree = ""; }; C407FEBCA96257041F34657CB4E402A6 /* LookinObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinObject.h; path = Src/Shared/LookinObject.h; sourceTree = ""; }; C530A07C10CC3C497B8AA6BD6DE8052D /* LookinTuple.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinTuple.h; path = Src/Shared/LookinTuple.h; sourceTree = ""; }; C5A40322BA2B1C0B2B5F7CABD8A212F2 /* UITextView+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UITextView+LookinServer.m"; path = "Src/Server/Category/UITextView+LookinServer.m"; sourceTree = ""; }; C6609C187DF26848E8C472E1D75F3C91 /* LKS_PerspectiveToolbarButtons.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_PerspectiveToolbarButtons.m; path = Src/Server/Perspective/LKS_PerspectiveToolbarButtons.m; sourceTree = ""; }; C7D818A123DBDDE9CD05E9C12841308A /* CALayer+Lookin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "CALayer+Lookin.m"; path = "Src/Shared/Category/CALayer+Lookin.m"; sourceTree = ""; }; CB13006AA2FDB3AE3E4B22D3D9F5489D /* LookinHierarchyFile.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinHierarchyFile.m; path = Src/Shared/LookinHierarchyFile.m; sourceTree = ""; }; CC8747E5B43F011547141A5E7D968B8E /* LookinServer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = LookinServer.release.xcconfig; sourceTree = ""; }; CE764CE3893B37689C0BC44ECA3B4B91 /* LKS_LocalInspectViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_LocalInspectViewController.h; path = Src/Server/Inspect/LKS_LocalInspectViewController.h; sourceTree = ""; }; CE8A797C14470C70649EE63152213162 /* LookinIvarTrace.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinIvarTrace.m; path = Src/Shared/LookinIvarTrace.m; sourceTree = ""; }; CF2D7FC011FE161AC8F55805FF17FA56 /* Lookin_PTUSBHub.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = Lookin_PTUSBHub.m; path = Src/Shared/Peertalk/Lookin_PTUSBHub.m; sourceTree = ""; }; D43309255489FF7F10A373E13EBAECCB /* LKS_LocalInspectManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_LocalInspectManager.m; path = Src/Server/Inspect/LKS_LocalInspectManager.m; sourceTree = ""; }; D4CD73A1E50A101687814F7ABF04265E /* LookinServer.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = LookinServer.modulemap; sourceTree = ""; }; D83BF3E2881F2D2FFE4AFC22771FCE36 /* NSString+Lookin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+Lookin.m"; path = "Src/Shared/Category/NSString+Lookin.m"; sourceTree = ""; }; D8B765FF61800231762A18FFE0F6B382 /* Lookin_PTProtocol.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = Lookin_PTProtocol.m; path = Src/Shared/Peertalk/Lookin_PTProtocol.m; sourceTree = ""; }; D9AB342FF6C5A234675B5A531C1139CE /* LKS_AttrModificationPatchHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_AttrModificationPatchHandler.h; path = Src/Server/Connection/RequestHandler/LKS_AttrModificationPatchHandler.h; sourceTree = ""; }; D9AB931C6A78ED2A1999B2FBFE1B2058 /* LookinCodingValueType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinCodingValueType.h; path = Src/Shared/LookinCodingValueType.h; sourceTree = ""; }; DBBB0BD8EE0599FB15FBA352E0A04D30 /* UIViewController+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIViewController+LookinServer.h"; path = "Src/Server/Category/UIViewController+LookinServer.h"; sourceTree = ""; }; DE9E6B913EEB5F5A1C9A7A85856175D1 /* CALayer+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "CALayer+LookinServer.h"; path = "Src/Server/Category/CALayer+LookinServer.h"; sourceTree = ""; }; DF9096D9A2FD6A999E61346368CAA7ED /* UILabel+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UILabel+LookinServer.h"; path = "Src/Server/Category/UILabel+LookinServer.h"; sourceTree = ""; }; E27C154D81D31A232159AB57767C52AE /* UIGestureRecognizer+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIGestureRecognizer+LookinServer.h"; path = "Src/Server/Category/UIGestureRecognizer+LookinServer.h"; sourceTree = ""; }; E3525E795CBB069BECBB66727A9040B7 /* LKS_PerspectiveViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_PerspectiveViewController.m; path = Src/Server/Perspective/LKS_PerspectiveViewController.m; sourceTree = ""; }; E3B76BDD53364E83F98E635EB179731A /* NSObject+LookinServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSObject+LookinServer.h"; path = "Src/Server/Category/NSObject+LookinServer.h"; sourceTree = ""; }; E3DEC557F4FBE7C86105D097E474E510 /* LookinAttributesGroup.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinAttributesGroup.h; path = Src/Shared/LookinAttributesGroup.h; sourceTree = ""; }; E4F03438230841C57C8CCE5423D89E3E /* LookinConnectionResponseAttachment.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinConnectionResponseAttachment.m; path = Src/Shared/LookinConnectionResponseAttachment.m; sourceTree = ""; }; E74AA900536DACC16713277804225CFF /* LookinAutoLayoutConstraint.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinAutoLayoutConstraint.m; path = Src/Shared/LookinAutoLayoutConstraint.m; sourceTree = ""; }; E85CC932DE80B014D9094D2EAB34A2BB /* LKS_PerspectiveManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_PerspectiveManager.h; path = Src/Server/Perspective/LKS_PerspectiveManager.h; sourceTree = ""; }; EA12ACB760FC94D0D4DA39BA6AF7E6B5 /* CALayer+Lookin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "CALayer+Lookin.h"; path = "Src/Shared/Category/CALayer+Lookin.h"; sourceTree = ""; }; EA38C1286D76AB74A0A3F07249310FE4 /* LKS_Helper.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_Helper.m; path = Src/Server/Others/LKS_Helper.m; sourceTree = ""; }; EABC1F238084414A9438F0EDD1898DB5 /* LookinMsgAttribute.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinMsgAttribute.h; path = Src/Shared/Message/LookinMsgAttribute.h; sourceTree = ""; }; EB6D809DF947CA72C9E61926B4EB3428 /* LookinAttrType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinAttrType.h; path = Src/Shared/LookinAttrType.h; sourceTree = ""; }; ECABCAA9FECE7257B4267ADB213E3283 /* UIColor+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIColor+LookinServer.m"; path = "Src/Server/Category/UIColor+LookinServer.m"; sourceTree = ""; }; ED0BE4AA53E1F0B4CEFB210EB072B5B8 /* LookinHierarchyFile.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinHierarchyFile.h; path = Src/Shared/LookinHierarchyFile.h; sourceTree = ""; }; EDA3ADE695F2893B80C54B136A905F4E /* LookinServer-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "LookinServer-umbrella.h"; sourceTree = ""; }; EE4C7F689F691D2932E6C966D2FF4D79 /* LKS_PerspectiveHierarchyView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_PerspectiveHierarchyView.m; path = Src/Server/Perspective/LKS_PerspectiveHierarchyView.m; sourceTree = ""; }; EF7E557AED816E41355C82D9B01503E8 /* LKS_PerspectiveItemLayer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LKS_PerspectiveItemLayer.h; path = Src/Server/Perspective/LKS_PerspectiveItemLayer.h; sourceTree = ""; }; F25DC14B557F5D850EB4B9EC29125151 /* LKS_PerspectiveLayer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_PerspectiveLayer.m; path = Src/Server/Perspective/LKS_PerspectiveLayer.m; sourceTree = ""; }; F29C8C8AF671DD8AD6A6F71861EA1879 /* UITableView+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UITableView+LookinServer.m"; path = "Src/Server/Category/UITableView+LookinServer.m"; sourceTree = ""; }; F4EE9799E4B8DF680C384F179C7DEA96 /* LookinDisplayItemDetail.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinDisplayItemDetail.m; path = Src/Shared/LookinDisplayItemDetail.m; sourceTree = ""; }; F5AA9A2709B8E14E300CA2BC507F5C40 /* UIGestureRecognizer+LookinServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIGestureRecognizer+LookinServer.m"; path = "Src/Server/Category/UIGestureRecognizer+LookinServer.m"; sourceTree = ""; }; F77E54FD0F6D7095EE92036D9B822191 /* LKS_PerspectiveItemLayer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_PerspectiveItemLayer.m; path = Src/Server/Perspective/LKS_PerspectiveItemLayer.m; sourceTree = ""; }; F860090A54E794DBDDE6230A75E4739F /* LookinAttributeModification.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinAttributeModification.h; path = Src/Shared/LookinAttributeModification.h; sourceTree = ""; }; FB6A511D0F970464D508C64B44400C49 /* LookinAttributeModification.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinAttributeModification.m; path = Src/Shared/LookinAttributeModification.m; sourceTree = ""; }; FCAC034F412FE0127C5A680672CF74AE /* LKS_MethodTraceManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_MethodTraceManager.m; path = Src/Server/Others/LKS_MethodTraceManager.m; sourceTree = ""; }; FD83B9BC258C8DE9140CD28149D90C8F /* LookinMsgAttribute.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LookinMsgAttribute.m; path = Src/Shared/Message/LookinMsgAttribute.m; sourceTree = ""; }; FEC7FD91328A4C8DC0E6D7BCE863BF81 /* LookinHierarchyInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = LookinHierarchyInfo.h; path = Src/Shared/LookinHierarchyInfo.h; sourceTree = ""; }; FF0F6F9E63946A3F858110F6F4A7960E /* LKS_AttrGroupsMaker.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = LKS_AttrGroupsMaker.m; path = Src/Server/Others/LKS_AttrGroupsMaker.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 5787DF1786916C6CB4C51CC3328B719B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 0F44787643816923BAC087056C4A2080 /* Foundation.framework in Frameworks */, B54C6FB5A76EDC47073B283362FB7469 /* UIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3862AB1009CF14A102E76F02E5C20C34 /* LookinServer */ = { isa = PBXGroup; children = ( EA12ACB760FC94D0D4DA39BA6AF7E6B5 /* CALayer+Lookin.h */, C7D818A123DBDDE9CD05E9C12841308A /* CALayer+Lookin.m */, DE9E6B913EEB5F5A1C9A7A85856175D1 /* CALayer+LookinServer.h */, 700AAE793D800E8DD0EB6BC4B0ADBBF7 /* CALayer+LookinServer.m */, 0BFF5A0B9A97313479A5ACC0713BBB88 /* LKS_AttrGroupsMaker.h */, FF0F6F9E63946A3F858110F6F4A7960E /* LKS_AttrGroupsMaker.m */, 7C2456EC8212C2D2646BF28F0307667A /* LKS_AttrModificationHandler.h */, 991728F74F4232B04DA2F7EF601817A0 /* LKS_AttrModificationHandler.m */, D9AB342FF6C5A234675B5A531C1139CE /* LKS_AttrModificationPatchHandler.h */, 51D4F1461926A74C5F2355B790ED8C4C /* LKS_AttrModificationPatchHandler.m */, A7DDA8A26682379CB80FC5F701737A35 /* LKS_ConnectionManager.h */, 18267B21FB282FB0B18EC75340C86FA3 /* LKS_ConnectionManager.m */, 81F4FD3C4B4EA45A4F30C40F3439154D /* LKS_EventHandlerMaker.h */, 7FC48F9F883D5F0A27890E9C02AF37B7 /* LKS_EventHandlerMaker.m */, A8B0FC1B422235EFC24A5C70F1D78072 /* LKS_ExportManager.h */, 87A73AAFE241AE1A9B0B1ECC3637BB09 /* LKS_ExportManager.m */, 0E7FEAAFC6B39DC7F79F3474722C57CD /* LKS_Helper.h */, EA38C1286D76AB74A0A3F07249310FE4 /* LKS_Helper.m */, 173E783D805E674CA7CABEF6EB38D617 /* LKS_HierarchyDetailsHandler.h */, 21F08BBDCB1DE1C436221C396C470AD9 /* LKS_HierarchyDetailsHandler.m */, 0A57D122B6D3D68B27A0801624E11D79 /* LKS_HierarchyDisplayItemsMaker.h */, 656101743950D6F3B29B041FABBABBBA /* LKS_HierarchyDisplayItemsMaker.m */, 2772D6A405FA998F1381C4FFC69BBCB6 /* LKS_LocalInspectManager.h */, D43309255489FF7F10A373E13EBAECCB /* LKS_LocalInspectManager.m */, 022AE5FFEC8ED71480F4495D0C937D05 /* LKS_LocalInspectPanelLabelView.h */, 7E443763A7FABE91F240735CAA10A8EC /* LKS_LocalInspectPanelLabelView.m */, CE764CE3893B37689C0BC44ECA3B4B91 /* LKS_LocalInspectViewController.h */, 6F2232DD63EB754EAC74ABEFFF76FE5A /* LKS_LocalInspectViewController.m */, B08F267BE32A0116787F7BB24B80E175 /* LKS_MethodTraceManager.h */, FCAC034F412FE0127C5A680672CF74AE /* LKS_MethodTraceManager.m */, 9A84F074D462823887DC12C48CF1A23D /* LKS_ObjectRegistry.h */, 9B8AD05BE1E641DD05378CDD1790D19A /* LKS_ObjectRegistry.m */, 1A11CB612DBE8B848DB0A4FDE2FE0AC4 /* LKS_PerspectiveDataSource.h */, 6B106512A34844A36D1126D046386C11 /* LKS_PerspectiveDataSource.m */, 2CBCB52C0DEB35E82AF29E260674BEC9 /* LKS_PerspectiveHierarchyCell.h */, 5E9E73467EA12C21865FE8795FBCCC7B /* LKS_PerspectiveHierarchyCell.m */, 430091E00751A8309CBEFCB975D628B2 /* LKS_PerspectiveHierarchyView.h */, EE4C7F689F691D2932E6C966D2FF4D79 /* LKS_PerspectiveHierarchyView.m */, EF7E557AED816E41355C82D9B01503E8 /* LKS_PerspectiveItemLayer.h */, F77E54FD0F6D7095EE92036D9B822191 /* LKS_PerspectiveItemLayer.m */, 1A038A6A3CA099582D1CDBF695869B04 /* LKS_PerspectiveLayer.h */, F25DC14B557F5D850EB4B9EC29125151 /* LKS_PerspectiveLayer.m */, E85CC932DE80B014D9094D2EAB34A2BB /* LKS_PerspectiveManager.h */, 515D40D758457E6B5B1173030C01612F /* LKS_PerspectiveManager.m */, AC84153F310DFC708D8164A5A577F77B /* LKS_PerspectiveToolbarButtons.h */, C6609C187DF26848E8C472E1D75F3C91 /* LKS_PerspectiveToolbarButtons.m */, 637189BE5F1131ED2B6238784DCD7D27 /* LKS_PerspectiveViewController.h */, E3525E795CBB069BECBB66727A9040B7 /* LKS_PerspectiveViewController.m */, 42A2DA1C4547F338D0840E159B27BBC1 /* LKS_RequestHandler.h */, 70A99B3B51A5DA2F5F3FFA7A193677A0 /* LKS_RequestHandler.m */, 941BD4B62238E984825C8E89087A91F4 /* LKS_TraceManager.h */, AB4C8818C35EE45FF37BA06805CD7D8E /* LKS_TraceManager.m */, 4C5E5E31A936D294D1C60BCD4CB9F702 /* Lookin_PTChannel.h */, 21F862FE51621788FE3B5B10B9658E14 /* Lookin_PTChannel.m */, 3E97A80192FBDADDC55188CF6091D5E3 /* Lookin_PTPrivate.h */, 219AA6511B5F17D2215297AA9D40FA0F /* Lookin_PTProtocol.h */, D8B765FF61800231762A18FFE0F6B382 /* Lookin_PTProtocol.m */, 179A2FE851754AACFEEDCF6AB4C759E7 /* Lookin_PTUSBHub.h */, CF2D7FC011FE161AC8F55805FF17FA56 /* Lookin_PTUSBHub.m */, 867C4CCA7408106ED81C200C0923172A /* LookinAppInfo.h */, 4215DBA0FCC1E7DBED9F012AE258EEF7 /* LookinAppInfo.m */, C20376EF9715E89F9FAB805E395FC794 /* LookinAttribute.h */, 0F20D43D8013D763C2F1557F458A5878 /* LookinAttribute.m */, F860090A54E794DBDDE6230A75E4739F /* LookinAttributeModification.h */, FB6A511D0F970464D508C64B44400C49 /* LookinAttributeModification.m */, E3DEC557F4FBE7C86105D097E474E510 /* LookinAttributesGroup.h */, 87003642A2A1C6CC8D560F23C205ED96 /* LookinAttributesGroup.m */, 7493D9CF640ACB6E2372474DA69C4DBE /* LookinAttributesSection.h */, 0FE713547D1B7A6F064D3C459B11643D /* LookinAttributesSection.m */, 30A15219ABF51508E1B5E7F41021FCF3 /* LookinAttrIdentifiers.h */, 2F0724A757C4E810ADCA40BF3CE53E06 /* LookinAttrIdentifiers.m */, EB6D809DF947CA72C9E61926B4EB3428 /* LookinAttrType.h */, 36B7DA9575C5ACE426132494D8083ED2 /* LookinAutoLayoutConstraint.h */, E74AA900536DACC16713277804225CFF /* LookinAutoLayoutConstraint.m */, D9AB931C6A78ED2A1999B2FBFE1B2058 /* LookinCodingValueType.h */, 1DAB7747E848BF750F935DB1509AF889 /* LookinConnectionAttachment.h */, 2232E67A42835952B7296F474DBE7E5C /* LookinConnectionAttachment.m */, 8B8C4C1259B87ADA2BA1155FD8682977 /* LookinConnectionResponseAttachment.h */, E4F03438230841C57C8CCE5423D89E3E /* LookinConnectionResponseAttachment.m */, 0EC99F32DAB1371DC1BC2B4DE054EBB1 /* LookinDashboardBlueprint.h */, BDC348B013DE04566F37F77372AC0051 /* LookinDashboardBlueprint.m */, 3909A6FD0C3D8CEB54CAAEB9D79DC25C /* LookinDefines.h */, 8CD1D33AA0DDB360941DF2F6E823CAF5 /* LookinDisplayItem.h */, 46288120050C8C2D6841B9477D8CB3AC /* LookinDisplayItem.m */, 7479CE957173AC886F78C2AC7C84AE5E /* LookinDisplayItemDetail.h */, F4EE9799E4B8DF680C384F179C7DEA96 /* LookinDisplayItemDetail.m */, 7E6212748693A9421890641FD76646BA /* LookinEventHandler.h */, 551E524A4439B10891291D09502E75F9 /* LookinEventHandler.m */, ED0BE4AA53E1F0B4CEFB210EB072B5B8 /* LookinHierarchyFile.h */, CB13006AA2FDB3AE3E4B22D3D9F5489D /* LookinHierarchyFile.m */, FEC7FD91328A4C8DC0E6D7BCE863BF81 /* LookinHierarchyInfo.h */, BB6373DF2DC64B584B8CF09C4150C433 /* LookinHierarchyInfo.m */, C2242704AF70D5C582CC2B6198FD68E0 /* LookinIvarTrace.h */, CE8A797C14470C70649EE63152213162 /* LookinIvarTrace.m */, 60445E6794C31E0A2C99993647C024EC /* LookinMethodTraceRecord.h */, BD6B5A8BA9AB2C7289B5C3AF5CE7637D /* LookinMethodTraceRecord.m */, EABC1F238084414A9438F0EDD1898DB5 /* LookinMsgAttribute.h */, FD83B9BC258C8DE9140CD28149D90C8F /* LookinMsgAttribute.m */, 0EC9DA8B816F7ACB2A377E14F2B2742E /* LookinMsgTargetAction.h */, 5E62B8AF634F85D998245B5DD74B9A7B /* LookinMsgTargetAction.m */, C407FEBCA96257041F34657CB4E402A6 /* LookinObject.h */, B4807F2269356902D5229A64D3702476 /* LookinObject.m */, 465E6E3CA198A46580FAB98EDF3A4650 /* LookinScreenshotFetchManager.h */, 182681E841B47CA3389B60AB51ED6557 /* LookinServer.h */, 6152D80B4CE36E418A4B43B6BC0659F1 /* LookinServerDefines.h */, 1EED9D3B1A085A02A5DA4405DC471C06 /* LookinStaticAsyncUpdateTask.h */, 1D84A4451E510F809149A89ECD44325B /* LookinStaticAsyncUpdateTask.m */, C530A07C10CC3C497B8AA6BD6DE8052D /* LookinTuple.h */, 16CE2F8C86820C423CE6BE2AE6F52F46 /* LookinTuple.m */, 78021CFD427249E79E99A44F636233A4 /* LookinWeakContainer.h */, 9988BB7C7CD4DB52B5C26669B32EDEB6 /* LookinWeakContainer.m */, 40557903743DD96F426AD2B887B19435 /* NSArray+Lookin.h */, 92BE346C95BF1CD82EE3A572BAE514F3 /* NSArray+Lookin.m */, 6F3B972C5B0BC04A7DD297F63757AE18 /* NSObject+Lookin.h */, 8A048573D79E53B8ED42A1FE066C1EA2 /* NSObject+Lookin.m */, E3B76BDD53364E83F98E635EB179731A /* NSObject+LookinServer.h */, 15624D3BC8B0BBC6561F583620D5503A /* NSObject+LookinServer.m */, AEC483F02167148745A30C09435FB5CF /* NSSet+Lookin.h */, 6B4800BE9AA6A7DBFDF9F166216FE328 /* NSSet+Lookin.m */, 8BE23A84D94F58A983283437C76B01EA /* NSString+Lookin.h */, D83BF3E2881F2D2FFE4AFC22771FCE36 /* NSString+Lookin.m */, 224D485BBB4E6CC4AAF6953AEEBE11A2 /* Peertalk.h */, 40E4E28CD8268379C395F944C80E5D24 /* UIBlurEffect+LookinServer.h */, 29885A010A1ED17B3073AC278B9CD638 /* UIBlurEffect+LookinServer.m */, 2C0ACC09086E7B9ABB0D6E23122FBC38 /* UIColor+LookinServer.h */, ECABCAA9FECE7257B4267ADB213E3283 /* UIColor+LookinServer.m */, E27C154D81D31A232159AB57767C52AE /* UIGestureRecognizer+LookinServer.h */, F5AA9A2709B8E14E300CA2BC507F5C40 /* UIGestureRecognizer+LookinServer.m */, 64244C3B0B7A8712804C5B3E3B1E6739 /* UIImage+LookinServer.h */, 246618B47B4E7B3A98F764EE2AB3D655 /* UIImage+LookinServer.m */, 7469939E6B32E3BE277DF6CD1809B0C4 /* UIImageView+LookinServer.h */, BBB9FB08942E7D1C225B340C732C8423 /* UIImageView+LookinServer.m */, DF9096D9A2FD6A999E61346368CAA7ED /* UILabel+LookinServer.h */, 6E191CE81EFBC8BCBEB3FB1BA5BA26C5 /* UILabel+LookinServer.m */, 92F6252A0756D75A03CE9423F3290EE9 /* UITableView+LookinServer.h */, F29C8C8AF671DD8AD6A6F71861EA1879 /* UITableView+LookinServer.m */, 8309B1276C55989BB2E4DFB8CECBEC85 /* UITextField+LookinServer.h */, 3361441F9DE5F0C1FF71ED1246DE1347 /* UITextField+LookinServer.m */, B034E538DC0BB5FECD524A9C5C4D13D7 /* UITextView+LookinServer.h */, C5A40322BA2B1C0B2B5F7CABD8A212F2 /* UITextView+LookinServer.m */, 56E60FA565B2F3D48818431F6A85B480 /* UIView+LookinServer.h */, 7F2894DDD0E7CDB23764E9BACDF09E09 /* UIView+LookinServer.m */, DBBB0BD8EE0599FB15FBA352E0A04D30 /* UIViewController+LookinServer.h */, B4F1E7E5E9079471F411ED35B385AF1C /* UIViewController+LookinServer.m */, 9E6A258A86907EBBDB399A0A13A4BBE5 /* UIVisualEffectView+LookinServer.h */, 49591CFFFE70FE73881333F76E3E0D63 /* UIVisualEffectView+LookinServer.m */, 5357AFB086BDEE64E71C9543E4307A0D /* Support Files */, ); name = LookinServer; path = LookinServer; sourceTree = ""; }; 5357AFB086BDEE64E71C9543E4307A0D /* Support Files */ = { isa = PBXGroup; children = ( D4CD73A1E50A101687814F7ABF04265E /* LookinServer.modulemap */, B18EB5F410154612C3DBD3105D53A343 /* LookinServer-dummy.m */, 57C8F91060EB0C2BE160F350E73A8D0C /* LookinServer-Info.plist */, 71205DCD6C69B060581558DEB238117D /* LookinServer-prefix.pch */, EDA3ADE695F2893B80C54B136A905F4E /* LookinServer-umbrella.h */, 2D1FAE0CBF86AD8A024D1773DEFB7138 /* LookinServer.debug.xcconfig */, CC8747E5B43F011547141A5E7D968B8E /* LookinServer.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/LookinServer"; sourceTree = ""; }; 68B88F80396E7A15666152A59954A48D = { isa = PBXGroup; children = ( E3873160BD2B7D5D67CE5F8482938AF8 /* Frameworks */, 3862AB1009CF14A102E76F02E5C20C34 /* LookinServer */, B40ADCBD0EA03356A98C1B8AAB71581C /* Products */, ); sourceTree = ""; }; B40ADCBD0EA03356A98C1B8AAB71581C /* Products */ = { isa = PBXGroup; children = ( 9DA9FD79B390D311AB61B8493E143F18 /* LookinServer */, ); name = Products; sourceTree = ""; }; CE5076082FA56FA206F54AA30BBDBA13 /* iOS */ = { isa = PBXGroup; children = ( 529308949481ABD4AB88E1E73E3E257A /* Foundation.framework */, 070553B20C0DF9A38585E6C4EAD989AE /* UIKit.framework */, ); name = iOS; sourceTree = ""; }; E3873160BD2B7D5D67CE5F8482938AF8 /* Frameworks */ = { isa = PBXGroup; children = ( CE5076082FA56FA206F54AA30BBDBA13 /* iOS */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 17D08EE89475C5AD85D1262B059C5B04 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( B4BFA1A5E58FCDD2BA5D4261D71302A9 /* CALayer+Lookin.h in Headers */, CEF0CBC49993952ED1604A188D1563AB /* CALayer+LookinServer.h in Headers */, F2967247DC7D19DDCEBEE97D626741D8 /* LKS_AttrGroupsMaker.h in Headers */, 61A82B81A37C4EF0DE9245DF03BCC0B2 /* LKS_AttrModificationHandler.h in Headers */, AD8129C7689DB229DA465BD6C4100D21 /* LKS_AttrModificationPatchHandler.h in Headers */, 588F4168AD89A508BB0443FC637EA740 /* LKS_ConnectionManager.h in Headers */, 7D796BD98330217929ED6786E25D328E /* LKS_EventHandlerMaker.h in Headers */, 5220A06D70DA6E2E50C4A6947923C228 /* LKS_ExportManager.h in Headers */, 89A793565D3DA2487F0400F51E97E51A /* LKS_Helper.h in Headers */, 7B9FEF91BA9C3F85EE09C3CC4E2A8704 /* LKS_HierarchyDetailsHandler.h in Headers */, 38584E6F140A852D0FC933C6453DC727 /* LKS_HierarchyDisplayItemsMaker.h in Headers */, EEF7475AA070A169E5479462C4D7E441 /* LKS_LocalInspectManager.h in Headers */, 1DF8E0315478D3C7360E2E6A1175A234 /* LKS_LocalInspectPanelLabelView.h in Headers */, B21BD55B46BD2A28CF186366F7B20E2E /* LKS_LocalInspectViewController.h in Headers */, 3BEC9A7C9CC95CAF7EA103FC4ABD6958 /* LKS_MethodTraceManager.h in Headers */, 59D9096FFE602883B44BE1490A401BA3 /* LKS_ObjectRegistry.h in Headers */, CDFC89C87D324B64B7499DD1604348AA /* LKS_PerspectiveDataSource.h in Headers */, 71B30263D74D639BF4E4691B8A13D8F1 /* LKS_PerspectiveHierarchyCell.h in Headers */, A0DE3EA47497800844AF034DB6F24252 /* LKS_PerspectiveHierarchyView.h in Headers */, 45E3043648A92DBD685AE0C864652A67 /* LKS_PerspectiveItemLayer.h in Headers */, EBDBD9E36412D0FA1E2780ED9765E6B6 /* LKS_PerspectiveLayer.h in Headers */, 85A8B0F6DE99D2661790D9587DE88ACB /* LKS_PerspectiveManager.h in Headers */, 29BEE3561388B64AF95D3514D5D06A04 /* LKS_PerspectiveToolbarButtons.h in Headers */, 2FE5520BE07DDA2734D019A4CB043F70 /* LKS_PerspectiveViewController.h in Headers */, 244E750327B0F3772822E822AE74C4B1 /* LKS_RequestHandler.h in Headers */, 1A07C183B19438380EC391F638EAB6C4 /* LKS_TraceManager.h in Headers */, ED22780431607AF0AC5178858EE9B47D /* Lookin_PTChannel.h in Headers */, BDA4B3D5927885B71F129F17B4D68949 /* Lookin_PTPrivate.h in Headers */, 686B63A2DB1582BA86FD2474862A55A2 /* Lookin_PTProtocol.h in Headers */, EDB75BEDB97A0DCA8AC5925B5A630763 /* Lookin_PTUSBHub.h in Headers */, B466251D9036F2EFC186A34E76524780 /* LookinAppInfo.h in Headers */, 282C55A16088154CC5A1D5DEDA5496BD /* LookinAttribute.h in Headers */, 5C9E1A1CBF7201E4AAE584DD0D9303D0 /* LookinAttributeModification.h in Headers */, E81887F5984EF9D94B822A01D1D84B22 /* LookinAttributesGroup.h in Headers */, B29549D4F3B66555C98D8181395290C3 /* LookinAttributesSection.h in Headers */, BE1853E860F777A2557B51C90A4B777E /* LookinAttrIdentifiers.h in Headers */, A66B08B83E58ED25DE9F62F7860A0B83 /* LookinAttrType.h in Headers */, 7E6EB0135FFE02705D5953A6EDD53670 /* LookinAutoLayoutConstraint.h in Headers */, 86260B5E519495CBA470ADD62F5F7EFE /* LookinCodingValueType.h in Headers */, FAACFEE16C085FB5F6D2F9D24C97D0F2 /* LookinConnectionAttachment.h in Headers */, A8ABD68C212B85DBF0DB98B6BE93771E /* LookinConnectionResponseAttachment.h in Headers */, A8203C32C9F49EAC14E3EF4F7AAA2F1B /* LookinDashboardBlueprint.h in Headers */, 2CF42A33A3EFB7E758A00EAE88CA48A6 /* LookinDefines.h in Headers */, 88303415F115915F79C2A03F5BE82076 /* LookinDisplayItem.h in Headers */, C6D113E45DF8A85A2417FBA19976723D /* LookinDisplayItemDetail.h in Headers */, 1C33FC41BC4C4A2BF858360886B333F4 /* LookinEventHandler.h in Headers */, 7C294DC14AFC7D036D4A44F755187029 /* LookinHierarchyFile.h in Headers */, 2D76B474884CE186A953AB361C3A0754 /* LookinHierarchyInfo.h in Headers */, 90B9E88EB07332D674B580EA9523D778 /* LookinIvarTrace.h in Headers */, D8A570C0CFDCF761E580B74116D85154 /* LookinMethodTraceRecord.h in Headers */, EF592766200A98B22B45A7FE854418CE /* LookinMsgAttribute.h in Headers */, 37773027F74EDBAF1EBEA0F49D0736B8 /* LookinMsgTargetAction.h in Headers */, 817F906E97C8BDC685735D7ACA16BF67 /* LookinObject.h in Headers */, 24788392D96B78DACD52C327D7C81588 /* LookinScreenshotFetchManager.h in Headers */, AD3B97F3C235643CC0BF3FAA024282DF /* LookinServer.h in Headers */, 4CA8C1C4D57F45A59342E14A03B8F05B /* LookinServer-umbrella.h in Headers */, 162B974D5CFAA781469962B3E3452586 /* LookinServerDefines.h in Headers */, 938AB95A69CB2DBC5586C7BB9A6FB3C4 /* LookinStaticAsyncUpdateTask.h in Headers */, B94FD9A94D4B58CB9EC9C3E323E1B96C /* LookinTuple.h in Headers */, 8DA545D912C08215993B294F79A135D7 /* LookinWeakContainer.h in Headers */, 323CCED73BD97CE6A7061AB9DCF29ABD /* NSArray+Lookin.h in Headers */, B1D0A281262122304BEB8EE4D8656B89 /* NSObject+Lookin.h in Headers */, CF0933DBA480911BF4C1108EAC878BDA /* NSObject+LookinServer.h in Headers */, 73E919F3A51CE449FF8ADD8DE5F31166 /* NSSet+Lookin.h in Headers */, 05DE3FCE3B92E2C903FA0A27C2949E5C /* NSString+Lookin.h in Headers */, 4062F9C27D84C7947F3ECE1EF54F8105 /* Peertalk.h in Headers */, 637D22A34CDB36C98E7251295D61F38D /* UIBlurEffect+LookinServer.h in Headers */, D4398BC5DF54688C6A49EA7644C2AA85 /* UIColor+LookinServer.h in Headers */, 2AC92219E8826061D9670134CF8D9834 /* UIGestureRecognizer+LookinServer.h in Headers */, F2809BD02FFD2D96AFF92F909DD11088 /* UIImage+LookinServer.h in Headers */, 8852CB988A3DEC15C36D642391B16F79 /* UIImageView+LookinServer.h in Headers */, 243F6946BE59EA71989BE9CC0E4395CE /* UILabel+LookinServer.h in Headers */, 395FC03D4A5C78FEBF6B71C65915FE25 /* UITableView+LookinServer.h in Headers */, 75C4F601BA3E44DED010203F2FA45C2A /* UITextField+LookinServer.h in Headers */, 3B3AEA75418D1246AA27FAD925A55D9F /* UITextView+LookinServer.h in Headers */, 03BF733EE0CCA34067BE750A86CB4FE3 /* UIView+LookinServer.h in Headers */, 5EA56E8AABB0D5BD07ED6A782DA8B1AC /* UIViewController+LookinServer.h in Headers */, FBE0905907E8A2E96C60D614BE7AD96B /* UIVisualEffectView+LookinServer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 648108237631EEA52299D347ABDCA30C /* LookinServer */ = { isa = PBXNativeTarget; buildConfigurationList = A81AC9CE73467B43AFA824E7E87A9E35 /* Build configuration list for PBXNativeTarget "LookinServer" */; buildPhases = ( 17D08EE89475C5AD85D1262B059C5B04 /* Headers */, 9331766519D21B4E8A7DDF6D1C1D68D9 /* Sources */, 5787DF1786916C6CB4C51CC3328B719B /* Frameworks */, 10F81DFF86A758A46DACAEA081EAD365 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = LookinServer; productName = LookinServer; productReference = 9DA9FD79B390D311AB61B8493E143F18 /* LookinServer */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ EE0D1B414354E75CF24F6A71E4A0DDCD /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 5BC541B16AE882BD8254D3EA82BA48C0 /* Build configuration list for PBXProject "LookinServer" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 68B88F80396E7A15666152A59954A48D; productRefGroup = B40ADCBD0EA03356A98C1B8AAB71581C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 648108237631EEA52299D347ABDCA30C /* LookinServer */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 10F81DFF86A758A46DACAEA081EAD365 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 9331766519D21B4E8A7DDF6D1C1D68D9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 1FAE39D41FE1F9741E04003A6C903582 /* CALayer+Lookin.m in Sources */, EEF5224101D9FB9215C83192D9FFA475 /* CALayer+LookinServer.m in Sources */, EAF37F0CADDFB6B59F3537F1C4345A41 /* LKS_AttrGroupsMaker.m in Sources */, F731001E2F0512B9FD9C46DA0D9B7CB3 /* LKS_AttrModificationHandler.m in Sources */, 5388ABE101FDE4B892036F8CDF6D244F /* LKS_AttrModificationPatchHandler.m in Sources */, 7666D4A2B5B913494C898D0C480C1361 /* LKS_ConnectionManager.m in Sources */, D8391668AAC27BE0089925F58E3D948F /* LKS_EventHandlerMaker.m in Sources */, FADA811653EE71E91BB62922879788D5 /* LKS_ExportManager.m in Sources */, C65099DD0C74F8C6DAEB416D5CB78ABF /* LKS_Helper.m in Sources */, 41E1BB7BA9DD30BEA8B2CA1878BD08E8 /* LKS_HierarchyDetailsHandler.m in Sources */, 7F37B6373DE062C9B0B24DD8E7AE11F5 /* LKS_HierarchyDisplayItemsMaker.m in Sources */, 676EEFF596210E09477FD56A7A00A413 /* LKS_LocalInspectManager.m in Sources */, 3AE087196930BBC5B29288CC6FC012A8 /* LKS_LocalInspectPanelLabelView.m in Sources */, A2C5F8D1319F000A4952F3752DD43A58 /* LKS_LocalInspectViewController.m in Sources */, E4FDE57E1B38FCAE9B3884F4A4F863FC /* LKS_MethodTraceManager.m in Sources */, CA8EA2FE75BC0DA7F5598CA0632CDFFE /* LKS_ObjectRegistry.m in Sources */, 5F1D8D32B4FF5122EBDF3C8DA4F29C16 /* LKS_PerspectiveDataSource.m in Sources */, 00A917F15A02C36E85016A1EC5151F48 /* LKS_PerspectiveHierarchyCell.m in Sources */, AFE7D8A53068BF01D48B797654F8D432 /* LKS_PerspectiveHierarchyView.m in Sources */, 3387D7AE3F4445B00FBC375F19A1C1E3 /* LKS_PerspectiveItemLayer.m in Sources */, 5415C8359379460B1C0973DFFAA43BC1 /* LKS_PerspectiveLayer.m in Sources */, 2F15C8851FC095F0E86485957577397D /* LKS_PerspectiveManager.m in Sources */, 718515A9A80108D18EA957F3F1D2737C /* LKS_PerspectiveToolbarButtons.m in Sources */, 7BC0882A3A100DB0ADDDEC34A7E9226A /* LKS_PerspectiveViewController.m in Sources */, 0A5D6BE168F45534389D58B0E5C402F2 /* LKS_RequestHandler.m in Sources */, D835AB44CED4D20B062627054BBFCCBD /* LKS_TraceManager.m in Sources */, 4FC57AC9601D64119D70C2D753D9C2AE /* Lookin_PTChannel.m in Sources */, E3EE06E9F651979DBE44DE18CBC537AA /* Lookin_PTProtocol.m in Sources */, 929990A7CD2FA0F831E363B1575D342D /* Lookin_PTUSBHub.m in Sources */, 7031600C6E773A7B7DD831B7936CBB38 /* LookinAppInfo.m in Sources */, 52AD271308B525C3896B492C90B04041 /* LookinAttribute.m in Sources */, D4240909C3EB581AAD9D4F30A54D5060 /* LookinAttributeModification.m in Sources */, 96D3B5E4BF637A8C58E69ADD0F7466F7 /* LookinAttributesGroup.m in Sources */, 4101D5C79944A7EE20D39C305E3925E7 /* LookinAttributesSection.m in Sources */, 0B19793D921F29CA064CF7CFEC4DACFF /* LookinAttrIdentifiers.m in Sources */, 9723A5322855C2CE944B6ADA7144AF3D /* LookinAutoLayoutConstraint.m in Sources */, 66ACB4C67161227890538AB1D296E2D9 /* LookinConnectionAttachment.m in Sources */, BBECE031D3606D9AEF902AC02B5D6B2A /* LookinConnectionResponseAttachment.m in Sources */, CFBCC88A4F3DEF0CE69E543801EBA607 /* LookinDashboardBlueprint.m in Sources */, C77C105596399C54E4ACDB7081069F09 /* LookinDisplayItem.m in Sources */, 8287DC67E9027000D2FAF4BC8C47900F /* LookinDisplayItemDetail.m in Sources */, 5392E9741A719C21EC773F166E4CC320 /* LookinEventHandler.m in Sources */, 58483A17890415B06F360903F67C0A9B /* LookinHierarchyFile.m in Sources */, 773059DF5A7F7830A39BDEC5F5E592EA /* LookinHierarchyInfo.m in Sources */, FF00082EEA92F5B850C4EBAC6052065B /* LookinIvarTrace.m in Sources */, C3090402C42FEE35E46BE9ACA62E59D5 /* LookinMethodTraceRecord.m in Sources */, 7FD7C3C2E6628BD153FBE19EE0B862C6 /* LookinMsgAttribute.m in Sources */, 81031A88AF1A82C8CA05312E921AD20F /* LookinMsgTargetAction.m in Sources */, AB72691991AB2233825ABF6562E75ABC /* LookinObject.m in Sources */, 28D119755232554E19AF0D22A47E9A6D /* LookinServer-dummy.m in Sources */, AF2E535AFA72186438117A4E70BCFD50 /* LookinStaticAsyncUpdateTask.m in Sources */, B45B85409AE28EDE3532938ECB54301E /* LookinTuple.m in Sources */, BDC2179794DF128F9C6B76E79688ED7A /* LookinWeakContainer.m in Sources */, B39AE573AC30734EC60348DD0B533DA4 /* NSArray+Lookin.m in Sources */, 226CDFCBA1E5B2AFC1C73CEEBF4C3B9D /* NSObject+Lookin.m in Sources */, 93CE3C05AC802BDD1E4DACC8AD1D04FB /* NSObject+LookinServer.m in Sources */, BEE3F7679AECC5605C87AC2CC57F642B /* NSSet+Lookin.m in Sources */, 75AA7C8E605783943689788D6E0613DE /* NSString+Lookin.m in Sources */, 26C1AA2A72BA4911E95AEC4B218892F4 /* UIBlurEffect+LookinServer.m in Sources */, 5A4663B94CB417B32F89C525235FA8B6 /* UIColor+LookinServer.m in Sources */, D85F1AEDA79F234519A58954C3663A17 /* UIGestureRecognizer+LookinServer.m in Sources */, 80B065E4294688AEE94640333908E2D5 /* UIImage+LookinServer.m in Sources */, 002AFEB451777DB9A3DC1418A64FDAEB /* UIImageView+LookinServer.m in Sources */, 0ED01054EF8DFC123A9AD872AFC9CD15 /* UILabel+LookinServer.m in Sources */, 25D304D758958B0D9C27B8FC8EB0ACBB /* UITableView+LookinServer.m in Sources */, C1D8A55A4355FACAC236D48FBB55B106 /* UITextField+LookinServer.m in Sources */, E432E79F5333083DA305D61EE51357C6 /* UITextView+LookinServer.m in Sources */, 0B38FBE4A49B85218C8BB54696D057F7 /* UIView+LookinServer.m in Sources */, FECE5A4ACEDF90AC48C93974E4D14E98 /* UIViewController+LookinServer.m in Sources */, 9AE34167B727AB574413F989BBD79C55 /* UIVisualEffectView+LookinServer.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 0AE2F4FA72B3D9098AB15DB55BBD123D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; 4F31189115124CC56D0AF2644D76DD43 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 2D1FAE0CBF86AD8A024D1773DEFB7138 /* LookinServer.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/LookinServer/LookinServer-prefix.pch"; INFOPLIST_FILE = "Target Support Files/LookinServer/LookinServer-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/LookinServer/LookinServer.modulemap"; PRODUCT_MODULE_NAME = LookinServer; PRODUCT_NAME = LookinServer; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 65C066F2C67D9C397F800693D050AA79 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 9477D9AEE95C738BAEA920AAF3E4D8A6 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = CC8747E5B43F011547141A5E7D968B8E /* LookinServer.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/LookinServer/LookinServer-prefix.pch"; INFOPLIST_FILE = "Target Support Files/LookinServer/LookinServer-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/LookinServer/LookinServer.modulemap"; PRODUCT_MODULE_NAME = LookinServer; PRODUCT_NAME = LookinServer; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 5BC541B16AE882BD8254D3EA82BA48C0 /* Build configuration list for PBXProject "LookinServer" */ = { isa = XCConfigurationList; buildConfigurations = ( 65C066F2C67D9C397F800693D050AA79 /* Debug */, 0AE2F4FA72B3D9098AB15DB55BBD123D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; A81AC9CE73467B43AFA824E7E87A9E35 /* Build configuration list for PBXNativeTarget "LookinServer" */ = { isa = XCConfigurationList; buildConfigurations = ( 4F31189115124CC56D0AF2644D76DD43 /* Debug */, 9477D9AEE95C738BAEA920AAF3E4D8A6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = EE0D1B414354E75CF24F6A71E4A0DDCD /* Project object */; } ================================================ FILE: JetChat/Pods/MBProgressHUD/LICENSE ================================================ Copyright © 2009-2020 Matej Bukovinski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/MBProgressHUD/MBProgressHUD.h ================================================ // // MBProgressHUD.h // Version 1.2.0 // Created by Matej Bukovinski on 2.4.09. // // This code is distributed under the terms and conditions of the MIT license. // Copyright © 2009-2016 Matej Bukovinski // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #import @class MBBackgroundView; @protocol MBProgressHUDDelegate; extern CGFloat const MBProgressMaxOffset; typedef NS_ENUM(NSInteger, MBProgressHUDMode) { /// UIActivityIndicatorView. MBProgressHUDModeIndeterminate, /// A round, pie-chart like, progress view. MBProgressHUDModeDeterminate, /// Horizontal progress bar. MBProgressHUDModeDeterminateHorizontalBar, /// Ring-shaped progress view. MBProgressHUDModeAnnularDeterminate, /// Shows a custom view. MBProgressHUDModeCustomView, /// Shows only labels. MBProgressHUDModeText }; typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) { /// Opacity animation MBProgressHUDAnimationFade, /// Opacity + scale animation (zoom in when appearing zoom out when disappearing) MBProgressHUDAnimationZoom, /// Opacity + scale animation (zoom out style) MBProgressHUDAnimationZoomOut, /// Opacity + scale animation (zoom in style) MBProgressHUDAnimationZoomIn }; typedef NS_ENUM(NSInteger, MBProgressHUDBackgroundStyle) { /// Solid color background MBProgressHUDBackgroundStyleSolidColor, /// UIVisualEffectView or UIToolbar.layer background view MBProgressHUDBackgroundStyleBlur }; typedef void (^MBProgressHUDCompletionBlock)(void); NS_ASSUME_NONNULL_BEGIN /** * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. * * This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class. * The MBProgressHUD window spans over the entire space given to it by the initWithFrame: constructor and catches all * user input on this region, thereby preventing the user operations on components below the view. * * @note To still allow touches to pass through the HUD, you can set hud.userInteractionEnabled = NO. * @attention MBProgressHUD is a UI class and should therefore only be accessed on the main thread. */ @interface MBProgressHUD : UIView /** * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. * * @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden. * * @param view The view that the HUD will be added to * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use * animations while appearing. * @return A reference to the created HUD. * * @see hideHUDForView:animated: * @see animationType */ + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; /// @name Showing and hiding /** * Finds the top-most HUD subview that hasn't finished and hides it. The counterpart to this method is showHUDAddedTo:animated:. * * @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden. * * @param view The view that is going to be searched for a HUD subview. * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use * animations while disappearing. * @return YES if a HUD was found and removed, NO otherwise. * * @see showHUDAddedTo:animated: * @see animationType */ + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; /** * Finds the top-most HUD subview that hasn't finished and returns it. * * @param view The view that is going to be searched. * @return A reference to the last HUD subview discovered. */ + (nullable MBProgressHUD *)HUDForView:(UIView *)view NS_SWIFT_NAME(forView(_:)); /** * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with * view.bounds as the parameter. * * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as * the HUD's superview (i.e., the view that the HUD will be added to). */ - (instancetype)initWithView:(UIView *)view; /** * Displays the HUD. * * @note You need to make sure that the main thread completes its run loop soon after this method call so that * the user interface can be updated. Call this method when your task is already set up to be executed in a new thread * (e.g., when using something like NSOperation or making an asynchronous call like NSURLRequest). * * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use * animations while appearing. * * @see animationType */ - (void)showAnimated:(BOOL)animated; /** * Hides the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to * hide the HUD when your task completes. * * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use * animations while disappearing. * * @see animationType */ - (void)hideAnimated:(BOOL)animated; /** * Hides the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to * hide the HUD when your task completes. * * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use * animations while disappearing. * @param delay Delay in seconds until the HUD is hidden. * * @see animationType */ - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay; /** * The HUD delegate object. Receives HUD state notifications. */ @property (weak, nonatomic) id delegate; /** * Called after the HUD is hidden. */ @property (copy, nullable) MBProgressHUDCompletionBlock completionBlock; /** * Grace period is the time (in seconds) that the invoked method may be run without * showing the HUD. If the task finishes before the grace time runs out, the HUD will * not be shown at all. * This may be used to prevent HUD display for very short tasks. * Defaults to 0 (no grace time). * @note The graceTime needs to be set before the hud is shown. You thus can't use `showHUDAddedTo:animated:`, * but instead need to alloc / init the HUD, configure the grace time and than show it manually. */ @property (assign, nonatomic) NSTimeInterval graceTime; /** * The minimum time (in seconds) that the HUD is shown. * This avoids the problem of the HUD being shown and than instantly hidden. * Defaults to 0 (no minimum show time). */ @property (assign, nonatomic) NSTimeInterval minShowTime; /** * Removes the HUD from its parent view when hidden. * Defaults to NO. */ @property (assign, nonatomic) BOOL removeFromSuperViewOnHide; /// @name Appearance /** * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. */ @property (assign, nonatomic) MBProgressHUDMode mode; /** * A color that gets forwarded to all labels and supported indicators. Also sets the tintColor * for custom views on iOS 7+. Set to nil to manage color individually. * Defaults to semi-translucent black on iOS 7 and later and white on earlier iOS versions. */ @property (strong, nonatomic, nullable) UIColor *contentColor UI_APPEARANCE_SELECTOR; /** * The animation type that should be used when the HUD is shown and hidden. */ @property (assign, nonatomic) MBProgressHUDAnimation animationType UI_APPEARANCE_SELECTOR; /** * The bezel offset relative to the center of the view. You can use MBProgressMaxOffset * and -MBProgressMaxOffset to move the HUD all the way to the screen edge in each direction. * E.g., CGPointMake(0.f, MBProgressMaxOffset) would position the HUD centered on the bottom edge. */ @property (assign, nonatomic) CGPoint offset UI_APPEARANCE_SELECTOR; /** * The amount of space between the HUD edge and the HUD elements (labels, indicators or custom views). * This also represents the minimum bezel distance to the edge of the HUD view. * Defaults to 20.f */ @property (assign, nonatomic) CGFloat margin UI_APPEARANCE_SELECTOR; /** * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). */ @property (assign, nonatomic) CGSize minSize UI_APPEARANCE_SELECTOR; /** * Force the HUD dimensions to be equal if possible. */ @property (assign, nonatomic, getter = isSquare) BOOL square UI_APPEARANCE_SELECTOR; /** * When enabled, the bezel center gets slightly affected by the device accelerometer data. * Defaults to NO. * * @note This can cause main thread checker assertions on certain devices. https://github.com/jdg/MBProgressHUD/issues/552 */ @property (assign, nonatomic, getter=areDefaultMotionEffectsEnabled) BOOL defaultMotionEffectsEnabled UI_APPEARANCE_SELECTOR; /// @name Progress /** * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. */ @property (assign, nonatomic) float progress; /// @name ProgressObject /** * The NSProgress object feeding the progress information to the progress indicator. */ @property (strong, nonatomic, nullable) NSProgress *progressObject; /// @name Views /** * The view containing the labels and indicator (or customView). */ @property (strong, nonatomic, readonly) MBBackgroundView *bezelView; /** * View covering the entire HUD area, placed behind bezelView. */ @property (strong, nonatomic, readonly) MBBackgroundView *backgroundView; /** * The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView. * The view should implement intrinsicContentSize for proper sizing. For best results use approximately 37 by 37 pixels. */ @property (strong, nonatomic, nullable) UIView *customView; /** * A label that holds an optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit * the entire text. */ @property (strong, nonatomic, readonly) UILabel *label; /** * A label that holds an optional details message displayed below the labelText message. The details text can span multiple lines. */ @property (strong, nonatomic, readonly) UILabel *detailsLabel; /** * A button that is placed below the labels. Visible only if a target / action is added and a title is assigned.. */ @property (strong, nonatomic, readonly) UIButton *button; @end @protocol MBProgressHUDDelegate @optional /** * Called after the HUD was fully hidden from the screen. */ - (void)hudWasHidden:(MBProgressHUD *)hud; @end /** * A progress view for showing definite progress by filling up a circle (pie chart). */ @interface MBRoundProgressView : UIView /** * Progress (0.0 to 1.0) */ @property (nonatomic, assign) float progress; /** * Indicator progress color. * Defaults to white [UIColor whiteColor]. */ @property (nonatomic, strong) UIColor *progressTintColor; /** * Indicator background (non-progress) color. * Only applicable on iOS versions older than iOS 7. * Defaults to translucent white (alpha 0.1). */ @property (nonatomic, strong) UIColor *backgroundTintColor; /* * Display mode - NO = round or YES = annular. Defaults to round. */ @property (nonatomic, assign, getter = isAnnular) BOOL annular; @end /** * A flat bar progress view. */ @interface MBBarProgressView : UIView /** * Progress (0.0 to 1.0) */ @property (nonatomic, assign) float progress; /** * Bar border line color. * Defaults to white [UIColor whiteColor]. */ @property (nonatomic, strong) UIColor *lineColor; /** * Bar background color. * Defaults to clear [UIColor clearColor]; */ @property (nonatomic, strong) UIColor *progressRemainingColor; /** * Bar progress color. * Defaults to white [UIColor whiteColor]. */ @property (nonatomic, strong) UIColor *progressColor; @end @interface MBBackgroundView : UIView /** * The background style. * Defaults to MBProgressHUDBackgroundStyleBlur. */ @property (nonatomic) MBProgressHUDBackgroundStyle style; /** * The blur effect style, when using MBProgressHUDBackgroundStyleBlur. * Defaults to UIBlurEffectStyleLight. */ @property (nonatomic) UIBlurEffectStyle blurEffectStyle; /** * The background color or the blur tint color. * * Defaults to nil on iOS 13 and later and * `[UIColor colorWithWhite:0.8f alpha:0.6f]` * on older systems. */ @property (nonatomic, strong, nullable) UIColor *color; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MBProgressHUD/MBProgressHUD.m ================================================ // // MBProgressHUD.m // Version 1.2.0 // Created by Matej Bukovinski on 2.4.09. // #import "MBProgressHUD.h" #import #define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread."); CGFloat const MBProgressMaxOffset = 1000000.f; static const CGFloat MBDefaultPadding = 4.f; static const CGFloat MBDefaultLabelFontSize = 16.f; static const CGFloat MBDefaultDetailsLabelFontSize = 12.f; @interface MBProgressHUD () @property (nonatomic, assign) BOOL useAnimation; @property (nonatomic, assign, getter=hasFinished) BOOL finished; @property (nonatomic, strong) UIView *indicator; @property (nonatomic, strong) NSDate *showStarted; @property (nonatomic, strong) NSArray *paddingConstraints; @property (nonatomic, strong) NSArray *bezelConstraints; @property (nonatomic, strong) UIView *topSpacer; @property (nonatomic, strong) UIView *bottomSpacer; @property (nonatomic, strong) UIMotionEffectGroup *bezelMotionEffects; @property (nonatomic, weak) NSTimer *graceTimer; @property (nonatomic, weak) NSTimer *minShowTimer; @property (nonatomic, weak) NSTimer *hideDelayTimer; @property (nonatomic, weak) CADisplayLink *progressObjectDisplayLink; @end @interface MBProgressHUDRoundedButton : UIButton @end @implementation MBProgressHUD #pragma mark - Class methods + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { MBProgressHUD *hud = [[self alloc] initWithView:view]; hud.removeFromSuperViewOnHide = YES; [view addSubview:hud]; [hud showAnimated:animated]; return hud; } + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { MBProgressHUD *hud = [self HUDForView:view]; if (hud != nil) { hud.removeFromSuperViewOnHide = YES; [hud hideAnimated:animated]; return YES; } return NO; } + (MBProgressHUD *)HUDForView:(UIView *)view { NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; for (UIView *subview in subviewsEnum) { if ([subview isKindOfClass:self]) { MBProgressHUD *hud = (MBProgressHUD *)subview; if (hud.hasFinished == NO) { return hud; } } } return nil; } #pragma mark - Lifecycle - (void)commonInit { // Set default values for properties _animationType = MBProgressHUDAnimationFade; _mode = MBProgressHUDModeIndeterminate; _margin = 20.0f; _defaultMotionEffectsEnabled = NO; if (@available(iOS 13.0, tvOS 13, *)) { _contentColor = [[UIColor labelColor] colorWithAlphaComponent:0.7f]; } else { _contentColor = [UIColor colorWithWhite:0.f alpha:0.7f]; } // Transparent background self.opaque = NO; self.backgroundColor = [UIColor clearColor]; // Make it invisible for now self.alpha = 0.0f; self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.layer.allowsGroupOpacity = NO; [self setupViews]; [self updateIndicators]; [self registerForNotifications]; } - (instancetype)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { [self commonInit]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { [self commonInit]; } return self; } - (id)initWithView:(UIView *)view { NSAssert(view, @"View must not be nil."); return [self initWithFrame:view.bounds]; } - (void)dealloc { [self unregisterFromNotifications]; } #pragma mark - Show & hide - (void)showAnimated:(BOOL)animated { MBMainThreadAssert(); [self.minShowTimer invalidate]; self.useAnimation = animated; self.finished = NO; // If the grace time is set, postpone the HUD display if (self.graceTime > 0.0) { NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; self.graceTimer = timer; } // ... otherwise show the HUD immediately else { [self showUsingAnimation:self.useAnimation]; } } - (void)hideAnimated:(BOOL)animated { MBMainThreadAssert(); [self.graceTimer invalidate]; self.useAnimation = animated; self.finished = YES; // If the minShow time is set, calculate how long the HUD was shown, // and postpone the hiding operation if necessary if (self.minShowTime > 0.0 && self.showStarted) { NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted]; if (interv < self.minShowTime) { NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; self.minShowTimer = timer; return; } } // ... otherwise hide the HUD immediately [self hideUsingAnimation:self.useAnimation]; } - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay { // Cancel any scheduled hideAnimated:afterDelay: calls [self.hideDelayTimer invalidate]; NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; self.hideDelayTimer = timer; } #pragma mark - Timer callbacks - (void)handleGraceTimer:(NSTimer *)theTimer { // Show the HUD only if the task is still running if (!self.hasFinished) { [self showUsingAnimation:self.useAnimation]; } } - (void)handleMinShowTimer:(NSTimer *)theTimer { [self hideUsingAnimation:self.useAnimation]; } - (void)handleHideTimer:(NSTimer *)timer { [self hideAnimated:[timer.userInfo boolValue]]; } #pragma mark - View Hierrarchy - (void)didMoveToSuperview { [self updateForCurrentOrientationAnimated:NO]; } #pragma mark - Internal show & hide operations - (void)showUsingAnimation:(BOOL)animated { // Cancel any previous animations [self.bezelView.layer removeAllAnimations]; [self.backgroundView.layer removeAllAnimations]; // Cancel any scheduled hideAnimated:afterDelay: calls [self.hideDelayTimer invalidate]; self.showStarted = [NSDate date]; self.alpha = 1.f; // Needed in case we hide and re-show with the same NSProgress object attached. [self setNSProgressDisplayLinkEnabled:YES]; // Set up motion effects only at this point to avoid needlessly // creating the effect if it was disabled after initialization. [self updateBezelMotionEffects]; if (animated) { [self animateIn:YES withType:self.animationType completion:NULL]; } else { self.bezelView.alpha = 1.f; self.backgroundView.alpha = 1.f; } } - (void)hideUsingAnimation:(BOOL)animated { // Cancel any scheduled hideAnimated:afterDelay: calls. // This needs to happen here instead of in done, // to avoid races if another hideAnimated:afterDelay: // call comes in while the HUD is animating out. [self.hideDelayTimer invalidate]; if (animated && self.showStarted) { self.showStarted = nil; [self animateIn:NO withType:self.animationType completion:^(BOOL finished) { [self done]; }]; } else { self.showStarted = nil; self.bezelView.alpha = 0.f; self.backgroundView.alpha = 1.f; [self done]; } } - (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion { // Automatically determine the correct zoom animation type if (type == MBProgressHUDAnimationZoom) { type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut; } CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f); CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f); // Set starting state UIView *bezelView = self.bezelView; if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) { bezelView.transform = small; } else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) { bezelView.transform = large; } // Perform animations dispatch_block_t animations = ^{ if (animatingIn) { bezelView.transform = CGAffineTransformIdentity; } else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) { bezelView.transform = large; } else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) { bezelView.transform = small; } CGFloat alpha = animatingIn ? 1.f : 0.f; bezelView.alpha = alpha; self.backgroundView.alpha = alpha; }; [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion]; } - (void)done { [self setNSProgressDisplayLinkEnabled:NO]; if (self.hasFinished) { self.alpha = 0.0f; if (self.removeFromSuperViewOnHide) { [self removeFromSuperview]; } } MBProgressHUDCompletionBlock completionBlock = self.completionBlock; if (completionBlock) { completionBlock(); } id delegate = self.delegate; if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { [delegate performSelector:@selector(hudWasHidden:) withObject:self]; } } #pragma mark - UI - (void)setupViews { UIColor *defaultColor = self.contentColor; MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds]; backgroundView.style = MBProgressHUDBackgroundStyleSolidColor; backgroundView.backgroundColor = [UIColor clearColor]; backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; backgroundView.alpha = 0.f; [self addSubview:backgroundView]; _backgroundView = backgroundView; MBBackgroundView *bezelView = [MBBackgroundView new]; bezelView.translatesAutoresizingMaskIntoConstraints = NO; bezelView.layer.cornerRadius = 5.f; bezelView.alpha = 0.f; [self addSubview:bezelView]; _bezelView = bezelView; UILabel *label = [UILabel new]; label.adjustsFontSizeToFitWidth = NO; label.textAlignment = NSTextAlignmentCenter; label.textColor = defaultColor; label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize]; label.opaque = NO; label.backgroundColor = [UIColor clearColor]; _label = label; UILabel *detailsLabel = [UILabel new]; detailsLabel.adjustsFontSizeToFitWidth = NO; detailsLabel.textAlignment = NSTextAlignmentCenter; detailsLabel.textColor = defaultColor; detailsLabel.numberOfLines = 0; detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize]; detailsLabel.opaque = NO; detailsLabel.backgroundColor = [UIColor clearColor]; _detailsLabel = detailsLabel; UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom]; button.titleLabel.textAlignment = NSTextAlignmentCenter; button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize]; [button setTitleColor:defaultColor forState:UIControlStateNormal]; _button = button; for (UIView *view in @[label, detailsLabel, button]) { view.translatesAutoresizingMaskIntoConstraints = NO; [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal]; [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical]; [bezelView addSubview:view]; } UIView *topSpacer = [UIView new]; topSpacer.translatesAutoresizingMaskIntoConstraints = NO; topSpacer.hidden = YES; [bezelView addSubview:topSpacer]; _topSpacer = topSpacer; UIView *bottomSpacer = [UIView new]; bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO; bottomSpacer.hidden = YES; [bezelView addSubview:bottomSpacer]; _bottomSpacer = bottomSpacer; } - (void)updateIndicators { UIView *indicator = self.indicator; BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]]; MBProgressHUDMode mode = self.mode; if (mode == MBProgressHUDModeIndeterminate) { if (!isActivityIndicator) { // Update to indeterminate indicator UIActivityIndicatorView *activityIndicator; [indicator removeFromSuperview]; #if !TARGET_OS_MACCATALYST if (@available(iOS 13.0, tvOS 13.0, *)) { #endif activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge]; activityIndicator.color = [UIColor whiteColor]; #if !TARGET_OS_MACCATALYST } else { activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; } #endif [activityIndicator startAnimating]; indicator = activityIndicator; [self.bezelView addSubview:indicator]; } } else if (mode == MBProgressHUDModeDeterminateHorizontalBar) { // Update to bar determinate indicator [indicator removeFromSuperview]; indicator = [[MBBarProgressView alloc] init]; [self.bezelView addSubview:indicator]; } else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { if (!isRoundIndicator) { // Update to determinante indicator [indicator removeFromSuperview]; indicator = [[MBRoundProgressView alloc] init]; [self.bezelView addSubview:indicator]; } if (mode == MBProgressHUDModeAnnularDeterminate) { [(MBRoundProgressView *)indicator setAnnular:YES]; } } else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) { // Update custom view indicator [indicator removeFromSuperview]; indicator = self.customView; [self.bezelView addSubview:indicator]; } else if (mode == MBProgressHUDModeText) { [indicator removeFromSuperview]; indicator = nil; } indicator.translatesAutoresizingMaskIntoConstraints = NO; self.indicator = indicator; if ([indicator respondsToSelector:@selector(setProgress:)]) { [(id)indicator setValue:@(self.progress) forKey:@"progress"]; } [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal]; [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical]; [self updateViewsForColor:self.contentColor]; [self setNeedsUpdateConstraints]; } - (void)updateViewsForColor:(UIColor *)color { if (!color) return; self.label.textColor = color; self.detailsLabel.textColor = color; [self.button setTitleColor:color forState:UIControlStateNormal]; // UIAppearance settings are prioritized. If they are preset the set color is ignored. UIView *indicator = self.indicator; if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) { UIActivityIndicatorView *appearance = nil; #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 appearance = [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil]; #else // For iOS 9+ appearance = [UIActivityIndicatorView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; #endif if (appearance.color == nil) { ((UIActivityIndicatorView *)indicator).color = color; } } else if ([indicator isKindOfClass:[MBRoundProgressView class]]) { MBRoundProgressView *appearance = nil; #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 appearance = [MBRoundProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil]; #else appearance = [MBRoundProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; #endif if (appearance.progressTintColor == nil) { ((MBRoundProgressView *)indicator).progressTintColor = color; } if (appearance.backgroundTintColor == nil) { ((MBRoundProgressView *)indicator).backgroundTintColor = [color colorWithAlphaComponent:0.1]; } } else if ([indicator isKindOfClass:[MBBarProgressView class]]) { MBBarProgressView *appearance = nil; #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 appearance = [MBBarProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil]; #else appearance = [MBBarProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; #endif if (appearance.progressColor == nil) { ((MBBarProgressView *)indicator).progressColor = color; } if (appearance.lineColor == nil) { ((MBBarProgressView *)indicator).lineColor = color; } } else { [indicator setTintColor:color]; } } - (void)updateBezelMotionEffects { MBBackgroundView *bezelView = self.bezelView; UIMotionEffectGroup *bezelMotionEffects = self.bezelMotionEffects; if (self.defaultMotionEffectsEnabled && !bezelMotionEffects) { CGFloat effectOffset = 10.f; UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; effectX.maximumRelativeValue = @(effectOffset); effectX.minimumRelativeValue = @(-effectOffset); UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; effectY.maximumRelativeValue = @(effectOffset); effectY.minimumRelativeValue = @(-effectOffset); UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init]; group.motionEffects = @[effectX, effectY]; self.bezelMotionEffects = group; [bezelView addMotionEffect:group]; } else if (bezelMotionEffects) { self.bezelMotionEffects = nil; [bezelView removeMotionEffect:bezelMotionEffects]; } } #pragma mark - Layout - (void)updateConstraints { UIView *bezel = self.bezelView; UIView *topSpacer = self.topSpacer; UIView *bottomSpacer = self.bottomSpacer; CGFloat margin = self.margin; NSMutableArray *bezelConstraints = [NSMutableArray array]; NSDictionary *metrics = @{@"margin": @(margin)}; NSMutableArray *subviews = [NSMutableArray arrayWithObjects:self.topSpacer, self.label, self.detailsLabel, self.button, self.bottomSpacer, nil]; if (self.indicator) [subviews insertObject:self.indicator atIndex:1]; // Remove existing constraints [self removeConstraints:self.constraints]; [topSpacer removeConstraints:topSpacer.constraints]; [bottomSpacer removeConstraints:bottomSpacer.constraints]; if (self.bezelConstraints) { [bezel removeConstraints:self.bezelConstraints]; self.bezelConstraints = nil; } // Center bezel in container (self), applying the offset if set CGPoint offset = self.offset; NSMutableArray *centeringConstraints = [NSMutableArray array]; [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:offset.x]]; [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:offset.y]]; [self applyPriority:998.f toConstraints:centeringConstraints]; [self addConstraints:centeringConstraints]; // Ensure minimum side margin is kept NSMutableArray *sideConstraints = [NSMutableArray array]; [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]]; [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]]; [self applyPriority:999.f toConstraints:sideConstraints]; [self addConstraints:sideConstraints]; // Minimum bezel size, if set CGSize minimumSize = self.minSize; if (!CGSizeEqualToSize(minimumSize, CGSizeZero)) { NSMutableArray *minSizeConstraints = [NSMutableArray array]; [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.width]]; [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.height]]; [self applyPriority:997.f toConstraints:minSizeConstraints]; [bezelConstraints addObjectsFromArray:minSizeConstraints]; } // Square aspect ratio, if set if (self.square) { NSLayoutConstraint *square = [NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeWidth multiplier:1.f constant:0]; square.priority = 997.f; [bezelConstraints addObject:square]; } // Top and bottom spacing [topSpacer addConstraint:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]]; [bottomSpacer addConstraint:[NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]]; // Top and bottom spaces should be equal [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f]]; // Layout subviews in bezel NSMutableArray *paddingConstraints = [NSMutableArray new]; [subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) { // Center in bezel [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f]]; // Ensure the minimum edge margin is kept [bezelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[view]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(view)]]; // Element spacing if (idx == 0) { // First, ensure spacing to bezel edge [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]]; } else if (idx == subviews.count - 1) { // Last, ensure spacing to bezel edge [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]]; } if (idx > 0) { // Has previous NSLayoutConstraint *padding = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:subviews[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]; [bezelConstraints addObject:padding]; [paddingConstraints addObject:padding]; } }]; [bezel addConstraints:bezelConstraints]; self.bezelConstraints = bezelConstraints; self.paddingConstraints = [paddingConstraints copy]; [self updatePaddingConstraints]; [super updateConstraints]; } - (void)layoutSubviews { // There is no need to update constraints if they are going to // be recreated in [super layoutSubviews] due to needsUpdateConstraints being set. // This also avoids an issue on iOS 8, where updatePaddingConstraints // would trigger a zombie object access. if (!self.needsUpdateConstraints) { [self updatePaddingConstraints]; } [super layoutSubviews]; } - (void)updatePaddingConstraints { // Set padding dynamically, depending on whether the view is visible or not __block BOOL hasVisibleAncestors = NO; [self.paddingConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *padding, NSUInteger idx, BOOL *stop) { UIView *firstView = (UIView *)padding.firstItem; UIView *secondView = (UIView *)padding.secondItem; BOOL firstVisible = !firstView.hidden && !CGSizeEqualToSize(firstView.intrinsicContentSize, CGSizeZero); BOOL secondVisible = !secondView.hidden && !CGSizeEqualToSize(secondView.intrinsicContentSize, CGSizeZero); // Set if both views are visible or if there's a visible view on top that doesn't have padding // added relative to the current view yet padding.constant = (firstVisible && (secondVisible || hasVisibleAncestors)) ? MBDefaultPadding : 0.f; hasVisibleAncestors |= secondVisible; }]; } - (void)applyPriority:(UILayoutPriority)priority toConstraints:(NSArray *)constraints { for (NSLayoutConstraint *constraint in constraints) { constraint.priority = priority; } } #pragma mark - Properties - (void)setMode:(MBProgressHUDMode)mode { if (mode != _mode) { _mode = mode; [self updateIndicators]; } } - (void)setCustomView:(UIView *)customView { if (customView != _customView) { _customView = customView; if (self.mode == MBProgressHUDModeCustomView) { [self updateIndicators]; } } } - (void)setOffset:(CGPoint)offset { if (!CGPointEqualToPoint(offset, _offset)) { _offset = offset; [self setNeedsUpdateConstraints]; } } - (void)setMargin:(CGFloat)margin { if (margin != _margin) { _margin = margin; [self setNeedsUpdateConstraints]; } } - (void)setMinSize:(CGSize)minSize { if (!CGSizeEqualToSize(minSize, _minSize)) { _minSize = minSize; [self setNeedsUpdateConstraints]; } } - (void)setSquare:(BOOL)square { if (square != _square) { _square = square; [self setNeedsUpdateConstraints]; } } - (void)setProgressObjectDisplayLink:(CADisplayLink *)progressObjectDisplayLink { if (progressObjectDisplayLink != _progressObjectDisplayLink) { [_progressObjectDisplayLink invalidate]; _progressObjectDisplayLink = progressObjectDisplayLink; [_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; } } - (void)setProgressObject:(NSProgress *)progressObject { if (progressObject != _progressObject) { _progressObject = progressObject; [self setNSProgressDisplayLinkEnabled:YES]; } } - (void)setProgress:(float)progress { if (progress != _progress) { _progress = progress; UIView *indicator = self.indicator; if ([indicator respondsToSelector:@selector(setProgress:)]) { [(id)indicator setValue:@(self.progress) forKey:@"progress"]; } } } - (void)setContentColor:(UIColor *)contentColor { if (contentColor != _contentColor && ![contentColor isEqual:_contentColor]) { _contentColor = contentColor; [self updateViewsForColor:contentColor]; } } - (void)setDefaultMotionEffectsEnabled:(BOOL)defaultMotionEffectsEnabled { if (defaultMotionEffectsEnabled != _defaultMotionEffectsEnabled) { _defaultMotionEffectsEnabled = defaultMotionEffectsEnabled; [self updateBezelMotionEffects]; } } #pragma mark - NSProgress - (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled { // We're using CADisplayLink, because NSProgress can change very quickly and observing it may starve the main thread, // so we're refreshing the progress only every frame draw if (enabled && self.progressObject) { // Only create if not already active. if (!self.progressObjectDisplayLink) { self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)]; } } else { self.progressObjectDisplayLink = nil; } } - (void)updateProgressFromProgressObject { self.progress = self.progressObject.fractionCompleted; } #pragma mark - Notifications - (void)registerForNotifications { #if !TARGET_OS_TV && !TARGET_OS_MACCATALYST NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(statusBarOrientationDidChange:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; #endif } - (void)unregisterFromNotifications { #if !TARGET_OS_TV && !TARGET_OS_MACCATALYST NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; #endif } #if !TARGET_OS_TV && !TARGET_OS_MACCATALYST - (void)statusBarOrientationDidChange:(NSNotification *)notification { UIView *superview = self.superview; if (!superview) { return; } else { [self updateForCurrentOrientationAnimated:YES]; } } #endif - (void)updateForCurrentOrientationAnimated:(BOOL)animated { // Stay in sync with the superview in any case if (self.superview) { self.frame = self.superview.bounds; } // Not needed on iOS 8+, compile out when the deployment target allows, // to avoid sharedApplication problems on extension targets #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 // Only needed pre iOS 8 when added to a window BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0; if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return; // Make extension friendly. Will not get called on extensions (iOS 8+) due to the above check. // This just ensures we don't get a warning about extension-unsafe API. Class UIApplicationClass = NSClassFromString(@"UIApplication"); if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) return; UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; UIInterfaceOrientation orientation = application.statusBarOrientation; CGFloat radians = 0; if (UIInterfaceOrientationIsLandscape(orientation)) { radians = orientation == UIInterfaceOrientationLandscapeLeft ? -(CGFloat)M_PI_2 : (CGFloat)M_PI_2; // Window coordinates differ! self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); } else { radians = orientation == UIInterfaceOrientationPortraitUpsideDown ? (CGFloat)M_PI : 0.f; } if (animated) { [UIView animateWithDuration:0.3 animations:^{ self.transform = CGAffineTransformMakeRotation(radians); }]; } else { self.transform = CGAffineTransformMakeRotation(radians); } #endif } @end @implementation MBRoundProgressView #pragma mark - Lifecycle - (id)init { return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)]; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor clearColor]; self.opaque = NO; _progress = 0.f; _annular = NO; _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f]; _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f]; } return self; } #pragma mark - Layout - (CGSize)intrinsicContentSize { return CGSizeMake(37.f, 37.f); } #pragma mark - Properties - (void)setProgress:(float)progress { if (progress != _progress) { _progress = progress; [self setNeedsDisplay]; } } - (void)setProgressTintColor:(UIColor *)progressTintColor { NSAssert(progressTintColor, @"The color should not be nil."); if (progressTintColor != _progressTintColor && ![progressTintColor isEqual:_progressTintColor]) { _progressTintColor = progressTintColor; [self setNeedsDisplay]; } } - (void)setBackgroundTintColor:(UIColor *)backgroundTintColor { NSAssert(backgroundTintColor, @"The color should not be nil."); if (backgroundTintColor != _backgroundTintColor && ![backgroundTintColor isEqual:_backgroundTintColor]) { _backgroundTintColor = backgroundTintColor; [self setNeedsDisplay]; } } #pragma mark - Drawing - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); if (_annular) { // Draw background CGFloat lineWidth = 2.f; UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath]; processBackgroundPath.lineWidth = lineWidth; processBackgroundPath.lineCapStyle = kCGLineCapButt; CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); CGFloat radius = (self.bounds.size.width - lineWidth)/2; CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees CGFloat endAngle = (2 * (float)M_PI) + startAngle; [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; [_backgroundTintColor set]; [processBackgroundPath stroke]; // Draw progress UIBezierPath *processPath = [UIBezierPath bezierPath]; processPath.lineCapStyle = kCGLineCapSquare; processPath.lineWidth = lineWidth; endAngle = (self.progress * 2 * (float)M_PI) + startAngle; [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; [_progressTintColor set]; [processPath stroke]; } else { // Draw background CGFloat lineWidth = 2.f; CGRect allRect = self.bounds; CGRect circleRect = CGRectInset(allRect, lineWidth/2.f, lineWidth/2.f); CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); [_progressTintColor setStroke]; [_backgroundTintColor setFill]; CGContextSetLineWidth(context, lineWidth); CGContextStrokeEllipseInRect(context, circleRect); // 90 degrees CGFloat startAngle = - ((float)M_PI / 2.f); // Draw progress UIBezierPath *processPath = [UIBezierPath bezierPath]; processPath.lineCapStyle = kCGLineCapButt; processPath.lineWidth = lineWidth * 2.f; CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - (processPath.lineWidth / 2.f); CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle; [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; // Ensure that we don't get color overlapping when _progressTintColor alpha < 1.f. CGContextSetBlendMode(context, kCGBlendModeCopy); [_progressTintColor set]; [processPath stroke]; } } @end @implementation MBBarProgressView #pragma mark - Lifecycle - (id)init { return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)]; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { _progress = 0.f; _lineColor = [UIColor whiteColor]; _progressColor = [UIColor whiteColor]; _progressRemainingColor = [UIColor clearColor]; self.backgroundColor = [UIColor clearColor]; self.opaque = NO; } return self; } #pragma mark - Layout - (CGSize)intrinsicContentSize { return CGSizeMake(120.f, 10.f); } #pragma mark - Properties - (void)setProgress:(float)progress { if (progress != _progress) { _progress = progress; [self setNeedsDisplay]; } } - (void)setProgressColor:(UIColor *)progressColor { NSAssert(progressColor, @"The color should not be nil."); if (progressColor != _progressColor && ![progressColor isEqual:_progressColor]) { _progressColor = progressColor; [self setNeedsDisplay]; } } - (void)setProgressRemainingColor:(UIColor *)progressRemainingColor { NSAssert(progressRemainingColor, @"The color should not be nil."); if (progressRemainingColor != _progressRemainingColor && ![progressRemainingColor isEqual:_progressRemainingColor]) { _progressRemainingColor = progressRemainingColor; [self setNeedsDisplay]; } } #pragma mark - Drawing - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetLineWidth(context, 2); CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]); CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]); // Draw background and Border CGFloat radius = (rect.size.height / 2) - 2; CGContextMoveToPoint(context, 2, rect.size.height/2); CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); CGContextDrawPath(context, kCGPathFillStroke); CGContextSetFillColorWithColor(context, [_progressColor CGColor]); radius = radius - 2; CGFloat amount = self.progress * rect.size.width; // Progress in the middle area if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) { CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); CGContextAddLineToPoint(context, amount, 4); CGContextAddLineToPoint(context, amount, radius + 4); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); CGContextAddLineToPoint(context, amount, rect.size.height - 4); CGContextAddLineToPoint(context, amount, radius + 4); CGContextFillPath(context); } // Progress in the right arc else if (amount > radius + 4) { CGFloat x = amount - (rect.size.width - radius - 4); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4); CGFloat angle = -acos(x/radius); if (isnan(angle)) angle = 0; CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0); CGContextAddLineToPoint(context, amount, rect.size.height/2); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4); angle = acos(x/radius); if (isnan(angle)) angle = 0; CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1); CGContextAddLineToPoint(context, amount, rect.size.height/2); CGContextFillPath(context); } // Progress is in the left arc else if (amount < radius + 4 && amount > 0) { CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); CGContextFillPath(context); } } @end @interface MBBackgroundView () @property UIVisualEffectView *effectView; @end @implementation MBBackgroundView #pragma mark - Lifecycle - (instancetype)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { _style = MBProgressHUDBackgroundStyleBlur; if (@available(iOS 13.0, *)) { #if TARGET_OS_TV _blurEffectStyle = UIBlurEffectStyleRegular; #else _blurEffectStyle = UIBlurEffectStyleSystemThickMaterial; #endif // Leaving the color unassigned yields best results. } else { _blurEffectStyle = UIBlurEffectStyleLight; _color = [UIColor colorWithWhite:0.8f alpha:0.6f]; } self.clipsToBounds = YES; [self updateForBackgroundStyle]; } return self; } #pragma mark - Layout - (CGSize)intrinsicContentSize { // Smallest size possible. Content pushes against this. return CGSizeZero; } #pragma mark - Appearance - (void)setStyle:(MBProgressHUDBackgroundStyle)style { if (_style != style) { _style = style; [self updateForBackgroundStyle]; } } - (void)setColor:(UIColor *)color { NSAssert(color, @"The color should not be nil."); if (color != _color && ![color isEqual:_color]) { _color = color; [self updateViewsForColor:color]; } } - (void)setBlurEffectStyle:(UIBlurEffectStyle)blurEffectStyle { if (_blurEffectStyle == blurEffectStyle) { return; } _blurEffectStyle = blurEffectStyle; [self updateForBackgroundStyle]; } /////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - Views - (void)updateForBackgroundStyle { [self.effectView removeFromSuperview]; self.effectView = nil; MBProgressHUDBackgroundStyle style = self.style; if (style == MBProgressHUDBackgroundStyleBlur) { UIBlurEffect *effect = [UIBlurEffect effectWithStyle:self.blurEffectStyle]; UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect]; [self insertSubview:effectView atIndex:0]; effectView.frame = self.bounds; effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; self.backgroundColor = self.color; self.layer.allowsGroupOpacity = NO; self.effectView = effectView; } else { self.backgroundColor = self.color; } } - (void)updateViewsForColor:(UIColor *)color { if (self.style == MBProgressHUDBackgroundStyleBlur) { self.backgroundColor = self.color; } else { self.backgroundColor = self.color; } } @end @implementation MBProgressHUDRoundedButton #pragma mark - Lifecycle - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { CALayer *layer = self.layer; layer.borderWidth = 1.f; } return self; } #pragma mark - Layout - (void)layoutSubviews { [super layoutSubviews]; // Fully rounded corners CGFloat height = CGRectGetHeight(self.bounds); self.layer.cornerRadius = ceil(height / 2.f); } - (CGSize)intrinsicContentSize { // Only show if we have associated control events and a title if ((self.allControlEvents == 0) || ([self titleForState:UIControlStateNormal].length == 0)) return CGSizeZero; CGSize size = [super intrinsicContentSize]; // Add some side padding size.width += 20.f; return size; } #pragma mark - Color - (void)setTitleColor:(UIColor *)color forState:(UIControlState)state { [super setTitleColor:color forState:state]; // Update related colors [self setHighlighted:self.highlighted]; self.layer.borderColor = color.CGColor; } - (void)setHighlighted:(BOOL)highlighted { [super setHighlighted:highlighted]; UIColor *baseColor = [self titleColorForState:UIControlStateSelected]; self.backgroundColor = highlighted ? [baseColor colorWithAlphaComponent:0.1f] : [UIColor clearColor]; } @end ================================================ FILE: JetChat/Pods/MBProgressHUD/README.mdown ================================================ # MBProgressHUD [![Build Status](https://travis-ci.org/matej/MBProgressHUD.svg?branch=master)](https://travis-ci.org/matej/MBProgressHUD) [![codecov.io](https://codecov.io/github/matej/MBProgressHUD/coverage.svg?branch=master)](https://codecov.io/github/matej/MBProgressHUD?branch=master) [![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application) [![Accio supported](https://img.shields.io/badge/Accio-supported-0A7CF5.svg?style=flat)](https://github.com/JamitLabs/Accio) [![CocoaPods compatible](https://img.shields.io/cocoapods/v/MBProgressHUD.svg?style=flat)](https://cocoapods.org/pods/MBProgressHUD) [![License: MIT](https://img.shields.io/cocoapods/l/MBProgressHUD.svg?style=flat)](http://opensource.org/licenses/MIT) `MBProgressHUD` is an iOS drop-in class that displays a translucent HUD with an indicator and/or labels while work is being done in a background thread. The HUD is meant as a replacement for the undocumented, private `UIKit` `UIProgressHUD` with some additional features. [![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/1-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/1.png) [![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/2-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/2.png) [![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/3-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/3.png) [![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/4-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/4.png) [![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/5-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/5.png) [![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/6-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/6.png) [![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/7-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/7.png) **NOTE:** The class has recently undergone a major rewrite. The old version is available in the [legacy](https://github.com/jdg/MBProgressHUD/tree/legacy) branch, should you need it. ## Requirements `MBProgressHUD` works on iOS 9.0+. It depends on the following Apple frameworks, which should already be included with most Xcode templates: * Foundation.framework * UIKit.framework * CoreGraphics.framework You will need the latest developer tools in order to build `MBProgressHUD`. Old Xcode versions might work, but compatibility will not be explicitly maintained. ## Adding MBProgressHUD to your project ### CocoaPods [CocoaPods](http://cocoapods.org) is the recommended way to add MBProgressHUD to your project. 1. Add a pod entry for MBProgressHUD to your Podfile `pod 'MBProgressHUD', '~> 1.2.0'` 2. Install the pod(s) by running `pod install`. 3. Include MBProgressHUD wherever you need it with `#import "MBProgressHUD.h"`. ### Carthage 1. Add MBProgressHUD to your Cartfile. e.g., `github "jdg/MBProgressHUD" ~> 1.2.0` 2. Run `carthage update` 3. Follow the rest of the [standard Carthage installation instructions](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application) to add MBProgressHUD to your project. ### SwiftPM / Accio 1. Add the following to your `Package.swift`: ```swift .package(url: "https://github.com/jdg/MBProgressHUD.git", .upToNextMajor(from: "1.2.0")), ``` 2. Next, add `MBProgressHUD` to your App targets dependencies like so: ```swift .target(name: "App", dependencies: ["MBProgressHUD"]), ``` 3. Then open your project in Xcode 11+ (SwiftPM) or run `accio update` (Accio). ### Source files Alternatively you can directly add the `MBProgressHUD.h` and `MBProgressHUD.m` source files to your project. 1. Download the [latest code version](https://github.com/matej/MBProgressHUD/archive/master.zip) or add the repository as a git submodule to your git-tracked project. 2. Open your project in Xcode, then drag and drop `MBProgressHUD.h` and `MBProgressHUD.m` onto your project (use the "Product Navigator view"). Make sure to select Copy items when asked if you extracted the code archive outside of your project. 3. Include MBProgressHUD wherever you need it with `#import "MBProgressHUD.h"`. ### Static library You can also add MBProgressHUD as a static library to your project or workspace. 1. Download the [latest code version](https://github.com/matej/MBProgressHUD/downloads) or add the repository as a git submodule to your git-tracked project. 2. Open your project in Xcode, then drag and drop `MBProgressHUD.xcodeproj` onto your project or workspace (use the "Product Navigator view"). 3. Select your target and go to the Build phases tab. In the Link Binary With Libraries section select the add button. On the sheet find and add `libMBProgressHUD.a`. You might also need to add `MBProgressHUD` to the Target Dependencies list. 4. Include MBProgressHUD wherever you need it with `#import `. ## Usage The main guideline you need to follow when dealing with MBProgressHUD while running long-running tasks is keeping the main thread work-free, so the UI can be updated promptly. The recommended way of using MBProgressHUD is therefore to set it up on the main thread and then spinning the task, that you want to perform, off onto a new thread. ```objective-c [MBProgressHUD showHUDAddedTo:self.view animated:YES]; dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ // Do something... dispatch_async(dispatch_get_main_queue(), ^{ [MBProgressHUD hideHUDForView:self.view animated:YES]; }); }); ``` You can add the HUD on any view or window. It is however a good idea to avoid adding the HUD to certain `UIKit` views with complex view hierarchies - like `UITableView` or `UICollectionView`. Those can mutate their subviews in unexpected ways and thereby break HUD display. If you need to configure the HUD you can do this by using the MBProgressHUD reference that showHUDAddedTo:animated: returns. ```objective-c MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; hud.mode = MBProgressHUDModeAnnularDeterminate; hud.label.text = @"Loading"; [self doSomethingInBackgroundWithProgressCallback:^(float progress) { hud.progress = progress; } completionCallback:^{ [hud hideAnimated:YES]; }]; ``` You can also use a `NSProgress` object and MBProgressHUD will update itself when there is progress reported through that object. ```objective-c MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; hud.mode = MBProgressHUDModeAnnularDeterminate; hud.label.text = @"Loading"; NSProgress *progress = [self doSomethingInBackgroundCompletion:^{ [hud hideAnimated:YES]; }]; hud.progressObject = progress; ``` Keep in mind that UI updates, inclining calls to MBProgressHUD should always be done on the main thread. If you need to run your long-running task in the main thread, you should perform it with a slight delay, so UIKit will have enough time to update the UI (i.e., draw the HUD) before you block the main thread with your task. ```objective-c [MBProgressHUD showHUDAddedTo:self.view animated:YES]; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // Do something... [MBProgressHUD hideHUDForView:self.view animated:YES]; }); ``` You should be aware that any HUD updates issued inside the above block won't be displayed until the block completes. For more examples, including how to use MBProgressHUD with asynchronous operations such as NSURLConnection, take a look at the bundled demo project. Extensive API documentation is provided in the header file (MBProgressHUD.h). ## License This code is distributed under the terms and conditions of the [MIT license](LICENSE). ## Change-log A brief summary of each MBProgressHUD release can be found in the [CHANGELOG](CHANGELOG.mdown). ================================================ FILE: JetChat/Pods/MBProgressHUD.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 037073A136B0C073EB5F52424D6B1BB8 /* MBProgressHUD-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 42C47F506901F394A6B87B52C93CBB29 /* MBProgressHUD-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 076D27D6B05F23750F64BDF401E5A782 /* MBProgressHUD-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 86513DA5686F0CB9D2C6B8C43DC08509 /* MBProgressHUD-dummy.m */; }; 25967011F83F124E0594BACE3EF96BBF /* MBProgressHUD.h in Headers */ = {isa = PBXBuildFile; fileRef = 2493CC838F0DEEA55BB32A493E1B22CA /* MBProgressHUD.h */; settings = {ATTRIBUTES = (Public, ); }; }; 48CB33CBFC9DA157F7ECB042BBA81F4B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C8CB644149EBA15FE2799824117D13 /* Foundation.framework */; }; B627A1CE9CE836750B74FBA065FD364B /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3A964DCE9349768E5D973DA9C163909 /* QuartzCore.framework */; }; C3BF696D1161E0137930872CE216B500 /* MBProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B758965F453BD454319ECB6E388E20 /* MBProgressHUD.m */; }; E059FD66FD5A17A75010D00F3BB5FFBA /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40FC7CFC05C7B0963F209ED15D260C1B /* CoreGraphics.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2493CC838F0DEEA55BB32A493E1B22CA /* MBProgressHUD.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = MBProgressHUD.h; sourceTree = ""; }; 34B758965F453BD454319ECB6E388E20 /* MBProgressHUD.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = MBProgressHUD.m; sourceTree = ""; }; 40FC7CFC05C7B0963F209ED15D260C1B /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; 42C47F506901F394A6B87B52C93CBB29 /* MBProgressHUD-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MBProgressHUD-umbrella.h"; sourceTree = ""; }; 5451F7443D5044D3386A2D7761A7C0CE /* MBProgressHUD.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = MBProgressHUD.modulemap; sourceTree = ""; }; 85171AE1C36A580DE4A0F0DBC84D7256 /* MBProgressHUD.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MBProgressHUD.release.xcconfig; sourceTree = ""; }; 86513DA5686F0CB9D2C6B8C43DC08509 /* MBProgressHUD-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "MBProgressHUD-dummy.m"; sourceTree = ""; }; C1C8CB644149EBA15FE2799824117D13 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; D4D0090781C017225C10FE4EA74951C6 /* MBProgressHUD */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = MBProgressHUD; path = MBProgressHUD.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E4EA42E7EE4F36B816B49A46E042DA84 /* MBProgressHUD-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "MBProgressHUD-Info.plist"; sourceTree = ""; }; F3A964DCE9349768E5D973DA9C163909 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; FA293FAE958AA319ADD3D932CFE3D158 /* MBProgressHUD.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MBProgressHUD.debug.xcconfig; sourceTree = ""; }; FA978DD72B4F2901ED0C996435F2B863 /* MBProgressHUD-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MBProgressHUD-prefix.pch"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 0748F7A3E92A4F6896C3AD7979D66A55 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E059FD66FD5A17A75010D00F3BB5FFBA /* CoreGraphics.framework in Frameworks */, 48CB33CBFC9DA157F7ECB042BBA81F4B /* Foundation.framework in Frameworks */, B627A1CE9CE836750B74FBA065FD364B /* QuartzCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 7C6264ED89D8125FF43CE634A9019CBC /* MBProgressHUD */ = { isa = PBXGroup; children = ( 2493CC838F0DEEA55BB32A493E1B22CA /* MBProgressHUD.h */, 34B758965F453BD454319ECB6E388E20 /* MBProgressHUD.m */, CCC56EC139C744ED28E902661A74996A /* Support Files */, ); name = MBProgressHUD; path = MBProgressHUD; sourceTree = ""; }; 8231109976E16F0571AEC2597787EB54 /* Products */ = { isa = PBXGroup; children = ( D4D0090781C017225C10FE4EA74951C6 /* MBProgressHUD */, ); name = Products; sourceTree = ""; }; A476C4BC7FF34ECF35F26C8EDF7BE888 /* Frameworks */ = { isa = PBXGroup; children = ( FFCAC7BB148282CF51B872C2C78FB057 /* iOS */, ); name = Frameworks; sourceTree = ""; }; CCC56EC139C744ED28E902661A74996A /* Support Files */ = { isa = PBXGroup; children = ( 5451F7443D5044D3386A2D7761A7C0CE /* MBProgressHUD.modulemap */, 86513DA5686F0CB9D2C6B8C43DC08509 /* MBProgressHUD-dummy.m */, E4EA42E7EE4F36B816B49A46E042DA84 /* MBProgressHUD-Info.plist */, FA978DD72B4F2901ED0C996435F2B863 /* MBProgressHUD-prefix.pch */, 42C47F506901F394A6B87B52C93CBB29 /* MBProgressHUD-umbrella.h */, FA293FAE958AA319ADD3D932CFE3D158 /* MBProgressHUD.debug.xcconfig */, 85171AE1C36A580DE4A0F0DBC84D7256 /* MBProgressHUD.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/MBProgressHUD"; sourceTree = ""; }; D69CAE501A86EBF20792505E5C334F2F = { isa = PBXGroup; children = ( A476C4BC7FF34ECF35F26C8EDF7BE888 /* Frameworks */, 7C6264ED89D8125FF43CE634A9019CBC /* MBProgressHUD */, 8231109976E16F0571AEC2597787EB54 /* Products */, ); sourceTree = ""; }; FFCAC7BB148282CF51B872C2C78FB057 /* iOS */ = { isa = PBXGroup; children = ( 40FC7CFC05C7B0963F209ED15D260C1B /* CoreGraphics.framework */, C1C8CB644149EBA15FE2799824117D13 /* Foundation.framework */, F3A964DCE9349768E5D973DA9C163909 /* QuartzCore.framework */, ); name = iOS; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 31FE7E1086FF4501A1534DC21B365F07 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 25967011F83F124E0594BACE3EF96BBF /* MBProgressHUD.h in Headers */, 037073A136B0C073EB5F52424D6B1BB8 /* MBProgressHUD-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 0F48A71D28991325602E8C8A19DF1F64 /* MBProgressHUD */ = { isa = PBXNativeTarget; buildConfigurationList = 1AE3AE988920231457E6DBFE7928C499 /* Build configuration list for PBXNativeTarget "MBProgressHUD" */; buildPhases = ( 31FE7E1086FF4501A1534DC21B365F07 /* Headers */, CCE5D85924611FB041516193B2F22542 /* Sources */, 0748F7A3E92A4F6896C3AD7979D66A55 /* Frameworks */, 0283BE74DDF9BCB3BCAFB59454BA2E83 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = MBProgressHUD; productName = MBProgressHUD; productReference = D4D0090781C017225C10FE4EA74951C6 /* MBProgressHUD */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F188521B0503F3EA13576724215C91CE /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 0C7F9D1463AF7697929CDB5D4F017753 /* Build configuration list for PBXProject "MBProgressHUD" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = D69CAE501A86EBF20792505E5C334F2F; productRefGroup = 8231109976E16F0571AEC2597787EB54 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 0F48A71D28991325602E8C8A19DF1F64 /* MBProgressHUD */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 0283BE74DDF9BCB3BCAFB59454BA2E83 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ CCE5D85924611FB041516193B2F22542 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( C3BF696D1161E0137930872CE216B500 /* MBProgressHUD.m in Sources */, 076D27D6B05F23750F64BDF401E5A782 /* MBProgressHUD-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 32DC9FCF6095105967F82F5DEB7C1A0C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; AF6AFA87438CEA1FEBEF364E9921BDA1 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 85171AE1C36A580DE4A0F0DBC84D7256 /* MBProgressHUD.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/MBProgressHUD/MBProgressHUD-prefix.pch"; INFOPLIST_FILE = "Target Support Files/MBProgressHUD/MBProgressHUD-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/MBProgressHUD/MBProgressHUD.modulemap"; PRODUCT_MODULE_NAME = MBProgressHUD; PRODUCT_NAME = MBProgressHUD; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; D834483A97A711EB4DAF4CD859DACD7D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; E877CA7A315A749543DCA94F93164AB2 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = FA293FAE958AA319ADD3D932CFE3D158 /* MBProgressHUD.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/MBProgressHUD/MBProgressHUD-prefix.pch"; INFOPLIST_FILE = "Target Support Files/MBProgressHUD/MBProgressHUD-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/MBProgressHUD/MBProgressHUD.modulemap"; PRODUCT_MODULE_NAME = MBProgressHUD; PRODUCT_NAME = MBProgressHUD; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 0C7F9D1463AF7697929CDB5D4F017753 /* Build configuration list for PBXProject "MBProgressHUD" */ = { isa = XCConfigurationList; buildConfigurations = ( D834483A97A711EB4DAF4CD859DACD7D /* Debug */, 32DC9FCF6095105967F82F5DEB7C1A0C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 1AE3AE988920231457E6DBFE7928C499 /* Build configuration list for PBXNativeTarget "MBProgressHUD" */ = { isa = XCConfigurationList; buildConfigurations = ( E877CA7A315A749543DCA94F93164AB2 /* Debug */, AF6AFA87438CEA1FEBEF364E9921BDA1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F188521B0503F3EA13576724215C91CE /* Project object */; } ================================================ FILE: JetChat/Pods/MJRefresh/LICENSE ================================================ Copyright (c) 2013-2015 MJRefresh (https://github.com/CoderMJLee/MJRefresh) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshAutoFooter.h ================================================ // // MJRefreshAutoFooter.h // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshFooter.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshAutoFooter : MJRefreshFooter /** 是否自动刷新(默认为YES) */ @property (assign, nonatomic, getter=isAutomaticallyRefresh) BOOL automaticallyRefresh; /** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */ @property (assign, nonatomic) CGFloat appearencePercentTriggerAutoRefresh MJRefreshDeprecated("请使用triggerAutomaticallyRefreshPercent属性"); /** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */ @property (assign, nonatomic) CGFloat triggerAutomaticallyRefreshPercent; /** 自动触发次数, 默认为 1, 仅在拖拽 ScrollView 时才生效, 如果为 -1, 则为无限触发 */ @property (nonatomic) NSInteger autoTriggerTimes; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshAutoFooter.m ================================================ // // MJRefreshAutoFooter.m // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshAutoFooter.h" #import "NSBundle+MJRefresh.h" #import "UIView+MJExtension.h" #import "UIScrollView+MJExtension.h" #import "UIScrollView+MJRefresh.h" @interface MJRefreshAutoFooter() /** 一个新的拖拽 */ @property (nonatomic) BOOL triggerByDrag; @property (nonatomic) NSInteger leftTriggerTimes; @end @implementation MJRefreshAutoFooter #pragma mark - 初始化 - (void)willMoveToSuperview:(UIView *)newSuperview { [super willMoveToSuperview:newSuperview]; if (newSuperview) { // 新的父控件 if (self.hidden == NO) { self.scrollView.mj_insetB += self.mj_h; } // 设置位置 self.mj_y = _scrollView.mj_contentH; } else { // 被移除了 if (self.hidden == NO) { self.scrollView.mj_insetB -= self.mj_h; } } } #pragma mark - 过期方法 - (void)setAppearencePercentTriggerAutoRefresh:(CGFloat)appearencePercentTriggerAutoRefresh { self.triggerAutomaticallyRefreshPercent = appearencePercentTriggerAutoRefresh; } - (CGFloat)appearencePercentTriggerAutoRefresh { return self.triggerAutomaticallyRefreshPercent; } #pragma mark - 实现父类的方法 - (void)prepare { [super prepare]; // 默认底部控件100%出现时才会自动刷新 self.triggerAutomaticallyRefreshPercent = 1.0; // 设置为默认状态 self.automaticallyRefresh = YES; self.autoTriggerTimes = 1; } - (void)scrollViewContentSizeDidChange:(NSDictionary *)change { [super scrollViewContentSizeDidChange:change]; CGSize size = [change[NSKeyValueChangeNewKey] CGSizeValue]; CGFloat contentHeight = size.height == 0 ? self.scrollView.mj_contentH : size.height; // 设置位置 CGFloat y = contentHeight + self.ignoredScrollViewContentInsetBottom; if (self.mj_y != y) { self.mj_y = y; } } - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change { [super scrollViewContentOffsetDidChange:change]; if (self.state != MJRefreshStateIdle || !self.automaticallyRefresh || self.mj_y == 0) return; if (_scrollView.mj_insetT + _scrollView.mj_contentH > _scrollView.mj_h) { // 内容超过一个屏幕 // 这里的_scrollView.mj_contentH替换掉self.mj_y更为合理 if (_scrollView.mj_offsetY >= _scrollView.mj_contentH - _scrollView.mj_h + self.mj_h * self.triggerAutomaticallyRefreshPercent + _scrollView.mj_insetB - self.mj_h) { // 防止手松开时连续调用 CGPoint old = [change[@"old"] CGPointValue]; CGPoint new = [change[@"new"] CGPointValue]; if (new.y <= old.y) return; if (_scrollView.isDragging) { self.triggerByDrag = YES; } // 当底部刷新控件完全出现时,才刷新 [self beginRefreshing]; } } } - (void)scrollViewPanStateDidChange:(NSDictionary *)change { [super scrollViewPanStateDidChange:change]; if (self.state != MJRefreshStateIdle) return; UIGestureRecognizerState panState = _scrollView.panGestureRecognizer.state; switch (panState) { // 手松开 case UIGestureRecognizerStateEnded: { if (_scrollView.mj_insetT + _scrollView.mj_contentH <= _scrollView.mj_h) { // 不够一个屏幕 if (_scrollView.mj_offsetY >= - _scrollView.mj_insetT) { // 向上拽 self.triggerByDrag = YES; [self beginRefreshing]; } } else { // 超出一个屏幕 if (_scrollView.mj_offsetY >= _scrollView.mj_contentH + _scrollView.mj_insetB - _scrollView.mj_h) { self.triggerByDrag = YES; [self beginRefreshing]; } } } break; case UIGestureRecognizerStateBegan: { [self resetTriggerTimes]; } break; default: break; } } - (BOOL)unlimitedTrigger { return self.leftTriggerTimes == -1; } - (void)beginRefreshing { if (self.triggerByDrag && self.leftTriggerTimes <= 0 && !self.unlimitedTrigger) { return; } [super beginRefreshing]; } - (void)setState:(MJRefreshState)state { MJRefreshCheckState if (state == MJRefreshStateRefreshing) { [self executeRefreshingCallback]; } else if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { if (self.triggerByDrag) { if (!self.unlimitedTrigger) { self.leftTriggerTimes -= 1; } self.triggerByDrag = NO; } if (MJRefreshStateRefreshing == oldState) { if (self.scrollView.pagingEnabled) { CGPoint offset = self.scrollView.contentOffset; offset.y -= self.scrollView.mj_insetB; [UIView animateWithDuration:self.slowAnimationDuration animations:^{ self.scrollView.contentOffset = offset; if (self.endRefreshingAnimationBeginAction) { self.endRefreshingAnimationBeginAction(); } } completion:^(BOOL finished) { if (self.endRefreshingCompletionBlock) { self.endRefreshingCompletionBlock(); } }]; return; } if (self.endRefreshingCompletionBlock) { self.endRefreshingCompletionBlock(); } } } } - (void)resetTriggerTimes { self.leftTriggerTimes = self.autoTriggerTimes; } - (void)setHidden:(BOOL)hidden { BOOL lastHidden = self.isHidden; [super setHidden:hidden]; if (!lastHidden && hidden) { self.state = MJRefreshStateIdle; self.scrollView.mj_insetB -= self.mj_h; } else if (lastHidden && !hidden) { self.scrollView.mj_insetB += self.mj_h; // 设置位置 self.mj_y = _scrollView.mj_contentH; } } - (void)setAutoTriggerTimes:(NSInteger)autoTriggerTimes { _autoTriggerTimes = autoTriggerTimes; self.leftTriggerTimes = autoTriggerTimes; } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshBackFooter.h ================================================ // // MJRefreshBackFooter.h // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshFooter.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshBackFooter : MJRefreshFooter @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshBackFooter.m ================================================ // // MJRefreshBackFooter.m // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshBackFooter.h" #import "NSBundle+MJRefresh.h" #import "UIView+MJExtension.h" #import "UIScrollView+MJExtension.h" #import "UIScrollView+MJRefresh.h" @interface MJRefreshBackFooter() @property (assign, nonatomic) NSInteger lastRefreshCount; @property (assign, nonatomic) CGFloat lastBottomDelta; @end @implementation MJRefreshBackFooter #pragma mark - 初始化 - (void)willMoveToSuperview:(UIView *)newSuperview { [super willMoveToSuperview:newSuperview]; [self scrollViewContentSizeDidChange:nil]; } #pragma mark - 实现父类的方法 - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change { [super scrollViewContentOffsetDidChange:change]; // 如果正在刷新,直接返回 if (self.state == MJRefreshStateRefreshing) return; _scrollViewOriginalInset = self.scrollView.mj_inset; // 当前的contentOffset CGFloat currentOffsetY = self.scrollView.mj_offsetY; // 尾部控件刚好出现的offsetY CGFloat happenOffsetY = [self happenOffsetY]; // 如果是向下滚动到看不见尾部控件,直接返回 if (currentOffsetY <= happenOffsetY) return; CGFloat pullingPercent = (currentOffsetY - happenOffsetY) / self.mj_h; // 如果已全部加载,仅设置pullingPercent,然后返回 if (self.state == MJRefreshStateNoMoreData) { self.pullingPercent = pullingPercent; return; } if (self.scrollView.isDragging) { self.pullingPercent = pullingPercent; // 普通 和 即将刷新 的临界点 CGFloat normal2pullingOffsetY = happenOffsetY + self.mj_h; if (self.state == MJRefreshStateIdle && currentOffsetY > normal2pullingOffsetY) { // 转为即将刷新状态 self.state = MJRefreshStatePulling; } else if (self.state == MJRefreshStatePulling && currentOffsetY <= normal2pullingOffsetY) { // 转为普通状态 self.state = MJRefreshStateIdle; } } else if (self.state == MJRefreshStatePulling) {// 即将刷新 && 手松开 // 开始刷新 [self beginRefreshing]; } else if (pullingPercent < 1) { self.pullingPercent = pullingPercent; } } - (void)scrollViewContentSizeDidChange:(NSDictionary *)change { [super scrollViewContentSizeDidChange:change]; CGSize size = [change[NSKeyValueChangeNewKey] CGSizeValue]; CGFloat contentHeight = size.height == 0 ? self.scrollView.mj_contentH : size.height; // 内容的高度 contentHeight += self.ignoredScrollViewContentInsetBottom; // 表格的高度 CGFloat scrollHeight = self.scrollView.mj_h - self.scrollViewOriginalInset.top - self.scrollViewOriginalInset.bottom + self.ignoredScrollViewContentInsetBottom; // 设置位置 CGFloat y = MAX(contentHeight, scrollHeight); if (self.mj_y != y) { self.mj_y = y; } } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态来设置属性 if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { // 刷新完毕 if (MJRefreshStateRefreshing == oldState) { [UIView animateWithDuration:self.slowAnimationDuration animations:^{ if (self.endRefreshingAnimationBeginAction) { self.endRefreshingAnimationBeginAction(); } self.scrollView.mj_insetB -= self.lastBottomDelta; // 自动调整透明度 if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0; } completion:^(BOOL finished) { self.pullingPercent = 0.0; if (self.endRefreshingCompletionBlock) { self.endRefreshingCompletionBlock(); } }]; } CGFloat deltaH = [self heightForContentBreakView]; // 刚刷新完毕 if (MJRefreshStateRefreshing == oldState && deltaH > 0 && self.scrollView.mj_totalDataCount != self.lastRefreshCount) { self.scrollView.mj_offsetY = self.scrollView.mj_offsetY; } } else if (state == MJRefreshStateRefreshing) { // 记录刷新前的数量 self.lastRefreshCount = self.scrollView.mj_totalDataCount; [UIView animateWithDuration:self.fastAnimationDuration animations:^{ CGFloat bottom = self.mj_h + self.scrollViewOriginalInset.bottom; CGFloat deltaH = [self heightForContentBreakView]; if (deltaH < 0) { // 如果内容高度小于view的高度 bottom -= deltaH; } self.lastBottomDelta = bottom - self.scrollView.mj_insetB; self.scrollView.mj_insetB = bottom; self.scrollView.mj_offsetY = [self happenOffsetY] + self.mj_h; } completion:^(BOOL finished) { [self executeRefreshingCallback]; }]; } } #pragma mark - 私有方法 #pragma mark 获得scrollView的内容 超出 view 的高度 - (CGFloat)heightForContentBreakView { CGFloat h = self.scrollView.frame.size.height - self.scrollViewOriginalInset.bottom - self.scrollViewOriginalInset.top; return self.scrollView.contentSize.height - h; } #pragma mark 刚好看到上拉刷新控件时的contentOffset.y - (CGFloat)happenOffsetY { CGFloat deltaH = [self heightForContentBreakView]; if (deltaH > 0) { return deltaH - self.scrollViewOriginalInset.top; } else { return - self.scrollViewOriginalInset.top; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshComponent.h ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // MJRefreshComponent.h // MJRefresh // // Created by MJ Lee on 15/3/4. // Copyright (c) 2015年 小码哥. All rights reserved. // 刷新控件的基类 #import #if __has_include() #import #else #import "MJRefreshConst.h" #endif NS_ASSUME_NONNULL_BEGIN /** 刷新控件的状态 */ typedef NS_ENUM(NSInteger, MJRefreshState) { /** 普通闲置状态 */ MJRefreshStateIdle = 1, /** 松开就可以进行刷新的状态 */ MJRefreshStatePulling, /** 正在刷新中的状态 */ MJRefreshStateRefreshing, /** 即将刷新的状态 */ MJRefreshStateWillRefresh, /** 所有数据加载完毕,没有更多的数据了 */ MJRefreshStateNoMoreData }; /** 进入刷新状态的回调 */ typedef void (^MJRefreshComponentRefreshingBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead"); /** 开始刷新后的回调(进入刷新状态后的回调) */ typedef void (^MJRefreshComponentBeginRefreshingCompletionBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead"); /** 结束刷新后的回调 */ typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead"); /** 刷新用到的回调类型 */ typedef void (^MJRefreshComponentAction)(void); /** 刷新控件的基类 */ @interface MJRefreshComponent : UIView { /** 记录scrollView刚开始的inset */ UIEdgeInsets _scrollViewOriginalInset; /** 父控件 */ __weak UIScrollView *_scrollView; } #pragma mark - 刷新动画时间控制 /** 快速动画时间(一般用在刷新开始的回弹动画), 默认 0.25 */ @property (nonatomic) NSTimeInterval fastAnimationDuration; /** 慢速动画时间(一般用在刷新结束后的回弹动画), 默认 0.4*/ @property (nonatomic) NSTimeInterval slowAnimationDuration; /** 关闭全部默认动画效果, 可以简单粗暴地解决 CollectionView 的回弹动画 bug */ - (instancetype)setAnimationDisabled; #pragma mark - 刷新回调 /** 正在刷新的回调 */ @property (copy, nonatomic, nullable) MJRefreshComponentAction refreshingBlock; /** 设置回调对象和回调方法 */ - (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action; /** 回调对象 */ @property (weak, nonatomic) id refreshingTarget; /** 回调方法 */ @property (assign, nonatomic) SEL refreshingAction; /** 触发回调(交给子类去调用) */ - (void)executeRefreshingCallback; #pragma mark - 刷新状态控制 /** 进入刷新状态 */ - (void)beginRefreshing; - (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock; /** 开始刷新后的回调(进入刷新状态后的回调) */ @property (copy, nonatomic, nullable) MJRefreshComponentAction beginRefreshingCompletionBlock; /** 带动画的结束刷新的回调 */ @property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingAnimateCompletionBlock MJRefreshDeprecated("first deprecated in 3.3.0 - Use `endRefreshingAnimationBeginAction` instead"); @property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingAnimationBeginAction; /** 结束刷新的回调 */ @property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingCompletionBlock; /** 结束刷新状态 */ - (void)endRefreshing; - (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock; /** 是否正在刷新 */ @property (assign, nonatomic, readonly, getter=isRefreshing) BOOL refreshing; /** 刷新状态 一般交给子类内部实现 */ @property (assign, nonatomic) MJRefreshState state; #pragma mark - 交给子类去访问 /** 记录scrollView刚开始的inset */ @property (assign, nonatomic, readonly) UIEdgeInsets scrollViewOriginalInset; /** 父控件 */ @property (weak, nonatomic, readonly) UIScrollView *scrollView; #pragma mark - 交给子类们去实现 /** 初始化 */ - (void)prepare NS_REQUIRES_SUPER; /** 摆放子控件frame */ - (void)placeSubviews NS_REQUIRES_SUPER; /** 当scrollView的contentOffset发生改变的时候调用 */ - (void)scrollViewContentOffsetDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER; /** 当scrollView的contentSize发生改变的时候调用 */ - (void)scrollViewContentSizeDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER; /** 当scrollView的拖拽状态发生改变的时候调用 */ - (void)scrollViewPanStateDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER; /** 多语言配置 language 发生变化时调用 `MJRefreshConfig.defaultConfig.language` 发生改变时调用. ⚠️ 父类会调用 `placeSubviews` 方法, 请勿在 placeSubviews 中调用本方法, 造成死循环. 子类在需要重新布局时, 在配置完修改后, 最后再调用 super 方法, 否则可能导致配置修改后, 定位先于修改执行. */ - (void)i18nDidChange NS_REQUIRES_SUPER; #pragma mark - 其他 /** 拉拽的百分比(交给子类重写) */ @property (assign, nonatomic) CGFloat pullingPercent; /** 根据拖拽比例自动切换透明度 */ @property (assign, nonatomic, getter=isAutoChangeAlpha) BOOL autoChangeAlpha MJRefreshDeprecated("请使用automaticallyChangeAlpha属性"); /** 根据拖拽比例自动切换透明度 */ @property (assign, nonatomic, getter=isAutomaticallyChangeAlpha) BOOL automaticallyChangeAlpha; @end @interface UILabel(MJRefresh) + (instancetype)mj_label; - (CGFloat)mj_textWidth; @end @interface MJRefreshComponent (ChainingGrammar) #pragma mark - <<< 为 Swift 扩展链式语法 >>> - /// 自动变化透明度 - (instancetype)autoChangeTransparency:(BOOL)isAutoChange; /// 刷新开始后立即调用的回调 - (instancetype)afterBeginningAction:(MJRefreshComponentAction)action; /// 刷新动画开始后立即调用的回调 - (instancetype)endingAnimationBeginningAction:(MJRefreshComponentAction)action; /// 刷新结束后立即调用的回调 - (instancetype)afterEndingAction:(MJRefreshComponentAction)action; /// 需要子类必须实现 /// @param scrollView 赋值给的 ScrollView 的 Header/Footer/Trailer - (instancetype)linkTo:(UIScrollView *)scrollView; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshComponent.m ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // MJRefreshComponent.m // MJRefresh // // Created by MJ Lee on 15/3/4. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshComponent.h" #import "MJRefreshConst.h" #import "MJRefreshConfig.h" #import "UIView+MJExtension.h" #import "UIScrollView+MJExtension.h" #import "UIScrollView+MJRefresh.h" #import "NSBundle+MJRefresh.h" @interface MJRefreshComponent() @property (strong, nonatomic) UIPanGestureRecognizer *pan; @end @implementation MJRefreshComponent #pragma mark - 初始化 - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { // 准备工作 [self prepare]; // 默认是普通状态 self.state = MJRefreshStateIdle; self.fastAnimationDuration = 0.25; self.slowAnimationDuration = 0.4; } return self; } - (void)prepare { // 基本属性 self.autoresizingMask = UIViewAutoresizingFlexibleWidth; self.backgroundColor = [UIColor clearColor]; } - (void)layoutSubviews { [self placeSubviews]; [super layoutSubviews]; } - (void)placeSubviews{} - (void)willMoveToSuperview:(UIView *)newSuperview { [super willMoveToSuperview:newSuperview]; // 如果不是UIScrollView,不做任何事情 if (newSuperview && ![newSuperview isKindOfClass:[UIScrollView class]]) return; // 旧的父控件移除监听 [self removeObservers]; if (newSuperview) { // 新的父控件 // 记录UIScrollView _scrollView = (UIScrollView *)newSuperview; // 设置宽度 self.mj_w = _scrollView.mj_w; // 设置位置 self.mj_x = -_scrollView.mj_insetL; // 设置永远支持垂直弹簧效果 _scrollView.alwaysBounceVertical = YES; // 记录UIScrollView最开始的contentInset _scrollViewOriginalInset = _scrollView.mj_inset; // 添加监听 [self addObservers]; } } - (void)drawRect:(CGRect)rect { [super drawRect:rect]; if (self.state == MJRefreshStateWillRefresh) { // 预防view还没显示出来就调用了beginRefreshing self.state = MJRefreshStateRefreshing; } } #pragma mark - KVO监听 - (void)addObservers { NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentOffset options:options context:nil]; [self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentSize options:options context:nil]; self.pan = self.scrollView.panGestureRecognizer; [self.pan addObserver:self forKeyPath:MJRefreshKeyPathPanState options:options context:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(i18nDidChange) name:MJRefreshDidChangeLanguageNotification object:MJRefreshConfig.defaultConfig]; } - (void)removeObservers { [self.superview removeObserver:self forKeyPath:MJRefreshKeyPathContentOffset]; [self.superview removeObserver:self forKeyPath:MJRefreshKeyPathContentSize]; [self.pan removeObserver:self forKeyPath:MJRefreshKeyPathPanState]; self.pan = nil; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { // 遇到这些情况就直接返回 if (!self.userInteractionEnabled) return; // 这个就算看不见也需要处理 if ([keyPath isEqualToString:MJRefreshKeyPathContentSize]) { [self scrollViewContentSizeDidChange:change]; } // 看不见 if (self.hidden) return; if ([keyPath isEqualToString:MJRefreshKeyPathContentOffset]) { [self scrollViewContentOffsetDidChange:change]; } else if ([keyPath isEqualToString:MJRefreshKeyPathPanState]) { [self scrollViewPanStateDidChange:change]; } } - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{} - (void)scrollViewContentSizeDidChange:(NSDictionary *)change{} - (void)scrollViewPanStateDidChange:(NSDictionary *)change{} - (void)i18nDidChange { [self placeSubviews]; } #pragma mark - 公共方法 #pragma mark 设置回调对象和回调方法 - (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action { self.refreshingTarget = target; self.refreshingAction = action; } - (void)setState:(MJRefreshState)state { _state = state; // 加入主队列的目的是等setState:方法调用完毕、设置完文字后再去布局子控件 MJRefreshDispatchAsyncOnMainQueue([self setNeedsLayout];) } #pragma mark 进入刷新状态 - (void)beginRefreshing { [UIView animateWithDuration:self.fastAnimationDuration animations:^{ self.alpha = 1.0; }]; self.pullingPercent = 1.0; // 只要正在刷新,就完全显示 if (self.window) { self.state = MJRefreshStateRefreshing; } else { // 预防正在刷新中时,调用本方法使得header inset回置失败 if (self.state != MJRefreshStateRefreshing) { self.state = MJRefreshStateWillRefresh; // 刷新(预防从另一个控制器回到这个控制器的情况,回来要重新刷新一下) [self setNeedsDisplay]; } } } - (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock { self.beginRefreshingCompletionBlock = completionBlock; [self beginRefreshing]; } #pragma mark 结束刷新状态 - (void)endRefreshing { MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateIdle;) } - (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock { self.endRefreshingCompletionBlock = completionBlock; [self endRefreshing]; } #pragma mark 是否正在刷新 - (BOOL)isRefreshing { return self.state == MJRefreshStateRefreshing || self.state == MJRefreshStateWillRefresh; } #pragma mark 自动切换透明度 - (void)setAutoChangeAlpha:(BOOL)autoChangeAlpha { self.automaticallyChangeAlpha = autoChangeAlpha; } - (BOOL)isAutoChangeAlpha { return self.isAutomaticallyChangeAlpha; } - (void)setAutomaticallyChangeAlpha:(BOOL)automaticallyChangeAlpha { _automaticallyChangeAlpha = automaticallyChangeAlpha; if (self.isRefreshing) return; if (automaticallyChangeAlpha) { self.alpha = self.pullingPercent; } else { self.alpha = 1.0; } } #pragma mark 根据拖拽进度设置透明度 - (void)setPullingPercent:(CGFloat)pullingPercent { _pullingPercent = pullingPercent; if (self.isRefreshing) return; if (self.isAutomaticallyChangeAlpha) { self.alpha = pullingPercent; } } #pragma mark - 内部方法 - (void)executeRefreshingCallback { if (self.refreshingBlock) { self.refreshingBlock(); } if ([self.refreshingTarget respondsToSelector:self.refreshingAction]) { MJRefreshMsgSend(MJRefreshMsgTarget(self.refreshingTarget), self.refreshingAction, self); } if (self.beginRefreshingCompletionBlock) { self.beginRefreshingCompletionBlock(); } } #pragma mark - 刷新动画时间控制 - (instancetype)setAnimationDisabled { self.fastAnimationDuration = 0; self.slowAnimationDuration = 0; return self; } #pragma mark - <<< Deprecation compatible function >>> - - (void)setEndRefreshingAnimateCompletionBlock:(MJRefreshComponentEndRefreshingCompletionBlock)endRefreshingAnimateCompletionBlock { _endRefreshingAnimationBeginAction = endRefreshingAnimateCompletionBlock; } @end @implementation UILabel(MJRefresh) + (instancetype)mj_label { UILabel *label = [[self alloc] init]; label.font = MJRefreshLabelFont; label.textColor = MJRefreshLabelTextColor; label.autoresizingMask = UIViewAutoresizingFlexibleWidth; label.textAlignment = NSTextAlignmentCenter; label.backgroundColor = [UIColor clearColor]; return label; } - (CGFloat)mj_textWidth { CGFloat stringWidth = 0; CGSize size = CGSizeMake(MAXFLOAT, MAXFLOAT); if (self.attributedText) { if (self.attributedText.length == 0) { return 0; } stringWidth = [self.attributedText boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin context:nil].size.width; } else { if (self.text.length == 0) { return 0; } NSAssert(self.font != nil, @"请检查 mj_label's `font` 是否设置正确"); stringWidth = [self.text boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.font} context:nil].size.width; } return stringWidth; } @end #pragma mark - <<< 为 Swift 扩展链式语法 >>> - @implementation MJRefreshComponent (ChainingGrammar) - (instancetype)autoChangeTransparency:(BOOL)isAutoChange { self.automaticallyChangeAlpha = isAutoChange; return self; } - (instancetype)afterBeginningAction:(MJRefreshComponentAction)action { self.beginRefreshingCompletionBlock = action; return self; } - (instancetype)endingAnimationBeginningAction:(MJRefreshComponentAction)action { self.endRefreshingAnimationBeginAction = action; return self; } - (instancetype)afterEndingAction:(MJRefreshComponentAction)action { self.endRefreshingCompletionBlock = action; return self; } - (instancetype)linkTo:(UIScrollView *)scrollView { return self; } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshFooter.h ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // MJRefreshFooter.h // MJRefresh // // Created by MJ Lee on 15/3/5. // Copyright (c) 2015年 小码哥. All rights reserved. // 上拉刷新控件 #if __has_include() #import #else #import "MJRefreshComponent.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshFooter : MJRefreshComponent /** 创建footer */ + (instancetype)footerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock; /** 创建footer */ + (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; /** 提示没有更多的数据 */ - (void)endRefreshingWithNoMoreData; - (void)noticeNoMoreData MJRefreshDeprecated("使用endRefreshingWithNoMoreData"); /** 重置没有更多的数据(消除没有更多数据的状态) */ - (void)resetNoMoreData; /** 忽略多少scrollView的contentInset的bottom */ @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetBottom; /** 自动根据有无数据来显示和隐藏(有数据就显示,没有数据隐藏。默认是NO) */ @property (assign, nonatomic, getter=isAutomaticallyHidden) BOOL automaticallyHidden MJRefreshDeprecated("已废弃此属性,开发者请自行控制footer的显示和隐藏"); @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshFooter.m ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // MJRefreshFooter.m // MJRefresh // // Created by MJ Lee on 15/3/5. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshFooter.h" #import "UIScrollView+MJRefresh.h" #import "UIView+MJExtension.h" @interface MJRefreshFooter() @end @implementation MJRefreshFooter #pragma mark - 构造方法 + (instancetype)footerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock { MJRefreshFooter *cmp = [[self alloc] init]; cmp.refreshingBlock = refreshingBlock; return cmp; } + (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action { MJRefreshFooter *cmp = [[self alloc] init]; [cmp setRefreshingTarget:target refreshingAction:action]; return cmp; } #pragma mark - 重写父类的方法 - (void)prepare { [super prepare]; // 设置自己的高度 self.mj_h = MJRefreshFooterHeight; // 默认不会自动隐藏 // self.automaticallyHidden = NO; } #pragma mark . 链式语法部分 . - (instancetype)linkTo:(UIScrollView *)scrollView { scrollView.mj_footer = self; return self; } #pragma mark - 公共方法 - (void)endRefreshingWithNoMoreData { MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateNoMoreData;) } - (void)noticeNoMoreData { [self endRefreshingWithNoMoreData]; } - (void)resetNoMoreData { MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateIdle;) } - (void)setAutomaticallyHidden:(BOOL)automaticallyHidden { _automaticallyHidden = automaticallyHidden; } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshHeader.h ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // MJRefreshHeader.h // MJRefresh // // Created by MJ Lee on 15/3/4. // Copyright (c) 2015年 小码哥. All rights reserved. // 下拉刷新控件:负责监控用户下拉的状态 #if __has_include() #import #else #import "MJRefreshComponent.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshHeader : MJRefreshComponent /** 创建header */ + (instancetype)headerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock; /** 创建header */ + (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; /** 这个key用来存储上一次下拉刷新成功的时间 */ @property (copy, nonatomic) NSString *lastUpdatedTimeKey; /** 上一次下拉刷新成功的时间 */ @property (strong, nonatomic, readonly, nullable) NSDate *lastUpdatedTime; /** 忽略多少scrollView的contentInset的top */ @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetTop; /** 默认是关闭状态, 如果遇到 CollectionView 的动画异常问题可以尝试打开 */ @property (nonatomic) BOOL isCollectionViewAnimationBug; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshHeader.m ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // MJRefreshHeader.m // MJRefresh // // Created by MJ Lee on 15/3/4. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshHeader.h" #import "UIView+MJExtension.h" #import "UIScrollView+MJExtension.h" #import "UIScrollView+MJRefresh.h" NSString * const MJRefreshHeaderRefreshing2IdleBoundsKey = @"MJRefreshHeaderRefreshing2IdleBounds"; NSString * const MJRefreshHeaderRefreshingBoundsKey = @"MJRefreshHeaderRefreshingBounds"; @interface MJRefreshHeader() @property (assign, nonatomic) CGFloat insetTDelta; @end @implementation MJRefreshHeader #pragma mark - 构造方法 + (instancetype)headerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock { MJRefreshHeader *cmp = [[self alloc] init]; cmp.refreshingBlock = refreshingBlock; return cmp; } + (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action { MJRefreshHeader *cmp = [[self alloc] init]; [cmp setRefreshingTarget:target refreshingAction:action]; return cmp; } #pragma mark - 覆盖父类的方法 - (void)prepare { [super prepare]; // 设置key self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey; // 设置高度 self.mj_h = MJRefreshHeaderHeight; } - (void)placeSubviews { [super placeSubviews]; // 设置y值(当自己的高度发生改变了,肯定要重新调整Y值,所以放到placeSubviews方法中设置y值) self.mj_y = - self.mj_h - self.ignoredScrollViewContentInsetTop; } - (void)resetInset { if (@available(iOS 11.0, *)) { } else { // 如果 iOS 10 及以下系统在刷新时, push 新的 VC, 等待刷新完成后回来, 会导致顶部 Insets.top 异常, 不能 resetInset, 检查一下这种特殊情况 if (!self.window) { return; } } // sectionheader停留解决 CGFloat insetT = - self.scrollView.mj_offsetY > _scrollViewOriginalInset.top ? - self.scrollView.mj_offsetY : _scrollViewOriginalInset.top; insetT = insetT > self.mj_h + _scrollViewOriginalInset.top ? self.mj_h + _scrollViewOriginalInset.top : insetT; self.insetTDelta = _scrollViewOriginalInset.top - insetT; // 避免 CollectionView 在使用根据 Autolayout 和 内容自动伸缩 Cell, 刷新时导致的 Layout 异常渲染问题 if (self.scrollView.mj_insetT != insetT) { self.scrollView.mj_insetT = insetT; } } - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change { [super scrollViewContentOffsetDidChange:change]; // 在刷新的refreshing状态 if (self.state == MJRefreshStateRefreshing) { [self resetInset]; return; } // 跳转到下一个控制器时,contentInset可能会变 _scrollViewOriginalInset = self.scrollView.mj_inset; // 当前的contentOffset CGFloat offsetY = self.scrollView.mj_offsetY; // 头部控件刚好出现的offsetY CGFloat happenOffsetY = - self.scrollViewOriginalInset.top; // 如果是向上滚动到看不见头部控件,直接返回 // >= -> > if (offsetY > happenOffsetY) return; // 普通 和 即将刷新 的临界点 CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h; CGFloat pullingPercent = (happenOffsetY - offsetY) / self.mj_h; if (self.scrollView.isDragging) { // 如果正在拖拽 self.pullingPercent = pullingPercent; if (self.state == MJRefreshStateIdle && offsetY < normal2pullingOffsetY) { // 转为即将刷新状态 self.state = MJRefreshStatePulling; } else if (self.state == MJRefreshStatePulling && offsetY >= normal2pullingOffsetY) { // 转为普通状态 self.state = MJRefreshStateIdle; } } else if (self.state == MJRefreshStatePulling) {// 即将刷新 && 手松开 // 开始刷新 [self beginRefreshing]; } else if (pullingPercent < 1) { self.pullingPercent = pullingPercent; } } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态做事情 if (state == MJRefreshStateIdle) { if (oldState != MJRefreshStateRefreshing) return; [self headerEndingAction]; } else if (state == MJRefreshStateRefreshing) { [self headerRefreshingAction]; } } - (void)headerEndingAction { // 保存刷新时间 [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:self.lastUpdatedTimeKey]; [[NSUserDefaults standardUserDefaults] synchronize]; // 默认使用 UIViewAnimation 动画 if (!self.isCollectionViewAnimationBug) { // 恢复inset和offset [UIView animateWithDuration:self.slowAnimationDuration animations:^{ self.scrollView.mj_insetT += self.insetTDelta; if (self.endRefreshingAnimationBeginAction) { self.endRefreshingAnimationBeginAction(); } // 自动调整透明度 if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0; } completion:^(BOOL finished) { self.pullingPercent = 0.0; if (self.endRefreshingCompletionBlock) { self.endRefreshingCompletionBlock(); } }]; return; } /** 这个解决方法的思路出自 https://github.com/CoderMJLee/MJRefresh/pull/844 修改了用+ [UIView animateWithDuration: animations:]实现的修改contentInset的动画 fix issue#225 https://github.com/CoderMJLee/MJRefresh/issues/225 另一种解法 pull#737 https://github.com/CoderMJLee/MJRefresh/pull/737 同时, 处理了 Refreshing 中的动画替换. */ // 由于修改 Inset 会导致 self.pullingPercent 联动设置 self.alpha, 故提前获取 alpha 值, 后续用于还原 alpha 动画 CGFloat viewAlpha = self.alpha; self.scrollView.mj_insetT += self.insetTDelta; // 禁用交互, 如果不禁用可能会引起渲染问题. self.scrollView.userInteractionEnabled = NO; //CAAnimation keyPath 不支持 contentInset 用Bounds的动画代替 CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"]; boundsAnimation.fromValue = [NSValue valueWithCGRect:CGRectOffset(self.scrollView.bounds, 0, self.insetTDelta)]; boundsAnimation.duration = self.slowAnimationDuration; //在delegate里移除 boundsAnimation.removedOnCompletion = NO; boundsAnimation.fillMode = kCAFillModeBoth; boundsAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; boundsAnimation.delegate = self; [boundsAnimation setValue:MJRefreshHeaderRefreshing2IdleBoundsKey forKey:@"identity"]; [self.scrollView.layer addAnimation:boundsAnimation forKey:MJRefreshHeaderRefreshing2IdleBoundsKey]; if (self.endRefreshingAnimationBeginAction) { self.endRefreshingAnimationBeginAction(); } // 自动调整透明度的动画 if (self.isAutomaticallyChangeAlpha) { CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; opacityAnimation.fromValue = @(viewAlpha); opacityAnimation.toValue = @(0.0); opacityAnimation.duration = self.slowAnimationDuration; opacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; [self.layer addAnimation:opacityAnimation forKey:@"MJRefreshHeaderRefreshing2IdleOpacity"]; // 由于修改了 inset 导致, pullingPercent 被设置值, alpha 已经被提前修改为 0 了. 所以这里不用置 0, 但为了代码的严谨性, 不依赖其他的特殊实现方式, 这里还是置 0. self.alpha = 0; } } - (void)headerRefreshingAction { // 默认使用 UIViewAnimation 动画 if (!self.isCollectionViewAnimationBug) { [UIView animateWithDuration:self.fastAnimationDuration animations:^{ if (self.scrollView.panGestureRecognizer.state != UIGestureRecognizerStateCancelled) { CGFloat top = self.scrollViewOriginalInset.top + self.mj_h; // 增加滚动区域top self.scrollView.mj_insetT = top; // 设置滚动位置 CGPoint offset = self.scrollView.contentOffset; offset.y = -top; [self.scrollView setContentOffset:offset animated:NO]; } } completion:^(BOOL finished) { [self executeRefreshingCallback]; }]; return; } if (self.scrollView.panGestureRecognizer.state != UIGestureRecognizerStateCancelled) { CGFloat top = self.scrollViewOriginalInset.top + self.mj_h; // 禁用交互, 如果不禁用可能会引起渲染问题. self.scrollView.userInteractionEnabled = NO; // CAAnimation keyPath不支持 contentOffset 用Bounds的动画代替 CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"]; CGRect bounds = self.scrollView.bounds; bounds.origin.y = -top; boundsAnimation.fromValue = [NSValue valueWithCGRect:self.scrollView.bounds]; boundsAnimation.toValue = [NSValue valueWithCGRect:bounds]; boundsAnimation.duration = self.fastAnimationDuration; //在delegate里移除 boundsAnimation.removedOnCompletion = NO; boundsAnimation.fillMode = kCAFillModeBoth; boundsAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; boundsAnimation.delegate = self; [boundsAnimation setValue:MJRefreshHeaderRefreshingBoundsKey forKey:@"identity"]; [self.scrollView.layer addAnimation:boundsAnimation forKey:MJRefreshHeaderRefreshingBoundsKey]; } else { [self executeRefreshingCallback]; } } #pragma mark . 链式语法部分 . - (instancetype)linkTo:(UIScrollView *)scrollView { scrollView.mj_header = self; return self; } #pragma mark - CAAnimationDelegate - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { NSString *identity = [anim valueForKey:@"identity"]; if ([identity isEqualToString:MJRefreshHeaderRefreshing2IdleBoundsKey]) { self.pullingPercent = 0.0; self.scrollView.userInteractionEnabled = YES; if (self.endRefreshingCompletionBlock) { self.endRefreshingCompletionBlock(); } } else if ([identity isEqualToString:MJRefreshHeaderRefreshingBoundsKey]) { // 避免出现 end 先于 Refreshing 状态 if (self.state != MJRefreshStateIdle) { CGFloat top = self.scrollViewOriginalInset.top + self.mj_h; self.scrollView.mj_insetT = top; // 设置最终滚动位置 CGPoint offset = self.scrollView.contentOffset; offset.y = -top; [self.scrollView setContentOffset:offset animated:NO]; } self.scrollView.userInteractionEnabled = YES; [self executeRefreshingCallback]; } if ([self.scrollView.layer animationForKey:MJRefreshHeaderRefreshing2IdleBoundsKey]) { [self.scrollView.layer removeAnimationForKey:MJRefreshHeaderRefreshing2IdleBoundsKey]; } if ([self.scrollView.layer animationForKey:MJRefreshHeaderRefreshingBoundsKey]) { [self.scrollView.layer removeAnimationForKey:MJRefreshHeaderRefreshingBoundsKey]; } } #pragma mark - 公共方法 - (NSDate *)lastUpdatedTime { return [[NSUserDefaults standardUserDefaults] objectForKey:self.lastUpdatedTimeKey]; } - (void)setIgnoredScrollViewContentInsetTop:(CGFloat)ignoredScrollViewContentInsetTop { _ignoredScrollViewContentInsetTop = ignoredScrollViewContentInsetTop; self.mj_y = - self.mj_h - _ignoredScrollViewContentInsetTop; } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshTrailer.h ================================================ // // MJRefreshTrailer.h // MJRefresh // // Created by kinarobin on 2020/5/3. // Copyright © 2020 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshComponent.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshTrailer : MJRefreshComponent /** 创建trailer*/ + (instancetype)trailerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock; /** 创建trailer */ + (instancetype)trailerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; /** 忽略多少scrollView的contentInset的right */ @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetRight; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Base/MJRefreshTrailer.m ================================================ // // MJRefreshTrailer.m // MJRefresh // // Created by kinarobin on 2020/5/3. // Copyright © 2020 小码哥. All rights reserved. // #import "MJRefreshTrailer.h" #import "UIView+MJExtension.h" #import "UIScrollView+MJRefresh.h" #import "UIScrollView+MJExtension.h" @interface MJRefreshTrailer() @property (assign, nonatomic) NSInteger lastRefreshCount; @property (assign, nonatomic) CGFloat lastRightDelta; @end @implementation MJRefreshTrailer #pragma mark - 构造方法 + (instancetype)trailerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock { MJRefreshTrailer *cmp = [[self alloc] init]; cmp.refreshingBlock = refreshingBlock; return cmp; } + (instancetype)trailerWithRefreshingTarget:(id)target refreshingAction:(SEL)action { MJRefreshTrailer *cmp = [[self alloc] init]; [cmp setRefreshingTarget:target refreshingAction:action]; return cmp; } - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change { [super scrollViewContentOffsetDidChange:change]; // 如果正在刷新,直接返回 if (self.state == MJRefreshStateRefreshing) return; _scrollViewOriginalInset = self.scrollView.mj_inset; // 当前的contentOffset CGFloat currentOffsetX = self.scrollView.mj_offsetX; // 尾部控件刚好出现的offsetX CGFloat happenOffsetX = [self happenOffsetX]; // 如果是向右滚动到看不见右边控件,直接返回 if (currentOffsetX <= happenOffsetX) return; CGFloat pullingPercent = (currentOffsetX - happenOffsetX) / self.mj_w; // 如果已全部加载,仅设置pullingPercent,然后返回 if (self.state == MJRefreshStateNoMoreData) { self.pullingPercent = pullingPercent; return; } if (self.scrollView.isDragging) { self.pullingPercent = pullingPercent; // 普通 和 即将刷新 的临界点 CGFloat normal2pullingOffsetX = happenOffsetX + self.mj_w; if (self.state == MJRefreshStateIdle && currentOffsetX > normal2pullingOffsetX) { self.state = MJRefreshStatePulling; } else if (self.state == MJRefreshStatePulling && currentOffsetX <= normal2pullingOffsetX) { // 转为普通状态 self.state = MJRefreshStateIdle; } } else if (self.state == MJRefreshStatePulling) {// 即将刷新 && 手松开 // 开始刷新 [self beginRefreshing]; } else if (pullingPercent < 1) { self.pullingPercent = pullingPercent; } } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态来设置属性 if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { // 刷新完毕 if (MJRefreshStateRefreshing == oldState) { [UIView animateWithDuration:self.slowAnimationDuration animations:^{ if (self.endRefreshingAnimationBeginAction) { self.endRefreshingAnimationBeginAction(); } self.scrollView.mj_insetR -= self.lastRightDelta; // 自动调整透明度 if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0; } completion:^(BOOL finished) { self.pullingPercent = 0.0; if (self.endRefreshingCompletionBlock) { self.endRefreshingCompletionBlock(); } }]; } CGFloat deltaW = [self widthForContentBreakView]; // 刚刷新完毕 if (MJRefreshStateRefreshing == oldState && deltaW > 0 && self.scrollView.mj_totalDataCount != self.lastRefreshCount) { self.scrollView.mj_offsetX = self.scrollView.mj_offsetX; } } else if (state == MJRefreshStateRefreshing) { // 记录刷新前的数量 self.lastRefreshCount = self.scrollView.mj_totalDataCount; [UIView animateWithDuration:self.fastAnimationDuration animations:^{ CGFloat right = self.mj_w + self.scrollViewOriginalInset.right; CGFloat deltaW = [self widthForContentBreakView]; if (deltaW < 0) { // 如果内容宽度小于view的宽度 right -= deltaW; } self.lastRightDelta = right - self.scrollView.mj_insetR; self.scrollView.mj_insetR = right; // 设置滚动位置 CGPoint offset = self.scrollView.contentOffset; offset.x = [self happenOffsetX] + self.mj_w; [self.scrollView setContentOffset:offset animated:NO]; } completion:^(BOOL finished) { [self executeRefreshingCallback]; }]; } } - (void)scrollViewContentSizeDidChange:(NSDictionary *)change { [super scrollViewContentSizeDidChange:change]; // 内容的宽度 CGFloat contentWidth = self.scrollView.mj_contentW + self.ignoredScrollViewContentInsetRight; // 表格的宽度 CGFloat scrollWidth = self.scrollView.mj_w - self.scrollViewOriginalInset.left - self.scrollViewOriginalInset.right + self.ignoredScrollViewContentInsetRight; // 设置位置和尺寸 self.mj_x = MAX(contentWidth, scrollWidth); } - (void)placeSubviews { [super placeSubviews]; self.mj_h = _scrollView.mj_h; // 设置自己的宽度 self.mj_w = MJRefreshTrailWidth; } - (void)willMoveToSuperview:(UIView *)newSuperview { [super willMoveToSuperview:newSuperview]; if (newSuperview) { // 设置支持水平弹簧效果 _scrollView.alwaysBounceHorizontal = YES; _scrollView.alwaysBounceVertical = NO; } } #pragma mark . 链式语法部分 . - (instancetype)linkTo:(UIScrollView *)scrollView { scrollView.mj_trailer = self; return self; } #pragma mark - 刚好看到上拉刷新控件时的contentOffset.x - (CGFloat)happenOffsetX { CGFloat deltaW = [self widthForContentBreakView]; if (deltaW > 0) { return deltaW - self.scrollViewOriginalInset.left; } else { return - self.scrollViewOriginalInset.left; } } #pragma mark 获得scrollView的内容 超出 view 的宽度 - (CGFloat)widthForContentBreakView { CGFloat w = self.scrollView.frame.size.width - self.scrollViewOriginalInset.right - self.scrollViewOriginalInset.left; return self.scrollView.contentSize.width - w; } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.h ================================================ // // MJRefreshAutoGifFooter.h // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshAutoStateFooter.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshAutoGifFooter : MJRefreshAutoStateFooter @property (weak, nonatomic, readonly) UIImageView *gifView; /** 设置state状态下的动画图片images 动画持续时间duration*/ - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state; - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.m ================================================ // // MJRefreshAutoGifFooter.m // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshAutoGifFooter.h" #import "NSBundle+MJRefresh.h" #import "UIView+MJExtension.h" #import "UIScrollView+MJExtension.h" #import "UIScrollView+MJRefresh.h" @interface MJRefreshAutoGifFooter() { __unsafe_unretained UIImageView *_gifView; } /** 所有状态对应的动画图片 */ @property (strong, nonatomic) NSMutableDictionary *stateImages; /** 所有状态对应的动画时间 */ @property (strong, nonatomic) NSMutableDictionary *stateDurations; @end @implementation MJRefreshAutoGifFooter #pragma mark - 懒加载 - (UIImageView *)gifView { if (!_gifView) { UIImageView *gifView = [[UIImageView alloc] init]; [self addSubview:_gifView = gifView]; } return _gifView; } - (NSMutableDictionary *)stateImages { if (!_stateImages) { self.stateImages = [NSMutableDictionary dictionary]; } return _stateImages; } - (NSMutableDictionary *)stateDurations { if (!_stateDurations) { self.stateDurations = [NSMutableDictionary dictionary]; } return _stateDurations; } #pragma mark - 公共方法 - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state { if (images == nil) return self; self.stateImages[@(state)] = images; self.stateDurations[@(state)] = @(duration); /* 根据图片设置控件的高度 */ UIImage *image = [images firstObject]; if (image.size.height > self.mj_h) { self.mj_h = image.size.height; } return self; } - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state { return [self setImages:images duration:images.count * 0.1 forState:state]; } #pragma mark - 实现父类的方法 - (void)prepare { [super prepare]; // 初始化间距 self.labelLeftInset = 20; } - (void)placeSubviews { [super placeSubviews]; if (self.gifView.constraints.count) return; self.gifView.frame = self.bounds; if (self.isRefreshingTitleHidden) { self.gifView.contentMode = UIViewContentModeCenter; } else { self.gifView.contentMode = UIViewContentModeRight; self.gifView.mj_w = self.mj_w * 0.5 - self.labelLeftInset - self.stateLabel.mj_textWidth * 0.5; } } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态做事情 if (state == MJRefreshStateRefreshing) { NSArray *images = self.stateImages[@(state)]; if (images.count == 0) return; [self.gifView stopAnimating]; self.gifView.hidden = NO; if (images.count == 1) { // 单张图片 self.gifView.image = [images lastObject]; } else { // 多张图片 self.gifView.animationImages = images; self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue]; [self.gifView startAnimating]; } } else if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { [self.gifView stopAnimating]; self.gifView.hidden = YES; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.h ================================================ // // MJRefreshAutoNormalFooter.h // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshAutoStateFooter.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshAutoNormalFooter : MJRefreshAutoStateFooter @property (weak, nonatomic, readonly) UIActivityIndicatorView *loadingView; /** 菊花的样式 */ @property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle MJRefreshDeprecated("first deprecated in 3.2.2 - Use `loadingView` property"); @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.m ================================================ // // MJRefreshAutoNormalFooter.m // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshAutoNormalFooter.h" #import "NSBundle+MJRefresh.h" #import "UIView+MJExtension.h" #import "UIScrollView+MJExtension.h" #import "UIScrollView+MJRefresh.h" @interface MJRefreshAutoNormalFooter() @property (weak, nonatomic) UIActivityIndicatorView *loadingView; @end @implementation MJRefreshAutoNormalFooter #pragma mark - 懒加载子控件 - (UIActivityIndicatorView *)loadingView { if (!_loadingView) { UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorViewStyle]; loadingView.hidesWhenStopped = YES; [self addSubview:_loadingView = loadingView]; } return _loadingView; } - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle { _activityIndicatorViewStyle = activityIndicatorViewStyle; [self.loadingView removeFromSuperview]; self.loadingView = nil; [self setNeedsLayout]; } #pragma mark - 重写父类的方法 - (void)prepare { [super prepare]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 if (@available(iOS 13.0, *)) { _activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium; return; } #endif _activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; } - (void)placeSubviews { [super placeSubviews]; if (self.loadingView.constraints.count) return; // 圈圈 CGFloat loadingCenterX = self.mj_w * 0.5; if (!self.isRefreshingTitleHidden) { loadingCenterX -= self.stateLabel.mj_textWidth * 0.5 + self.labelLeftInset; } CGFloat loadingCenterY = self.mj_h * 0.5; self.loadingView.center = CGPointMake(loadingCenterX, loadingCenterY); } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态做事情 if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { [self.loadingView stopAnimating]; } else if (state == MJRefreshStateRefreshing) { [self.loadingView startAnimating]; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.h ================================================ // // MJRefreshAutoStateFooter.h // MJRefresh // // Created by MJ Lee on 15/6/13. // Copyright © 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshAutoFooter.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshAutoStateFooter : MJRefreshAutoFooter /** 文字距离圈圈、箭头的距离 */ @property (assign, nonatomic) CGFloat labelLeftInset; /** 显示刷新状态的label */ @property (weak, nonatomic, readonly) UILabel *stateLabel; /** 设置state状态下的文字 */ - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state; /** 隐藏刷新状态的文字 */ @property (assign, nonatomic, getter=isRefreshingTitleHidden) BOOL refreshingTitleHidden; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.m ================================================ // // MJRefreshAutoStateFooter.m // MJRefresh // // Created by MJ Lee on 15/6/13. // Copyright © 2015年 小码哥. All rights reserved. // #import "MJRefreshAutoStateFooter.h" #import "NSBundle+MJRefresh.h" @interface MJRefreshAutoFooter (TapTriggerFix) - (void)beginRefreshingWithoutValidation; @end @implementation MJRefreshAutoFooter (TapTriggerFix) - (void)beginRefreshingWithoutValidation { [super beginRefreshing]; } @end @interface MJRefreshAutoStateFooter() { /** 显示刷新状态的label */ __unsafe_unretained UILabel *_stateLabel; } /** 所有状态对应的文字 */ @property (strong, nonatomic) NSMutableDictionary *stateTitles; @end @implementation MJRefreshAutoStateFooter #pragma mark - 懒加载 - (NSMutableDictionary *)stateTitles { if (!_stateTitles) { self.stateTitles = [NSMutableDictionary dictionary]; } return _stateTitles; } - (UILabel *)stateLabel { if (!_stateLabel) { [self addSubview:_stateLabel = [UILabel mj_label]]; } return _stateLabel; } #pragma mark - 公共方法 - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state { if (title == nil) return self; self.stateTitles[@(state)] = title; self.stateLabel.text = self.stateTitles[@(self.state)]; return self; } #pragma mark - 私有方法 - (void)stateLabelClick { if (self.state == MJRefreshStateIdle) { [super beginRefreshingWithoutValidation]; } } - (void)textConfiguration { // 初始化文字 [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterIdleText] forState:MJRefreshStateIdle]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterRefreshingText] forState:MJRefreshStateRefreshing]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterNoMoreDataText] forState:MJRefreshStateNoMoreData]; } #pragma mark - 重写父类的方法 - (void)prepare { [super prepare]; // 初始化间距 self.labelLeftInset = MJRefreshLabelLeftInset; [self textConfiguration]; // 监听label self.stateLabel.userInteractionEnabled = YES; [self.stateLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(stateLabelClick)]]; } - (void)i18nDidChange { [self textConfiguration]; [super i18nDidChange]; } - (void)placeSubviews { [super placeSubviews]; if (self.stateLabel.constraints.count) return; // 状态标签 self.stateLabel.frame = self.bounds; } - (void)setState:(MJRefreshState)state { MJRefreshCheckState if (self.isRefreshingTitleHidden && state == MJRefreshStateRefreshing) { self.stateLabel.text = nil; } else { self.stateLabel.text = self.stateTitles[@(state)]; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.h ================================================ // // MJRefreshBackGifFooter.h // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshBackStateFooter.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshBackGifFooter : MJRefreshBackStateFooter @property (weak, nonatomic, readonly) UIImageView *gifView; /** 设置state状态下的动画图片images 动画持续时间duration*/ - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state; - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.m ================================================ // // MJRefreshBackGifFooter.m // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshBackGifFooter.h" #import "NSBundle+MJRefresh.h" #import "UIView+MJExtension.h" #import "UIScrollView+MJExtension.h" #import "UIScrollView+MJRefresh.h" @interface MJRefreshBackGifFooter() { __unsafe_unretained UIImageView *_gifView; } /** 所有状态对应的动画图片 */ @property (strong, nonatomic) NSMutableDictionary *stateImages; /** 所有状态对应的动画时间 */ @property (strong, nonatomic) NSMutableDictionary *stateDurations; @end @implementation MJRefreshBackGifFooter #pragma mark - 懒加载 - (UIImageView *)gifView { if (!_gifView) { UIImageView *gifView = [[UIImageView alloc] init]; [self addSubview:_gifView = gifView]; } return _gifView; } - (NSMutableDictionary *)stateImages { if (!_stateImages) { self.stateImages = [NSMutableDictionary dictionary]; } return _stateImages; } - (NSMutableDictionary *)stateDurations { if (!_stateDurations) { self.stateDurations = [NSMutableDictionary dictionary]; } return _stateDurations; } #pragma mark - 公共方法 - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state { if (images == nil) return self; self.stateImages[@(state)] = images; self.stateDurations[@(state)] = @(duration); /* 根据图片设置控件的高度 */ UIImage *image = [images firstObject]; if (image.size.height > self.mj_h) { self.mj_h = image.size.height; } return self; } - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state { return [self setImages:images duration:images.count * 0.1 forState:state]; } #pragma mark - 实现父类的方法 - (void)prepare { [super prepare]; // 初始化间距 self.labelLeftInset = 20; } - (void)setPullingPercent:(CGFloat)pullingPercent { [super setPullingPercent:pullingPercent]; NSArray *images = self.stateImages[@(MJRefreshStateIdle)]; if (self.state != MJRefreshStateIdle || images.count == 0) return; [self.gifView stopAnimating]; NSUInteger index = images.count * pullingPercent; if (index >= images.count) index = images.count - 1; self.gifView.image = images[index]; } - (void)placeSubviews { [super placeSubviews]; if (self.gifView.constraints.count) return; self.gifView.frame = self.bounds; if (self.stateLabel.hidden) { self.gifView.contentMode = UIViewContentModeCenter; } else { self.gifView.contentMode = UIViewContentModeRight; self.gifView.mj_w = self.mj_w * 0.5 - self.labelLeftInset - self.stateLabel.mj_textWidth * 0.5; } } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态做事情 if (state == MJRefreshStatePulling || state == MJRefreshStateRefreshing) { NSArray *images = self.stateImages[@(state)]; if (images.count == 0) return; self.gifView.hidden = NO; [self.gifView stopAnimating]; if (images.count == 1) { // 单张图片 self.gifView.image = [images lastObject]; } else { // 多张图片 self.gifView.animationImages = images; self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue]; [self.gifView startAnimating]; } } else if (state == MJRefreshStateIdle) { self.gifView.hidden = NO; } else if (state == MJRefreshStateNoMoreData) { self.gifView.hidden = YES; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.h ================================================ // // MJRefreshBackNormalFooter.h // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshBackStateFooter.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshBackNormalFooter : MJRefreshBackStateFooter @property (weak, nonatomic, readonly) UIImageView *arrowView; @property (weak, nonatomic, readonly) UIActivityIndicatorView *loadingView; /** 菊花的样式 */ @property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle MJRefreshDeprecated("first deprecated in 3.2.2 - Use `loadingView` property"); @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.m ================================================ // // MJRefreshBackNormalFooter.m // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshBackNormalFooter.h" #import "NSBundle+MJRefresh.h" #import "UIView+MJExtension.h" @interface MJRefreshBackNormalFooter() { __unsafe_unretained UIImageView *_arrowView; } @property (weak, nonatomic) UIActivityIndicatorView *loadingView; @end @implementation MJRefreshBackNormalFooter #pragma mark - 懒加载子控件 - (UIImageView *)arrowView { if (!_arrowView) { UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_arrowImage]]; [self addSubview:_arrowView = arrowView]; } return _arrowView; } - (UIActivityIndicatorView *)loadingView { if (!_loadingView) { UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorViewStyle]; loadingView.hidesWhenStopped = YES; [self addSubview:_loadingView = loadingView]; } return _loadingView; } - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle { _activityIndicatorViewStyle = activityIndicatorViewStyle; [self.loadingView removeFromSuperview]; self.loadingView = nil; [self setNeedsLayout]; } #pragma mark - 重写父类的方法 - (void)prepare { [super prepare]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 if (@available(iOS 13.0, *)) { _activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium; return; } #endif _activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; } - (void)placeSubviews { [super placeSubviews]; // 箭头的中心点 CGFloat arrowCenterX = self.mj_w * 0.5; if (!self.stateLabel.hidden) { arrowCenterX -= self.labelLeftInset + self.stateLabel.mj_textWidth * 0.5; } CGFloat arrowCenterY = self.mj_h * 0.5; CGPoint arrowCenter = CGPointMake(arrowCenterX, arrowCenterY); // 箭头 if (self.arrowView.constraints.count == 0) { self.arrowView.mj_size = self.arrowView.image.size; self.arrowView.center = arrowCenter; } // 圈圈 if (self.loadingView.constraints.count == 0) { self.loadingView.center = arrowCenter; } self.arrowView.tintColor = self.stateLabel.textColor; } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态做事情 if (state == MJRefreshStateIdle) { if (oldState == MJRefreshStateRefreshing) { self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI); [UIView animateWithDuration:self.slowAnimationDuration animations:^{ self.loadingView.alpha = 0.0; } completion:^(BOOL finished) { // 防止动画结束后,状态已经不是MJRefreshStateIdle if (self.state != MJRefreshStateIdle) return; self.loadingView.alpha = 1.0; [self.loadingView stopAnimating]; self.arrowView.hidden = NO; }]; } else { self.arrowView.hidden = NO; [self.loadingView stopAnimating]; [UIView animateWithDuration:self.fastAnimationDuration animations:^{ self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI); }]; } } else if (state == MJRefreshStatePulling) { self.arrowView.hidden = NO; [self.loadingView stopAnimating]; [UIView animateWithDuration:self.fastAnimationDuration animations:^{ self.arrowView.transform = CGAffineTransformIdentity; }]; } else if (state == MJRefreshStateRefreshing) { self.arrowView.hidden = YES; [self.loadingView startAnimating]; } else if (state == MJRefreshStateNoMoreData) { self.arrowView.hidden = YES; [self.loadingView stopAnimating]; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.h ================================================ // // MJRefreshBackStateFooter.h // MJRefresh // // Created by MJ Lee on 15/6/13. // Copyright © 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshBackFooter.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshBackStateFooter : MJRefreshBackFooter /** 文字距离圈圈、箭头的距离 */ @property (assign, nonatomic) CGFloat labelLeftInset; /** 显示刷新状态的label */ @property (weak, nonatomic, readonly) UILabel *stateLabel; /** 设置state状态下的文字 */ - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state; /** 获取state状态下的title */ - (NSString *)titleForState:(MJRefreshState)state; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.m ================================================ // // MJRefreshBackStateFooter.m // MJRefresh // // Created by MJ Lee on 15/6/13. // Copyright © 2015年 小码哥. All rights reserved. // #import "MJRefreshBackStateFooter.h" #import "NSBundle+MJRefresh.h" @interface MJRefreshBackStateFooter() { /** 显示刷新状态的label */ __unsafe_unretained UILabel *_stateLabel; } /** 所有状态对应的文字 */ @property (strong, nonatomic) NSMutableDictionary *stateTitles; @end @implementation MJRefreshBackStateFooter #pragma mark - 懒加载 - (NSMutableDictionary *)stateTitles { if (!_stateTitles) { self.stateTitles = [NSMutableDictionary dictionary]; } return _stateTitles; } - (UILabel *)stateLabel { if (!_stateLabel) { [self addSubview:_stateLabel = [UILabel mj_label]]; } return _stateLabel; } #pragma mark - 公共方法 - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state { if (title == nil) return self; self.stateTitles[@(state)] = title; self.stateLabel.text = self.stateTitles[@(self.state)]; return self; } - (NSString *)titleForState:(MJRefreshState)state { return self.stateTitles[@(state)]; } - (void)textConfiguration { // 初始化文字 [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterIdleText] forState:MJRefreshStateIdle]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterPullingText] forState:MJRefreshStatePulling]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterRefreshingText] forState:MJRefreshStateRefreshing]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterNoMoreDataText] forState:MJRefreshStateNoMoreData]; } #pragma mark - 重写父类的方法 - (void)prepare { [super prepare]; // 初始化间距 self.labelLeftInset = MJRefreshLabelLeftInset; [self textConfiguration]; } - (void)i18nDidChange { [self textConfiguration]; [super i18nDidChange]; } - (void)placeSubviews { [super placeSubviews]; if (self.stateLabel.constraints.count) return; // 状态标签 self.stateLabel.frame = self.bounds; } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 设置状态文字 self.stateLabel.text = self.stateTitles[@(state)]; } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Header/MJRefreshGifHeader.h ================================================ // // MJRefreshGifHeader.h // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshStateHeader.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshGifHeader : MJRefreshStateHeader @property (weak, nonatomic, readonly) UIImageView *gifView; /** 设置state状态下的动画图片images 动画持续时间duration*/ - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state; - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Header/MJRefreshGifHeader.m ================================================ // // MJRefreshGifHeader.m // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshGifHeader.h" #import "UIView+MJExtension.h" #import "UIScrollView+MJExtension.h" @interface MJRefreshGifHeader() { __unsafe_unretained UIImageView *_gifView; } /** 所有状态对应的动画图片 */ @property (strong, nonatomic) NSMutableDictionary *stateImages; /** 所有状态对应的动画时间 */ @property (strong, nonatomic) NSMutableDictionary *stateDurations; @end @implementation MJRefreshGifHeader #pragma mark - 懒加载 - (UIImageView *)gifView { if (!_gifView) { UIImageView *gifView = [[UIImageView alloc] init]; [self addSubview:_gifView = gifView]; } return _gifView; } - (NSMutableDictionary *)stateImages { if (!_stateImages) { self.stateImages = [NSMutableDictionary dictionary]; } return _stateImages; } - (NSMutableDictionary *)stateDurations { if (!_stateDurations) { self.stateDurations = [NSMutableDictionary dictionary]; } return _stateDurations; } #pragma mark - 公共方法 - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state { if (images == nil) return self; self.stateImages[@(state)] = images; self.stateDurations[@(state)] = @(duration); /* 根据图片设置控件的高度 */ UIImage *image = [images firstObject]; if (image.size.height > self.mj_h) { self.mj_h = image.size.height; } return self; } - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state { return [self setImages:images duration:images.count * 0.1 forState:state]; } #pragma mark - 实现父类的方法 - (void)prepare { [super prepare]; // 初始化间距 self.labelLeftInset = 20; } - (void)setPullingPercent:(CGFloat)pullingPercent { [super setPullingPercent:pullingPercent]; NSArray *images = self.stateImages[@(MJRefreshStateIdle)]; if (self.state != MJRefreshStateIdle || images.count == 0) return; // 停止动画 [self.gifView stopAnimating]; // 设置当前需要显示的图片 NSUInteger index = images.count * pullingPercent; if (index >= images.count) index = images.count - 1; self.gifView.image = images[index]; } - (void)placeSubviews { [super placeSubviews]; if (self.gifView.constraints.count) return; self.gifView.frame = self.bounds; if (self.stateLabel.hidden && self.lastUpdatedTimeLabel.hidden) { self.gifView.contentMode = UIViewContentModeCenter; } else { self.gifView.contentMode = UIViewContentModeRight; CGFloat stateWidth = self.stateLabel.mj_textWidth; CGFloat timeWidth = 0.0; if (!self.lastUpdatedTimeLabel.hidden) { timeWidth = self.lastUpdatedTimeLabel.mj_textWidth; } CGFloat textWidth = MAX(stateWidth, timeWidth); self.gifView.mj_w = self.mj_w * 0.5 - textWidth * 0.5 - self.labelLeftInset; } } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态做事情 if (state == MJRefreshStatePulling || state == MJRefreshStateRefreshing) { NSArray *images = self.stateImages[@(state)]; if (images.count == 0) return; [self.gifView stopAnimating]; if (images.count == 1) { // 单张图片 self.gifView.image = [images lastObject]; } else { // 多张图片 self.gifView.animationImages = images; self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue]; [self.gifView startAnimating]; } } else if (state == MJRefreshStateIdle) { [self.gifView stopAnimating]; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Header/MJRefreshNormalHeader.h ================================================ // // MJRefreshNormalHeader.h // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshStateHeader.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshNormalHeader : MJRefreshStateHeader @property (weak, nonatomic, readonly) UIImageView *arrowView; @property (weak, nonatomic, readonly) UIActivityIndicatorView *loadingView; /** 菊花的样式 */ @property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle MJRefreshDeprecated("first deprecated in 3.2.2 - Use `loadingView` property"); @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Header/MJRefreshNormalHeader.m ================================================ // // MJRefreshNormalHeader.m // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshNormalHeader.h" #import "NSBundle+MJRefresh.h" #import "UIScrollView+MJRefresh.h" #import "UIView+MJExtension.h" @interface MJRefreshNormalHeader() { __unsafe_unretained UIImageView *_arrowView; } @property (weak, nonatomic) UIActivityIndicatorView *loadingView; @end @implementation MJRefreshNormalHeader #pragma mark - 懒加载子控件 - (UIImageView *)arrowView { if (!_arrowView) { UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_arrowImage]]; [self addSubview:_arrowView = arrowView]; } return _arrowView; } - (UIActivityIndicatorView *)loadingView { if (!_loadingView) { UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorViewStyle]; loadingView.hidesWhenStopped = YES; [self addSubview:_loadingView = loadingView]; } return _loadingView; } #pragma mark - 公共方法 - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle { _activityIndicatorViewStyle = activityIndicatorViewStyle; [self.loadingView removeFromSuperview]; self.loadingView = nil; [self setNeedsLayout]; } #pragma mark - 重写父类的方法 - (void)prepare { [super prepare]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 if (@available(iOS 13.0, *)) { _activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium; return; } #endif _activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; } - (void)placeSubviews { [super placeSubviews]; // 箭头的中心点 CGFloat arrowCenterX = self.mj_w * 0.5; if (!self.stateLabel.hidden) { CGFloat stateWidth = self.stateLabel.mj_textWidth; CGFloat timeWidth = 0.0; if (!self.lastUpdatedTimeLabel.hidden) { timeWidth = self.lastUpdatedTimeLabel.mj_textWidth; } CGFloat textWidth = MAX(stateWidth, timeWidth); arrowCenterX -= textWidth / 2 + self.labelLeftInset; } CGFloat arrowCenterY = self.mj_h * 0.5; CGPoint arrowCenter = CGPointMake(arrowCenterX, arrowCenterY); // 箭头 if (self.arrowView.constraints.count == 0) { self.arrowView.mj_size = self.arrowView.image.size; self.arrowView.center = arrowCenter; } // 圈圈 if (self.loadingView.constraints.count == 0) { self.loadingView.center = arrowCenter; } self.arrowView.tintColor = self.stateLabel.textColor; } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态做事情 if (state == MJRefreshStateIdle) { if (oldState == MJRefreshStateRefreshing) { self.arrowView.transform = CGAffineTransformIdentity; [UIView animateWithDuration:self.slowAnimationDuration animations:^{ self.loadingView.alpha = 0.0; } completion:^(BOOL finished) { // 如果执行完动画发现不是idle状态,就直接返回,进入其他状态 if (self.state != MJRefreshStateIdle) return; self.loadingView.alpha = 1.0; [self.loadingView stopAnimating]; self.arrowView.hidden = NO; }]; } else { [self.loadingView stopAnimating]; self.arrowView.hidden = NO; [UIView animateWithDuration:self.fastAnimationDuration animations:^{ self.arrowView.transform = CGAffineTransformIdentity; }]; } } else if (state == MJRefreshStatePulling) { [self.loadingView stopAnimating]; self.arrowView.hidden = NO; [UIView animateWithDuration:self.fastAnimationDuration animations:^{ self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI); }]; } else if (state == MJRefreshStateRefreshing) { self.loadingView.alpha = 1.0; // 防止refreshing -> idle的动画完毕动作没有被执行 [self.loadingView startAnimating]; self.arrowView.hidden = YES; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Header/MJRefreshStateHeader.h ================================================ // // MJRefreshStateHeader.h // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshHeader.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshStateHeader : MJRefreshHeader #pragma mark - 刷新时间相关 /** 利用这个block来决定显示的更新时间文字 */ @property (copy, nonatomic, nullable) NSString *(^lastUpdatedTimeText)(NSDate * _Nullable lastUpdatedTime); /** 显示上一次刷新时间的label */ @property (weak, nonatomic, readonly) UILabel *lastUpdatedTimeLabel; #pragma mark - 状态相关 /** 文字距离圈圈、箭头的距离 */ @property (assign, nonatomic) CGFloat labelLeftInset; /** 显示刷新状态的label */ @property (weak, nonatomic, readonly) UILabel *stateLabel; /** 设置state状态下的文字 */ - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state; @end @interface MJRefreshStateHeader (ChainingGrammar) - (instancetype)modifyLastUpdatedTimeText:(NSString * (^)(NSDate * _Nullable lastUpdatedTime))handler; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Header/MJRefreshStateHeader.m ================================================ // // MJRefreshStateHeader.m // MJRefresh // // Created by MJ Lee on 15/4/24. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "MJRefreshStateHeader.h" #import "MJRefreshConst.h" #import "NSBundle+MJRefresh.h" #import "UIView+MJExtension.h" #import "UIScrollView+MJExtension.h" @interface MJRefreshStateHeader() { /** 显示上一次刷新时间的label */ __unsafe_unretained UILabel *_lastUpdatedTimeLabel; /** 显示刷新状态的label */ __unsafe_unretained UILabel *_stateLabel; } /** 所有状态对应的文字 */ @property (strong, nonatomic) NSMutableDictionary *stateTitles; @end @implementation MJRefreshStateHeader #pragma mark - 懒加载 - (NSMutableDictionary *)stateTitles { if (!_stateTitles) { self.stateTitles = [NSMutableDictionary dictionary]; } return _stateTitles; } - (UILabel *)stateLabel { if (!_stateLabel) { [self addSubview:_stateLabel = [UILabel mj_label]]; } return _stateLabel; } - (UILabel *)lastUpdatedTimeLabel { if (!_lastUpdatedTimeLabel) { [self addSubview:_lastUpdatedTimeLabel = [UILabel mj_label]]; } return _lastUpdatedTimeLabel; } - (void)setLastUpdatedTimeText:(NSString * _Nonnull (^)(NSDate * _Nullable))lastUpdatedTimeText{ _lastUpdatedTimeText = lastUpdatedTimeText; // 重新设置key(重新显示时间) self.lastUpdatedTimeKey = self.lastUpdatedTimeKey; } #pragma mark - 公共方法 - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state { if (title == nil) return self; self.stateTitles[@(state)] = title; self.stateLabel.text = self.stateTitles[@(self.state)]; return self; } #pragma mark key的处理 - (void)setLastUpdatedTimeKey:(NSString *)lastUpdatedTimeKey { [super setLastUpdatedTimeKey:lastUpdatedTimeKey]; // 如果label隐藏了,就不用再处理 if (self.lastUpdatedTimeLabel.hidden) return; NSDate *lastUpdatedTime = [[NSUserDefaults standardUserDefaults] objectForKey:lastUpdatedTimeKey]; // 如果有block if (self.lastUpdatedTimeText) { self.lastUpdatedTimeLabel.text = self.lastUpdatedTimeText(lastUpdatedTime); return; } if (lastUpdatedTime) { // 1.获得年月日 NSCalendar *calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; NSUInteger unitFlags = NSCalendarUnitYear| NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute; NSDateComponents *cmp1 = [calendar components:unitFlags fromDate:lastUpdatedTime]; NSDateComponents *cmp2 = [calendar components:unitFlags fromDate:[NSDate date]]; // 2.格式化日期 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; BOOL isToday = NO; if ([cmp1 day] == [cmp2 day]) { // 今天 formatter.dateFormat = @" HH:mm"; isToday = YES; } else if ([cmp1 year] == [cmp2 year]) { // 今年 formatter.dateFormat = @"MM-dd HH:mm"; } else { formatter.dateFormat = @"yyyy-MM-dd HH:mm"; } NSString *time = [formatter stringFromDate:lastUpdatedTime]; // 3.显示日期 self.lastUpdatedTimeLabel.text = [NSString stringWithFormat:@"%@%@%@", [NSBundle mj_localizedStringForKey:MJRefreshHeaderLastTimeText], isToday ? [NSBundle mj_localizedStringForKey:MJRefreshHeaderDateTodayText] : @"", time]; } else { self.lastUpdatedTimeLabel.text = [NSString stringWithFormat:@"%@%@", [NSBundle mj_localizedStringForKey:MJRefreshHeaderLastTimeText], [NSBundle mj_localizedStringForKey:MJRefreshHeaderNoneLastDateText]]; } } - (void)textConfiguration { // 初始化文字 [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderIdleText] forState:MJRefreshStateIdle]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderPullingText] forState:MJRefreshStatePulling]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderRefreshingText] forState:MJRefreshStateRefreshing]; self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey; } #pragma mark - 覆盖父类的方法 - (void)prepare { [super prepare]; // 初始化间距 self.labelLeftInset = MJRefreshLabelLeftInset; [self textConfiguration]; } - (void)i18nDidChange { [self textConfiguration]; [super i18nDidChange]; } - (void)placeSubviews { [super placeSubviews]; if (self.stateLabel.hidden) return; BOOL noConstrainsOnStatusLabel = self.stateLabel.constraints.count == 0; if (self.lastUpdatedTimeLabel.hidden) { // 状态 if (noConstrainsOnStatusLabel) self.stateLabel.frame = self.bounds; } else { CGFloat stateLabelH = self.mj_h * 0.5; // 状态 if (noConstrainsOnStatusLabel) { self.stateLabel.mj_x = 0; self.stateLabel.mj_y = 0; self.stateLabel.mj_w = self.mj_w; self.stateLabel.mj_h = stateLabelH; } // 更新时间 if (self.lastUpdatedTimeLabel.constraints.count == 0) { self.lastUpdatedTimeLabel.mj_x = 0; self.lastUpdatedTimeLabel.mj_y = stateLabelH; self.lastUpdatedTimeLabel.mj_w = self.mj_w; self.lastUpdatedTimeLabel.mj_h = self.mj_h - self.lastUpdatedTimeLabel.mj_y; } } } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 设置状态文字 self.stateLabel.text = self.stateTitles[@(state)]; // 重新设置key(重新显示时间) self.lastUpdatedTimeKey = self.lastUpdatedTimeKey; } @end #pragma mark - <<< 为 Swift 扩展链式语法 >>> - @implementation MJRefreshStateHeader (ChainingGrammar) - (instancetype)modifyLastUpdatedTimeText:(NSString * _Nonnull (^)(NSDate * _Nullable))handler { self.lastUpdatedTimeText = handler; return self; } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Trailer/MJRefreshNormalTrailer.h ================================================ // // MJRefreshNormalTrailer.h // MJRefresh // // Created by kinarobin on 2020/5/3. // Copyright © 2020 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshStateTrailer.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshNormalTrailer : MJRefreshStateTrailer @property (weak, nonatomic, readonly) UIImageView *arrowView; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Trailer/MJRefreshNormalTrailer.m ================================================ // // MJRefreshNormalTrailer.m // MJRefresh // // Created by kinarobin on 2020/5/3. // Copyright © 2020 小码哥. All rights reserved. // #import "MJRefreshNormalTrailer.h" #import "NSBundle+MJRefresh.h" #import "UIView+MJExtension.h" @interface MJRefreshNormalTrailer() { __unsafe_unretained UIImageView *_arrowView; } @end @implementation MJRefreshNormalTrailer #pragma mark - 懒加载子控件 - (UIImageView *)arrowView { if (!_arrowView) { UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_trailArrowImage]]; [self addSubview:_arrowView = arrowView]; } return _arrowView; } - (void)placeSubviews { [super placeSubviews]; CGSize arrowSize = self.arrowView.image.size; // 箭头的中心点 CGPoint selfCenter = CGPointMake(self.mj_w * 0.5, self.mj_h * 0.5); CGPoint arrowCenter = CGPointMake(arrowSize.width * 0.5 + 5, self.mj_h * 0.5); BOOL stateHidden = self.stateLabel.isHidden; if (self.arrowView.constraints.count == 0) { self.arrowView.mj_size = self.arrowView.image.size; self.arrowView.center = stateHidden ? selfCenter : arrowCenter ; } self.arrowView.tintColor = self.stateLabel.textColor; if (stateHidden) return; BOOL noConstrainsOnStatusLabel = self.stateLabel.constraints.count == 0; CGFloat stateLabelW = ceil(self.stateLabel.font.pointSize); // 状态 if (noConstrainsOnStatusLabel) { BOOL arrowHidden = self.arrowView.isHidden; CGFloat stateCenterX = (self.mj_w + arrowSize.width) * 0.5; self.stateLabel.center = arrowHidden ? selfCenter : CGPointMake(stateCenterX, self.mj_h * 0.5); self.stateLabel.mj_size = CGSizeMake(stateLabelW, self.mj_h) ; } } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 根据状态做事情 if (state == MJRefreshStateIdle) { if (oldState == MJRefreshStateRefreshing) { [UIView animateWithDuration:self.fastAnimationDuration animations:^{ self.arrowView.transform = CGAffineTransformMakeRotation(M_PI); } completion:^(BOOL finished) { self.arrowView.transform = CGAffineTransformIdentity; }]; } else { [UIView animateWithDuration:self.fastAnimationDuration animations:^{ self.arrowView.transform = CGAffineTransformIdentity; }]; } } else if (state == MJRefreshStatePulling) { [UIView animateWithDuration:self.fastAnimationDuration animations:^{ self.arrowView.transform = CGAffineTransformMakeRotation(M_PI); }]; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Trailer/MJRefreshStateTrailer.h ================================================ // // MJRefreshStateTrailer.h // MJRefresh // // Created by kinarobin on 2020/5/3. // Copyright © 2020 小码哥. All rights reserved. // #if __has_include() #import #else #import "MJRefreshTrailer.h" #endif NS_ASSUME_NONNULL_BEGIN @interface MJRefreshStateTrailer : MJRefreshTrailer #pragma mark - 状态相关 /** 显示刷新状态的label */ @property (weak, nonatomic, readonly) UILabel *stateLabel; /** 设置state状态下的文字 */ - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/Custom/Trailer/MJRefreshStateTrailer.m ================================================ // // MJRefreshStateTrailer.m // MJRefresh // // Created by kinarobin on 2020/5/3. // Copyright © 2020 小码哥. All rights reserved. // #import "MJRefreshStateTrailer.h" #import "NSBundle+MJRefresh.h" #import "UIView+MJExtension.h" @interface MJRefreshStateTrailer() { /** 显示刷新状态的label */ __unsafe_unretained UILabel *_stateLabel; } /** 所有状态对应的文字 */ @property (strong, nonatomic) NSMutableDictionary *stateTitles; @end @implementation MJRefreshStateTrailer #pragma mark - 懒加载 - (NSMutableDictionary *)stateTitles { if (!_stateTitles) { self.stateTitles = [NSMutableDictionary dictionary]; } return _stateTitles; } - (UILabel *)stateLabel { if (!_stateLabel) { UILabel *stateLabel = [UILabel mj_label]; stateLabel.numberOfLines = 0; [self addSubview:_stateLabel = stateLabel]; } return _stateLabel; } #pragma mark - 公共方法 - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state { if (title == nil) return self; self.stateTitles[@(state)] = title; self.stateLabel.text = self.stateTitles[@(self.state)]; return self; } - (void)textConfiguration { // 初始化文字 [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerIdleText] forState:MJRefreshStateIdle]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerPullingText] forState:MJRefreshStatePulling]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerPullingText] forState:MJRefreshStateRefreshing]; } #pragma mark - 覆盖父类的方法 - (void)prepare { [super prepare]; [self textConfiguration]; } - (void)i18nDidChange { [self textConfiguration]; [super i18nDidChange]; } - (void)setState:(MJRefreshState)state { MJRefreshCheckState // 设置状态文字 self.stateLabel.text = self.stateTitles[@(state)]; } - (void)placeSubviews { [super placeSubviews]; if (self.stateLabel.hidden) return; BOOL noConstrainsOnStatusLabel = self.stateLabel.constraints.count == 0; CGFloat stateLabelW = ceil(self.stateLabel.font.pointSize); // 状态 if (noConstrainsOnStatusLabel) { self.stateLabel.center = CGPointMake(self.mj_w * 0.5, self.mj_h * 0.5); self.stateLabel.mj_size = CGSizeMake(stateLabelW, self.mj_h) ; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/MJRefresh.bundle/ko.lproj/Localizable.strings ================================================ "MJRefreshHeaderIdleText" = "아래로 당겨 새로고침"; "MJRefreshHeaderPullingText" = "놓으면 새로고침"; "MJRefreshHeaderRefreshingText" = "로딩중..."; "MJRefreshAutoFooterIdleText" = "탭 또는 위로 당겨 로드함"; "MJRefreshAutoFooterRefreshingText" = "로딩중..."; "MJRefreshAutoFooterNoMoreDataText" = "더이상 데이터 없음"; "MJRefreshBackFooterIdleText" = "위로 당겨 더 로드 가능"; "MJRefreshBackFooterPullingText" = "놓으면 더 로드됨."; "MJRefreshBackFooterRefreshingText" = "로딩중..."; "MJRefreshBackFooterNoMoreDataText" = "더이상 데이터 없음"; "MJRefreshHeaderLastTimeText" = "마지막 업데이트: "; "MJRefreshHeaderDateTodayText" = "오늘"; "MJRefreshHeaderNoneLastDateText" = "기록 없음"; ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/MJRefresh.bundle/zh-Hant.lproj/Localizable.strings ================================================ "MJRefreshHeaderIdleText" = "下拉可以刷新"; "MJRefreshHeaderPullingText" = "鬆開立即刷新"; "MJRefreshHeaderRefreshingText" = "正在刷新數據中..."; "MJRefreshTrailerIdleText" = "滑動查看圖文詳情"; "MJRefreshTrailerPullingText" = "釋放查看圖文詳情"; "MJRefreshAutoFooterIdleText" = "點擊或上拉加載更多"; "MJRefreshAutoFooterRefreshingText" = "正在加載更多的數據..."; "MJRefreshAutoFooterNoMoreDataText" = "已經全部加載完畢"; "MJRefreshBackFooterIdleText" = "上拉可以加載更多"; "MJRefreshBackFooterPullingText" = "鬆開立即加載更多"; "MJRefreshBackFooterRefreshingText" = "正在加載更多的數據..."; "MJRefreshBackFooterNoMoreDataText" = "已經全部加載完畢"; "MJRefreshHeaderLastTimeText" = "最後更新:"; "MJRefreshHeaderDateTodayText" = "今天"; "MJRefreshHeaderNoneLastDateText" = "無記錄"; ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/MJRefresh.h ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh #import #if __has_include() FOUNDATION_EXPORT double MJRefreshVersionNumber; FOUNDATION_EXPORT const unsigned char MJRefreshVersionString[]; #import #import #import #import #import #import #import #import #import #import #import #import #import #else #import "UIScrollView+MJRefresh.h" #import "UIScrollView+MJExtension.h" #import "UIView+MJExtension.h" #import "MJRefreshNormalHeader.h" #import "MJRefreshGifHeader.h" #import "MJRefreshBackNormalFooter.h" #import "MJRefreshBackGifFooter.h" #import "MJRefreshAutoNormalFooter.h" #import "MJRefreshAutoGifFooter.h" #import "MJRefreshNormalTrailer.h" #import "MJRefreshConfig.h" #import "NSBundle+MJRefresh.h" #import "MJRefreshConst.h" #endif ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/MJRefreshConfig.h ================================================ // // MJRefreshConfig.h // // Created by Frank on 2018/11/27. // Copyright © 2018 小码哥. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface MJRefreshConfig : NSObject /** 默认使用的语言版本, 默认为 nil. 将随系统的语言自动改变 */ @property (copy, nonatomic, nullable) NSString *languageCode; /** 默认使用的语言资源文件名, 默认为 nil, 即默认的 Localizable.strings. - Attention: 文件名不包含后缀.strings */ @property (copy, nonatomic, nullable) NSString *i18nFilename; /** i18n 多语言资源加载自定义 Bundle. - Attention: 默认为 nil 采用内置逻辑. 这里设置后将忽略内置逻辑的多语言模式, 采用自定义的多语言 bundle */ @property (nonatomic, nullable) NSBundle *i18nBundle; /** Singleton Config instance */ @property (class, nonatomic, readonly) MJRefreshConfig *defaultConfig; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/MJRefreshConfig.m ================================================ // // MJRefreshConfig.m // // Created by Frank on 2018/11/27. // Copyright © 2018 小码哥. All rights reserved. // #import "MJRefreshConfig.h" #import "MJRefreshConst.h" #import "NSBundle+MJRefresh.h" @interface MJRefreshConfig (Bundle) + (void)resetLanguageResourceCache; @end @implementation MJRefreshConfig static MJRefreshConfig *mj_RefreshConfig = nil; + (instancetype)defaultConfig { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ mj_RefreshConfig = [[self alloc] init]; }); return mj_RefreshConfig; } - (void)setLanguageCode:(NSString *)languageCode { if ([languageCode isEqualToString:_languageCode]) { return; } _languageCode = languageCode; // 重置语言资源 [MJRefreshConfig resetLanguageResourceCache]; [NSNotificationCenter.defaultCenter postNotificationName:MJRefreshDidChangeLanguageNotification object:self]; } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/MJRefreshConst.h ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh #import #import #import // 弱引用 #define MJWeakSelf __weak typeof(self) weakSelf = self; // 日志输出 #ifdef DEBUG #define MJRefreshLog(...) NSLog(__VA_ARGS__) #else #define MJRefreshLog(...) #endif // 过期提醒 #define MJRefreshDeprecated(DESCRIPTION) __attribute__((deprecated(DESCRIPTION))) // 运行时objc_msgSend #define MJRefreshMsgSend(...) ((void (*)(void *, SEL, UIView *))objc_msgSend)(__VA_ARGS__) #define MJRefreshMsgTarget(target) (__bridge void *)(target) // RGB颜色 #define MJRefreshColor(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0] // 文字颜色 #define MJRefreshLabelTextColor MJRefreshColor(90, 90, 90) // 字体大小 #define MJRefreshLabelFont [UIFont boldSystemFontOfSize:14] // 常量 UIKIT_EXTERN const CGFloat MJRefreshLabelLeftInset; UIKIT_EXTERN const CGFloat MJRefreshHeaderHeight; UIKIT_EXTERN const CGFloat MJRefreshFooterHeight; UIKIT_EXTERN const CGFloat MJRefreshTrailWidth; UIKIT_EXTERN const CGFloat MJRefreshFastAnimationDuration; UIKIT_EXTERN const CGFloat MJRefreshSlowAnimationDuration; UIKIT_EXTERN NSString *const MJRefreshKeyPathContentOffset; UIKIT_EXTERN NSString *const MJRefreshKeyPathContentSize; UIKIT_EXTERN NSString *const MJRefreshKeyPathContentInset; UIKIT_EXTERN NSString *const MJRefreshKeyPathPanState; UIKIT_EXTERN NSString *const MJRefreshHeaderLastUpdatedTimeKey; UIKIT_EXTERN NSString *const MJRefreshHeaderIdleText; UIKIT_EXTERN NSString *const MJRefreshHeaderPullingText; UIKIT_EXTERN NSString *const MJRefreshHeaderRefreshingText; UIKIT_EXTERN NSString *const MJRefreshTrailerIdleText; UIKIT_EXTERN NSString *const MJRefreshTrailerPullingText; UIKIT_EXTERN NSString *const MJRefreshAutoFooterIdleText; UIKIT_EXTERN NSString *const MJRefreshAutoFooterRefreshingText; UIKIT_EXTERN NSString *const MJRefreshAutoFooterNoMoreDataText; UIKIT_EXTERN NSString *const MJRefreshBackFooterIdleText; UIKIT_EXTERN NSString *const MJRefreshBackFooterPullingText; UIKIT_EXTERN NSString *const MJRefreshBackFooterRefreshingText; UIKIT_EXTERN NSString *const MJRefreshBackFooterNoMoreDataText; UIKIT_EXTERN NSString *const MJRefreshHeaderLastTimeText; UIKIT_EXTERN NSString *const MJRefreshHeaderDateTodayText; UIKIT_EXTERN NSString *const MJRefreshHeaderNoneLastDateText; UIKIT_EXTERN NSString *const MJRefreshDidChangeLanguageNotification; // 状态检查 #define MJRefreshCheckState \ MJRefreshState oldState = self.state; \ if (state == oldState) return; \ [super setState:state]; // 异步主线程执行,不强持有Self #define MJRefreshDispatchAsyncOnMainQueue(x) \ __weak typeof(self) weakSelf = self; \ dispatch_async(dispatch_get_main_queue(), ^{ \ typeof(weakSelf) self = weakSelf; \ {x} \ }); /// 替换方法实现 /// @param _fromClass 源类 /// @param _originSelector 源类的 Selector /// @param _toClass 目标类 /// @param _newSelector 目标类的 Selector CG_INLINE BOOL MJRefreshExchangeImplementations( Class _fromClass, SEL _originSelector, Class _toClass, SEL _newSelector) { if (!_fromClass || !_toClass) { return NO; } Method oriMethod = class_getInstanceMethod(_fromClass, _originSelector); Method newMethod = class_getInstanceMethod(_toClass, _newSelector); if (!newMethod) { return NO; } BOOL isAddedMethod = class_addMethod(_fromClass, _originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)); if (isAddedMethod) { // 如果 class_addMethod 成功了,说明之前 fromClass 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续 toClass 的这个方法被调用时可能会 crash IMP emptyIMP = imp_implementationWithBlock(^(id selfObject) {}); IMP oriMethodIMP = method_getImplementation(oriMethod) ?: emptyIMP; const char *oriMethodTypeEncoding = method_getTypeEncoding(oriMethod) ?: "v@:"; class_replaceMethod(_toClass, _newSelector, oriMethodIMP, oriMethodTypeEncoding); } else { method_exchangeImplementations(oriMethod, newMethod); } return YES; } ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/MJRefreshConst.m ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh #import const CGFloat MJRefreshLabelLeftInset = 25; const CGFloat MJRefreshHeaderHeight = 54.0; const CGFloat MJRefreshFooterHeight = 44.0; const CGFloat MJRefreshTrailWidth = 60.0; const CGFloat MJRefreshFastAnimationDuration = 0.25; const CGFloat MJRefreshSlowAnimationDuration = 0.4; NSString *const MJRefreshKeyPathContentOffset = @"contentOffset"; NSString *const MJRefreshKeyPathContentInset = @"contentInset"; NSString *const MJRefreshKeyPathContentSize = @"contentSize"; NSString *const MJRefreshKeyPathPanState = @"state"; NSString *const MJRefreshHeaderLastUpdatedTimeKey = @"MJRefreshHeaderLastUpdatedTimeKey"; NSString *const MJRefreshHeaderIdleText = @"MJRefreshHeaderIdleText"; NSString *const MJRefreshHeaderPullingText = @"MJRefreshHeaderPullingText"; NSString *const MJRefreshHeaderRefreshingText = @"MJRefreshHeaderRefreshingText"; NSString *const MJRefreshTrailerIdleText = @"MJRefreshTrailerIdleText"; NSString *const MJRefreshTrailerPullingText = @"MJRefreshTrailerPullingText"; NSString *const MJRefreshAutoFooterIdleText = @"MJRefreshAutoFooterIdleText"; NSString *const MJRefreshAutoFooterRefreshingText = @"MJRefreshAutoFooterRefreshingText"; NSString *const MJRefreshAutoFooterNoMoreDataText = @"MJRefreshAutoFooterNoMoreDataText"; NSString *const MJRefreshBackFooterIdleText = @"MJRefreshBackFooterIdleText"; NSString *const MJRefreshBackFooterPullingText = @"MJRefreshBackFooterPullingText"; NSString *const MJRefreshBackFooterRefreshingText = @"MJRefreshBackFooterRefreshingText"; NSString *const MJRefreshBackFooterNoMoreDataText = @"MJRefreshBackFooterNoMoreDataText"; NSString *const MJRefreshHeaderLastTimeText = @"MJRefreshHeaderLastTimeText"; NSString *const MJRefreshHeaderDateTodayText = @"MJRefreshHeaderDateTodayText"; NSString *const MJRefreshHeaderNoneLastDateText = @"MJRefreshHeaderNoneLastDateText"; NSString *const MJRefreshDidChangeLanguageNotification = @"MJRefreshDidChangeLanguageNotification"; ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/NSBundle+MJRefresh.h ================================================ // // NSBundle+MJRefresh.h // MJRefresh // // Created by MJ Lee on 16/6/13. // Copyright © 2016年 小码哥. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface NSBundle (MJRefresh) + (instancetype)mj_refreshBundle; + (UIImage *)mj_arrowImage; + (UIImage *)mj_trailArrowImage; + (NSString *)mj_localizedStringForKey:(NSString *)key value:(nullable NSString *)value; + (NSString *)mj_localizedStringForKey:(NSString *)key; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/NSBundle+MJRefresh.m ================================================ // // NSBundle+MJRefresh.m // MJRefresh // // Created by MJ Lee on 16/6/13. // Copyright © 2016年 小码哥. All rights reserved. // #import "NSBundle+MJRefresh.h" #import "MJRefreshComponent.h" #import "MJRefreshConfig.h" static NSBundle *mj_defaultI18nBundle = nil; static NSBundle *mj_systemI18nBundle = nil; @implementation NSBundle (MJRefresh) + (instancetype)mj_refreshBundle { static NSBundle *refreshBundle = nil; if (refreshBundle == nil) { #ifdef SWIFT_PACKAGE NSBundle *containnerBundle = SWIFTPM_MODULE_BUNDLE; #else NSBundle *containnerBundle = [NSBundle bundleForClass:[MJRefreshComponent class]]; #endif refreshBundle = [NSBundle bundleWithPath:[containnerBundle pathForResource:@"MJRefresh" ofType:@"bundle"]]; } return refreshBundle; } + (UIImage *)mj_arrowImage { static UIImage *arrowImage = nil; if (arrowImage == nil) { arrowImage = [[UIImage imageWithContentsOfFile:[[self mj_refreshBundle] pathForResource:@"arrow@2x" ofType:@"png"]] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; } return arrowImage; } + (UIImage *)mj_trailArrowImage { static UIImage *arrowImage = nil; if (arrowImage == nil) { arrowImage = [[UIImage imageWithContentsOfFile:[[self mj_refreshBundle] pathForResource:@"trail_arrow@2x" ofType:@"png"]] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; } return arrowImage; } + (NSString *)mj_localizedStringForKey:(NSString *)key { return [self mj_localizedStringForKey:key value:nil]; } + (NSString *)mj_localizedStringForKey:(NSString *)key value:(NSString *)value { NSString *table = MJRefreshConfig.defaultConfig.i18nFilename; // 如果没有缓存, 则走初始化逻辑 if (mj_defaultI18nBundle == nil) { NSString *language = MJRefreshConfig.defaultConfig.languageCode; // 如果配置中没有配置语言 if (!language) { language = [NSLocale preferredLanguages].firstObject; } NSBundle *bundle = MJRefreshConfig.defaultConfig.i18nBundle; // 首先优先使用公共配置中的 i18nBundle, 如果为空则使用 mainBundle bundle = bundle ? bundle : NSBundle.mainBundle; // 按语言选取语言包 NSString *i18nFolderPath = [bundle pathForResource:language ofType:@"lproj"]; mj_defaultI18nBundle = [NSBundle bundleWithPath:i18nFolderPath]; // 检查语言包, 如果没有查找到, 则默认使用 mainBundle mj_defaultI18nBundle = mj_defaultI18nBundle ? mj_defaultI18nBundle : NSBundle.mainBundle; // 获取 MJRefresh 自有的语言包 if (mj_systemI18nBundle == nil) { mj_systemI18nBundle = [self mj_defaultI18nBundleWithLanguage:language]; } } // 首先在 MJRefresh 内置语言文件中寻找 value = [mj_systemI18nBundle localizedStringForKey:key value:value table:nil]; // 然后在 MainBundle 对应语言文件中寻找 value = [mj_defaultI18nBundle localizedStringForKey:key value:value table:table]; return value; } + (NSBundle *)mj_defaultI18nBundleWithLanguage:(NSString *)language { if ([language hasPrefix:@"en"]) { language = @"en"; } else if ([language hasPrefix:@"zh"]) { if ([language rangeOfString:@"Hans"].location != NSNotFound) { language = @"zh-Hans"; // 简体中文 } else { // zh-Hant\zh-HK\zh-TW language = @"zh-Hant"; // 繁體中文 } } else if ([language hasPrefix:@"ko"]) { language = @"ko"; } else if ([language hasPrefix:@"ru"]) { language = @"ru"; } else if ([language hasPrefix:@"uk"]) { language = @"uk"; } else { language = @"en"; } // 从MJRefresh.bundle中查找资源 return [NSBundle bundleWithPath:[[NSBundle mj_refreshBundle] pathForResource:language ofType:@"lproj"]]; } @end @implementation MJRefreshConfig (Bundle) + (void)resetLanguageResourceCache { mj_defaultI18nBundle = nil; mj_systemI18nBundle = nil; } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/UICollectionViewLayout+MJRefresh.h ================================================ // // UICollectionViewLayout+MJRefresh.h // // 该类是用来解决 Footer 在底端加载完成后, 仍停留在原处的 bug. // 此问题出现在 iOS 14 及以下系统上. // Reference: https://github.com/CoderMJLee/MJRefresh/issues/1552 // // Created by jiasong on 2021/11/15. // Copyright © 2021 小码哥. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface UICollectionViewLayout (MJRefresh) @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/UICollectionViewLayout+MJRefresh.m ================================================ // // UICollectionViewLayout+MJRefresh.m // // 该类是用来解决 Footer 在底端加载完成后, 仍停留在原处的 bug. // 此问题出现在 iOS 14 及以下系统上. // Reference: https://github.com/CoderMJLee/MJRefresh/issues/1552 // // Created by jiasong on 2021/11/15. // Copyright © 2021 小码哥. All rights reserved. // #import "UICollectionViewLayout+MJRefresh.h" #import "MJRefreshConst.h" #import "MJRefreshFooter.h" #import "UIScrollView+MJRefresh.h" @implementation UICollectionViewLayout (MJRefresh) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ MJRefreshExchangeImplementations(self.class, @selector(finalizeCollectionViewUpdates), self.class, @selector(mj_finalizeCollectionViewUpdates)); }); } - (void)mj_finalizeCollectionViewUpdates { [self mj_finalizeCollectionViewUpdates]; __kindof MJRefreshFooter *footer = self.collectionView.mj_footer; CGSize newSize = self.collectionViewContentSize; CGSize oldSize = self.collectionView.contentSize; if (footer != nil && !CGSizeEqualToSize(newSize, oldSize)) { NSDictionary *changed = @{ NSKeyValueChangeNewKey: [NSValue valueWithCGSize:newSize], NSKeyValueChangeOldKey: [NSValue valueWithCGSize:oldSize], }; [CATransaction begin]; [CATransaction setDisableActions:YES]; [footer scrollViewContentSizeDidChange:changed]; [CATransaction commit]; } } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/UIScrollView+MJExtension.h ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // UIScrollView+Extension.h // MJRefresh // // Created by MJ Lee on 14-5-28. // Copyright (c) 2014年 小码哥. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface UIScrollView (MJExtension) @property (readonly, nonatomic) UIEdgeInsets mj_inset; @property (assign, nonatomic) CGFloat mj_insetT; @property (assign, nonatomic) CGFloat mj_insetB; @property (assign, nonatomic) CGFloat mj_insetL; @property (assign, nonatomic) CGFloat mj_insetR; @property (assign, nonatomic) CGFloat mj_offsetX; @property (assign, nonatomic) CGFloat mj_offsetY; @property (assign, nonatomic) CGFloat mj_contentW; @property (assign, nonatomic) CGFloat mj_contentH; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/UIScrollView+MJExtension.m ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // UIScrollView+Extension.m // MJRefresh // // Created by MJ Lee on 14-5-28. // Copyright (c) 2014年 小码哥. All rights reserved. // #import "UIScrollView+MJExtension.h" #import #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability-new" @implementation UIScrollView (MJExtension) static BOOL respondsToAdjustedContentInset_; + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ respondsToAdjustedContentInset_ = [self instancesRespondToSelector:@selector(adjustedContentInset)]; }); } - (UIEdgeInsets)mj_inset { #ifdef __IPHONE_11_0 if (respondsToAdjustedContentInset_) { return self.adjustedContentInset; } #endif return self.contentInset; } - (void)setMj_insetT:(CGFloat)mj_insetT { UIEdgeInsets inset = self.contentInset; inset.top = mj_insetT; #ifdef __IPHONE_11_0 if (respondsToAdjustedContentInset_) { inset.top -= (self.adjustedContentInset.top - self.contentInset.top); } #endif self.contentInset = inset; } - (CGFloat)mj_insetT { return self.mj_inset.top; } - (void)setMj_insetB:(CGFloat)mj_insetB { UIEdgeInsets inset = self.contentInset; inset.bottom = mj_insetB; #ifdef __IPHONE_11_0 if (respondsToAdjustedContentInset_) { inset.bottom -= (self.adjustedContentInset.bottom - self.contentInset.bottom); } #endif self.contentInset = inset; } - (CGFloat)mj_insetB { return self.mj_inset.bottom; } - (void)setMj_insetL:(CGFloat)mj_insetL { UIEdgeInsets inset = self.contentInset; inset.left = mj_insetL; #ifdef __IPHONE_11_0 if (respondsToAdjustedContentInset_) { inset.left -= (self.adjustedContentInset.left - self.contentInset.left); } #endif self.contentInset = inset; } - (CGFloat)mj_insetL { return self.mj_inset.left; } - (void)setMj_insetR:(CGFloat)mj_insetR { UIEdgeInsets inset = self.contentInset; inset.right = mj_insetR; #ifdef __IPHONE_11_0 if (respondsToAdjustedContentInset_) { inset.right -= (self.adjustedContentInset.right - self.contentInset.right); } #endif self.contentInset = inset; } - (CGFloat)mj_insetR { return self.mj_inset.right; } - (void)setMj_offsetX:(CGFloat)mj_offsetX { CGPoint offset = self.contentOffset; offset.x = mj_offsetX; self.contentOffset = offset; } - (CGFloat)mj_offsetX { return self.contentOffset.x; } - (void)setMj_offsetY:(CGFloat)mj_offsetY { CGPoint offset = self.contentOffset; offset.y = mj_offsetY; self.contentOffset = offset; } - (CGFloat)mj_offsetY { return self.contentOffset.y; } - (void)setMj_contentW:(CGFloat)mj_contentW { CGSize size = self.contentSize; size.width = mj_contentW; self.contentSize = size; } - (CGFloat)mj_contentW { return self.contentSize.width; } - (void)setMj_contentH:(CGFloat)mj_contentH { CGSize size = self.contentSize; size.height = mj_contentH; self.contentSize = size; } - (CGFloat)mj_contentH { return self.contentSize.height; } @end #pragma clang diagnostic pop ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/UIScrollView+MJRefresh.h ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // UIScrollView+MJRefresh.h // MJRefresh // // Created by MJ Lee on 15/3/4. // Copyright (c) 2015年 小码哥. All rights reserved. // 给ScrollView增加下拉刷新、上拉刷新、 左滑刷新的功能 #import #if __has_include() #import #else #import "MJRefreshConst.h" #endif @class MJRefreshHeader, MJRefreshFooter, MJRefreshTrailer; NS_ASSUME_NONNULL_BEGIN @interface UIScrollView (MJRefresh) /** 下拉刷新控件 */ @property (strong, nonatomic, nullable) MJRefreshHeader *mj_header; @property (strong, nonatomic, nullable) MJRefreshHeader *header MJRefreshDeprecated("使用mj_header"); /** 上拉刷新控件 */ @property (strong, nonatomic, nullable) MJRefreshFooter *mj_footer; @property (strong, nonatomic, nullable) MJRefreshFooter *footer MJRefreshDeprecated("使用mj_footer"); /** 左滑刷新控件 */ @property (strong, nonatomic, nullable) MJRefreshTrailer *mj_trailer; #pragma mark - other - (NSInteger)mj_totalDataCount; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/UIScrollView+MJRefresh.m ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // UIScrollView+MJRefresh.m // MJRefresh // // Created by MJ Lee on 15/3/4. // Copyright (c) 2015年 小码哥. All rights reserved. // #import "UIScrollView+MJRefresh.h" #import "MJRefreshHeader.h" #import "MJRefreshFooter.h" #import "MJRefreshTrailer.h" #import @implementation UIScrollView (MJRefresh) #pragma mark - header static const char MJRefreshHeaderKey = '\0'; - (void)setMj_header:(MJRefreshHeader *)mj_header { if (mj_header != self.mj_header) { // 删除旧的,添加新的 [self.mj_header removeFromSuperview]; if (mj_header) { [self insertSubview:mj_header atIndex:0]; } // 存储新的 objc_setAssociatedObject(self, &MJRefreshHeaderKey, mj_header, OBJC_ASSOCIATION_RETAIN); } } - (MJRefreshHeader *)mj_header { return objc_getAssociatedObject(self, &MJRefreshHeaderKey); } #pragma mark - footer static const char MJRefreshFooterKey = '\0'; - (void)setMj_footer:(MJRefreshFooter *)mj_footer { if (mj_footer != self.mj_footer) { // 删除旧的,添加新的 [self.mj_footer removeFromSuperview]; if (mj_footer) { [self insertSubview:mj_footer atIndex:0]; } // 存储新的 objc_setAssociatedObject(self, &MJRefreshFooterKey, mj_footer, OBJC_ASSOCIATION_RETAIN); } } - (MJRefreshFooter *)mj_footer { return objc_getAssociatedObject(self, &MJRefreshFooterKey); } #pragma mark - footer static const char MJRefreshTrailerKey = '\0'; - (void)setMj_trailer:(MJRefreshTrailer *)mj_trailer { if (mj_trailer != self.mj_trailer) { // 删除旧的,添加新的 [self.mj_trailer removeFromSuperview]; if (mj_trailer) { [self insertSubview:mj_trailer atIndex:0]; } // 存储新的 objc_setAssociatedObject(self, &MJRefreshTrailerKey, mj_trailer, OBJC_ASSOCIATION_RETAIN); } } - (MJRefreshTrailer *)mj_trailer { return objc_getAssociatedObject(self, &MJRefreshTrailerKey); } #pragma mark - 过期 - (void)setFooter:(MJRefreshFooter *)footer { self.mj_footer = footer; } - (MJRefreshFooter *)footer { return self.mj_footer; } - (void)setHeader:(MJRefreshHeader *)header { self.mj_header = header; } - (MJRefreshHeader *)header { return self.mj_header; } #pragma mark - other - (NSInteger)mj_totalDataCount { NSInteger totalCount = 0; if ([self isKindOfClass:[UITableView class]]) { UITableView *tableView = (UITableView *)self; for (NSInteger section = 0; section < tableView.numberOfSections; section++) { totalCount += [tableView numberOfRowsInSection:section]; } } else if ([self isKindOfClass:[UICollectionView class]]) { UICollectionView *collectionView = (UICollectionView *)self; for (NSInteger section = 0; section < collectionView.numberOfSections; section++) { totalCount += [collectionView numberOfItemsInSection:section]; } } return totalCount; } @end ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/UIView+MJExtension.h ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // UIView+Extension.h // MJRefresh // // Created by MJ Lee on 14-5-28. // Copyright (c) 2014年 小码哥. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface UIView (MJExtension) @property (assign, nonatomic) CGFloat mj_x; @property (assign, nonatomic) CGFloat mj_y; @property (assign, nonatomic) CGFloat mj_w; @property (assign, nonatomic) CGFloat mj_h; @property (assign, nonatomic) CGSize mj_size; @property (assign, nonatomic) CGPoint mj_origin; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/MJRefresh/MJRefresh/UIView+MJExtension.m ================================================ // 代码地址: https://github.com/CoderMJLee/MJRefresh // UIView+Extension.m // MJRefresh // // Created by MJ Lee on 14-5-28. // Copyright (c) 2014年 小码哥. All rights reserved. // #import "UIView+MJExtension.h" @implementation UIView (MJExtension) - (void)setMj_x:(CGFloat)mj_x { CGRect frame = self.frame; frame.origin.x = mj_x; self.frame = frame; } - (CGFloat)mj_x { return self.frame.origin.x; } - (void)setMj_y:(CGFloat)mj_y { CGRect frame = self.frame; frame.origin.y = mj_y; self.frame = frame; } - (CGFloat)mj_y { return self.frame.origin.y; } - (void)setMj_w:(CGFloat)mj_w { CGRect frame = self.frame; frame.size.width = mj_w; self.frame = frame; } - (CGFloat)mj_w { return self.frame.size.width; } - (void)setMj_h:(CGFloat)mj_h { CGRect frame = self.frame; frame.size.height = mj_h; self.frame = frame; } - (CGFloat)mj_h { return self.frame.size.height; } - (void)setMj_size:(CGSize)mj_size { CGRect frame = self.frame; frame.size = mj_size; self.frame = frame; } - (CGSize)mj_size { return self.frame.size; } - (void)setMj_origin:(CGPoint)mj_origin { CGRect frame = self.frame; frame.origin = mj_origin; self.frame = frame; } - (CGPoint)mj_origin { return self.frame.origin; } @end ================================================ FILE: JetChat/Pods/MJRefresh/README.md ================================================ ## MJRefresh [![SPM supported](https://img.shields.io/badge/SPM-supported-4BC51D.svg?style=flat)](https://github.com/apple/swift-package-manager) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![podversion](https://img.shields.io/cocoapods/v/MJRefresh.svg)](https://cocoapods.org/pods/MJRefresh) * An easy way to use pull-to-refresh [📜✍🏻**Release Notes**: more details](https://github.com/CoderMJLee/MJRefresh/releases) ## Contents - New Features - [Dynamic i18n Switching](#dynamic_i18n_switching) - [SPM Supported](#spm_supported) - [Swift Chaining Grammar Supported](#swift_chaining_grammar_supported) * Getting Started * [Features【Support what kinds of controls to refresh】](#Support_what_kinds_of_controls_to_refresh) * [Installation【How to use MJRefresh】](#How_to_use_MJRefresh) * [Who's using【More than hundreds of Apps are using MJRefresh】](#More_than_hundreds_of_Apps_are_using_MJRefresh) * [Classes【The Class Structure Chart of MJRefresh】](#The_Class_Structure_Chart_of_MJRefresh) * Comment API * [MJRefreshComponent.h](#MJRefreshComponent.h) * [MJRefreshHeader.h](#MJRefreshHeader.h) * [MJRefreshFooter.h](#MJRefreshFooter.h) * [MJRefreshAutoFooter.h](#MJRefreshAutoFooter.h) * [MJRefreshTrailer.h](#MJRefreshTrailer.h) * Examples * [Reference](#Reference) * [The drop-down refresh 01-Default](#The_drop-down_refresh_01-Default) * [The drop-down refresh 02-Animation image](#The_drop-down_refresh_02-Animation_image) * [The drop-down refresh 03-Hide the time](#The_drop-down_refresh_03-Hide_the_time) * [The drop-down refresh 04-Hide status and time](#The_drop-down_refresh_04-Hide_status_and_time) * [The drop-down refresh 05-DIY title](#The_drop-down_refresh_05-DIY_title) * [The drop-down refresh 06-DIY the control of refresh](#The_drop-down_refresh_06-DIY_the_control_of_refresh) * [The pull to refresh 01-Default](#The_pull_to_refresh_01-Default) * [The pull to refresh 02-Animation image](#The_pull_to_refresh_02-Animation_image) * [The pull to refresh 03-Hide the title of refresh status](#The_pull_to_refresh_03-Hide_the_title_of_refresh_status) * [The pull to refresh 04-All loaded](#The_pull_to_refresh_04-All_loaded) * [The pull to refresh 05-DIY title](#The_pull_to_refresh_05-DIY_title) * [The pull to refresh 06-Hidden After loaded](#The_pull_to_refresh_06-Hidden_After_loaded) * [The pull to refresh 07-Automatic back of the pull01](#The_pull_to_refresh_07-Automatic_back_of_the_pull01) * [The pull to refresh 08-Automatic back of the pull02](#The_pull_to_refresh_08-Automatic_back_of_the_pull02) * [The pull to refresh 09-DIY the control of refresh(Automatic refresh)](#The_pull_to_refresh_09-DIY_the_control_of_refresh(Automatic_refresh)) * [The pull to refresh 10-DIY the control of refresh(Automatic back)](#The_pull_to_refresh_10-DIY_the_control_of_refresh(Automatic_back)) * [UICollectionView01-The pull and drop-down refresh](#UICollectionView01-The_pull_and_drop-down_refresh) * [UICollectionView02-The trailer refresh](#UICollectionView02-The_trailer_refresh) * [WKWebView01-The drop-down refresh](#WKWebView01-The_drop-down_refresh) * [Hope](#Hope) ## New Features ### Dynamic i18n Switching Now `MJRefresh components` will be rerendered automatically with `MJRefreshConfig.default.language` setting. #### Example Go `i18n` folder and see lots of cases. Simulator example is behind `i18n tab` in right-top corner. #### Setting language ```swift MJRefreshConfig.default.language = "zh-hans" ``` #### Setting i18n file name ```swift MJRefreshConfig.default.i18nFilename = "i18n File Name(not include type<.strings>)" ``` #### Setting i18n language bundle ```swift MJRefreshConfig.default.i18nBundle = ``` #### Adopting the feature in your DIY component 1. Just override `i18nDidChange` function and reset texts. ```swift // must use this localization methods Bundle.mj_localizedString(forKey: "") // or Bundle.mj_localizedString(forKey: "", value:"") override func i18nDidChange() { // Reset texts function setupTexts() // Make sure to call super after resetting texts. It will call placeSubViews for applying new layout. super.i18nDidChange() } ``` 2. Receiving `MJRefreshDidChangeLanguageNotification` notification. ### SPM Supported Released from [`3.7.1`](https://github.com/CoderMJLee/MJRefresh/releases/tag/3.7.1) ### Swift Chaining Grammar Supported ```swift // Example as MJRefreshNormalHeader func addRefreshHeader() { MJRefreshNormalHeader { [weak self] in // load some data }.autoChangeTransparency(true) .link(to: tableView) } ``` ## Support what kinds of controls to refresh * `UIScrollView`、`UITableView`、`UICollectionView`、`WKWebView` ## How to use MJRefresh * Installation with CocoaPods:`pod 'MJRefresh'` * Installation with [Carthage](https://github.com/Carthage/Carthage):`github "CoderMJLee/MJRefresh"` * Manual import: * Drag All files in the `MJRefresh` folder to project * Import the main file:`#import "MJRefresh.h"` ```objc Base Custom MJRefresh.bundle MJRefresh.h MJRefreshConst.h MJRefreshConst.m UIScrollView+MJExtension.h UIScrollView+MJExtension.m UIScrollView+MJRefresh.h UIScrollView+MJRefresh.m UIView+MJExtension.h UIView+MJExtension.m ``` ## More than hundreds of Apps are using MJRefresh * More information of App can focus on:[M了个J-博客园](http://www.cnblogs.com/mjios/p/4409853.html) ## The Class Structure Chart of MJRefresh ![](http://images0.cnblogs.com/blog2015/497279/201506/132232456139177.png) - `The class of red text` in the chart:You can use them directly - The drop-down refresh control types - Normal:`MJRefreshNormalHeader` - Gif:`MJRefreshGifHeader` - The pull to refresh control types - Auto refresh - Normal:`MJRefreshAutoNormalFooter` - Gif:`MJRefreshAutoGifFooter` - Auto Back - Normal:`MJRefreshBackNormalFooter` - Gif:`MJRefreshBackGifFooter` - `The class of non-red text` in the chart:For inheritance,to use DIY the control of refresh - About how to DIY the control of refresh,You can refer the Class in below Chart
## MJRefreshComponent.h ```objc /** The Base Class of refresh control */ @interface MJRefreshComponent : UIView #pragma mark - Control the state of Refresh /** BeginRefreshing */ - (void)beginRefreshing; /** EndRefreshing */ - (void)endRefreshing; /** IsRefreshing */ - (BOOL)isRefreshing; #pragma mark - Other /** According to the drag ratio to change alpha automatically */ @property (assign, nonatomic, getter=isAutomaticallyChangeAlpha) BOOL automaticallyChangeAlpha; @end ``` ## MJRefreshHeader.h ```objc @interface MJRefreshHeader : MJRefreshComponent /** Creat header */ + (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock; /** Creat header */ + (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; /** This key is used to storage the time that the last time of drown-down successfully */ @property (copy, nonatomic) NSString *lastUpdatedTimeKey; /** The last time of drown-down successfully */ @property (strong, nonatomic, readonly) NSDate *lastUpdatedTime; /** Ignored scrollView contentInset top */ @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetTop; @end ``` ## MJRefreshFooter.h ```objc @interface MJRefreshFooter : MJRefreshComponent /** Creat footer */ + (instancetype)footerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock; /** Creat footer */ + (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; /** NoticeNoMoreData */ - (void)noticeNoMoreData; /** ResetNoMoreData(Clear the status of NoMoreData ) */ - (void)resetNoMoreData; /** Ignored scrollView contentInset bottom */ @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetBottom; @end ``` ## MJRefreshAutoFooter.h ```objc @interface MJRefreshAutoFooter : MJRefreshFooter /** Is Automatically Refresh(Default is Yes) */ @property (assign, nonatomic, getter=isAutomaticallyRefresh) BOOL automaticallyRefresh; /** When there is much at the bottom of the control is automatically refresh(Default is 1.0,Is at the bottom of the control appears in full, will refresh automatically) */ @property (assign, nonatomic) CGFloat triggerAutomaticallyRefreshPercent; @end ``` ## MJRefreshTrailer.h ```objc @interface MJRefreshTrailer : MJRefreshComponent /** 创建trailer */ + (instancetype)trailerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock; /** 创建trailer */ + (instancetype)trailerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; /** 忽略多少scrollView的contentInset的right */ @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetRight; @end ``` ## Reference ```objc * Due to there are more functions of this framework,Don't write specific text describe its usage * You can directly reference examples MJTableViewController、MJCollectionViewController、MJWebViewController,More intuitive and fast. ``` ## The drop-down refresh 01-Default ```objc self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ //Call this Block When enter the refresh status automatically }]; 或 // Set the callback(Once you enter the refresh status,then call the action of target,that is call [self loadNewData]) self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)]; // Enter the refresh status immediately [self.tableView.mj_header beginRefreshing]; ``` ![(下拉刷新01-普通)](http://images0.cnblogs.com/blog2015/497279/201506/141204343486151.gif) ## The drop-down refresh 02-Animation image ```objc // Set the callback(一Once you enter the refresh status,then call the action of target,that is call [self loadNewData]) MJRefreshGifHeader *header = [MJRefreshGifHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)]; // Set the ordinary state of animated images [header setImages:idleImages forState:MJRefreshStateIdle]; // Set the pulling state of animated images(Enter the status of refreshing as soon as loosen) [header setImages:pullingImages forState:MJRefreshStatePulling]; // Set the refreshing state of animated images [header setImages:refreshingImages forState:MJRefreshStateRefreshing]; // Set header self.tableView.mj_header = header; ``` ![(下拉刷新02-动画图片)](http://images0.cnblogs.com/blog2015/497279/201506/141204402238389.gif) ## The drop-down refresh 03-Hide the time ```objc // Hide the time header.lastUpdatedTimeLabel.hidden = YES; ``` ![(下拉刷新03-隐藏时间)](http://images0.cnblogs.com/blog2015/497279/201506/141204456132944.gif) ## The drop-down refresh 04-Hide status and time ```objc // Hide the time header.lastUpdatedTimeLabel.hidden = YES; // Hide the status header.stateLabel.hidden = YES; ``` ![(下拉刷新04-隐藏状态和时间0)](http://images0.cnblogs.com/blog2015/497279/201506/141204508639539.gif) ## The drop-down refresh 05-DIY title ```objc // Set title [header setTitle:@"Pull down to refresh" forState:MJRefreshStateIdle]; [header setTitle:@"Release to refresh" forState:MJRefreshStatePulling]; [header setTitle:@"Loading ..." forState:MJRefreshStateRefreshing]; // Set font header.stateLabel.font = [UIFont systemFontOfSize:15]; header.lastUpdatedTimeLabel.font = [UIFont systemFontOfSize:14]; // Set textColor header.stateLabel.textColor = [UIColor redColor]; header.lastUpdatedTimeLabel.textColor = [UIColor blueColor]; ``` ![(下拉刷新05-自定义文字)](http://images0.cnblogs.com/blog2015/497279/201506/141204563633593.gif) ## The drop-down refresh 06-DIY the control of refresh ```objc self.tableView.mj_header = [MJDIYHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)]; // Implementation reference to MJDIYHeader.h和MJDIYHeader.m ``` ![(下拉刷新06-自定义刷新控件)](http://images0.cnblogs.com/blog2015/497279/201506/141205019261159.gif) ## The pull to refresh 01-Default ```objc self.tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{ //Call this Block When enter the refresh status automatically }]; 或 // Set the callback(Once you enter the refresh status,then call the action of target,that is call [self loadMoreData]) self.tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreData)]; ``` ![(上拉刷新01-默认)](http://images0.cnblogs.com/blog2015/497279/201506/141205090047696.gif) ## The pull to refresh 02-Animation image ```objc // Set the callback(Once you enter the refresh status,then call the action of target,that is call [self loadMoreData]) MJRefreshAutoGifFooter *footer = [MJRefreshAutoGifFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreData)]; // Set the refresh image [footer setImages:refreshingImages forState:MJRefreshStateRefreshing]; // Set footer self.tableView.mj_footer = footer; ``` ![(上拉刷新02-动画图片)](http://images0.cnblogs.com/blog2015/497279/201506/141205141445793.gif) ## The pull to refresh 03-Hide the title of refresh status ```objc // Hide the title of refresh status footer.refreshingTitleHidden = YES; // If does have not above method,then use footer.stateLabel.hidden = YES; ``` ![(上拉刷新03-隐藏刷新状态的文字)](http://images0.cnblogs.com/blog2015/497279/201506/141205200985774.gif) ## The pull to refresh 04-All loaded ```objc //Become the status of NoMoreData [footer noticeNoMoreData]; ``` ![(上拉刷新04-全部加载完毕)](http://images0.cnblogs.com/blog2015/497279/201506/141205248634686.gif) ## The pull to refresh 05-DIY title ```objc // Set title [footer setTitle:@"Click or drag up to refresh" forState:MJRefreshStateIdle]; [footer setTitle:@"Loading more ..." forState:MJRefreshStateRefreshing]; [footer setTitle:@"No more data" forState:MJRefreshStateNoMoreData]; // Set font footer.stateLabel.font = [UIFont systemFontOfSize:17]; // Set textColor footer.stateLabel.textColor = [UIColor blueColor]; ``` ![(上拉刷新05-自定义文字)](http://images0.cnblogs.com/blog2015/497279/201506/141205295511153.gif) ## The pull to refresh 06-Hidden After loaded ```objc //Hidden current control of the pull to refresh self.tableView.mj_footer.hidden = YES; ``` ![(上拉刷新06-加载后隐藏)](http://images0.cnblogs.com/blog2015/497279/201506/141205343481821.gif) ## The pull to refresh 07-Automatic back of the pull01 ```objc self.tableView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreData)]; ``` ![(上拉刷新07-自动回弹的上拉01)](http://images0.cnblogs.com/blog2015/497279/201506/141205392239231.gif) ## The pull to refresh 08-Automatic back of the pull02 ```objc MJRefreshBackGifFooter *footer = [MJRefreshBackGifFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreData)]; // Set the normal state of the animated image [footer setImages:idleImages forState:MJRefreshStateIdle]; // Set the pulling state of animated images(Enter the status of refreshing as soon as loosen) [footer setImages:pullingImages forState:MJRefreshStatePulling]; // Set the refreshing state of animated images [footer setImages:refreshingImages forState:MJRefreshStateRefreshing]; // Set footer self.tableView.mj_footer = footer; ``` ![(上拉刷新07-自动回弹的上拉02)](http://images0.cnblogs.com/blog2015/497279/201506/141205441443628.gif) ## The pull to refresh 09-DIY the control of refresh(Automatic refresh) ```objc self.tableView.mj_footer = [MJDIYAutoFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreData)]; // Implementation reference to MJDIYAutoFooter.h和MJDIYAutoFooter.m ``` ![(上拉刷新09-自定义刷新控件(自动刷新))](http://images0.cnblogs.com/blog2015/497279/201506/141205500195866.gif) ## The pull to refresh 10-DIY the control of refresh(Automatic back) ```objc self.tableView.mj_footer = [MJDIYBackFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreData)]; // Implementation reference to MJDIYBackFooter.h和MJDIYBackFooter.m ``` ![(上拉刷新10-自定义刷新控件(自动回弹))](http://images0.cnblogs.com/blog2015/497279/201506/141205560666819.gif) ## UICollectionView01-The pull and drop-down refresh ```objc // The drop-down refresh self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ //Call this Block When enter the refresh status automatically }]; // The pull to refresh self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{ //Call this Block When enter the refresh status automatically }]; ``` ![(UICollectionView01-上下拉刷新)](http://images0.cnblogs.com/blog2015/497279/201506/141206021603758.gif) ## UICollectionView02-The trailer refresh ```objc // The trailer refresh self.collectionView.mj_trailer = [MJRefreshNormalTrailer trailerWithRefreshingBlock:^{ //Call this Block When enter the refresh status automatically }]; ``` ![(UICollectionView02-左拉刷新)](Gif/trailer_refresh.gif) ## WKWebView01-The drop-down refresh ```objc //Add the control of The drop-down refresh self.webView.scrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ //Call this Block When enter the refresh status automatically }]; ``` ![(UICollectionView01-上下拉刷新)](http://images0.cnblogs.com/blog2015/497279/201506/141206080514524.gif) ## Remind * ARC * iOS>=9.0 * iPhone \ iPad screen anyway ## 寻求志同道合的小伙伴 - 因本人工作忙,没有太多时间去维护MJRefresh,在此向广大框架使用者说声:非常抱歉!😞 - 现寻求志同道合的小伙伴一起维护此框架,有兴趣的小伙伴可以[发邮件](mailto:richermj123go@vip.qq.com)给我,非常感谢😊 - 如果一切OK,我将开放框架维护权限(github、pod等) - 目前已经找到3位小伙伴(^-^)V ================================================ FILE: JetChat/Pods/MJRefresh.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 044148683535C16980608537C5F57641 /* MJRefresh-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 88EC9CDE1F38CB917F06B8581C40EF9F /* MJRefresh-dummy.m */; }; 04B982CA09C3EEC1C2721A0E17FEFA70 /* MJRefreshConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 84717A5BABFB2F8DFEC12A963CB8E84F /* MJRefreshConfig.m */; }; 0631A6359C6E0AB9CDC7FC7494712B41 /* MJRefreshBackGifFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F1254624CFFF20A5BC1FDDD2A11C783 /* MJRefreshBackGifFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 064E4EBD8AFD0B1A8F4A19FF79877BDF /* NSBundle+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = AF153425B43C3B63B24DA04B5B549B35 /* NSBundle+MJRefresh.m */; }; 09AD0E8B43AD6A78D977BF2602E04C21 /* MJRefreshBackFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E4A7B3C28E83362DF5773769836CB50 /* MJRefreshBackFooter.m */; }; 0F6291F10BC74C816845A043FC23643B /* MJRefreshGifHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 552A5870302CAFBAE47233C5A1CEE886 /* MJRefreshGifHeader.m */; }; 1494B3436E9A256649430FAFFEC77A06 /* UICollectionViewLayout+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 41C870F0D2AED6473935AA49880C2FBB /* UICollectionViewLayout+MJRefresh.m */; }; 2A49BBFE0A0F2409103843EBED4DC163 /* MJRefreshFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 440DE16BCA9050A295451626E295365A /* MJRefreshFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2A5BF80AE6E38DBE4E6ACB42DFF32BD7 /* MJRefreshBackStateFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 38399A9DA3D4E0AD430E5B1DB845BF30 /* MJRefreshBackStateFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2E72898ECF760143B0E6B2E5ED3D899E /* UIScrollView+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C7F1661904FE1AA3D857A55203E7A8A /* UIScrollView+MJRefresh.m */; }; 30E921FD58AEA234351DB4E819F3D901 /* MJRefreshHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 597AFD62EE4F27A5EC3366F19A56C486 /* MJRefreshHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 36A174F76D6235A04247AE2A7286A47C /* MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = 43FA5A3519983D874088BA091F8D2F31 /* MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; 39A26AE6B330EB17FB5C5DFF5D8DDDDA /* MJRefreshBackNormalFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 110B6CB3BB90F69D8C666D0D5622E987 /* MJRefreshBackNormalFooter.m */; }; 39BFB68E73BF53D708803B87FD7EE9A3 /* UICollectionViewLayout+MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = 4843F0AB9CD66A398982860B23DB8C08 /* UICollectionViewLayout+MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4BFBB6C5DB5AE5FB828BAC90B317A15D /* MJRefreshBackNormalFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 2441D5ABF56275A2DDBB085CCBAD07F8 /* MJRefreshBackNormalFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4EF86F05B9601FC15CCF4630366E7DB5 /* MJRefreshAutoNormalFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = B93B60E92B5B06E2C169CBDF109FCF25 /* MJRefreshAutoNormalFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 55C88F53D787B5CD219A75F911348CFA /* MJRefresh.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 4FFC6CA961AC0BC2EDA9DAF248E20F9F /* MJRefresh.bundle */; }; 5BB11ECE71AA55DE558749834197A18F /* MJRefreshComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = 20C8C276C58BA33F0BE612A66EBB0F4F /* MJRefreshComponent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5FDDC41638DAB5526DB83EEDDE765480 /* MJRefreshTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = F24D5737E23F380E2A898E0A06A24144 /* MJRefreshTrailer.m */; }; 5FE13741788A0F1F3BBE358AF43D2D54 /* MJRefreshBackGifFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = C104589F417DBD4B9991FA27E7B1FC77 /* MJRefreshBackGifFooter.m */; }; 62282E81A85F9B3AAC8E63B35E04D120 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 44D8D7E522F43A834BE87B11A1631010 /* Foundation.framework */; }; 64CE5823916F5ABB5BC2C233C2BE615F /* UIView+MJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E49F183B4188644E8AAFCC9362BA03 /* UIView+MJExtension.m */; }; 6FE3402231E508D0BB1E65B214C33F3B /* MJRefreshNormalTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = 16B1D7318E43E810EA1C3FBC6B593BF6 /* MJRefreshNormalTrailer.m */; }; 70FFB5A7DB70F9CFF7E21C1DA3E17E01 /* MJRefreshStateHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 07CF168181A20529F6423699D107E0F9 /* MJRefreshStateHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7723A9E9155267D7906448928958642B /* MJRefreshFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = A8E6FDB2DB5C2983705FBFC2E8A72394 /* MJRefreshFooter.m */; }; 7964435863CAB2D78592AC59D8B2BFFD /* MJRefreshAutoGifFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 18525C2DF514DEAA9058936A4733DA89 /* MJRefreshAutoGifFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7CB29F84EBD91E4205E150D4D7D62B49 /* UIScrollView+MJExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = F72920975A4445262D665FD6A1A5C709 /* UIScrollView+MJExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7DA723284870CA3E34FBF8E5CD7D496A /* MJRefreshAutoFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = C8B3AEB9B5686FD63BCE2C766262199D /* MJRefreshAutoFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 84FF04545FA08788AB79B3CF82C6EC2A /* UIScrollView+MJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = FBE581D901FAEA98EE7A5F00053AF99F /* UIScrollView+MJExtension.m */; }; 856CCDC34B8E6ADDCD58C5DA33DB804D /* MJRefreshNormalTrailer.h in Headers */ = {isa = PBXBuildFile; fileRef = F22B891B577B7F5B3314DFAE6C8F082B /* MJRefreshNormalTrailer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8814B3AF495A5C5F49BD2EF60E34F7C0 /* MJRefreshConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = BE5D1E958F378B4FF7DB66A35205E336 /* MJRefreshConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; 99D22CDEEEC491ED6107FD2C8DC86C1A /* MJRefreshStateTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = F48BDC185FCBDCD47E715F70771286B0 /* MJRefreshStateTrailer.m */; }; 9DA915A8839FC6E659B1DBD64C2B2919 /* MJRefreshAutoNormalFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 86B31BFE09FEC0B110DA2C00E4BB1491 /* MJRefreshAutoNormalFooter.m */; }; 9FCF9642A629338D1436C5BEB2F92C7A /* MJRefreshComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 4384FFAF81B615AF96CD7F645DB137D7 /* MJRefreshComponent.m */; }; A3B657B524C1284D2A568DB9D90CEFE0 /* MJRefreshBackStateFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = A8C0591EB1CAB9072FA9ECC6202998D3 /* MJRefreshBackStateFooter.m */; }; A6DECF9EB28B651518899B0D9809C662 /* MJRefreshHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 79AB405B86F044A6AFA279EC64CB6107 /* MJRefreshHeader.m */; }; B799C6B90D9CCF7806DF237000BD3270 /* MJRefreshConst.m in Sources */ = {isa = PBXBuildFile; fileRef = A11834F399DA22FD7A2B0B202430F9F5 /* MJRefreshConst.m */; }; C2803AF134E94E3647FE96BE84DAB3F0 /* MJRefreshAutoFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 58470DEE9A8470EFE8266E1993A05241 /* MJRefreshAutoFooter.m */; }; CDCCA13F77984F0FF6942C457143B29B /* NSBundle+MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = 49196E29F6B3E998F36BD98B35A02A5F /* NSBundle+MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; CEFD08B2DCC67D97459F709E346C1853 /* UIScrollView+MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = BB2C25B72655E400E5ADB28F6E184252 /* UIScrollView+MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; CF01414B77A82D421A354D1E10EC86E4 /* MJRefreshNormalHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 57228039DF831E66B3DE48538A815CCB /* MJRefreshNormalHeader.m */; }; D330BC4099A8F1F02238E366602FE30B /* MJRefreshNormalHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = A8F0E60501D4E6514FAF4931A03DEB8C /* MJRefreshNormalHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; D3C3C611A329269A156F45DFDF9A8E91 /* MJRefreshTrailer.h in Headers */ = {isa = PBXBuildFile; fileRef = C4AECE341F3334977C288F281B85B8F1 /* MJRefreshTrailer.h */; settings = {ATTRIBUTES = (Public, ); }; }; DDC4F011871F4BC739261E836E57AA0D /* MJRefreshStateTrailer.h in Headers */ = {isa = PBXBuildFile; fileRef = EDFABDED2A4C1393CAAD1AFE0F9F7150 /* MJRefreshStateTrailer.h */; settings = {ATTRIBUTES = (Public, ); }; }; E1ED067CC110CA06D142331F4DB11006 /* MJRefreshAutoStateFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = E4AB49BB3F3F31BAB69A28EA8AA6E3AF /* MJRefreshAutoStateFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; E408AB9C050888D7D1464B48BAEA1A48 /* MJRefreshBackFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = CD5B4D3371949F610ADDD9FC67D4B6C4 /* MJRefreshBackFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; EA4AC451B3B3F5C04A5F579A0B6FC13D /* MJRefreshStateHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ADEC2938E2C9610A0A5A75219C3F346 /* MJRefreshStateHeader.m */; }; EAB0EC8F738FB7DF533ACDB36EABB695 /* MJRefreshConst.h in Headers */ = {isa = PBXBuildFile; fileRef = C5E2F2E4535C8E281B04CD110E070B2A /* MJRefreshConst.h */; settings = {ATTRIBUTES = (Public, ); }; }; F0E39A057D909F1CB663FF2E9F575D88 /* MJRefreshGifHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FB69DE6E8D5CCB134C4D8CF5056E190 /* MJRefreshGifHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; F178D39A3D452970EE18BB815C051392 /* MJRefreshAutoGifFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = B7D65C0208542BE8CD4EF41E85425CA2 /* MJRefreshAutoGifFooter.m */; }; F3296330F194054B2141781FD74C899D /* MJRefreshAutoStateFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F96527B02271AA11CB11E6C013A4D2F /* MJRefreshAutoStateFooter.m */; }; F7DA170322BA81CFF80856A20AD7270F /* MJRefresh-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = CC82BD11EEC7A008772DBE988ED5B703 /* MJRefresh-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; FA78E940165504DE8B6E1B2749D74007 /* UIView+MJExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = A5529E7753E36B8C344544368027B253 /* UIView+MJExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 07CF168181A20529F6423699D107E0F9 /* MJRefreshStateHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshStateHeader.h; path = MJRefresh/Custom/Header/MJRefreshStateHeader.h; sourceTree = ""; }; 080FA0AC5E996B54E16C34A29EFFCE82 /* MJRefresh.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MJRefresh.release.xcconfig; sourceTree = ""; }; 110B6CB3BB90F69D8C666D0D5622E987 /* MJRefreshBackNormalFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackNormalFooter.m; path = MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.m; sourceTree = ""; }; 13E49F183B4188644E8AAFCC9362BA03 /* UIView+MJExtension.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+MJExtension.m"; path = "MJRefresh/UIView+MJExtension.m"; sourceTree = ""; }; 16B1D7318E43E810EA1C3FBC6B593BF6 /* MJRefreshNormalTrailer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshNormalTrailer.m; path = MJRefresh/Custom/Trailer/MJRefreshNormalTrailer.m; sourceTree = ""; }; 18525C2DF514DEAA9058936A4733DA89 /* MJRefreshAutoGifFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoGifFooter.h; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.h; sourceTree = ""; }; 1F5E209BD12893261ED8A11DD32E1213 /* MJRefresh-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MJRefresh-prefix.pch"; sourceTree = ""; }; 1FB69DE6E8D5CCB134C4D8CF5056E190 /* MJRefreshGifHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshGifHeader.h; path = MJRefresh/Custom/Header/MJRefreshGifHeader.h; sourceTree = ""; }; 20C8C276C58BA33F0BE612A66EBB0F4F /* MJRefreshComponent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshComponent.h; path = MJRefresh/Base/MJRefreshComponent.h; sourceTree = ""; }; 2441D5ABF56275A2DDBB085CCBAD07F8 /* MJRefreshBackNormalFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackNormalFooter.h; path = MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.h; sourceTree = ""; }; 2E4A7B3C28E83362DF5773769836CB50 /* MJRefreshBackFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackFooter.m; path = MJRefresh/Base/MJRefreshBackFooter.m; sourceTree = ""; }; 38399A9DA3D4E0AD430E5B1DB845BF30 /* MJRefreshBackStateFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackStateFooter.h; path = MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.h; sourceTree = ""; }; 41C870F0D2AED6473935AA49880C2FBB /* UICollectionViewLayout+MJRefresh.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UICollectionViewLayout+MJRefresh.m"; path = "MJRefresh/UICollectionViewLayout+MJRefresh.m"; sourceTree = ""; }; 4384FFAF81B615AF96CD7F645DB137D7 /* MJRefreshComponent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshComponent.m; path = MJRefresh/Base/MJRefreshComponent.m; sourceTree = ""; }; 43FA5A3519983D874088BA091F8D2F31 /* MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefresh.h; path = MJRefresh/MJRefresh.h; sourceTree = ""; }; 440DE16BCA9050A295451626E295365A /* MJRefreshFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshFooter.h; path = MJRefresh/Base/MJRefreshFooter.h; sourceTree = ""; }; 44D8D7E522F43A834BE87B11A1631010 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 4843F0AB9CD66A398982860B23DB8C08 /* UICollectionViewLayout+MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UICollectionViewLayout+MJRefresh.h"; path = "MJRefresh/UICollectionViewLayout+MJRefresh.h"; sourceTree = ""; }; 49196E29F6B3E998F36BD98B35A02A5F /* NSBundle+MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSBundle+MJRefresh.h"; path = "MJRefresh/NSBundle+MJRefresh.h"; sourceTree = ""; }; 4FFC6CA961AC0BC2EDA9DAF248E20F9F /* MJRefresh.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = MJRefresh.bundle; path = MJRefresh/MJRefresh.bundle; sourceTree = ""; }; 552A5870302CAFBAE47233C5A1CEE886 /* MJRefreshGifHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshGifHeader.m; path = MJRefresh/Custom/Header/MJRefreshGifHeader.m; sourceTree = ""; }; 57228039DF831E66B3DE48538A815CCB /* MJRefreshNormalHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshNormalHeader.m; path = MJRefresh/Custom/Header/MJRefreshNormalHeader.m; sourceTree = ""; }; 58470DEE9A8470EFE8266E1993A05241 /* MJRefreshAutoFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoFooter.m; path = MJRefresh/Base/MJRefreshAutoFooter.m; sourceTree = ""; }; 597AFD62EE4F27A5EC3366F19A56C486 /* MJRefreshHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshHeader.h; path = MJRefresh/Base/MJRefreshHeader.h; sourceTree = ""; }; 5C7F1661904FE1AA3D857A55203E7A8A /* UIScrollView+MJRefresh.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIScrollView+MJRefresh.m"; path = "MJRefresh/UIScrollView+MJRefresh.m"; sourceTree = ""; }; 5F96527B02271AA11CB11E6C013A4D2F /* MJRefreshAutoStateFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoStateFooter.m; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.m; sourceTree = ""; }; 6F1254624CFFF20A5BC1FDDD2A11C783 /* MJRefreshBackGifFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackGifFooter.h; path = MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.h; sourceTree = ""; }; 79AB405B86F044A6AFA279EC64CB6107 /* MJRefreshHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshHeader.m; path = MJRefresh/Base/MJRefreshHeader.m; sourceTree = ""; }; 7ADEC2938E2C9610A0A5A75219C3F346 /* MJRefreshStateHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshStateHeader.m; path = MJRefresh/Custom/Header/MJRefreshStateHeader.m; sourceTree = ""; }; 7F7EA281EE487F53B98FE21ADE26B2D0 /* MJRefresh */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = MJRefresh; path = MJRefresh.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 84717A5BABFB2F8DFEC12A963CB8E84F /* MJRefreshConfig.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshConfig.m; path = MJRefresh/MJRefreshConfig.m; sourceTree = ""; }; 86B31BFE09FEC0B110DA2C00E4BB1491 /* MJRefreshAutoNormalFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoNormalFooter.m; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.m; sourceTree = ""; }; 88EC9CDE1F38CB917F06B8581C40EF9F /* MJRefresh-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "MJRefresh-dummy.m"; sourceTree = ""; }; 8C70348EC26F3798A336467D302EB516 /* MJRefresh.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = MJRefresh.modulemap; sourceTree = ""; }; A11834F399DA22FD7A2B0B202430F9F5 /* MJRefreshConst.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshConst.m; path = MJRefresh/MJRefreshConst.m; sourceTree = ""; }; A1955EA45E021BB2F8316773BDF4A81A /* MJRefresh-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "MJRefresh-Info.plist"; sourceTree = ""; }; A5529E7753E36B8C344544368027B253 /* UIView+MJExtension.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+MJExtension.h"; path = "MJRefresh/UIView+MJExtension.h"; sourceTree = ""; }; A8C0591EB1CAB9072FA9ECC6202998D3 /* MJRefreshBackStateFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackStateFooter.m; path = MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.m; sourceTree = ""; }; A8E6FDB2DB5C2983705FBFC2E8A72394 /* MJRefreshFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshFooter.m; path = MJRefresh/Base/MJRefreshFooter.m; sourceTree = ""; }; A8F0E60501D4E6514FAF4931A03DEB8C /* MJRefreshNormalHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshNormalHeader.h; path = MJRefresh/Custom/Header/MJRefreshNormalHeader.h; sourceTree = ""; }; AF153425B43C3B63B24DA04B5B549B35 /* NSBundle+MJRefresh.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSBundle+MJRefresh.m"; path = "MJRefresh/NSBundle+MJRefresh.m"; sourceTree = ""; }; B7D65C0208542BE8CD4EF41E85425CA2 /* MJRefreshAutoGifFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoGifFooter.m; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.m; sourceTree = ""; }; B93B60E92B5B06E2C169CBDF109FCF25 /* MJRefreshAutoNormalFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoNormalFooter.h; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.h; sourceTree = ""; }; BB2C25B72655E400E5ADB28F6E184252 /* UIScrollView+MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIScrollView+MJRefresh.h"; path = "MJRefresh/UIScrollView+MJRefresh.h"; sourceTree = ""; }; BD928A31DF7414D699D39C07A7D4D90D /* MJRefresh.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MJRefresh.debug.xcconfig; sourceTree = ""; }; BE5D1E958F378B4FF7DB66A35205E336 /* MJRefreshConfig.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshConfig.h; path = MJRefresh/MJRefreshConfig.h; sourceTree = ""; }; C104589F417DBD4B9991FA27E7B1FC77 /* MJRefreshBackGifFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackGifFooter.m; path = MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.m; sourceTree = ""; }; C4AECE341F3334977C288F281B85B8F1 /* MJRefreshTrailer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshTrailer.h; path = MJRefresh/Base/MJRefreshTrailer.h; sourceTree = ""; }; C5E2F2E4535C8E281B04CD110E070B2A /* MJRefreshConst.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshConst.h; path = MJRefresh/MJRefreshConst.h; sourceTree = ""; }; C8B3AEB9B5686FD63BCE2C766262199D /* MJRefreshAutoFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoFooter.h; path = MJRefresh/Base/MJRefreshAutoFooter.h; sourceTree = ""; }; CC82BD11EEC7A008772DBE988ED5B703 /* MJRefresh-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MJRefresh-umbrella.h"; sourceTree = ""; }; CD5B4D3371949F610ADDD9FC67D4B6C4 /* MJRefreshBackFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackFooter.h; path = MJRefresh/Base/MJRefreshBackFooter.h; sourceTree = ""; }; E4AB49BB3F3F31BAB69A28EA8AA6E3AF /* MJRefreshAutoStateFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoStateFooter.h; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.h; sourceTree = ""; }; EDFABDED2A4C1393CAAD1AFE0F9F7150 /* MJRefreshStateTrailer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshStateTrailer.h; path = MJRefresh/Custom/Trailer/MJRefreshStateTrailer.h; sourceTree = ""; }; F22B891B577B7F5B3314DFAE6C8F082B /* MJRefreshNormalTrailer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshNormalTrailer.h; path = MJRefresh/Custom/Trailer/MJRefreshNormalTrailer.h; sourceTree = ""; }; F24D5737E23F380E2A898E0A06A24144 /* MJRefreshTrailer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshTrailer.m; path = MJRefresh/Base/MJRefreshTrailer.m; sourceTree = ""; }; F48BDC185FCBDCD47E715F70771286B0 /* MJRefreshStateTrailer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshStateTrailer.m; path = MJRefresh/Custom/Trailer/MJRefreshStateTrailer.m; sourceTree = ""; }; F72920975A4445262D665FD6A1A5C709 /* UIScrollView+MJExtension.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIScrollView+MJExtension.h"; path = "MJRefresh/UIScrollView+MJExtension.h"; sourceTree = ""; }; FBE581D901FAEA98EE7A5F00053AF99F /* UIScrollView+MJExtension.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIScrollView+MJExtension.m"; path = "MJRefresh/UIScrollView+MJExtension.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ ECF6A1047F7BFDF799E9A4E555BA6DFB /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 62282E81A85F9B3AAC8E63B35E04D120 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2241B6329EAC3709B00192C907CC6349 /* Products */ = { isa = PBXGroup; children = ( 7F7EA281EE487F53B98FE21ADE26B2D0 /* MJRefresh */, ); name = Products; sourceTree = ""; }; 4404FFA44EAF8A2EF28D46697C23A1B7 /* Support Files */ = { isa = PBXGroup; children = ( 8C70348EC26F3798A336467D302EB516 /* MJRefresh.modulemap */, 88EC9CDE1F38CB917F06B8581C40EF9F /* MJRefresh-dummy.m */, A1955EA45E021BB2F8316773BDF4A81A /* MJRefresh-Info.plist */, 1F5E209BD12893261ED8A11DD32E1213 /* MJRefresh-prefix.pch */, CC82BD11EEC7A008772DBE988ED5B703 /* MJRefresh-umbrella.h */, BD928A31DF7414D699D39C07A7D4D90D /* MJRefresh.debug.xcconfig */, 080FA0AC5E996B54E16C34A29EFFCE82 /* MJRefresh.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/MJRefresh"; sourceTree = ""; }; 9E19B62BAB71AD8BD42C1EC23B18FCFA /* MJRefresh */ = { isa = PBXGroup; children = ( 43FA5A3519983D874088BA091F8D2F31 /* MJRefresh.h */, C8B3AEB9B5686FD63BCE2C766262199D /* MJRefreshAutoFooter.h */, 58470DEE9A8470EFE8266E1993A05241 /* MJRefreshAutoFooter.m */, 18525C2DF514DEAA9058936A4733DA89 /* MJRefreshAutoGifFooter.h */, B7D65C0208542BE8CD4EF41E85425CA2 /* MJRefreshAutoGifFooter.m */, B93B60E92B5B06E2C169CBDF109FCF25 /* MJRefreshAutoNormalFooter.h */, 86B31BFE09FEC0B110DA2C00E4BB1491 /* MJRefreshAutoNormalFooter.m */, E4AB49BB3F3F31BAB69A28EA8AA6E3AF /* MJRefreshAutoStateFooter.h */, 5F96527B02271AA11CB11E6C013A4D2F /* MJRefreshAutoStateFooter.m */, CD5B4D3371949F610ADDD9FC67D4B6C4 /* MJRefreshBackFooter.h */, 2E4A7B3C28E83362DF5773769836CB50 /* MJRefreshBackFooter.m */, 6F1254624CFFF20A5BC1FDDD2A11C783 /* MJRefreshBackGifFooter.h */, C104589F417DBD4B9991FA27E7B1FC77 /* MJRefreshBackGifFooter.m */, 2441D5ABF56275A2DDBB085CCBAD07F8 /* MJRefreshBackNormalFooter.h */, 110B6CB3BB90F69D8C666D0D5622E987 /* MJRefreshBackNormalFooter.m */, 38399A9DA3D4E0AD430E5B1DB845BF30 /* MJRefreshBackStateFooter.h */, A8C0591EB1CAB9072FA9ECC6202998D3 /* MJRefreshBackStateFooter.m */, 20C8C276C58BA33F0BE612A66EBB0F4F /* MJRefreshComponent.h */, 4384FFAF81B615AF96CD7F645DB137D7 /* MJRefreshComponent.m */, BE5D1E958F378B4FF7DB66A35205E336 /* MJRefreshConfig.h */, 84717A5BABFB2F8DFEC12A963CB8E84F /* MJRefreshConfig.m */, C5E2F2E4535C8E281B04CD110E070B2A /* MJRefreshConst.h */, A11834F399DA22FD7A2B0B202430F9F5 /* MJRefreshConst.m */, 440DE16BCA9050A295451626E295365A /* MJRefreshFooter.h */, A8E6FDB2DB5C2983705FBFC2E8A72394 /* MJRefreshFooter.m */, 1FB69DE6E8D5CCB134C4D8CF5056E190 /* MJRefreshGifHeader.h */, 552A5870302CAFBAE47233C5A1CEE886 /* MJRefreshGifHeader.m */, 597AFD62EE4F27A5EC3366F19A56C486 /* MJRefreshHeader.h */, 79AB405B86F044A6AFA279EC64CB6107 /* MJRefreshHeader.m */, A8F0E60501D4E6514FAF4931A03DEB8C /* MJRefreshNormalHeader.h */, 57228039DF831E66B3DE48538A815CCB /* MJRefreshNormalHeader.m */, F22B891B577B7F5B3314DFAE6C8F082B /* MJRefreshNormalTrailer.h */, 16B1D7318E43E810EA1C3FBC6B593BF6 /* MJRefreshNormalTrailer.m */, 07CF168181A20529F6423699D107E0F9 /* MJRefreshStateHeader.h */, 7ADEC2938E2C9610A0A5A75219C3F346 /* MJRefreshStateHeader.m */, EDFABDED2A4C1393CAAD1AFE0F9F7150 /* MJRefreshStateTrailer.h */, F48BDC185FCBDCD47E715F70771286B0 /* MJRefreshStateTrailer.m */, C4AECE341F3334977C288F281B85B8F1 /* MJRefreshTrailer.h */, F24D5737E23F380E2A898E0A06A24144 /* MJRefreshTrailer.m */, 49196E29F6B3E998F36BD98B35A02A5F /* NSBundle+MJRefresh.h */, AF153425B43C3B63B24DA04B5B549B35 /* NSBundle+MJRefresh.m */, 4843F0AB9CD66A398982860B23DB8C08 /* UICollectionViewLayout+MJRefresh.h */, 41C870F0D2AED6473935AA49880C2FBB /* UICollectionViewLayout+MJRefresh.m */, F72920975A4445262D665FD6A1A5C709 /* UIScrollView+MJExtension.h */, FBE581D901FAEA98EE7A5F00053AF99F /* UIScrollView+MJExtension.m */, BB2C25B72655E400E5ADB28F6E184252 /* UIScrollView+MJRefresh.h */, 5C7F1661904FE1AA3D857A55203E7A8A /* UIScrollView+MJRefresh.m */, A5529E7753E36B8C344544368027B253 /* UIView+MJExtension.h */, 13E49F183B4188644E8AAFCC9362BA03 /* UIView+MJExtension.m */, CE1B93330A01D9F3EA6C1D248E41A976 /* Resources */, 4404FFA44EAF8A2EF28D46697C23A1B7 /* Support Files */, ); name = MJRefresh; path = MJRefresh; sourceTree = ""; }; AEAEFC226C911DF65796EF36956BD39F /* iOS */ = { isa = PBXGroup; children = ( 44D8D7E522F43A834BE87B11A1631010 /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; B95AE787CE3B21DBD82B3F8B8758A70C = { isa = PBXGroup; children = ( E7C90B4B2955B2339B9E591667727EE3 /* Frameworks */, 9E19B62BAB71AD8BD42C1EC23B18FCFA /* MJRefresh */, 2241B6329EAC3709B00192C907CC6349 /* Products */, ); sourceTree = ""; }; CE1B93330A01D9F3EA6C1D248E41A976 /* Resources */ = { isa = PBXGroup; children = ( 4FFC6CA961AC0BC2EDA9DAF248E20F9F /* MJRefresh.bundle */, ); name = Resources; sourceTree = ""; }; E7C90B4B2955B2339B9E591667727EE3 /* Frameworks */ = { isa = PBXGroup; children = ( AEAEFC226C911DF65796EF36956BD39F /* iOS */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 97436D20B895DD989F1F9F9E8431826C /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 36A174F76D6235A04247AE2A7286A47C /* MJRefresh.h in Headers */, F7DA170322BA81CFF80856A20AD7270F /* MJRefresh-umbrella.h in Headers */, 7DA723284870CA3E34FBF8E5CD7D496A /* MJRefreshAutoFooter.h in Headers */, 7964435863CAB2D78592AC59D8B2BFFD /* MJRefreshAutoGifFooter.h in Headers */, 4EF86F05B9601FC15CCF4630366E7DB5 /* MJRefreshAutoNormalFooter.h in Headers */, E1ED067CC110CA06D142331F4DB11006 /* MJRefreshAutoStateFooter.h in Headers */, E408AB9C050888D7D1464B48BAEA1A48 /* MJRefreshBackFooter.h in Headers */, 0631A6359C6E0AB9CDC7FC7494712B41 /* MJRefreshBackGifFooter.h in Headers */, 4BFBB6C5DB5AE5FB828BAC90B317A15D /* MJRefreshBackNormalFooter.h in Headers */, 2A5BF80AE6E38DBE4E6ACB42DFF32BD7 /* MJRefreshBackStateFooter.h in Headers */, 5BB11ECE71AA55DE558749834197A18F /* MJRefreshComponent.h in Headers */, 8814B3AF495A5C5F49BD2EF60E34F7C0 /* MJRefreshConfig.h in Headers */, EAB0EC8F738FB7DF533ACDB36EABB695 /* MJRefreshConst.h in Headers */, 2A49BBFE0A0F2409103843EBED4DC163 /* MJRefreshFooter.h in Headers */, F0E39A057D909F1CB663FF2E9F575D88 /* MJRefreshGifHeader.h in Headers */, 30E921FD58AEA234351DB4E819F3D901 /* MJRefreshHeader.h in Headers */, D330BC4099A8F1F02238E366602FE30B /* MJRefreshNormalHeader.h in Headers */, 856CCDC34B8E6ADDCD58C5DA33DB804D /* MJRefreshNormalTrailer.h in Headers */, 70FFB5A7DB70F9CFF7E21C1DA3E17E01 /* MJRefreshStateHeader.h in Headers */, DDC4F011871F4BC739261E836E57AA0D /* MJRefreshStateTrailer.h in Headers */, D3C3C611A329269A156F45DFDF9A8E91 /* MJRefreshTrailer.h in Headers */, CDCCA13F77984F0FF6942C457143B29B /* NSBundle+MJRefresh.h in Headers */, 39BFB68E73BF53D708803B87FD7EE9A3 /* UICollectionViewLayout+MJRefresh.h in Headers */, 7CB29F84EBD91E4205E150D4D7D62B49 /* UIScrollView+MJExtension.h in Headers */, CEFD08B2DCC67D97459F709E346C1853 /* UIScrollView+MJRefresh.h in Headers */, FA78E940165504DE8B6E1B2749D74007 /* UIView+MJExtension.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ E94AEF767442EDAAAAB2129CDFA2C2ED /* MJRefresh */ = { isa = PBXNativeTarget; buildConfigurationList = FF85C1B78EA845501A2D609AC51AE61F /* Build configuration list for PBXNativeTarget "MJRefresh" */; buildPhases = ( 97436D20B895DD989F1F9F9E8431826C /* Headers */, DDA7CA5162DB1F2930B422FA2BAFB1B8 /* Sources */, ECF6A1047F7BFDF799E9A4E555BA6DFB /* Frameworks */, 658B29EBF202D32FFE881C4071D1EBDA /* Resources */, ); buildRules = ( ); dependencies = ( ); name = MJRefresh; productName = MJRefresh; productReference = 7F7EA281EE487F53B98FE21ADE26B2D0 /* MJRefresh */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 3B10AF78032EF059DE917055F34E9117 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 3C89D7681CC38E9662853C5EFE32E911 /* Build configuration list for PBXProject "MJRefresh" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = B95AE787CE3B21DBD82B3F8B8758A70C; productRefGroup = 2241B6329EAC3709B00192C907CC6349 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( E94AEF767442EDAAAAB2129CDFA2C2ED /* MJRefresh */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 658B29EBF202D32FFE881C4071D1EBDA /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 55C88F53D787B5CD219A75F911348CFA /* MJRefresh.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ DDA7CA5162DB1F2930B422FA2BAFB1B8 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 044148683535C16980608537C5F57641 /* MJRefresh-dummy.m in Sources */, C2803AF134E94E3647FE96BE84DAB3F0 /* MJRefreshAutoFooter.m in Sources */, F178D39A3D452970EE18BB815C051392 /* MJRefreshAutoGifFooter.m in Sources */, 9DA915A8839FC6E659B1DBD64C2B2919 /* MJRefreshAutoNormalFooter.m in Sources */, F3296330F194054B2141781FD74C899D /* MJRefreshAutoStateFooter.m in Sources */, 09AD0E8B43AD6A78D977BF2602E04C21 /* MJRefreshBackFooter.m in Sources */, 5FE13741788A0F1F3BBE358AF43D2D54 /* MJRefreshBackGifFooter.m in Sources */, 39A26AE6B330EB17FB5C5DFF5D8DDDDA /* MJRefreshBackNormalFooter.m in Sources */, A3B657B524C1284D2A568DB9D90CEFE0 /* MJRefreshBackStateFooter.m in Sources */, 9FCF9642A629338D1436C5BEB2F92C7A /* MJRefreshComponent.m in Sources */, 04B982CA09C3EEC1C2721A0E17FEFA70 /* MJRefreshConfig.m in Sources */, B799C6B90D9CCF7806DF237000BD3270 /* MJRefreshConst.m in Sources */, 7723A9E9155267D7906448928958642B /* MJRefreshFooter.m in Sources */, 0F6291F10BC74C816845A043FC23643B /* MJRefreshGifHeader.m in Sources */, A6DECF9EB28B651518899B0D9809C662 /* MJRefreshHeader.m in Sources */, CF01414B77A82D421A354D1E10EC86E4 /* MJRefreshNormalHeader.m in Sources */, 6FE3402231E508D0BB1E65B214C33F3B /* MJRefreshNormalTrailer.m in Sources */, EA4AC451B3B3F5C04A5F579A0B6FC13D /* MJRefreshStateHeader.m in Sources */, 99D22CDEEEC491ED6107FD2C8DC86C1A /* MJRefreshStateTrailer.m in Sources */, 5FDDC41638DAB5526DB83EEDDE765480 /* MJRefreshTrailer.m in Sources */, 064E4EBD8AFD0B1A8F4A19FF79877BDF /* NSBundle+MJRefresh.m in Sources */, 1494B3436E9A256649430FAFFEC77A06 /* UICollectionViewLayout+MJRefresh.m in Sources */, 84FF04545FA08788AB79B3CF82C6EC2A /* UIScrollView+MJExtension.m in Sources */, 2E72898ECF760143B0E6B2E5ED3D899E /* UIScrollView+MJRefresh.m in Sources */, 64CE5823916F5ABB5BC2C233C2BE615F /* UIView+MJExtension.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2F102BB15FD3318862EC4E8411079D72 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 080FA0AC5E996B54E16C34A29EFFCE82 /* MJRefresh.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/MJRefresh/MJRefresh-prefix.pch"; INFOPLIST_FILE = "Target Support Files/MJRefresh/MJRefresh-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/MJRefresh/MJRefresh.modulemap"; PRODUCT_MODULE_NAME = MJRefresh; PRODUCT_NAME = MJRefresh; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 7B1B74251A3504D935018B581CBFE811 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; B8FA03E562237675A30E4C9C55BFEEA6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; CE858A41F8CE91C60CA080FCFBA7C8EA /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = BD928A31DF7414D699D39C07A7D4D90D /* MJRefresh.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/MJRefresh/MJRefresh-prefix.pch"; INFOPLIST_FILE = "Target Support Files/MJRefresh/MJRefresh-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/MJRefresh/MJRefresh.modulemap"; PRODUCT_MODULE_NAME = MJRefresh; PRODUCT_NAME = MJRefresh; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3C89D7681CC38E9662853C5EFE32E911 /* Build configuration list for PBXProject "MJRefresh" */ = { isa = XCConfigurationList; buildConfigurations = ( 7B1B74251A3504D935018B581CBFE811 /* Debug */, B8FA03E562237675A30E4C9C55BFEEA6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; FF85C1B78EA845501A2D609AC51AE61F /* Build configuration list for PBXNativeTarget "MJRefresh" */ = { isa = XCConfigurationList; buildConfigurations = ( CE858A41F8CE91C60CA080FCFBA7C8EA /* Debug */, 2F102BB15FD3318862EC4E8411079D72 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 3B10AF78032EF059DE917055F34E9117 /* Project object */; } ================================================ FILE: JetChat/Pods/Moya/License.md ================================================ The MIT License (MIT) Copyright (c) 2014-present Artsy, Ash Furrow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/Moya/Readme.md ================================================

# Moya 15.0.0 [![CircleCI](https://img.shields.io/circleci/project/github/Moya/Moya/master.svg)](https://circleci.com/gh/Moya/Moya/tree/master) [![codecov.io](https://codecov.io/github/Moya/Moya/coverage.svg?branch=master)](https://codecov.io/github/Moya/Moya?branch=master) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Accio supported](https://img.shields.io/badge/Accio-supported-0A7CF5.svg?style=flat)](https://github.com/JamitLabs/Accio) [![CocoaPods compatible](https://img.shields.io/cocoapods/v/Moya.svg)](https://cocoapods.org/pods/Moya) [![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) *A Chinese version of this document can be found [here](https://github.com/Moya/Moya/blob/master/Readme_CN.md).* You're a smart developer. You probably use [Alamofire](https://github.com/Alamofire/Alamofire) to abstract away access to `URLSession` and all those nasty details you don't really care about. But then, like lots of smart developers, you write ad hoc network abstraction layers. They are probably called "APIManager" or "NetworkModel", and they always end in tears. ![Moya Overview](web/diagram.png) Ad hoc network layers are common in iOS apps. They're bad for a few reasons: - Makes it hard to write new apps ("where do I begin?") - Makes it hard to maintain existing apps ("oh my god, this mess...") - Makes it hard to write unit tests ("how do I do this again?") So the basic idea of Moya is that we want some network abstraction layer that sufficiently encapsulates actually calling Alamofire directly. It should be simple enough that common things are easy, but comprehensive enough that complicated things are also easy. > If you use Alamofire to abstract away `URLSession`, why not use something to abstract away the nitty gritty of URLs, parameters, etc? Some awesome features of Moya: - Compile-time checking for correct API endpoint accesses. - Lets you define a clear usage of different endpoints with associated enum values. - Treats test stubs as first-class citizens so unit testing is super-easy. You can check out more about the project direction in the [vision document](https://github.com/Moya/Moya/blob/master/Vision.md). ## Sample Projects We have provided two sample projects in the repository. To use it download the repo, run `carthage update` to download the required libraries and open [Moya.xcodeproj](https://github.com/Moya/Moya/tree/master/Moya.xcodeproj). You'll see two schemes: `Basic` and `Multi-Target` - select one and then build & run! Source files for these are in the `Examples` directory in project navigator. Have fun! ## Project Status This project is actively under development, and is being used in [Artsy's new auction app](https://github.com/Artsy/eidolon). We consider it ready for production use. ## Installation ### Moya version vs Swift version. Below is a table that shows which version of Moya you should use for your Swift version. | Swift | Moya | RxMoya | ReactiveMoya | | ----- | -------------- |---------------- |--------------- | | 5.X | >= 13.0.0 | >= 13.0.0 | >= 13.0.0 | | 4.X | 9.0.0 - 12.0.1 | 10.0.0 - 12.0.1 | 9.0.0 - 12.0.1 | | 3.X | 8.0.0 - 8.0.5 | 8.0.0 - 8.0.5 | 8.0.0 - 8.0.5 | | 2.3 | 7.0.2 - 7.0.4 | 7.0.2 - 7.0.4 | 7.0.2 - 7.0.4 | | 2.2 | <= 7.0.1 | <= 7.0.1 | <= 7.0.1 | _Note: If you are using Swift 4.2 in your project, but you are using Xcode 10.2, Moya 13 should work correctly even though we use Swift 5.0._ **Upgrading to a new major version of Moya? Check out our [migration guides](https://github.com/Moya/Moya/blob/master/docs/MigrationGuides).** ### Swift Package Manager _Note: Instructions below are for using **SwiftPM** without the Xcode UI. It's the easiest to go to your Project Settings -> Swift Packages and add Moya from there._ To integrate using Apple's Swift package manager, without Xcode integration, add the following as a dependency to your `Package.swift`: ```swift .package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "15.0.0")) ``` and then specify `"Moya"` as a dependency of the Target in which you wish to use Moya. If you want to use reactive extensions, add also `"ReactiveMoya"`, `"RxMoya"` or `"CombineMoya"` as your target dependency respectively. Here's an example `PackageDescription`: ```swift // swift-tools-version:5.3 import PackageDescription let package = Package( name: "MyPackage", products: [ .library( name: "MyPackage", targets: ["MyPackage"]), ], dependencies: [ .package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "15.0.0")) ], targets: [ .target( name: "MyPackage", dependencies: ["ReactiveMoya"]) ] ) ``` Note: If you are using **ReactiveMoya**, we are using [our own fork of ReactiveSwift](https://github.com/Moya/ReactiveSwift). This fork adds 2 commits to remove testing dependencies on releases (starting 6.1.0). This is to prevent Xcode Previews on Xcode 11/11.1 to build testing dependencies (FB7316430). If you don't want to use our fork, you can just add another dependency to your SPM package list: `git@github.com:ReactiveCocoa/ReactiveSwift.git` and it should fetch the original repository. Combine note: if you're using **CombineMoya**, make sure that you use Xcode 11.5.0 or later. With earlier versions of Xcode you will have to manually add Combine as a weakly linked framework to your application target. ### Accio [Accio](https://github.com/JamitLabs/Accio) is a dependency manager based on SwiftPM which can build frameworks for iOS/macOS/tvOS/watchOS. Therefore the integration steps of Moya are exactly the same as described above. Once your `Package.swift` file is configured, run `accio update` instead of `swift package update`. ### CocoaPods For Moya, use the following entry in your Podfile: ```rb pod 'Moya', '~> 15.0' # or pod 'Moya/RxSwift', '~> 15.0' # or pod 'Moya/ReactiveSwift', '~> 15.0' #or pod 'Moya/Combine', '~> 15.0' ``` Then run `pod install`. In any file you'd like to use Moya in, don't forget to import the framework with `import Moya`. ### Carthage Carthage users can point to this repository and use whichever generated framework they'd like, `Moya`, `RxMoya`, `ReactiveMoya`, or `CombineMoya`. Make the following entry in your Cartfile: ``` github "Moya/Moya" ~> 15.0 ``` Then run `carthage update --use-xcframeworks`. If this is your first time using Carthage in the project, you'll need to go through some additional steps as explained [over at Carthage](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application). > NOTE: At this time, Carthage does not provide a way to build only specific repository submodules. All submodules and their dependencies will be built with the above command. However, you don't need to copy frameworks you aren't using into your project. For instance, if you aren't using `ReactiveSwift`, feel free to delete that framework along with `ReactiveMoya` from the Carthage Build directory after `carthage update` completes. Or if you are using `ReactiveSwift` but not `RxSwift` or `Combine`, then `RxMoya`, `RxTest`, `RxCocoa`, `CombineMoya` etc. can safely be deleted. ### Manually - Open up Terminal, `cd` into your top-level project directory, and run the following command *if* your project is not initialized as a git repository: ```bash $ git init ``` - Add Alamofire & Moya as a git [submodule](http://git-scm.com/docs/git-submodule) by running the following commands: ```bash $ git submodule add https://github.com/Alamofire/Alamofire.git $ git submodule add https://github.com/Moya/Moya.git ``` - Open the new `Alamofire` folder, and drag the `Alamofire.xcodeproj` into the Project Navigator of your application's Xcode project. Do the same with the `Moya.xcodeproj` in the `Moya` folder. > They should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter. - Verify that the deployment targets of the `xcodeproj`s match that of your application target in the Project Navigator. - Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar. - In the tab bar at the top of that window, open the "General" panel. - Click on the `+` button under the "Embedded Binaries" section. - You will see two different `Alamofire.xcodeproj` folders each with two different versions of the `Alamofire.framework` nested inside a `Products` folder. > It does not matter which `Products` folder you choose from, but it does matter whether you choose the top or bottom `Alamofire.framework`. - Select the top `Alamofire.framework` for iOS and the bottom one for macOS. > You can verify which one you selected by inspecting the build log for your project. The build target for `Alamofire` will be listed as either `Alamofire iOS`, `Alamofire macOS`, `Alamofire tvOS` or `Alamofire watchOS`. - Click on the `+` button under "Embedded Binaries" again and add the correct build target for `Moya`. - And that's it! > The three frameworks are automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device. ## Usage After [some setup](https://github.com/Moya/Moya/blob/master/docs/Examples/Basic.md), using Moya is really simple. You can access an API like this: ```swift provider = MoyaProvider() provider.request(.zen) { result in switch result { case let .success(moyaResponse): let data = moyaResponse.data let statusCode = moyaResponse.statusCode // do something with the response data or statusCode case let .failure(error): // this means there was a network failure - either the request // wasn't sent (connectivity), or no response was received (server // timed out). If the server responds with a 4xx or 5xx error, that // will be sent as a ".success"-ful response. } } ``` That's a basic example. Many API requests need parameters. Moya encodes these into the enum you use to access the endpoint, like this: ```swift provider = MoyaProvider() provider.request(.userProfile("ashfurrow")) { result in // do something with the result } ``` No more typos in URLs. No more missing parameter values. No more messing with parameter encoding. For more examples, see the [documentation](https://github.com/Moya/Moya/blob/master/docs/Examples). ## Reactive Extensions Even cooler are the reactive extensions. Moya provides reactive extensions for [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift), [RxSwift](https://github.com/ReactiveX/RxSwift), and [Combine](https://developer.apple.com/documentation/combine). ### ReactiveSwift [`ReactiveSwift` extension](https://github.com/Moya/Moya/blob/master/docs/ReactiveSwift.md) provides both `reactive.request(:callbackQueue:)` and `reactive.requestWithProgress(:callbackQueue:)` methods that immediately return `SignalProducer`s that you can start, bind, map, or whatever you want to do. To handle errors, for instance, we could do the following: ```swift provider = MoyaProvider() provider.reactive.request(.userProfile("ashfurrow")).start { event in switch event { case let .value(response): image = UIImage(data: response.data) case let .failed(error): print(error) default: break } } ``` ### RxSwift [`RxSwift` extension](https://github.com/Moya/Moya/blob/master/docs/RxSwift.md) also provide both `rx.request(:callbackQueue:)` and `rx.requestWithProgress(:callbackQueue:)` methods, but return type is different for both. In case of a normal `rx.request(:callbackQueue)`, the return type is `Single` which emits either single element or an error. In case of a `rx.requestWithProgress(:callbackQueue:)`, the return type is `Observable`, since we may get multiple events from progress and one last event which is a response. To handle errors, for instance, we could do the following: ```swift provider = MoyaProvider() provider.rx.request(.userProfile("ashfurrow")).subscribe { event in switch event { case let .success(response): image = UIImage(data: response.data) case let .error(error): print(error) } } ``` In addition to the option of using signals instead of callback blocks, there are also a series of signal operators for RxSwift and ReactiveSwift that will attempt to map the data received from the network response into either an image, some JSON, or a string, with `mapImage()`, `mapJSON()`, and `mapString()`, respectively. If the mapping is unsuccessful, you'll get an error on the signal. You also get handy methods for filtering out certain status codes. This means that you can place your code for handling API errors like 400's in the same places as code for handling invalid responses. ### Combine `Combine` extension provides `requestPublisher(:callbackQueue:)` and `requestWithProgressPublisher(:callbackQueue)` returning `AnyPublisher` and `AnyPublisher` respectively. Here's an example of `requestPublisher` usage: ```swift provider = MoyaProvider() let cancellable = provider.requestPublisher(.userProfile("ashfurrow")) .sink(receiveCompletion: { completion in guard case let .failure(error) = completion else { return } print(error) }, receiveValue: { response in image = UIImage(data: response.data) }) ``` ## Community Projects [Moya has a great community around it and some people have created some very helpful extensions](https://github.com/Moya/Moya/blob/master/docs/CommunityProjects.md). ## Contributing Hey! Do you like Moya? Awesome! We could actually really use your help! Open source isn't just writing code. Moya could use your help with any of the following: - Finding (and reporting!) bugs. - New feature suggestions. - Answering questions on issues. - Documentation improvements. - Reviewing pull requests. - Helping to manage issue priorities. - Fixing bugs/new features. If any of that sounds cool to you, send a pull request! After your first contribution, we will add you as a member to the repo so you can merge pull requests and help steer the ship :ship: You can read more details about that [in our contributor guidelines](https://github.com/Moya/Moya/blob/master/Contributing.md). Moya's community has a tremendous positive energy, and the maintainers are committed to keeping things awesome. Like [in the CocoaPods community](https://github.com/CocoaPods/CocoaPods/wiki/Communication-&-Design-Rules), always assume positive intent; even if a comment sounds mean-spirited, give the person the benefit of the doubt. Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by [its terms](https://github.com/Moya/Moya/blob/master/Code%20of%20Conduct.md). ### Adding new source files If you add or remove a source file from Moya, a corresponding change needs to be made to the `Moya.xcodeproj` project at the root of this repository. This project is used for Carthage. Don't worry, you'll get an automated warning when submitting a pull request if you forget. ### Help us improve Moya documentation Whether you’re a core member or a user trying it out for the first time, you can make a valuable contribution to Moya by improving the documentation. Help us by: - sending us feedback about something you thought was confusing or simply missing - suggesting better wording or ways of explaining certain topics - sending us a pull request via GitHub - improving the [Chinese documentation](https://github.com/Moya/Moya/blob/master/Readme_CN.md) ## License Moya is released under an MIT license. See [License.md](https://github.com/Moya/Moya/blob/master/License.md) for more information. ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/AnyEncodable.swift ================================================ import Foundation struct AnyEncodable: Encodable { private let encodable: Encodable public init(_ encodable: Encodable) { self.encodable = encodable } func encode(to encoder: Encoder) throws { try encodable.encode(to: encoder) } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Atomic.swift ================================================ // // Atomic.swift // Moya // // Created by Luciano Almeida on 15/12/19. // import Foundation @propertyWrapper final class Atomic { private var lock: NSRecursiveLock = NSRecursiveLock() private var value: Value var wrappedValue: Value { get { lock.lock(); defer { lock.unlock() } return value } set { lock.lock(); defer { lock.unlock() } value = newValue } } init(wrappedValue value: Value) { self.value = value } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Cancellable.swift ================================================ /// Protocol to define the opaque type returned from a request. public protocol Cancellable { /// A Boolean value stating whether a request is cancelled. var isCancelled: Bool { get } /// Cancels the represented request. func cancel() } internal class CancellableWrapper: Cancellable { internal var innerCancellable: Cancellable = SimpleCancellable() var isCancelled: Bool { innerCancellable.isCancelled } internal func cancel() { innerCancellable.cancel() } } internal class SimpleCancellable: Cancellable { var isCancelled = false func cancel() { isCancelled = true } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Endpoint.swift ================================================ import Foundation /// Used for stubbing responses. public enum EndpointSampleResponse { /// The network returned a response, including status code and data. case networkResponse(Int, Data) /// The network returned response which can be fully customized. case response(HTTPURLResponse, Data) /// The network failed to send the request, or failed to retrieve a response (eg a timeout). case networkError(NSError) } /// Class for reifying a target of the `Target` enum unto a concrete `Endpoint`. /// - Note: As of Moya 11.0.0 Endpoint is no longer generic. /// Existing code should work as is after removing the generic. /// See #1529 and #1524 for the discussion. open class Endpoint { public typealias SampleResponseClosure = () -> EndpointSampleResponse /// A string representation of the URL for the request. public let url: String /// A closure responsible for returning an `EndpointSampleResponse`. public let sampleResponseClosure: SampleResponseClosure /// The HTTP method for the request. public let method: Moya.Method /// The `Task` for the request. public let task: Task /// The HTTP header fields for the request. public let httpHeaderFields: [String: String]? public init(url: String, sampleResponseClosure: @escaping SampleResponseClosure, method: Moya.Method, task: Task, httpHeaderFields: [String: String]?) { self.url = url self.sampleResponseClosure = sampleResponseClosure self.method = method self.task = task self.httpHeaderFields = httpHeaderFields } /// Convenience method for creating a new `Endpoint` with the same properties as the receiver, but with added HTTP header fields. open func adding(newHTTPHeaderFields: [String: String]) -> Endpoint { Endpoint(url: url, sampleResponseClosure: sampleResponseClosure, method: method, task: task, httpHeaderFields: add(httpHeaderFields: newHTTPHeaderFields)) } /// Convenience method for creating a new `Endpoint` with the same properties as the receiver, but with replaced `task` parameter. open func replacing(task: Task) -> Endpoint { Endpoint(url: url, sampleResponseClosure: sampleResponseClosure, method: method, task: task, httpHeaderFields: httpHeaderFields) } fileprivate func add(httpHeaderFields headers: [String: String]?) -> [String: String]? { guard let unwrappedHeaders = headers, unwrappedHeaders.isEmpty == false else { return self.httpHeaderFields } var newHTTPHeaderFields = self.httpHeaderFields ?? [:] unwrappedHeaders.forEach { key, value in newHTTPHeaderFields[key] = value } return newHTTPHeaderFields } } /// Extension for converting an `Endpoint` into a `URLRequest`. public extension Endpoint { // swiftlint:disable cyclomatic_complexity /// Returns the `Endpoint` converted to a `URLRequest` if valid. Throws an error otherwise. func urlRequest() throws -> URLRequest { guard let requestURL = Foundation.URL(string: url) else { throw MoyaError.requestMapping(url) } var request = URLRequest(url: requestURL) request.httpMethod = method.rawValue request.allHTTPHeaderFields = httpHeaderFields switch task { case .requestPlain, .uploadFile, .uploadMultipart, .downloadDestination: return request case .requestData(let data): request.httpBody = data return request case let .requestJSONEncodable(encodable): return try request.encoded(encodable: encodable) case let .requestCustomJSONEncodable(encodable, encoder: encoder): return try request.encoded(encodable: encodable, encoder: encoder) case let .requestParameters(parameters, parameterEncoding): return try request.encoded(parameters: parameters, parameterEncoding: parameterEncoding) case let .uploadCompositeMultipart(_, urlParameters): let parameterEncoding = URLEncoding(destination: .queryString) return try request.encoded(parameters: urlParameters, parameterEncoding: parameterEncoding) case let .downloadParameters(parameters, parameterEncoding, _): return try request.encoded(parameters: parameters, parameterEncoding: parameterEncoding) case let .requestCompositeData(bodyData: bodyData, urlParameters: urlParameters): request.httpBody = bodyData let parameterEncoding = URLEncoding(destination: .queryString) return try request.encoded(parameters: urlParameters, parameterEncoding: parameterEncoding) case let .requestCompositeParameters(bodyParameters: bodyParameters, bodyEncoding: bodyParameterEncoding, urlParameters: urlParameters): if let bodyParameterEncoding = bodyParameterEncoding as? URLEncoding, bodyParameterEncoding.destination != .httpBody { fatalError("Only URLEncoding that `bodyEncoding` accepts is URLEncoding.httpBody. Others like `default`, `queryString` or `methodDependent` are prohibited - if you want to use them, add your parameters to `urlParameters` instead.") } let bodyfulRequest = try request.encoded(parameters: bodyParameters, parameterEncoding: bodyParameterEncoding) let urlEncoding = URLEncoding(destination: .queryString) return try bodyfulRequest.encoded(parameters: urlParameters, parameterEncoding: urlEncoding) } } // swiftlint:enable cyclomatic_complexity } /// Required for using `Endpoint` as a key type in a `Dictionary`. extension Endpoint: Equatable, Hashable { public func hash(into hasher: inout Hasher) { switch task { case let .uploadFile(file): hasher.combine(file) case let .uploadMultipart(multipartData), let .uploadCompositeMultipart(multipartData, _): hasher.combine(multipartData) default: break } if let request = try? urlRequest() { hasher.combine(request) } else { hasher.combine(url) } } /// Note: If both Endpoints fail to produce a URLRequest the comparison will /// fall back to comparing each Endpoint's hashValue. public static func == (lhs: Endpoint, rhs: Endpoint) -> Bool { let areEndpointsEqualInAdditionalProperties: Bool = { switch (lhs.task, rhs.task) { case (let .uploadFile(file1), let .uploadFile(file2)): return file1 == file2 case (let .uploadMultipart(multipartData1), let .uploadMultipart(multipartData2)), (let .uploadCompositeMultipart(multipartData1, _), let .uploadCompositeMultipart(multipartData2, _)): return multipartData1 == multipartData2 default: return true } }() let lhsRequest = try? lhs.urlRequest() let rhsRequest = try? rhs.urlRequest() if lhsRequest != nil, rhsRequest == nil { return false } if lhsRequest == nil, rhsRequest != nil { return false } if lhsRequest == nil, rhsRequest == nil { return lhs.hashValue == rhs.hashValue && areEndpointsEqualInAdditionalProperties } return lhsRequest == rhsRequest && areEndpointsEqualInAdditionalProperties } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Image.swift ================================================ #if canImport(UIKit) import UIKit.UIImage public typealias ImageType = UIImage #elseif canImport(AppKit) import AppKit.NSImage public typealias ImageType = NSImage #endif /// An alias for the SDK's image type. public typealias Image = ImageType ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Moya+Alamofire.swift ================================================ import Foundation import Alamofire public typealias Session = Alamofire.Session internal typealias Request = Alamofire.Request internal typealias DownloadRequest = Alamofire.DownloadRequest internal typealias UploadRequest = Alamofire.UploadRequest internal typealias DataRequest = Alamofire.DataRequest internal typealias URLRequestConvertible = Alamofire.URLRequestConvertible /// Represents an HTTP method. public typealias Method = Alamofire.HTTPMethod /// Choice of parameter encoding. public typealias ParameterEncoding = Alamofire.ParameterEncoding public typealias JSONEncoding = Alamofire.JSONEncoding public typealias URLEncoding = Alamofire.URLEncoding /// Multipart form. public typealias RequestMultipartFormData = Alamofire.MultipartFormData /// Multipart form data encoding result. public typealias DownloadDestination = Alamofire.DownloadRequest.Destination /// Make the Alamofire Request type conform to our type, to prevent leaking Alamofire to plugins. extension Request: RequestType { public var sessionHeaders: [String: String] { delegate?.sessionConfiguration.httpAdditionalHeaders as? [String: String] ?? [:] } } /// Represents Request interceptor type that can modify/act on Request public typealias RequestInterceptor = Alamofire.RequestInterceptor /// Internal token that can be used to cancel requests public final class CancellableToken: Cancellable, CustomDebugStringConvertible { let cancelAction: () -> Void let request: Request? public fileprivate(set) var isCancelled = false fileprivate var lock: DispatchSemaphore = DispatchSemaphore(value: 1) public func cancel() { _ = lock.wait(timeout: DispatchTime.distantFuture) defer { lock.signal() } guard !isCancelled else { return } isCancelled = true cancelAction() } public init(action: @escaping () -> Void) { self.cancelAction = action self.request = nil } init(request: Request) { self.request = request self.cancelAction = { request.cancel() } } /// A textual representation of this instance, suitable for debugging. public var debugDescription: String { guard let request = self.request else { return "Empty Request" } return request.cURLDescription() } } internal typealias RequestableCompletion = (HTTPURLResponse?, URLRequest?, Data?, Swift.Error?) -> Void internal protocol Requestable { func response(callbackQueue: DispatchQueue?, completionHandler: @escaping RequestableCompletion) -> Self } extension DataRequest: Requestable { internal func response(callbackQueue: DispatchQueue?, completionHandler: @escaping RequestableCompletion) -> Self { if let callbackQueue = callbackQueue { return response(queue: callbackQueue) { handler in completionHandler(handler.response, handler.request, handler.data, handler.error) } } else { return response { handler in completionHandler(handler.response, handler.request, handler.data, handler.error) } } } } extension DownloadRequest: Requestable { internal func response(callbackQueue: DispatchQueue?, completionHandler: @escaping RequestableCompletion) -> Self { if let callbackQueue = callbackQueue { return response(queue: callbackQueue) { handler in completionHandler(handler.response, handler.request, nil, handler.error) } } else { return response { handler in completionHandler(handler.response, handler.request, nil, handler.error) } } } } final class MoyaRequestInterceptor: RequestInterceptor { var prepare: ((URLRequest) -> URLRequest)? @Atomic var willSend: ((URLRequest) -> Void)? init(prepare: ((URLRequest) -> URLRequest)? = nil, willSend: ((URLRequest) -> Void)? = nil) { self.prepare = prepare self.willSend = willSend } func adapt(_ urlRequest: URLRequest, for session: Alamofire.Session, completion: @escaping (Result) -> Void) { let request = prepare?(urlRequest) ?? urlRequest willSend?(request) completion(.success(request)) } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/MoyaError.swift ================================================ import Foundation /// A type representing possible errors Moya can throw. public enum MoyaError: Swift.Error { /// Indicates a response failed to map to an image. case imageMapping(Response) /// Indicates a response failed to map to a JSON structure. case jsonMapping(Response) /// Indicates a response failed to map to a String. case stringMapping(Response) /// Indicates a response failed to map to a Decodable object. case objectMapping(Swift.Error, Response) /// Indicates that Encodable couldn't be encoded into Data case encodableMapping(Swift.Error) /// Indicates a response failed with an invalid HTTP status code. case statusCode(Response) /// Indicates a response failed due to an underlying `Error`. case underlying(Swift.Error, Response?) /// Indicates that an `Endpoint` failed to map to a `URLRequest`. case requestMapping(String) /// Indicates that an `Endpoint` failed to encode the parameters for the `URLRequest`. case parameterEncoding(Swift.Error) } public extension MoyaError { /// Depending on error type, returns a `Response` object. var response: Moya.Response? { switch self { case .imageMapping(let response): return response case .jsonMapping(let response): return response case .stringMapping(let response): return response case .objectMapping(_, let response): return response case .encodableMapping: return nil case .statusCode(let response): return response case .underlying(_, let response): return response case .requestMapping: return nil case .parameterEncoding: return nil } } /// Depending on error type, returns an underlying `Error`. internal var underlyingError: Swift.Error? { switch self { case .imageMapping: return nil case .jsonMapping: return nil case .stringMapping: return nil case .objectMapping(let error, _): return error case .encodableMapping(let error): return error case .statusCode: return nil case .underlying(let error, _): return error case .requestMapping: return nil case .parameterEncoding(let error): return error } } } // MARK: - Error Descriptions extension MoyaError: LocalizedError { public var errorDescription: String? { switch self { case .imageMapping: return "Failed to map data to an Image." case .jsonMapping: return "Failed to map data to JSON." case .stringMapping: return "Failed to map data to a String." case .objectMapping: return "Failed to map data to a Decodable object." case .encodableMapping: return "Failed to encode Encodable object into data." case .statusCode: return "Status code didn't fall within the given range." case .underlying(let error, _): return error.localizedDescription case .requestMapping: return "Failed to map Endpoint to a URLRequest." case .parameterEncoding(let error): return "Failed to encode parameters for URLRequest. \(error.localizedDescription)" } } } // MARK: - Error User Info extension MoyaError: CustomNSError { public var errorUserInfo: [String: Any] { var userInfo: [String: Any] = [:] userInfo[NSLocalizedDescriptionKey] = errorDescription userInfo[NSUnderlyingErrorKey] = underlyingError return userInfo } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/MoyaProvider+Defaults.swift ================================================ import Foundation /// These functions are default mappings to `MoyaProvider`'s properties: endpoints, requests, session etc. public extension MoyaProvider { final class func defaultEndpointMapping(for target: Target) -> Endpoint { Endpoint( url: URL(target: target).absoluteString, sampleResponseClosure: { .networkResponse(200, target.sampleData) }, method: target.method, task: target.task, httpHeaderFields: target.headers ) } final class func defaultRequestMapping(for endpoint: Endpoint, closure: RequestResultClosure) { do { let urlRequest = try endpoint.urlRequest() closure(.success(urlRequest)) } catch MoyaError.requestMapping(let url) { closure(.failure(MoyaError.requestMapping(url))) } catch MoyaError.parameterEncoding(let error) { closure(.failure(MoyaError.parameterEncoding(error))) } catch { closure(.failure(MoyaError.underlying(error, nil))) } } final class func defaultAlamofireSession() -> Session { let configuration = URLSessionConfiguration.default configuration.headers = .default return Session(configuration: configuration, startRequestsImmediately: false) } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/MoyaProvider+Internal.swift ================================================ import Foundation // MARK: - Method public extension Method { /// A Boolean value determining whether the request supports multipart. var supportsMultipart: Bool { switch self { case .post, .put, .patch, .connect: return true default: return false } } } // MARK: - MoyaProvider /// Internal extension to keep the inner-workings outside the main Moya.swift file. public extension MoyaProvider { /// Performs normal requests. func requestNormal(_ target: Target, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> Cancellable { let endpoint = self.endpoint(target) let stubBehavior = self.stubClosure(target) let cancellableToken = CancellableWrapper() // Allow plugins to modify response let pluginsWithCompletion: Moya.Completion = { result in let processedResult = self.plugins.reduce(result) { $1.process($0, target: target) } completion(processedResult) } if trackInflights { var inflightCompletionBlocks = self.inflightRequests[endpoint] inflightCompletionBlocks?.append(pluginsWithCompletion) self.internalInflightRequests[endpoint] = inflightCompletionBlocks if inflightCompletionBlocks != nil { return cancellableToken } else { self.internalInflightRequests[endpoint] = [pluginsWithCompletion] } } let performNetworking = { (requestResult: Result) in if cancellableToken.isCancelled { self.cancelCompletion(pluginsWithCompletion, target: target) return } var request: URLRequest! switch requestResult { case .success(let urlRequest): request = urlRequest case .failure(let error): pluginsWithCompletion(.failure(error)) return } let networkCompletion: Moya.Completion = { result in if self.trackInflights { self.inflightRequests[endpoint]?.forEach { $0(result) } self.internalInflightRequests.removeValue(forKey: endpoint) } else { pluginsWithCompletion(result) } } cancellableToken.innerCancellable = self.performRequest(target, request: request, callbackQueue: callbackQueue, progress: progress, completion: networkCompletion, endpoint: endpoint, stubBehavior: stubBehavior) } requestClosure(endpoint, performNetworking) return cancellableToken } // swiftlint:disable:next function_parameter_count private func performRequest(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion, endpoint: Endpoint, stubBehavior: Moya.StubBehavior) -> Cancellable { switch stubBehavior { case .never: switch endpoint.task { case .requestPlain, .requestData, .requestJSONEncodable, .requestCustomJSONEncodable, .requestParameters, .requestCompositeData, .requestCompositeParameters: return self.sendRequest(target, request: request, callbackQueue: callbackQueue, progress: progress, completion: completion) case .uploadFile(let file): return self.sendUploadFile(target, request: request, callbackQueue: callbackQueue, file: file, progress: progress, completion: completion) case .uploadMultipart(let multipartBody), .uploadCompositeMultipart(let multipartBody, _): guard !multipartBody.isEmpty && endpoint.method.supportsMultipart else { fatalError("\(target) is not a multipart upload target.") } return self.sendUploadMultipart(target, request: request, callbackQueue: callbackQueue, multipartBody: multipartBody, progress: progress, completion: completion) case .downloadDestination(let destination), .downloadParameters(_, _, let destination): return self.sendDownloadRequest(target, request: request, callbackQueue: callbackQueue, destination: destination, progress: progress, completion: completion) } default: return self.stubRequest(target, request: request, callbackQueue: callbackQueue, completion: completion, endpoint: endpoint, stubBehavior: stubBehavior) } } func cancelCompletion(_ completion: Moya.Completion, target: Target) { let error = MoyaError.underlying(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: nil), nil) plugins.forEach { $0.didReceive(.failure(error), target: target) } completion(.failure(error)) } /// Creates a function which, when called, executes the appropriate stubbing behavior for the given parameters. final func createStubFunction(_ token: CancellableToken, forTarget target: Target, withCompletion completion: @escaping Moya.Completion, endpoint: Endpoint, plugins: [PluginType], request: URLRequest) -> (() -> Void) { // swiftlint:disable:this function_parameter_count return { if token.isCancelled { self.cancelCompletion(completion, target: target) return } let validate = { (response: Moya.Response) -> Result in let validCodes = target.validationType.statusCodes guard !validCodes.isEmpty else { return .success(response) } if validCodes.contains(response.statusCode) { return .success(response) } else { let statusError = MoyaError.statusCode(response) let error = MoyaError.underlying(statusError, response) return .failure(error) } } switch endpoint.sampleResponseClosure() { case .networkResponse(let statusCode, let data): let response = Moya.Response(statusCode: statusCode, data: data, request: request, response: nil) let result = validate(response) plugins.forEach { $0.didReceive(result, target: target) } completion(result) case .response(let customResponse, let data): let response = Moya.Response(statusCode: customResponse.statusCode, data: data, request: request, response: customResponse) let result = validate(response) plugins.forEach { $0.didReceive(result, target: target) } completion(result) case .networkError(let error): let error = MoyaError.underlying(error, nil) plugins.forEach { $0.didReceive(.failure(error), target: target) } completion(.failure(error)) } } } /// Notify all plugins that a stub is about to be performed. You must call this if overriding `stubRequest`. final func notifyPluginsOfImpendingStub(for request: URLRequest, target: Target) -> URLRequest { let alamoRequest = session.request(request) alamoRequest.cancel() let preparedRequest = plugins.reduce(request) { $1.prepare($0, target: target) } let stubbedAlamoRequest = RequestTypeWrapper(request: alamoRequest, urlRequest: preparedRequest) plugins.forEach { $0.willSend(stubbedAlamoRequest, target: target) } return preparedRequest } } private extension MoyaProvider { private func interceptor(target: Target) -> MoyaRequestInterceptor { return MoyaRequestInterceptor(prepare: { [weak self] urlRequest in return self?.plugins.reduce(urlRequest) { $1.prepare($0, target: target) } ?? urlRequest }) } private func setup(interceptor: MoyaRequestInterceptor, with target: Target, and request: Request) { interceptor.willSend = { [weak self, weak request] urlRequest in guard let self = self, let request = request else { return } let stubbedAlamoRequest = RequestTypeWrapper(request: request, urlRequest: urlRequest) self.plugins.forEach { $0.willSend(stubbedAlamoRequest, target: target) } } } func sendUploadMultipart(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, multipartBody: [MultipartFormData], progress: Moya.ProgressBlock? = nil, completion: @escaping Moya.Completion) -> CancellableToken { let formData = RequestMultipartFormData() formData.applyMoyaMultipartFormData(multipartBody) let interceptor = self.interceptor(target: target) let uploadRequest: UploadRequest = session.requestQueue.sync { let uploadRequest = session.upload(multipartFormData: formData, with: request, interceptor: interceptor) setup(interceptor: interceptor, with: target, and: uploadRequest) return uploadRequest } let validationCodes = target.validationType.statusCodes let validatedRequest = validationCodes.isEmpty ? uploadRequest : uploadRequest.validate(statusCode: validationCodes) return sendAlamofireRequest(validatedRequest, target: target, callbackQueue: callbackQueue, progress: progress, completion: completion) } func sendUploadFile(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, file: URL, progress: ProgressBlock? = nil, completion: @escaping Completion) -> CancellableToken { let interceptor = self.interceptor(target: target) let uploadRequest: UploadRequest = session.requestQueue.sync { let uploadRequest = session.upload(file, with: request, interceptor: interceptor) setup(interceptor: interceptor, with: target, and: uploadRequest) return uploadRequest } let validationCodes = target.validationType.statusCodes let alamoRequest = validationCodes.isEmpty ? uploadRequest : uploadRequest.validate(statusCode: validationCodes) return sendAlamofireRequest(alamoRequest, target: target, callbackQueue: callbackQueue, progress: progress, completion: completion) } func sendDownloadRequest(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, destination: @escaping DownloadDestination, progress: ProgressBlock? = nil, completion: @escaping Completion) -> CancellableToken { let interceptor = self.interceptor(target: target) let downloadRequest: DownloadRequest = session.requestQueue.sync { let downloadRequest = session.download(request, interceptor: interceptor, to: destination) setup(interceptor: interceptor, with: target, and: downloadRequest) return downloadRequest } let validationCodes = target.validationType.statusCodes let alamoRequest = validationCodes.isEmpty ? downloadRequest : downloadRequest.validate(statusCode: validationCodes) return sendAlamofireRequest(alamoRequest, target: target, callbackQueue: callbackQueue, progress: progress, completion: completion) } func sendRequest(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> CancellableToken { let interceptor = self.interceptor(target: target) let initialRequest: DataRequest = session.requestQueue.sync { let initialRequest = session.request(request, interceptor: interceptor) setup(interceptor: interceptor, with: target, and: initialRequest) return initialRequest } let validationCodes = target.validationType.statusCodes let alamoRequest = validationCodes.isEmpty ? initialRequest : initialRequest.validate(statusCode: validationCodes) return sendAlamofireRequest(alamoRequest, target: target, callbackQueue: callbackQueue, progress: progress, completion: completion) } // swiftlint:disable:next cyclomatic_complexity func sendAlamofireRequest(_ alamoRequest: T, target: Target, callbackQueue: DispatchQueue?, progress progressCompletion: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> CancellableToken where T: Requestable, T: Request { // Give plugins the chance to alter the outgoing request let plugins = self.plugins var progressAlamoRequest = alamoRequest let progressClosure: (Progress) -> Void = { progress in let sendProgress: () -> Void = { progressCompletion?(ProgressResponse(progress: progress)) } if let callbackQueue = callbackQueue { callbackQueue.async(execute: sendProgress) } else { sendProgress() } } // Perform the actual request if progressCompletion != nil { switch progressAlamoRequest { case let downloadRequest as DownloadRequest: if let downloadRequest = downloadRequest.downloadProgress(closure: progressClosure) as? T { progressAlamoRequest = downloadRequest } case let uploadRequest as UploadRequest: if let uploadRequest = uploadRequest.uploadProgress(closure: progressClosure) as? T { progressAlamoRequest = uploadRequest } case let dataRequest as DataRequest: if let dataRequest = dataRequest.downloadProgress(closure: progressClosure) as? T { progressAlamoRequest = dataRequest } default: break } } let completionHandler: RequestableCompletion = { response, request, data, error in let result = convertResponseToResult(response, request: request, data: data, error: error) // Inform all plugins about the response plugins.forEach { $0.didReceive(result, target: target) } if let progressCompletion = progressCompletion { let value = try? result.get() switch progressAlamoRequest { case let downloadRequest as DownloadRequest: progressCompletion(ProgressResponse(progress: downloadRequest.downloadProgress, response: value)) case let uploadRequest as UploadRequest: progressCompletion(ProgressResponse(progress: uploadRequest.uploadProgress, response: value)) case let dataRequest as DataRequest: progressCompletion(ProgressResponse(progress: dataRequest.downloadProgress, response: value)) default: progressCompletion(ProgressResponse(response: value)) } } completion(result) } progressAlamoRequest = progressAlamoRequest.response(callbackQueue: callbackQueue, completionHandler: completionHandler) progressAlamoRequest.resume() return CancellableToken(request: progressAlamoRequest) } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/MoyaProvider.swift ================================================ import Foundation /// Closure to be executed when a request has completed. public typealias Completion = (_ result: Result) -> Void /// Closure to be executed when progress changes. public typealias ProgressBlock = (_ progress: ProgressResponse) -> Void /// A type representing the progress of a request. public struct ProgressResponse { /// The optional response of the request. public let response: Response? /// An object that conveys ongoing progress for a given request. public let progressObject: Progress? /// Initializes a `ProgressResponse`. public init(progress: Progress? = nil, response: Response? = nil) { self.progressObject = progress self.response = response } /// The fraction of the overall work completed by the progress object. public var progress: Double { if completed { return 1.0 } else if let progressObject = progressObject, progressObject.totalUnitCount > 0 { // if the Content-Length is specified we can rely on `fractionCompleted` return progressObject.fractionCompleted } else { // if the Content-Length is not specified, return progress 0.0 until it's completed return 0.0 } } /// A Boolean value stating whether the request is completed. public var completed: Bool { response != nil } } /// A protocol representing a minimal interface for a MoyaProvider. /// Used by the reactive provider extensions. public protocol MoyaProviderType: AnyObject { associatedtype Target: TargetType /// Designated request-making method. Returns a `Cancellable` token to cancel the request later. func request(_ target: Target, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> Cancellable } /// Request provider class. Requests should be made through this class only. open class MoyaProvider: MoyaProviderType { /// Closure that defines the endpoints for the provider. public typealias EndpointClosure = (Target) -> Endpoint /// Closure that decides if and what request should be performed. public typealias RequestResultClosure = (Result) -> Void /// Closure that resolves an `Endpoint` into a `RequestResult`. public typealias RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void /// Closure that decides if/how a request should be stubbed. public typealias StubClosure = (Target) -> Moya.StubBehavior /// A closure responsible for mapping a `TargetType` to an `EndPoint`. public let endpointClosure: EndpointClosure /// A closure deciding if and what request should be performed. public let requestClosure: RequestClosure /// A closure responsible for determining the stubbing behavior /// of a request for a given `TargetType`. public let stubClosure: StubClosure public let session: Session /// A list of plugins. /// e.g. for logging, network activity indicator or credentials. public let plugins: [PluginType] public let trackInflights: Bool open var inflightRequests: [Endpoint: [Moya.Completion]] { internalInflightRequests } @Atomic var internalInflightRequests: [Endpoint: [Moya.Completion]] = [:] /// Propagated to Alamofire as callback queue. If nil - the Alamofire default (as of their API in 2017 - the main queue) will be used. let callbackQueue: DispatchQueue? let lock: NSRecursiveLock = NSRecursiveLock() /// Initializes a provider. public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping, requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping, stubClosure: @escaping StubClosure = MoyaProvider.neverStub, callbackQueue: DispatchQueue? = nil, session: Session = MoyaProvider.defaultAlamofireSession(), plugins: [PluginType] = [], trackInflights: Bool = false) { self.endpointClosure = endpointClosure self.requestClosure = requestClosure self.stubClosure = stubClosure self.session = session self.plugins = plugins self.trackInflights = trackInflights self.callbackQueue = callbackQueue } /// Returns an `Endpoint` based on the token, method, and parameters by invoking the `endpointClosure`. open func endpoint(_ token: Target) -> Endpoint { endpointClosure(token) } /// Designated request-making method. Returns a `Cancellable` token to cancel the request later. @discardableResult open func request(_ target: Target, callbackQueue: DispatchQueue? = .none, progress: ProgressBlock? = .none, completion: @escaping Completion) -> Cancellable { let callbackQueue = callbackQueue ?? self.callbackQueue return requestNormal(target, callbackQueue: callbackQueue, progress: progress, completion: completion) } // swiftlint:disable function_parameter_count /// When overriding this method, call `notifyPluginsOfImpendingStub` to prepare your request /// and then use the returned `URLRequest` in the `createStubFunction` method. /// Note: this was previously in an extension, however it must be in the original class declaration to allow subclasses to override. @discardableResult open func stubRequest(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, completion: @escaping Moya.Completion, endpoint: Endpoint, stubBehavior: Moya.StubBehavior) -> CancellableToken { let callbackQueue = callbackQueue ?? self.callbackQueue let cancellableToken = CancellableToken { } let preparedRequest = notifyPluginsOfImpendingStub(for: request, target: target) let plugins = self.plugins let stub: () -> Void = createStubFunction(cancellableToken, forTarget: target, withCompletion: completion, endpoint: endpoint, plugins: plugins, request: preparedRequest) switch stubBehavior { case .immediate: switch callbackQueue { case .none: stub() case .some(let callbackQueue): callbackQueue.async(execute: stub) } case .delayed(let delay): let killTimeOffset = Int64(CDouble(delay) * CDouble(NSEC_PER_SEC)) let killTime = DispatchTime.now() + Double(killTimeOffset) / Double(NSEC_PER_SEC) (callbackQueue ?? DispatchQueue.main).asyncAfter(deadline: killTime) { stub() } case .never: fatalError("Method called to stub request when stubbing is disabled.") } return cancellableToken } // swiftlint:enable function_parameter_count } // MARK: Stubbing /// Controls how stub responses are returned. public enum StubBehavior { /// Do not stub. case never /// Return a response immediately. case immediate /// Return a response after a delay. case delayed(seconds: TimeInterval) } public extension MoyaProvider { // Swift won't let us put the StubBehavior enum inside the provider class, so we'll // at least add some class functions to allow easy access to common stubbing closures. /// Do not stub. final class func neverStub(_: Target) -> Moya.StubBehavior { .never } /// Return a response immediately. final class func immediatelyStub(_: Target) -> Moya.StubBehavior { .immediate } /// Return a response after a delay. final class func delayedStub(_ seconds: TimeInterval) -> (Target) -> Moya.StubBehavior { return { _ in .delayed(seconds: seconds) } } } /// A public function responsible for converting the result of a `URLRequest` to a Result. public func convertResponseToResult(_ response: HTTPURLResponse?, request: URLRequest?, data: Data?, error: Swift.Error?) -> Result { switch (response, data, error) { case let (.some(response), data, .none): let response = Moya.Response(statusCode: response.statusCode, data: data ?? Data(), request: request, response: response) return .success(response) case let (.some(response), _, .some(error)): let response = Moya.Response(statusCode: response.statusCode, data: data ?? Data(), request: request, response: response) let error = MoyaError.underlying(error, response) return .failure(error) case let (_, _, .some(error)): let error = MoyaError.underlying(error, nil) return .failure(error) default: let error = MoyaError.underlying(NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil), nil) return .failure(error) } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/MultiTarget.swift ================================================ import Foundation /// A `TargetType` used to enable `MoyaProvider` to process multiple `TargetType`s. public enum MultiTarget: TargetType { /// The embedded `TargetType`. case target(TargetType) /// Initializes a `MultiTarget`. public init(_ target: TargetType) { self = MultiTarget.target(target) } /// The embedded target's base `URL`. public var path: String { target.path } /// The baseURL of the embedded target. public var baseURL: URL { target.baseURL } /// The HTTP method of the embedded target. public var method: Moya.Method { target.method } /// The sampleData of the embedded target. public var sampleData: Data { target.sampleData } /// The `Task` of the embedded target. public var task: Task { target.task } /// The `ValidationType` of the embedded target. public var validationType: ValidationType { target.validationType } /// The headers of the embedded target. public var headers: [String: String]? { target.headers } /// The embedded `TargetType`. public var target: TargetType { switch self { case .target(let target): return target } } } extension MultiTarget: AccessTokenAuthorizable { public var authorizationType: AuthorizationType? { guard let authorizableTarget = target as? AccessTokenAuthorizable else { return nil } return authorizableTarget.authorizationType } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/MultipartFormData.swift ================================================ import Foundation import Alamofire /// Represents "multipart/form-data" for an upload. public struct MultipartFormData: Hashable { /// Method to provide the form data. public enum FormDataProvider: Hashable { case data(Foundation.Data) case file(URL) case stream(InputStream, UInt64) } public init(provider: FormDataProvider, name: String, fileName: String? = nil, mimeType: String? = nil) { self.provider = provider self.name = name self.fileName = fileName self.mimeType = mimeType } /// The method being used for providing form data. public let provider: FormDataProvider /// The name. public let name: String /// The file name. public let fileName: String? /// The MIME type public let mimeType: String? } // MARK: RequestMultipartFormData appending internal extension RequestMultipartFormData { func append(data: Data, bodyPart: MultipartFormData) { append(data, withName: bodyPart.name, fileName: bodyPart.fileName, mimeType: bodyPart.mimeType) } func append(fileURL url: URL, bodyPart: MultipartFormData) { if let fileName = bodyPart.fileName, let mimeType = bodyPart.mimeType { append(url, withName: bodyPart.name, fileName: fileName, mimeType: mimeType) } else { append(url, withName: bodyPart.name) } } func append(stream: InputStream, length: UInt64, bodyPart: MultipartFormData) { append(stream, withLength: length, name: bodyPart.name, fileName: bodyPart.fileName ?? "", mimeType: bodyPart.mimeType ?? "") } func applyMoyaMultipartFormData(_ multipartBody: [Moya.MultipartFormData]) { for bodyPart in multipartBody { switch bodyPart.provider { case .data(let data): append(data: data, bodyPart: bodyPart) case .file(let url): append(fileURL: url, bodyPart: bodyPart) case .stream(let stream, let length): append(stream: stream, length: length, bodyPart: bodyPart) } } } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Plugin.swift ================================================ import Foundation /// A Moya Plugin receives callbacks to perform side effects wherever a request is sent or received. /// /// for example, a plugin may be used to /// - log network requests /// - hide and show a network activity indicator /// - inject additional information into a request public protocol PluginType { /// Called to modify a request before sending. func prepare(_ request: URLRequest, target: TargetType) -> URLRequest /// Called immediately before a request is sent over the network (or stubbed). func willSend(_ request: RequestType, target: TargetType) /// Called after a response has been received, but before the MoyaProvider has invoked its completion handler. func didReceive(_ result: Result, target: TargetType) /// Called to modify a result before completion. func process(_ result: Result, target: TargetType) -> Result } public extension PluginType { func prepare(_ request: URLRequest, target: TargetType) -> URLRequest { request } func willSend(_ request: RequestType, target: TargetType) { } func didReceive(_ result: Result, target: TargetType) { } func process(_ result: Result, target: TargetType) -> Result { result } } /// Request type used by `willSend` plugin function. public protocol RequestType { // Note: // // We use this protocol instead of the Alamofire request to avoid leaking that abstraction. // A plugin should not know about Alamofire at all. /// Retrieve an `NSURLRequest` representation. var request: URLRequest? { get } /// Additional headers appended to the request when added to the session. var sessionHeaders: [String: String] { get } /// Authenticates the request with a username and password. func authenticate(username: String, password: String, persistence: URLCredential.Persistence) -> Self /// Authenticates the request with an `NSURLCredential` instance. func authenticate(with credential: URLCredential) -> Self /// cURL representation of the instance. /// /// - Returns: The cURL equivalent of the instance. func cURLDescription(calling handler: @escaping (String) -> Void) -> Self } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Plugins/AccessTokenPlugin.swift ================================================ import Foundation // MARK: - AccessTokenAuthorizable /// A protocol for controlling the behavior of `AccessTokenPlugin`. public protocol AccessTokenAuthorizable { /// Represents the authorization header to use for requests. var authorizationType: AuthorizationType? { get } } // MARK: - AuthorizationType /// An enum representing the header to use with an `AccessTokenPlugin` public enum AuthorizationType { /// The `"Basic"` header. case basic /// The `"Bearer"` header. case bearer /// Custom header implementation. case custom(String) public var value: String { switch self { case .basic: return "Basic" case .bearer: return "Bearer" case .custom(let customValue): return customValue } } } extension AuthorizationType: Equatable { public static func == (lhs: AuthorizationType, rhs: AuthorizationType) -> Bool { switch (lhs, rhs) { case (.basic, .basic), (.bearer, .bearer): return true case let (.custom(value1), .custom(value2)): return value1 == value2 default: return false } } } // MARK: - AccessTokenPlugin /** A plugin for adding basic or bearer-type authorization headers to requests. Example: ``` Authorization: Basic Authorization: Bearer Authorization: <Сustom> ``` */ public struct AccessTokenPlugin: PluginType { public typealias TokenClosure = (TargetType) -> String /// A closure returning the access token to be applied in the header. public let tokenClosure: TokenClosure /** Initialize a new `AccessTokenPlugin`. - parameters: - tokenClosure: A closure returning the token to be applied in the pattern `Authorization: ` */ public init(tokenClosure: @escaping TokenClosure) { self.tokenClosure = tokenClosure } /** Prepare a request by adding an authorization header if necessary. - parameters: - request: The request to modify. - target: The target of the request. - returns: The modified `URLRequest`. */ public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest { guard let authorizable = target as? AccessTokenAuthorizable, let authorizationType = authorizable.authorizationType else { return request } var request = request let realTarget = (target as? MultiTarget)?.target ?? target let authValue = authorizationType.value + " " + tokenClosure(realTarget) request.addValue(authValue, forHTTPHeaderField: "Authorization") return request } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Plugins/CredentialsPlugin.swift ================================================ import Foundation /// Provides each request with optional URLCredentials. public final class CredentialsPlugin: PluginType { public typealias CredentialClosure = (TargetType) -> URLCredential? let credentialsClosure: CredentialClosure /// Initializes a CredentialsPlugin. public init(credentialsClosure: @escaping CredentialClosure) { self.credentialsClosure = credentialsClosure } // MARK: Plugin public func willSend(_ request: RequestType, target: TargetType) { if let credentials = credentialsClosure(target) { _ = request.authenticate(with: credentials) } } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Plugins/NetworkActivityPlugin.swift ================================================ import Foundation /// Network activity change notification type. public enum NetworkActivityChangeType { case began, ended } /// Notify a request's network activity changes (request begins or ends). public final class NetworkActivityPlugin: PluginType { public typealias NetworkActivityClosure = (_ change: NetworkActivityChangeType, _ target: TargetType) -> Void let networkActivityClosure: NetworkActivityClosure /// Initializes a NetworkActivityPlugin. public init(networkActivityClosure: @escaping NetworkActivityClosure) { self.networkActivityClosure = networkActivityClosure } // MARK: Plugin /// Called by the provider as soon as the request is about to start public func willSend(_ request: RequestType, target: TargetType) { networkActivityClosure(.began, target) } /// Called by the provider as soon as a response arrives, even if the request is canceled. public func didReceive(_ result: Result, target: TargetType) { networkActivityClosure(.ended, target) } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Plugins/NetworkLoggerPlugin.swift ================================================ import Foundation /// Logs network activity (outgoing requests and incoming responses). public final class NetworkLoggerPlugin { public var configuration: Configuration /// Initializes a NetworkLoggerPlugin. public init(configuration: Configuration = Configuration()) { self.configuration = configuration } } // MARK: - PluginType extension NetworkLoggerPlugin: PluginType { public func willSend(_ request: RequestType, target: TargetType) { logNetworkRequest(request, target: target) { [weak self] output in self?.configuration.output(target, output) } } public func didReceive(_ result: Result, target: TargetType) { switch result { case .success(let response): configuration.output(target, logNetworkResponse(response, target: target, isFromError: false)) case let .failure(error): configuration.output(target, logNetworkError(error, target: target)) } } } // MARK: - Logging private extension NetworkLoggerPlugin { func logNetworkRequest(_ request: RequestType, target: TargetType, completion: @escaping ([String]) -> Void) { //cURL formatting if configuration.logOptions.contains(.formatRequestAscURL) { _ = request.cURLDescription { [weak self] output in guard let self = self else { return } completion([self.configuration.formatter.entry("Request", output, target)]) } return } //Request presence check guard let httpRequest = request.request else { completion([configuration.formatter.entry("Request", "(invalid request)", target)]) return } // Adding log entries for each given log option var output = [String]() output.append(configuration.formatter.entry("Request", httpRequest.description, target)) if configuration.logOptions.contains(.requestHeaders) { var allHeaders = request.sessionHeaders if let httpRequestHeaders = httpRequest.allHTTPHeaderFields { allHeaders.merge(httpRequestHeaders) { $1 } } output.append(configuration.formatter.entry("Request Headers", allHeaders.description, target)) } if configuration.logOptions.contains(.requestBody) { if let bodyStream = httpRequest.httpBodyStream { output.append(configuration.formatter.entry("Request Body Stream", bodyStream.description, target)) } if let body = httpRequest.httpBody { let stringOutput = configuration.formatter.requestData(body) output.append(configuration.formatter.entry("Request Body", stringOutput, target)) } } if configuration.logOptions.contains(.requestMethod), let httpMethod = httpRequest.httpMethod { output.append(configuration.formatter.entry("HTTP Request Method", httpMethod, target)) } completion(output) } func logNetworkResponse(_ response: Response, target: TargetType, isFromError: Bool) -> [String] { // Adding log entries for each given log option var output = [String]() //Response presence check if let httpResponse = response.response { output.append(configuration.formatter.entry("Response", httpResponse.description, target)) } else { output.append(configuration.formatter.entry("Response", "Received empty network response for \(target).", target)) } if (isFromError && configuration.logOptions.contains(.errorResponseBody)) || configuration.logOptions.contains(.successResponseBody) { let stringOutput = configuration.formatter.responseData(response.data) output.append(configuration.formatter.entry("Response Body", stringOutput, target)) } return output } func logNetworkError(_ error: MoyaError, target: TargetType) -> [String] { //Some errors will still have a response, like errors due to Alamofire's HTTP code validation. if let moyaResponse = error.response { return logNetworkResponse(moyaResponse, target: target, isFromError: true) } //Errors without an HTTPURLResponse are those due to connectivity, time-out and such. return [configuration.formatter.entry("Error", "Error calling \(target) : \(error)", target)] } } // MARK: - Configuration public extension NetworkLoggerPlugin { struct Configuration { // MARK: - Typealiases // swiftlint:disable nesting public typealias OutputType = (_ target: TargetType, _ items: [String]) -> Void // swiftlint:enable nesting // MARK: - Properties public var formatter: Formatter public var output: OutputType public var logOptions: LogOptions /// The designated way to instantiate a Configuration. /// /// - Parameters: /// - formatter: An object holding all formatter closures available for customization. /// - output: A closure responsible for writing the given log entries into your log system. /// The default value writes entries to the debug console. /// - logOptions: A set of options you can use to customize which request component is logged. public init(formatter: Formatter = Formatter(), output: @escaping OutputType = defaultOutput, logOptions: LogOptions = .default) { self.formatter = formatter self.output = output self.logOptions = logOptions } // MARK: - Defaults public static func defaultOutput(target: TargetType, items: [String]) { for item in items { print(item, separator: ",", terminator: "\n") } } } } public extension NetworkLoggerPlugin.Configuration { struct LogOptions: OptionSet { public let rawValue: Int public init(rawValue: Int) { self.rawValue = rawValue } /// The request's method will be logged. public static let requestMethod: LogOptions = LogOptions(rawValue: 1 << 0) /// The request's body will be logged. public static let requestBody: LogOptions = LogOptions(rawValue: 1 << 1) /// The request's headers will be logged. public static let requestHeaders: LogOptions = LogOptions(rawValue: 1 << 2) /// The request will be logged in the cURL format. /// /// If this option is used, the following components will be logged regardless of their respective options being set: /// - request's method /// - request's headers /// - request's body. public static let formatRequestAscURL: LogOptions = LogOptions(rawValue: 1 << 3) /// The body of a response that is a success will be logged. public static let successResponseBody: LogOptions = LogOptions(rawValue: 1 << 4) /// The body of a response that is an error will be logged. public static let errorResponseBody: LogOptions = LogOptions(rawValue: 1 << 5) //Aggregate options /// Only basic components will be logged. public static let `default`: LogOptions = [requestMethod, requestHeaders] /// All components will be logged. public static let verbose: LogOptions = [requestMethod, requestHeaders, requestBody, successResponseBody, errorResponseBody] } } public extension NetworkLoggerPlugin.Configuration { struct Formatter { // MARK: Typealiases // swiftlint:disable nesting public typealias DataFormatterType = (Data) -> (String) public typealias EntryFormatterType = (_ identifier: String, _ message: String, _ target: TargetType) -> String // swiftlint:enable nesting // MARK: Properties public var entry: EntryFormatterType public var requestData: DataFormatterType public var responseData: DataFormatterType /// The designated way to instantiate a Formatter. /// /// - Parameters: /// - entry: The closure formatting a message into a new log entry. /// - requestData: The closure converting HTTP request's body into a String. /// The default value assumes the body's data is an utf8 String. /// - responseData: The closure converting HTTP response's body into a String. /// The default value assumes the body's data is an utf8 String. public init(entry: @escaping EntryFormatterType = defaultEntryFormatter, requestData: @escaping DataFormatterType = defaultDataFormatter, responseData: @escaping DataFormatterType = defaultDataFormatter) { self.entry = entry self.requestData = requestData self.responseData = responseData } // MARK: Defaults public static func defaultDataFormatter(_ data: Data) -> String { return String(data: data, encoding: .utf8) ?? "## Cannot map data to String ##" } public static func defaultEntryFormatter(identifier: String, message: String, target: TargetType) -> String { let date = defaultEntryDateFormatter.string(from: Date()) return "Moya_Logger: [\(date)] \(identifier): \(message)" } static var defaultEntryDateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.timeStyle = .short formatter.dateStyle = .short return formatter }() } } public extension NetworkLoggerPlugin { /// Returns the default logger plugin class var `default`: NetworkLoggerPlugin { return NetworkLoggerPlugin(configuration: Configuration(logOptions: .default)) } /// Returns the default verbose logger plugin class var verbose: NetworkLoggerPlugin { return NetworkLoggerPlugin(configuration: Configuration(logOptions: .verbose)) } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/RequestTypeWrapper.swift ================================================ import Foundation // Workaround for new asynchronous handling of Alamofire's request creation. struct RequestTypeWrapper: RequestType { var request: URLRequest? { _urlRequest } var sessionHeaders: [String: String] { _request.sessionHeaders } private var _request: Request private var _urlRequest: URLRequest? init(request: Request, urlRequest: URLRequest?) { self._request = request self._urlRequest = urlRequest } func authenticate(username: String, password: String, persistence: URLCredential.Persistence) -> RequestTypeWrapper { let newRequest = _request.authenticate(username: username, password: password, persistence: persistence) return RequestTypeWrapper(request: newRequest, urlRequest: _urlRequest) } func authenticate(with credential: URLCredential) -> RequestTypeWrapper { let newRequest = _request.authenticate(with: credential) return RequestTypeWrapper(request: newRequest, urlRequest: _urlRequest) } func cURLDescription(calling handler: @escaping (String) -> Void) -> RequestTypeWrapper { _request.cURLDescription(calling: handler) return self } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Response.swift ================================================ import Foundation /// Represents a response to a `MoyaProvider.request`. public final class Response: CustomDebugStringConvertible, Equatable { /// The status code of the response. public let statusCode: Int /// The response data. public let data: Data /// The original URLRequest for the response. public let request: URLRequest? /// The HTTPURLResponse object. public let response: HTTPURLResponse? public init(statusCode: Int, data: Data, request: URLRequest? = nil, response: HTTPURLResponse? = nil) { self.statusCode = statusCode self.data = data self.request = request self.response = response } /// A text description of the `Response`. public var description: String { "Status Code: \(statusCode), Data Length: \(data.count)" } /// A text description of the `Response`. Suitable for debugging. public var debugDescription: String { description } public static func == (lhs: Response, rhs: Response) -> Bool { lhs.statusCode == rhs.statusCode && lhs.data == rhs.data && lhs.response == rhs.response } } public extension Response { /** Returns the `Response` if the `statusCode` falls within the specified range. - parameters: - statusCodes: The range of acceptable status codes. - throws: `MoyaError.statusCode` when others are encountered. */ func filter(statusCodes: R) throws -> Response where R.Bound == Int { guard statusCodes.contains(statusCode) else { throw MoyaError.statusCode(self) } return self } /** Returns the `Response` if it has the specified `statusCode`. - parameters: - statusCode: The acceptable status code. - throws: `MoyaError.statusCode` when others are encountered. */ func filter(statusCode: Int) throws -> Response { try filter(statusCodes: statusCode...statusCode) } /** Returns the `Response` if the `statusCode` falls within the range 200 - 299. - throws: `MoyaError.statusCode` when others are encountered. */ func filterSuccessfulStatusCodes() throws -> Response { try filter(statusCodes: 200...299) } /** Returns the `Response` if the `statusCode` falls within the range 200 - 399. - throws: `MoyaError.statusCode` when others are encountered. */ func filterSuccessfulStatusAndRedirectCodes() throws -> Response { try filter(statusCodes: 200...399) } /// Maps data received from the signal into an Image. func mapImage() throws -> Image { guard let image = Image(data: data) else { throw MoyaError.imageMapping(self) } return image } /// Maps data received from the signal into a JSON object. /// /// - parameter failsOnEmptyData: A Boolean value determining /// whether the mapping should fail if the data is empty. func mapJSON(failsOnEmptyData: Bool = true) throws -> Any { do { return try JSONSerialization.jsonObject(with: data, options: .allowFragments) } catch { if data.isEmpty && !failsOnEmptyData { return NSNull() } throw MoyaError.jsonMapping(self) } } /// Maps data received from the signal into a String. /// /// - parameter atKeyPath: Optional key path at which to parse string. func mapString(atKeyPath keyPath: String? = nil) throws -> String { if let keyPath = keyPath { // Key path was provided, try to parse string at key path guard let jsonDictionary = try mapJSON() as? NSDictionary, let string = jsonDictionary.value(forKeyPath: keyPath) as? String else { throw MoyaError.stringMapping(self) } return string } else { // Key path was not provided, parse entire response as string guard let string = String(data: data, encoding: .utf8) else { throw MoyaError.stringMapping(self) } return string } } /// Maps data received from the signal into a Decodable object. /// /// - parameter atKeyPath: Optional key path at which to parse object. /// - parameter using: A `JSONDecoder` instance which is used to decode data to an object. func map(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) throws -> D { let serializeToData: (Any) throws -> Data? = { (jsonObject) in guard JSONSerialization.isValidJSONObject(jsonObject) else { return nil } do { return try JSONSerialization.data(withJSONObject: jsonObject) } catch { throw MoyaError.jsonMapping(self) } } let jsonData: Data keyPathCheck: if let keyPath = keyPath { guard let jsonObject = (try mapJSON(failsOnEmptyData: failsOnEmptyData) as? NSDictionary)?.value(forKeyPath: keyPath) else { if failsOnEmptyData { throw MoyaError.jsonMapping(self) } else { jsonData = data break keyPathCheck } } if let data = try serializeToData(jsonObject) { jsonData = data } else { let wrappedJsonObject = ["value": jsonObject] let wrappedJsonData: Data if let data = try serializeToData(wrappedJsonObject) { wrappedJsonData = data } else { throw MoyaError.jsonMapping(self) } do { return try decoder.decode(DecodableWrapper.self, from: wrappedJsonData).value } catch let error { throw MoyaError.objectMapping(error, self) } } } else { jsonData = data } do { if jsonData.isEmpty && !failsOnEmptyData { if let emptyJSONObjectData = "{}".data(using: .utf8), let emptyDecodableValue = try? decoder.decode(D.self, from: emptyJSONObjectData) { return emptyDecodableValue } else if let emptyJSONArrayData = "[{}]".data(using: .utf8), let emptyDecodableValue = try? decoder.decode(D.self, from: emptyJSONArrayData) { return emptyDecodableValue } } return try decoder.decode(D.self, from: jsonData) } catch let error { throw MoyaError.objectMapping(error, self) } } } private struct DecodableWrapper: Decodable { let value: T } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/TargetType.swift ================================================ import Foundation /// The protocol used to define the specifications necessary for a `MoyaProvider`. public protocol TargetType { /// The target's base `URL`. var baseURL: URL { get } /// The path to be appended to `baseURL` to form the full `URL`. var path: String { get } /// The HTTP method used in the request. var method: Moya.Method { get } /// Provides stub data for use in testing. Default is `Data()`. var sampleData: Data { get } /// The type of HTTP task to be performed. var task: Task { get } /// The type of validation to perform on the request. Default is `.none`. var validationType: ValidationType { get } /// The headers to be used in the request. var headers: [String: String]? { get } } public extension TargetType { /// The type of validation to perform on the request. Default is `.none`. var validationType: ValidationType { .none } /// Provides stub data for use in testing. Default is `Data()`. var sampleData: Data { Data() } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/Task.swift ================================================ import Foundation /// Represents an HTTP task. public enum Task { /// A request with no additional data. case requestPlain /// A requests body set with data. case requestData(Data) /// A request body set with `Encodable` type case requestJSONEncodable(Encodable) /// A request body set with `Encodable` type and custom encoder case requestCustomJSONEncodable(Encodable, encoder: JSONEncoder) /// A requests body set with encoded parameters. case requestParameters(parameters: [String: Any], encoding: ParameterEncoding) /// A requests body set with data, combined with url parameters. case requestCompositeData(bodyData: Data, urlParameters: [String: Any]) /// A requests body set with encoded parameters combined with url parameters. case requestCompositeParameters(bodyParameters: [String: Any], bodyEncoding: ParameterEncoding, urlParameters: [String: Any]) /// A file upload task. case uploadFile(URL) /// A "multipart/form-data" upload task. case uploadMultipart([MultipartFormData]) /// A "multipart/form-data" upload task combined with url parameters. case uploadCompositeMultipart([MultipartFormData], urlParameters: [String: Any]) /// A file download task to a destination. case downloadDestination(DownloadDestination) /// A file download task to a destination with extra parameters using the given encoding. case downloadParameters(parameters: [String: Any], encoding: ParameterEncoding, destination: DownloadDestination) } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/URL+Moya.swift ================================================ import Foundation public extension URL { /// Initialize URL from Moya's `TargetType`. init(target: T) { // When a TargetType's path is empty, URL.appendingPathComponent may introduce trailing /, which may not be wanted in some cases // See: https://github.com/Moya/Moya/pull/1053 // And: https://github.com/Moya/Moya/issues/1049 let targetPath = target.path if targetPath.isEmpty { self = target.baseURL } else { self = target.baseURL.appendingPathComponent(targetPath) } } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/URLRequest+Encoding.swift ================================================ import Foundation internal extension URLRequest { mutating func encoded(encodable: Encodable, encoder: JSONEncoder = JSONEncoder()) throws -> URLRequest { do { let encodable = AnyEncodable(encodable) httpBody = try encoder.encode(encodable) let contentTypeHeaderName = "Content-Type" if value(forHTTPHeaderField: contentTypeHeaderName) == nil { setValue("application/json", forHTTPHeaderField: contentTypeHeaderName) } return self } catch { throw MoyaError.encodableMapping(error) } } func encoded(parameters: [String: Any], parameterEncoding: ParameterEncoding) throws -> URLRequest { do { return try parameterEncoding.encode(self, with: parameters) } catch { throw MoyaError.parameterEncoding(error) } } } ================================================ FILE: JetChat/Pods/Moya/Sources/Moya/ValidationType.swift ================================================ import Foundation /// Represents the status codes to validate through Alamofire. public enum ValidationType { /// No validation. case none /// Validate success codes (only 2xx). case successCodes /// Validate success codes and redirection codes (only 2xx and 3xx). case successAndRedirectCodes /// Validate only the given status codes. case customCodes([Int]) /// The list of HTTP status codes to validate. var statusCodes: [Int] { switch self { case .successCodes: return Array(200..<300) case .successAndRedirectCodes: return Array(200..<400) case .customCodes(let codes): return codes case .none: return [] } } } extension ValidationType: Equatable { public static func == (lhs: ValidationType, rhs: ValidationType) -> Bool { switch (lhs, rhs) { case (.none, .none), (.successCodes, .successCodes), (.successAndRedirectCodes, .successAndRedirectCodes): return true case (.customCodes(let code1), .customCodes(let code2)): return code1 == code2 default: return false } } } ================================================ FILE: JetChat/Pods/Moya/Sources/RxMoya/MoyaProvider+Rx.swift ================================================ import Foundation import RxSwift #if !COCOAPODS import Moya #endif extension MoyaProvider: ReactiveCompatible {} public extension Reactive where Base: MoyaProviderType { /// Designated request-making method. /// /// - Parameters: /// - token: Entity, which provides specifications necessary for a `MoyaProvider`. /// - callbackQueue: Callback queue. If nil - queue from provider initializer will be used. /// - Returns: Single response object. func request(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Single { Single.create { [weak base] single in let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in switch result { case let .success(response): single(.success(response)) case let .failure(error): single(.failure(error)) } } return Disposables.create { cancellableToken?.cancel() } } } /// Designated request-making method with progress. func requestWithProgress(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Observable { let progressBlock = AnyObserver.onNext let response: Observable = Observable.create { [weak base] observer in let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: progressBlock(observer)) { result in switch result { case .success: observer.onCompleted() case let .failure(error): observer.onError(error) } } return Disposables.create { cancellableToken?.cancel() } } // Accumulate all progress and combine them when the result comes return response.scan(ProgressResponse()) { last, progress in let progressObject = progress.progressObject ?? last.progressObject let response = progress.response ?? last.response return ProgressResponse(progress: progressObject, response: response) } } } ================================================ FILE: JetChat/Pods/Moya/Sources/RxMoya/Observable+Response.swift ================================================ import Foundation import RxSwift #if !COCOAPODS import Moya #endif #if canImport(UIKit) import UIKit.UIImage #elseif canImport(AppKit) import AppKit.NSImage #endif /// Extension for processing raw NSData generated by network access. public extension ObservableType where Element == Response { /// Filters out responses that don't fall within the given range, generating errors when others are encountered. func filter(statusCodes: R) -> Observable where R.Bound == Int { flatMap { Observable.just(try $0.filter(statusCodes: statusCodes)) } } /// Filters out responses that has the specified `statusCode`. func filter(statusCode: Int) -> Observable { flatMap { Observable.just(try $0.filter(statusCode: statusCode)) } } /// Filters out responses where `statusCode` falls within the range 200 - 299. func filterSuccessfulStatusCodes() -> Observable { flatMap { Observable.just(try $0.filterSuccessfulStatusCodes()) } } /// Filters out responses where `statusCode` falls within the range 200 - 399 func filterSuccessfulStatusAndRedirectCodes() -> Observable { flatMap { Observable.just(try $0.filterSuccessfulStatusAndRedirectCodes()) } } /// Maps data received from the signal into an Image. If the conversion fails, the signal errors. func mapImage() -> Observable { flatMap { Observable.just(try $0.mapImage()) } } /// Maps data received from the signal into a JSON object. If the conversion fails, the signal errors. func mapJSON(failsOnEmptyData: Bool = true) -> Observable { flatMap { Observable.just(try $0.mapJSON(failsOnEmptyData: failsOnEmptyData)) } } /// Maps received data at key path into a String. If the conversion fails, the signal errors. func mapString(atKeyPath keyPath: String? = nil) -> Observable { flatMap { Observable.just(try $0.mapString(atKeyPath: keyPath)) } } /// Maps received data at key path into a Decodable object. If the conversion fails, the signal errors. func map(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) -> Observable { flatMap { Observable.just(try $0.map(type, atKeyPath: keyPath, using: decoder, failsOnEmptyData: failsOnEmptyData)) } } } public extension ObservableType where Element == ProgressResponse { /** Filter completed progress response and maps to actual response - returns: response associated with ProgressResponse object */ func filterCompleted() -> Observable { self .filter { $0.completed } .flatMap { progress -> Observable in // Just a formatlity to satisfy the compiler (completed progresses have responses). switch progress.response { case .some(let response): return .just(response) case .none: return .empty() } } } /** Filter progress events of current ProgressResponse - returns: observable of progress events */ func filterProgress() -> Observable { self.filter { !$0.completed }.map { $0.progress } } } ================================================ FILE: JetChat/Pods/Moya/Sources/RxMoya/Single+Response.swift ================================================ import Foundation import RxSwift #if !COCOAPODS import Moya #endif #if canImport(UIKit) import UIKit.UIImage #elseif canImport(AppKit) import AppKit.NSImage #endif /// Extension for processing raw NSData generated by network access. public extension PrimitiveSequence where Trait == SingleTrait, Element == Response { /// Filters out responses that don't fall within the given closed range, generating errors when others are encountered. func filter(statusCodes: R) -> Single where R.Bound == Int { flatMap { .just(try $0.filter(statusCodes: statusCodes)) } } /// Filters out responses that have the specified `statusCode`. func filter(statusCode: Int) -> Single { flatMap { .just(try $0.filter(statusCode: statusCode)) } } /// Filters out responses where `statusCode` falls within the range 200 - 299. func filterSuccessfulStatusCodes() -> Single { flatMap { .just(try $0.filterSuccessfulStatusCodes()) } } /// Filters out responses where `statusCode` falls within the range 200 - 399 func filterSuccessfulStatusAndRedirectCodes() -> Single { flatMap { .just(try $0.filterSuccessfulStatusAndRedirectCodes()) } } /// Maps data received from the signal into an Image. If the conversion fails, the signal errors. func mapImage() -> Single { flatMap { .just(try $0.mapImage()) } } /// Maps data received from the signal into a JSON object. If the conversion fails, the signal errors. func mapJSON(failsOnEmptyData: Bool = true) -> Single { flatMap { .just(try $0.mapJSON(failsOnEmptyData: failsOnEmptyData)) } } /// Maps received data at key path into a String. If the conversion fails, the signal errors. func mapString(atKeyPath keyPath: String? = nil) -> Single { flatMap { .just(try $0.mapString(atKeyPath: keyPath)) } } /// Maps received data at key path into a Decodable object. If the conversion fails, the signal errors. func map(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) -> Single { flatMap { .just(try $0.map(type, atKeyPath: keyPath, using: decoder, failsOnEmptyData: failsOnEmptyData)) } } } ================================================ FILE: JetChat/Pods/Moya.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 01ACEE699FC085F136296B8AF6F8C5BA /* URL+Moya.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22B2672AC76D960574E34455D3979ED /* URL+Moya.swift */; }; 0302602ADB2109C3A2004E8B2811F276 /* NetworkLoggerPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 455650A9BC6B3ED7C27BC2C7E0A572AD /* NetworkLoggerPlugin.swift */; }; 0F3C744CD0C4D4524C00C7F0C3791314 /* AccessTokenPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AB7D214D4C939D8153AF5E413C4F5D /* AccessTokenPlugin.swift */; }; 1344DC962B9844581A18007FFAE3B45C /* ValidationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81D65AFF7FF59C2BF2742EAF5D9E5372 /* ValidationType.swift */; }; 18AC2A11EAFC249655D85CFAB438A1F3 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0930724F640D30ED5B8FDEB02C59D726 /* Image.swift */; }; 311E3E420E8C6A0A9E6D503C3F057C96 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A8404F2BA7B789C6879241DF73D194 /* AnyEncodable.swift */; }; 31B3E128E1791C83C0535E7BC5C93C1D /* NetworkActivityPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72B403A110D85D565053678943D11760 /* NetworkActivityPlugin.swift */; }; 4979BBB77EC19AAEA695BB4FE7EB5ACC /* RequestTypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96E4FA301B2939EA1583DC7556E4B7D /* RequestTypeWrapper.swift */; }; 4A76A84DABEEF02B7A85F0DC76F6171A /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F9887241C539F81F61EA94B0B6DC929 /* Atomic.swift */; }; 5CBF7D836203ED03102037FD3303C300 /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 745292EDA4E444D97BEA7411B338C934 /* MultipartFormData.swift */; }; 6E76F2A7F7EB752FDDD86B9939590B08 /* MoyaProvider+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02C4E078DE8E935212BB2A44DB36EFC /* MoyaProvider+Rx.swift */; }; 6F5B6A3F67B3991D3177380FA5344CC5 /* Moya-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D66B936530A151070C4F3782347FF3A /* Moya-dummy.m */; }; 762C5D8C6A2DD6CE0B6C72BCEFBE4EF7 /* Observable+Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D642D21C0182E2D823E5B97AA0011 /* Observable+Response.swift */; }; 7AA7FFCDB46EAADF5181A69AC07F0693 /* Single+Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF064AF468CC6BB296BD3D4C569C6943 /* Single+Response.swift */; }; 7E7D7BC74B429DDE17F02F7FF4EAE1AC /* MoyaError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC9C3C16C166CD6E59848DB27FC2E68 /* MoyaError.swift */; }; 8B7DFB253746CF099F8F6EC7133702B4 /* CredentialsPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F3D548EB954A3BA0AE96E711C46A17 /* CredentialsPlugin.swift */; }; 91CB3C5770C0587A46B42A878E6EE516 /* Cancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7FE29C05F22DDF7AC39480190F477A1 /* Cancellable.swift */; }; 9A835D6B46CCB959A699B358192DBAB5 /* MoyaProvider+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4750FC01F2640CE9933DA8D1996B9563 /* MoyaProvider+Internal.swift */; }; 9ACF0060CA686ADC85597CDFAFDDD058 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA418A654EDBE2BDB8EA056C2D152 /* Endpoint.swift */; }; B30635295B9DC522CBDD2E2788EBB935 /* MultiTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E93D266A34448D4B011E99E5163534F /* MultiTarget.swift */; }; C116B02D65F79E7DA2F94A4FFCC44E36 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD5694A301807D84A68CE794C9E4357B /* Foundation.framework */; }; C1B3397C440D9FAD31FCB8F6B0F4D39F /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEA479B62AF5CCF05E529E0B2818E04 /* Response.swift */; }; D5595E3AD2110BD80FC16C070A8306BF /* TargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322F1FC668F5F47BC8070D6FDA43D8B5 /* TargetType.swift */; }; D6EA82E37384787484A5FFA8E260DA33 /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6991DD35A37DD66E0A7E37106E406FB1 /* Plugin.swift */; }; D9EA2DB8AE053EC0A5A96EC9973C05E7 /* URLRequest+Encoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C9F3B317CE0190D7190524197B3AD5 /* URLRequest+Encoding.swift */; }; EF6E0AC790D632AE16F26D861D9309A7 /* MoyaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23F077C9D5023003534866465083DD3F /* MoyaProvider.swift */; }; F502EB630B2446DA5488F06CDA079486 /* MoyaProvider+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170932A7DCC736CFA7A22734F5F8A909 /* MoyaProvider+Defaults.swift */; }; F612B75A56482B9A205C69A86C419D2D /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C2839779150E6C10039B42954D57AC /* Task.swift */; }; FB154C1C7A52D62FB33721C51D172B3A /* Moya+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 107C25A90F8501C0A722E99DF04A5B90 /* Moya+Alamofire.swift */; }; FFF5C95C1AD83705533380A1CC1C5805 /* Moya-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = C2A6449AB68B8FB134B4339E41BFB629 /* Moya-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 0ADB2CE98FCDBC252875C901E472F24D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BC26EC028809C4983780807EBA8CE40B /* RxSwift.xcodeproj */; proxyType = 1; remoteGlobalIDString = F0179EE061353B7A322F596E97844774; remoteInfo = RxSwift; }; 77867E18D02DDCAF00F23052C1CAD358 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B31881F91D3ED2AEF947D94FC5632461 /* Alamofire.xcodeproj */; proxyType = 1; remoteGlobalIDString = 81B7E9B7CD0CADA087A4BB042FBA92E7; remoteInfo = Alamofire; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 01C9F3B317CE0190D7190524197B3AD5 /* URLRequest+Encoding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLRequest+Encoding.swift"; path = "Sources/Moya/URLRequest+Encoding.swift"; sourceTree = ""; }; 0930724F640D30ED5B8FDEB02C59D726 /* Image.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Image.swift; path = Sources/Moya/Image.swift; sourceTree = ""; }; 0F9887241C539F81F61EA94B0B6DC929 /* Atomic.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Atomic.swift; path = Sources/Moya/Atomic.swift; sourceTree = ""; }; 107C25A90F8501C0A722E99DF04A5B90 /* Moya+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Moya+Alamofire.swift"; path = "Sources/Moya/Moya+Alamofire.swift"; sourceTree = ""; }; 170932A7DCC736CFA7A22734F5F8A909 /* MoyaProvider+Defaults.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "MoyaProvider+Defaults.swift"; path = "Sources/Moya/MoyaProvider+Defaults.swift"; sourceTree = ""; }; 22F3D548EB954A3BA0AE96E711C46A17 /* CredentialsPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CredentialsPlugin.swift; path = Sources/Moya/Plugins/CredentialsPlugin.swift; sourceTree = ""; }; 23F077C9D5023003534866465083DD3F /* MoyaProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MoyaProvider.swift; path = Sources/Moya/MoyaProvider.swift; sourceTree = ""; }; 2DC9C3C16C166CD6E59848DB27FC2E68 /* MoyaError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MoyaError.swift; path = Sources/Moya/MoyaError.swift; sourceTree = ""; }; 2E93D266A34448D4B011E99E5163534F /* MultiTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultiTarget.swift; path = Sources/Moya/MultiTarget.swift; sourceTree = ""; }; 322F1FC668F5F47BC8070D6FDA43D8B5 /* TargetType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TargetType.swift; path = Sources/Moya/TargetType.swift; sourceTree = ""; }; 39C2839779150E6C10039B42954D57AC /* Task.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Task.swift; path = Sources/Moya/Task.swift; sourceTree = ""; }; 455650A9BC6B3ED7C27BC2C7E0A572AD /* NetworkLoggerPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkLoggerPlugin.swift; path = Sources/Moya/Plugins/NetworkLoggerPlugin.swift; sourceTree = ""; }; 4750FC01F2640CE9933DA8D1996B9563 /* MoyaProvider+Internal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "MoyaProvider+Internal.swift"; path = "Sources/Moya/MoyaProvider+Internal.swift"; sourceTree = ""; }; 56AB7D214D4C939D8153AF5E413C4F5D /* AccessTokenPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AccessTokenPlugin.swift; path = Sources/Moya/Plugins/AccessTokenPlugin.swift; sourceTree = ""; }; 5AB31CE0659B2C4383AA8842C4D8D0CC /* Moya-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Moya-Info.plist"; sourceTree = ""; }; 62ECA418A654EDBE2BDB8EA056C2D152 /* Endpoint.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Endpoint.swift; path = Sources/Moya/Endpoint.swift; sourceTree = ""; }; 6991DD35A37DD66E0A7E37106E406FB1 /* Plugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Plugin.swift; path = Sources/Moya/Plugin.swift; sourceTree = ""; }; 72B403A110D85D565053678943D11760 /* NetworkActivityPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkActivityPlugin.swift; path = Sources/Moya/Plugins/NetworkActivityPlugin.swift; sourceTree = ""; }; 745292EDA4E444D97BEA7411B338C934 /* MultipartFormData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartFormData.swift; path = Sources/Moya/MultipartFormData.swift; sourceTree = ""; }; 77593F03666F30314EA6446091382B1E /* Moya.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Moya.release.xcconfig; sourceTree = ""; }; 7D66B936530A151070C4F3782347FF3A /* Moya-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Moya-dummy.m"; sourceTree = ""; }; 81D65AFF7FF59C2BF2742EAF5D9E5372 /* ValidationType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ValidationType.swift; path = Sources/Moya/ValidationType.swift; sourceTree = ""; }; 8BFA7D2DA40B9A14C5B9C9CAC5EB34BB /* Moya.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Moya.debug.xcconfig; sourceTree = ""; }; 9EEA479B62AF5CCF05E529E0B2818E04 /* Response.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Response.swift; path = Sources/Moya/Response.swift; sourceTree = ""; }; A96E4FA301B2939EA1583DC7556E4B7D /* RequestTypeWrapper.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestTypeWrapper.swift; path = Sources/Moya/RequestTypeWrapper.swift; sourceTree = ""; }; AF7E5DC3831B61C2FBE59D819B1F2D1E /* Moya-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Moya-prefix.pch"; sourceTree = ""; }; B31881F91D3ED2AEF947D94FC5632461 /* Alamofire */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Alamofire; path = Alamofire.xcodeproj; sourceTree = ""; }; B913CC622BDCDE02506DA5B3E230F6E9 /* Moya */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Moya; path = Moya.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BC26EC028809C4983780807EBA8CE40B /* RxSwift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RxSwift; path = RxSwift.xcodeproj; sourceTree = ""; }; C2A6449AB68B8FB134B4339E41BFB629 /* Moya-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Moya-umbrella.h"; sourceTree = ""; }; DF064AF468CC6BB296BD3D4C569C6943 /* Single+Response.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Single+Response.swift"; path = "Sources/RxMoya/Single+Response.swift"; sourceTree = ""; }; E02C4E078DE8E935212BB2A44DB36EFC /* MoyaProvider+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "MoyaProvider+Rx.swift"; path = "Sources/RxMoya/MoyaProvider+Rx.swift"; sourceTree = ""; }; E22B2672AC76D960574E34455D3979ED /* URL+Moya.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URL+Moya.swift"; path = "Sources/Moya/URL+Moya.swift"; sourceTree = ""; }; E74A0370A09CCE8E84DD46397800F65C /* Moya.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Moya.modulemap; sourceTree = ""; }; E7FE29C05F22DDF7AC39480190F477A1 /* Cancellable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Cancellable.swift; path = Sources/Moya/Cancellable.swift; sourceTree = ""; }; EE9D642D21C0182E2D823E5B97AA0011 /* Observable+Response.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Observable+Response.swift"; path = "Sources/RxMoya/Observable+Response.swift"; sourceTree = ""; }; F8A8404F2BA7B789C6879241DF73D194 /* AnyEncodable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnyEncodable.swift; path = Sources/Moya/AnyEncodable.swift; sourceTree = ""; }; FD5694A301807D84A68CE794C9E4357B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 69F62A8CE4DFA122FE8EA8D426BB2F80 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C116B02D65F79E7DA2F94A4FFCC44E36 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2DF07B8FB1A3978673712826AE750E6F /* Core */ = { isa = PBXGroup; children = ( 56AB7D214D4C939D8153AF5E413C4F5D /* AccessTokenPlugin.swift */, F8A8404F2BA7B789C6879241DF73D194 /* AnyEncodable.swift */, 0F9887241C539F81F61EA94B0B6DC929 /* Atomic.swift */, E7FE29C05F22DDF7AC39480190F477A1 /* Cancellable.swift */, 22F3D548EB954A3BA0AE96E711C46A17 /* CredentialsPlugin.swift */, 62ECA418A654EDBE2BDB8EA056C2D152 /* Endpoint.swift */, 0930724F640D30ED5B8FDEB02C59D726 /* Image.swift */, 107C25A90F8501C0A722E99DF04A5B90 /* Moya+Alamofire.swift */, 2DC9C3C16C166CD6E59848DB27FC2E68 /* MoyaError.swift */, 23F077C9D5023003534866465083DD3F /* MoyaProvider.swift */, 170932A7DCC736CFA7A22734F5F8A909 /* MoyaProvider+Defaults.swift */, 4750FC01F2640CE9933DA8D1996B9563 /* MoyaProvider+Internal.swift */, 745292EDA4E444D97BEA7411B338C934 /* MultipartFormData.swift */, 2E93D266A34448D4B011E99E5163534F /* MultiTarget.swift */, 72B403A110D85D565053678943D11760 /* NetworkActivityPlugin.swift */, 455650A9BC6B3ED7C27BC2C7E0A572AD /* NetworkLoggerPlugin.swift */, 6991DD35A37DD66E0A7E37106E406FB1 /* Plugin.swift */, A96E4FA301B2939EA1583DC7556E4B7D /* RequestTypeWrapper.swift */, 9EEA479B62AF5CCF05E529E0B2818E04 /* Response.swift */, 322F1FC668F5F47BC8070D6FDA43D8B5 /* TargetType.swift */, 39C2839779150E6C10039B42954D57AC /* Task.swift */, E22B2672AC76D960574E34455D3979ED /* URL+Moya.swift */, 01C9F3B317CE0190D7190524197B3AD5 /* URLRequest+Encoding.swift */, 81D65AFF7FF59C2BF2742EAF5D9E5372 /* ValidationType.swift */, ); name = Core; sourceTree = ""; }; 35410AE0F28BB13B09C00ED07E54DA3A /* iOS */ = { isa = PBXGroup; children = ( FD5694A301807D84A68CE794C9E4357B /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; 5049A226E14881347E5B46C65C0664EC /* Dependencies */ = { isa = PBXGroup; children = ( B31881F91D3ED2AEF947D94FC5632461 /* Alamofire */, BC26EC028809C4983780807EBA8CE40B /* RxSwift */, ); name = Dependencies; sourceTree = ""; }; 54D8FDF739D96D66577AF7FFAADA014F = { isa = PBXGroup; children = ( 5049A226E14881347E5B46C65C0664EC /* Dependencies */, C0CAE840C1DDF0C57692603935B67F7E /* Frameworks */, 625D8FA31E44B50132A5ADC023F7A6BC /* Moya */, 853923AD88EFC428ED98DA662CCD4C07 /* Products */, ); sourceTree = ""; }; 625D8FA31E44B50132A5ADC023F7A6BC /* Moya */ = { isa = PBXGroup; children = ( 2DF07B8FB1A3978673712826AE750E6F /* Core */, F8DBCA15239525FBBCBED5779C9C441F /* RxSwift */, D7D2D0D46DB20DF3ABA73FF89B9633C8 /* Support Files */, ); name = Moya; path = Moya; sourceTree = ""; }; 853923AD88EFC428ED98DA662CCD4C07 /* Products */ = { isa = PBXGroup; children = ( B913CC622BDCDE02506DA5B3E230F6E9 /* Moya */, ); name = Products; sourceTree = ""; }; C0CAE840C1DDF0C57692603935B67F7E /* Frameworks */ = { isa = PBXGroup; children = ( 35410AE0F28BB13B09C00ED07E54DA3A /* iOS */, ); name = Frameworks; sourceTree = ""; }; D7D2D0D46DB20DF3ABA73FF89B9633C8 /* Support Files */ = { isa = PBXGroup; children = ( E74A0370A09CCE8E84DD46397800F65C /* Moya.modulemap */, 7D66B936530A151070C4F3782347FF3A /* Moya-dummy.m */, 5AB31CE0659B2C4383AA8842C4D8D0CC /* Moya-Info.plist */, AF7E5DC3831B61C2FBE59D819B1F2D1E /* Moya-prefix.pch */, C2A6449AB68B8FB134B4339E41BFB629 /* Moya-umbrella.h */, 8BFA7D2DA40B9A14C5B9C9CAC5EB34BB /* Moya.debug.xcconfig */, 77593F03666F30314EA6446091382B1E /* Moya.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/Moya"; sourceTree = ""; }; F8DBCA15239525FBBCBED5779C9C441F /* RxSwift */ = { isa = PBXGroup; children = ( E02C4E078DE8E935212BB2A44DB36EFC /* MoyaProvider+Rx.swift */, EE9D642D21C0182E2D823E5B97AA0011 /* Observable+Response.swift */, DF064AF468CC6BB296BD3D4C569C6943 /* Single+Response.swift */, ); name = RxSwift; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ AB2103F3E187BB60D6F7C52FF7BAD7FD /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( FFF5C95C1AD83705533380A1CC1C5805 /* Moya-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 42D4D39F3084C8EA060DE1C283323B37 /* Moya */ = { isa = PBXNativeTarget; buildConfigurationList = 989E9C42556F7F28CBAB27E22200C5CC /* Build configuration list for PBXNativeTarget "Moya" */; buildPhases = ( AB2103F3E187BB60D6F7C52FF7BAD7FD /* Headers */, B71F88D36B606022CFA113DE70C9FB7D /* Sources */, 69F62A8CE4DFA122FE8EA8D426BB2F80 /* Frameworks */, 70362EE3D80710CEC9EE17EBB94A6EFD /* Resources */, ); buildRules = ( ); dependencies = ( A89C00DB97D516E7F74AF5BB86B22DB9 /* PBXTargetDependency */, FA022B88AB0D39B3DE461061D06619AA /* PBXTargetDependency */, ); name = Moya; productName = Moya; productReference = B913CC622BDCDE02506DA5B3E230F6E9 /* Moya */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F6493C42A559A775EEFF8688503009C6 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = BD717B0E9ADA70AE4911AE9DB1298E78 /* Build configuration list for PBXProject "Moya" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 54D8FDF739D96D66577AF7FFAADA014F; productRefGroup = 853923AD88EFC428ED98DA662CCD4C07 /* Products */; projectDirPath = ""; projectReferences = ( { ProjectRef = B31881F91D3ED2AEF947D94FC5632461 /* Alamofire */; }, { ProjectRef = BC26EC028809C4983780807EBA8CE40B /* RxSwift */; }, ); projectRoot = ""; targets = ( 42D4D39F3084C8EA060DE1C283323B37 /* Moya */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 70362EE3D80710CEC9EE17EBB94A6EFD /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ B71F88D36B606022CFA113DE70C9FB7D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0F3C744CD0C4D4524C00C7F0C3791314 /* AccessTokenPlugin.swift in Sources */, 311E3E420E8C6A0A9E6D503C3F057C96 /* AnyEncodable.swift in Sources */, 4A76A84DABEEF02B7A85F0DC76F6171A /* Atomic.swift in Sources */, 91CB3C5770C0587A46B42A878E6EE516 /* Cancellable.swift in Sources */, 8B7DFB253746CF099F8F6EC7133702B4 /* CredentialsPlugin.swift in Sources */, 9ACF0060CA686ADC85597CDFAFDDD058 /* Endpoint.swift in Sources */, 18AC2A11EAFC249655D85CFAB438A1F3 /* Image.swift in Sources */, FB154C1C7A52D62FB33721C51D172B3A /* Moya+Alamofire.swift in Sources */, 6F5B6A3F67B3991D3177380FA5344CC5 /* Moya-dummy.m in Sources */, 7E7D7BC74B429DDE17F02F7FF4EAE1AC /* MoyaError.swift in Sources */, EF6E0AC790D632AE16F26D861D9309A7 /* MoyaProvider.swift in Sources */, F502EB630B2446DA5488F06CDA079486 /* MoyaProvider+Defaults.swift in Sources */, 9A835D6B46CCB959A699B358192DBAB5 /* MoyaProvider+Internal.swift in Sources */, 6E76F2A7F7EB752FDDD86B9939590B08 /* MoyaProvider+Rx.swift in Sources */, 5CBF7D836203ED03102037FD3303C300 /* MultipartFormData.swift in Sources */, B30635295B9DC522CBDD2E2788EBB935 /* MultiTarget.swift in Sources */, 31B3E128E1791C83C0535E7BC5C93C1D /* NetworkActivityPlugin.swift in Sources */, 0302602ADB2109C3A2004E8B2811F276 /* NetworkLoggerPlugin.swift in Sources */, 762C5D8C6A2DD6CE0B6C72BCEFBE4EF7 /* Observable+Response.swift in Sources */, D6EA82E37384787484A5FFA8E260DA33 /* Plugin.swift in Sources */, 4979BBB77EC19AAEA695BB4FE7EB5ACC /* RequestTypeWrapper.swift in Sources */, C1B3397C440D9FAD31FCB8F6B0F4D39F /* Response.swift in Sources */, 7AA7FFCDB46EAADF5181A69AC07F0693 /* Single+Response.swift in Sources */, D5595E3AD2110BD80FC16C070A8306BF /* TargetType.swift in Sources */, F612B75A56482B9A205C69A86C419D2D /* Task.swift in Sources */, 01ACEE699FC085F136296B8AF6F8C5BA /* URL+Moya.swift in Sources */, D9EA2DB8AE053EC0A5A96EC9973C05E7 /* URLRequest+Encoding.swift in Sources */, 1344DC962B9844581A18007FFAE3B45C /* ValidationType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ A89C00DB97D516E7F74AF5BB86B22DB9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Alamofire; targetProxy = 77867E18D02DDCAF00F23052C1CAD358 /* PBXContainerItemProxy */; }; FA022B88AB0D39B3DE461061D06619AA /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RxSwift; targetProxy = 0ADB2CE98FCDBC252875C901E472F24D /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 178FE4C0DD2DB78431ABC56EA39ACC7E /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 77593F03666F30314EA6446091382B1E /* Moya.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/Moya/Moya-prefix.pch"; INFOPLIST_FILE = "Target Support Files/Moya/Moya-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/Moya/Moya.modulemap"; PRODUCT_MODULE_NAME = Moya; PRODUCT_NAME = Moya; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.3; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 38BC10FF3030FD9CDA398BCA2C029FBD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; B8F8B8C3A939E5051966A770EF0DB546 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 8BFA7D2DA40B9A14C5B9C9CAC5EB34BB /* Moya.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/Moya/Moya-prefix.pch"; INFOPLIST_FILE = "Target Support Files/Moya/Moya-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/Moya/Moya.modulemap"; PRODUCT_MODULE_NAME = Moya; PRODUCT_NAME = Moya; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.3; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; EC7ACE4D2D5EE967F30A92C8331BB34D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 989E9C42556F7F28CBAB27E22200C5CC /* Build configuration list for PBXNativeTarget "Moya" */ = { isa = XCConfigurationList; buildConfigurations = ( B8F8B8C3A939E5051966A770EF0DB546 /* Debug */, 178FE4C0DD2DB78431ABC56EA39ACC7E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BD717B0E9ADA70AE4911AE9DB1298E78 /* Build configuration list for PBXProject "Moya" */ = { isa = XCConfigurationList; buildConfigurations = ( 38BC10FF3030FD9CDA398BCA2C029FBD /* Debug */, EC7ACE4D2D5EE967F30A92C8331BB34D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F6493C42A559A775EEFF8688503009C6 /* Project object */; } ================================================ FILE: JetChat/Pods/NSObject+Rx/HasDisposeBag.swift ================================================ import Foundation import RxSwift import ObjectiveC fileprivate var disposeBagContext: UInt8 = 0 /// each HasDisposeBag offers a unique RxSwift DisposeBag instance public protocol HasDisposeBag: class { /// a unique RxSwift DisposeBag instance var disposeBag: DisposeBag { get set } } extension HasDisposeBag { func synchronizedBag( _ action: () -> T) -> T { objc_sync_enter(self) let result = action() objc_sync_exit(self) return result } public var disposeBag: DisposeBag { get { return synchronizedBag { if let disposeObject = objc_getAssociatedObject(self, &disposeBagContext) as? DisposeBag { return disposeObject } let disposeObject = DisposeBag() objc_setAssociatedObject(self, &disposeBagContext, disposeObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return disposeObject } } set { synchronizedBag { objc_setAssociatedObject(self, &disposeBagContext, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } } ================================================ FILE: JetChat/Pods/NSObject+Rx/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Ash Furrow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/NSObject+Rx/NSObject+Rx.swift ================================================ import Foundation import RxSwift import ObjectiveC fileprivate var disposeBagContext: UInt8 = 0 extension Reactive where Base: AnyObject { func synchronizedBag( _ action: () -> T) -> T { objc_sync_enter(self.base) let result = action() objc_sync_exit(self.base) return result } } public extension Reactive where Base: AnyObject { /// a unique DisposeBag that is related to the Reactive.Base instance only for Reference type var disposeBag: DisposeBag { get { return synchronizedBag { if let disposeObject = objc_getAssociatedObject(base, &disposeBagContext) as? DisposeBag { return disposeObject } let disposeObject = DisposeBag() objc_setAssociatedObject(base, &disposeBagContext, disposeObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return disposeObject } } set { synchronizedBag { objc_setAssociatedObject(base, &disposeBagContext, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } } ================================================ FILE: JetChat/Pods/NSObject+Rx/Readme.md ================================================ [![CircleCI](https://circleci.com/gh/RxSwiftCommunity/NSObject-Rx/tree/master.svg?style=svg)](https://circleci.com/gh/RxSwiftCommunity/NSObject-Rx/tree/master) NSObject+Rx =========== If you're using [RxSwift](https://github.com/ReactiveX/RxSwift), you've probably encountered the following code more than a few times. ```swift class MyObject: Whatever { let disposeBag = DisposeBag() ... } ``` You're actually not the only one; it has been typed many, many times. [![Search screenshot showing many, many results.](assets/screenshot.png)](https://github.com/search?q=let+disposeBag+%3D+DisposeBag%28%29&type=Code&utf8=✓) Instead of adding a new property to every object, use this library to add it for you, to any subclass of `NSObject`. ```swift thing .bind(to: otherThing) .disposed(by: rx.disposeBag) ``` Sweet. It'll work just like a property: when the instance is deinit'd, the `DisposeBag` gets disposed. It's also a read/write property, so you can use your own, too. If you want to add a DisposeBag to an Object that does not inherit from NSObject, you can also implement the protocol `HasDisposeBag`, and you're good to go. This protocol provides a default DisposeBag called `disposeBag`. Installing ---------- #### CocoaPods Add to your `Podfile`: ```ruby pod 'NSObject+Rx' ``` And that'll be 👌 #### Carthage Add to `Cartfile`: ``` github "RxSwiftCommunity/NSObject-Rx" ``` Add frameworks to your project (no need to "copy items if needed") Run `carthage update` or `carthage update --platform ios` if you target iOS only Add run script build phase `/usr/local/bin/carthage copy-frameworks` with input files being: ``` $(SRCROOT)/Carthage/Build/iOS/RxSwift.framework $(SRCROOT)/Carthage/Build/iOS/NSObject_Rx.framework ``` And rule ✌️ Contributing ------------ Source files are in the root directory. We use CocoaPods to develop, check out the unit tests in the Demo project. License ------- MIT obvs. ![Tim Cook dancing to the sound of a permissive license.](http://i.imgur.com/mONiWzj.gif) ================================================ FILE: JetChat/Pods/NSObject+Rx.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 2F15069A540A8F8683A071A55D15C74A /* NSObject+Rx-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = ACFEC35DC574FF78440AE16F120DF03F /* NSObject+Rx-dummy.m */; }; 8B653D760D57786D334AAC97EE0C7456 /* NSObject+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1D27A2FD8318296ED63050631BA638 /* NSObject+Rx.swift */; }; A256A93934A30F4C1B895088807361B3 /* HasDisposeBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F56ABCA60272CFF67E49C9E1FE9ED7 /* HasDisposeBag.swift */; }; E34AEB36CB2AFD9877382205C698033D /* NSObject+Rx-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EFE9F2B7DF6AAE13005A7D0D682308C /* NSObject+Rx-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; FECE79771828D5513D79BE66335B0AAC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F8B994F4C475906D73B6BF8185B8329 /* Foundation.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 2DD8E42D8D553CDF1033EC0F25AB9CD3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 4E9AAB33597B354282A06A67390FD33E /* RxSwift.xcodeproj */; proxyType = 1; remoteGlobalIDString = F0179EE061353B7A322F596E97844774; remoteInfo = RxSwift; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 18BC41B01F45E49EE0B4E32F31BCF76D /* NSObject+Rx-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "NSObject+Rx-Info.plist"; sourceTree = ""; }; 3E1D27A2FD8318296ED63050631BA638 /* NSObject+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "NSObject+Rx.swift"; sourceTree = ""; }; 4E9AAB33597B354282A06A67390FD33E /* RxSwift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RxSwift; path = RxSwift.xcodeproj; sourceTree = ""; }; 4EFE9F2B7DF6AAE13005A7D0D682308C /* NSObject+Rx-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "NSObject+Rx-umbrella.h"; sourceTree = ""; }; 5F8B994F4C475906D73B6BF8185B8329 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 7A73EB54F353715C79AB18937C14A9EF /* NSObject+Rx-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "NSObject+Rx-prefix.pch"; sourceTree = ""; }; 7D2BED888BA052199CA8FB44128D4F0F /* NSObject+Rx.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "NSObject+Rx.debug.xcconfig"; sourceTree = ""; }; 81F56ABCA60272CFF67E49C9E1FE9ED7 /* HasDisposeBag.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HasDisposeBag.swift; sourceTree = ""; }; A5BDF849DF6103DCB0FFA98879260BBC /* NSObject+Rx.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "NSObject+Rx.release.xcconfig"; sourceTree = ""; }; ACFEC35DC574FF78440AE16F120DF03F /* NSObject+Rx-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Rx-dummy.m"; sourceTree = ""; }; BAC07ECF2A074705EB181BEC8227C5D1 /* NSObject+Rx.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "NSObject+Rx.modulemap"; sourceTree = ""; }; D2A8ACD43E3AA5097A118F0258AD5521 /* NSObject+Rx */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "NSObject+Rx"; path = NSObject_Rx.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2DB776BD7C1B801780EB9F49832A14B8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( FECE79771828D5513D79BE66335B0AAC /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 287CB4D8109BE99B083DA9127E0C84C5 = { isa = PBXGroup; children = ( ED68B683AA0B251ED3AC6F1A1EEDD64A /* Dependencies */, EB5D211BEA57E5DACE46A43C553DB96C /* Frameworks */, EAB2EEC93D0B6A95E1FA507BA530DA52 /* NSObject+Rx */, F2414054AD26CAB8A2FF13E30FF8357F /* Products */, ); sourceTree = ""; }; 5F7BA46112A8D4C24A0792683692C383 /* iOS */ = { isa = PBXGroup; children = ( 5F8B994F4C475906D73B6BF8185B8329 /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; E5D734131881FA289812E66B8267837E /* Support Files */ = { isa = PBXGroup; children = ( BAC07ECF2A074705EB181BEC8227C5D1 /* NSObject+Rx.modulemap */, ACFEC35DC574FF78440AE16F120DF03F /* NSObject+Rx-dummy.m */, 18BC41B01F45E49EE0B4E32F31BCF76D /* NSObject+Rx-Info.plist */, 7A73EB54F353715C79AB18937C14A9EF /* NSObject+Rx-prefix.pch */, 4EFE9F2B7DF6AAE13005A7D0D682308C /* NSObject+Rx-umbrella.h */, 7D2BED888BA052199CA8FB44128D4F0F /* NSObject+Rx.debug.xcconfig */, A5BDF849DF6103DCB0FFA98879260BBC /* NSObject+Rx.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/NSObject+Rx"; sourceTree = ""; }; EAB2EEC93D0B6A95E1FA507BA530DA52 /* NSObject+Rx */ = { isa = PBXGroup; children = ( 81F56ABCA60272CFF67E49C9E1FE9ED7 /* HasDisposeBag.swift */, 3E1D27A2FD8318296ED63050631BA638 /* NSObject+Rx.swift */, E5D734131881FA289812E66B8267837E /* Support Files */, ); name = "NSObject+Rx"; path = "NSObject+Rx"; sourceTree = ""; }; EB5D211BEA57E5DACE46A43C553DB96C /* Frameworks */ = { isa = PBXGroup; children = ( 5F7BA46112A8D4C24A0792683692C383 /* iOS */, ); name = Frameworks; sourceTree = ""; }; ED68B683AA0B251ED3AC6F1A1EEDD64A /* Dependencies */ = { isa = PBXGroup; children = ( 4E9AAB33597B354282A06A67390FD33E /* RxSwift */, ); name = Dependencies; sourceTree = ""; }; F2414054AD26CAB8A2FF13E30FF8357F /* Products */ = { isa = PBXGroup; children = ( D2A8ACD43E3AA5097A118F0258AD5521 /* NSObject+Rx */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ E8FBA7CEEEB108877386BFBCC4273865 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( E34AEB36CB2AFD9877382205C698033D /* NSObject+Rx-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 8959B62B3AF892ED95EA44BE841842BD /* NSObject+Rx */ = { isa = PBXNativeTarget; buildConfigurationList = 6A77C790DB08A8445E887E14012A1047 /* Build configuration list for PBXNativeTarget "NSObject+Rx" */; buildPhases = ( E8FBA7CEEEB108877386BFBCC4273865 /* Headers */, B605DDE5DF78A056BF1A5FEA5B25AF77 /* Sources */, 2DB776BD7C1B801780EB9F49832A14B8 /* Frameworks */, 2DC6E76C62FF5AC6DDD3008DDA4393D9 /* Resources */, ); buildRules = ( ); dependencies = ( DFF4E5964CCC76A3E6E7BB5FF92EAD58 /* PBXTargetDependency */, ); name = "NSObject+Rx"; productName = NSObject_Rx; productReference = D2A8ACD43E3AA5097A118F0258AD5521 /* NSObject+Rx */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ BD587980CBF20EAB86A9CBBEC227E77A /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 1A0C0A92F14C00D31616EEBA600D2ADD /* Build configuration list for PBXProject "NSObject+Rx" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 287CB4D8109BE99B083DA9127E0C84C5; productRefGroup = F2414054AD26CAB8A2FF13E30FF8357F /* Products */; projectDirPath = ""; projectReferences = ( { ProjectRef = 4E9AAB33597B354282A06A67390FD33E /* RxSwift */; }, ); projectRoot = ""; targets = ( 8959B62B3AF892ED95EA44BE841842BD /* NSObject+Rx */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2DC6E76C62FF5AC6DDD3008DDA4393D9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ B605DDE5DF78A056BF1A5FEA5B25AF77 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A256A93934A30F4C1B895088807361B3 /* HasDisposeBag.swift in Sources */, 8B653D760D57786D334AAC97EE0C7456 /* NSObject+Rx.swift in Sources */, 2F15069A540A8F8683A071A55D15C74A /* NSObject+Rx-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ DFF4E5964CCC76A3E6E7BB5FF92EAD58 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RxSwift; targetProxy = 2DD8E42D8D553CDF1033EC0F25AB9CD3 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 7D13F9A7B14A0A352BF931DBEE97A644 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; B957FFDE0FE3CA464163C506F8F3F695 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = A5BDF849DF6103DCB0FFA98879260BBC /* NSObject+Rx.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/NSObject+Rx/NSObject+Rx-prefix.pch"; INFOPLIST_FILE = "Target Support Files/NSObject+Rx/NSObject+Rx-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/NSObject+Rx/NSObject+Rx.modulemap"; PRODUCT_MODULE_NAME = NSObject_Rx; PRODUCT_NAME = NSObject_Rx; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.2; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; D4359E2EA93D80560746DFF5960E0E45 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; D6380C87853A06170AA6B4B21D2B63EE /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7D2BED888BA052199CA8FB44128D4F0F /* NSObject+Rx.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/NSObject+Rx/NSObject+Rx-prefix.pch"; INFOPLIST_FILE = "Target Support Files/NSObject+Rx/NSObject+Rx-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/NSObject+Rx/NSObject+Rx.modulemap"; PRODUCT_MODULE_NAME = NSObject_Rx; PRODUCT_NAME = NSObject_Rx; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1A0C0A92F14C00D31616EEBA600D2ADD /* Build configuration list for PBXProject "NSObject+Rx" */ = { isa = XCConfigurationList; buildConfigurations = ( 7D13F9A7B14A0A352BF931DBEE97A644 /* Debug */, D4359E2EA93D80560746DFF5960E0E45 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6A77C790DB08A8445E887E14012A1047 /* Build configuration list for PBXNativeTarget "NSObject+Rx" */ = { isa = XCConfigurationList; buildConfigurations = ( D6380C87853A06170AA6B4B21D2B63EE /* Debug */, B957FFDE0FE3CA464163C506F8F3F695 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = BD587980CBF20EAB86A9CBBEC227E77A /* Project object */; } ================================================ FILE: JetChat/Pods/Pods.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 59659CE0366CC5E00255421847A4D9E5 /* Pods-FY-IMChat-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C61FD2BA6F5AA699823B33298FC60CB /* Pods-FY-IMChat-dummy.m */; }; AB889899087A022F9CDC807FD8FDD58E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */; }; F45523F3401BB4CA0CB62234B6205D3A /* Pods-FY-IMChat-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 98FC81A49A355E0476EF6F20062425D1 /* Pods-FY-IMChat-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 179EE28B34EF9DFDDE20E7557E721E93 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CA7DE2133F5A597608F2965178D3A58B /* SwifterSwift.xcodeproj */; proxyType = 1; remoteGlobalIDString = 480FE5A632FE83D1C34E3D143792B43C; remoteInfo = SwifterSwift; }; 20E4979136D7BEF8A2A2246AF45C3095 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E5D2CCA1CBD9E9DBABE5E4AF29D0256B /* RxTheme.xcodeproj */; proxyType = 1; remoteGlobalIDString = 642CE8A56D477528D9E8BCDD0A6FEBB8; remoteInfo = RxTheme; }; 2729CC999F664F831C5427C679442B70 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 857C0E24CF40B7B3DA2BD6FA5647577B /* RxSwift.xcodeproj */; proxyType = 1; remoteGlobalIDString = F0179EE061353B7A322F596E97844774; remoteInfo = RxSwift; }; 27A7FA3CF9A7F44C66A4CBA3B3BABEA1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B17FBD4BAE6C8F56AF2C17EBA67635D2 /* R.swift.Library.xcodeproj */; proxyType = 1; remoteGlobalIDString = 61AC8FF22886EA444144B44F9AC733B8; remoteInfo = R.swift.Library; }; 2BD3BFA0F60EC170B8BD78DF4A02BF73 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 581C8733EC6B49617C9E986DEC414849 /* Kingfisher.xcodeproj */; proxyType = 1; remoteGlobalIDString = 05BB3BFEAA03766641420DCD88978468; remoteInfo = Kingfisher; }; 34A4839299095B0C0EC28D32E1EA8631 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C52B01BAA3BA7AD7306D321A5D0CA43E /* YYImage.xcodeproj */; proxyType = 1; remoteGlobalIDString = 79A687989E871964747CED931C10657C; remoteInfo = YYImage; }; 38C8E84E0C6BC9A02E472775186B8674 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 80768DA22DD40A31E5CBFB00A0C907E3 /* RxRelay.xcodeproj */; proxyType = 1; remoteGlobalIDString = 564FA919E05BFD512DA9163BAB640EEE; remoteInfo = RxRelay; }; 3E9D67A07F0A886FF6004CA2144BF4DF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 11BCE9367A626F89920B14671B71FF07 /* WCDB.swift.xcodeproj */; proxyType = 1; remoteGlobalIDString = 4EAAAD4A9736C05D0B4707614E368B84; remoteInfo = WCDB.swift; }; 4730D86AD92E3133810CDADE979D323E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = FA089FB2D3E623384DBE92B4E7C61590 /* SDWebImage.xcodeproj */; proxyType = 1; remoteGlobalIDString = 4ECF4E662EEBE4FA58102FF984D920CC; remoteInfo = SDWebImage; }; 515CA3968C8D75CEE2C174CF752AA174 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E19F7217AA5D518ED2D03F5DBD9318DE /* SnapKit.xcodeproj */; proxyType = 1; remoteGlobalIDString = 6913FCF4B90C224E55FE5C821AFB90D2; remoteInfo = SnapKit; }; 556A154513D26F324D5B6296BC0BCF04 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = FBAF30105A714565F2E375AAC158147A /* SQLiteRepairKit.xcodeproj */; proxyType = 1; remoteGlobalIDString = 1A6A317D19224BA6C654767A5DA5460D; remoteInfo = SQLiteRepairKit; }; 5C992C3ACA732AEBCFBFF28EB96BFC16 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 514DEE95CF54879F0F065FD68F04AF6F /* R.swift.xcodeproj */; proxyType = 1; remoteGlobalIDString = FF6ED22E1CDAF83ADE98360601DC4DE5; remoteInfo = R.swift; }; 5D0925AC06BF098BA82FACCBEEC72D21 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CF2B2063A9DEB48D730473BC03C4B9F6 /* WCDBOptimizedSQLCipher.xcodeproj */; proxyType = 1; remoteGlobalIDString = 8820E4661B26990965C45655F51ED18B; remoteInfo = WCDBOptimizedSQLCipher; }; 6D11C13A84D5B7C485A3851E27D6C31B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = EAF7D9CF742B5974293716634CFEB658 /* Alamofire.xcodeproj */; proxyType = 1; remoteGlobalIDString = 81B7E9B7CD0CADA087A4BB042FBA92E7; remoteInfo = Alamofire; }; 6E93F34FB70EAEDF8C5B59AC54221BA6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7D092B25BF1B9DB2B5DECE236780D189 /* Moya.xcodeproj */; proxyType = 1; remoteGlobalIDString = 42D4D39F3084C8EA060DE1C283323B37; remoteInfo = Moya; }; 70EC061318C69C673D7B3745D2B4DCDE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 374284FE69290B23B7ED62FF180A1C71 /* HandyJSON.xcodeproj */; proxyType = 1; remoteGlobalIDString = 42160F46A22E1CA007076183E782CF4B; remoteInfo = HandyJSON; }; 78550796E8A69A97F34F540E81E61AEB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E74166EC76A656C80377A9C7631815CC /* YBImageBrowser.xcodeproj */; proxyType = 1; remoteGlobalIDString = A13069CD227D6C648A6A2BF33159B4FA; remoteInfo = YBImageBrowser; }; 82FDE8BA4002CA5EA9A25C40FCABCF79 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 499E620EA4999D9FF1E3234277C2CF2A /* MBProgressHUD.xcodeproj */; proxyType = 1; remoteGlobalIDString = 0F48A71D28991325602E8C8A19DF1F64; remoteInfo = MBProgressHUD; }; 89DB88F1490B277041804DF46504434E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D9A216D78F694506A7F33ADF7C6AB76B /* FDFullscreenPopGesture.xcodeproj */; proxyType = 1; remoteGlobalIDString = A8BAA0D80184552CA0F0647BF03C3D1B; remoteInfo = FDFullscreenPopGesture; }; 8D0C130FFDEECBB71CA8FA4FAC8DB832 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D128140CC9E76B5402DC0A1A6F50EAFB /* SwiftyJSON.xcodeproj */; proxyType = 1; remoteGlobalIDString = AD1A79042DA54FF6DCBF6C015B0E27B5; remoteInfo = SwiftyJSON; }; 8D1DAC5546C4AB3C02A4050206695DA5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 10543E34CEFC1213E9B0EB7AA4DF255E /* UIView+FDCollapsibleConstraints.xcodeproj */; proxyType = 1; remoteGlobalIDString = F254007166B3EB42A09DBC736355B678; remoteInfo = "UIView+FDCollapsibleConstraints"; }; 9E3434EE805C22588545C3FDE0209F81 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A50B9332B0F888D2D5A5DA753F626655 /* YYText.xcodeproj */; proxyType = 1; remoteGlobalIDString = 40E4DD13C21C218AB8CF84FBB29F3285; remoteInfo = YYText; }; B116771450CA88BD525EECBFB296E62D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9B99FEFC29E8CDDB98C169ACEEA30F06 /* MJRefresh.xcodeproj */; proxyType = 1; remoteGlobalIDString = E94AEF767442EDAAAAB2129CDFA2C2ED; remoteInfo = MJRefresh; }; B3FA85EF66D6498A6F10E79323E5ABCA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0438DBF2C110FB4EE71FC78F51DDDF6A /* IGListDiffKit.xcodeproj */; proxyType = 1; remoteGlobalIDString = 9074A5DFB260E6240F743D74D3F432DD; remoteInfo = IGListDiffKit; }; B9D62E0742962486B4247FDE82DED067 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 1D9901AF1E23811CFB54AEA4ECD942BF /* UITableView+FDTemplateLayoutCell.xcodeproj */; proxyType = 1; remoteGlobalIDString = A5CF12962AE63218F7BCE07569CE36DE; remoteInfo = "UITableView+FDTemplateLayoutCell"; }; C7C6BBCA61141E4541F59E23A3E90255 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E13BAFCAD1DCB54A76BAB39718907ED5 /* IGListKit.xcodeproj */; proxyType = 1; remoteGlobalIDString = 6C5D6FE2744F299C4B5885492E7CFF8C; remoteInfo = IGListKit; }; D5EAA56F1E50EE55FD279F29B2B59634 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BF81471569EFE4A56814D0A7D1C24EC3 /* Localize-Swift.xcodeproj */; proxyType = 1; remoteGlobalIDString = 6C27EEC63FEC06A9DAB03531B0989F47; remoteInfo = "Localize-Swift"; }; DF5B3C1CAE37761B2C05A85CAE10DAB2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 46C770FB65D3D309792127CFD56FD9B8 /* LookinServer.xcodeproj */; proxyType = 1; remoteGlobalIDString = 648108237631EEA52299D347ABDCA30C; remoteInfo = LookinServer; }; E617B6696E88F6BF79E54EE80D8CA84F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = AF90DD2CA388D1789BAC140781F36FA1 /* SwiftDate.xcodeproj */; proxyType = 1; remoteGlobalIDString = 084C1733B4DEB4359B4EFB893E424972; remoteInfo = SwiftDate; }; E93988DDA9311B8385BB8950396A866B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 80F2A5AD23DCFB1AF4994707D2649ED2 /* NSObject+Rx.xcodeproj */; proxyType = 1; remoteGlobalIDString = 8959B62B3AF892ED95EA44BE841842BD; remoteInfo = "NSObject+Rx"; }; EF09737501CBDA7963CF2A90C03DB14D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 09DDDFAE034F2B7BEBFD89569E59AFFB /* ReachabilitySwift.xcodeproj */; proxyType = 1; remoteGlobalIDString = 02AAC45BCBE8F4C196016B6D96C7A992; remoteInfo = ReachabilitySwift; }; F8A3327D7F399134FE2388E99FAE6543 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 2ACBC9CE9A3FA36127A8E2A104F3FDFB /* RxCocoa.xcodeproj */; proxyType = 1; remoteGlobalIDString = BC5183FBB16A06C1D86620B00CFE6269; remoteInfo = RxCocoa; }; F913E5D1C9AEE1E2A723435FC667138F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0A3C1132C414C38CFDBF4556F1A58678 /* IQKeyboardManagerSwift.xcodeproj */; proxyType = 1; remoteGlobalIDString = 29F73A40F1F145F65BBA049AC76AB585; remoteInfo = IQKeyboardManagerSwift; }; FFA99A43553494A820BCD3EC55843B14 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D36D5E926088FE66266C81634BF0A649 /* TZImagePickerController.xcodeproj */; proxyType = 1; remoteGlobalIDString = 6090C91802307673CBF0A57EF1608978; remoteInfo = TZImagePickerController; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 0438DBF2C110FB4EE71FC78F51DDDF6A /* IGListDiffKit */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = IGListDiffKit; path = IGListDiffKit.xcodeproj; sourceTree = ""; }; 09DDDFAE034F2B7BEBFD89569E59AFFB /* ReachabilitySwift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReachabilitySwift; path = ReachabilitySwift.xcodeproj; sourceTree = ""; }; 0A3C1132C414C38CFDBF4556F1A58678 /* IQKeyboardManagerSwift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = IQKeyboardManagerSwift; path = IQKeyboardManagerSwift.xcodeproj; sourceTree = ""; }; 0C61FD2BA6F5AA699823B33298FC60CB /* Pods-FY-IMChat-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-FY-IMChat-dummy.m"; sourceTree = ""; }; 10543E34CEFC1213E9B0EB7AA4DF255E /* UIView+FDCollapsibleConstraints */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "UIView+FDCollapsibleConstraints"; path = "UIView+FDCollapsibleConstraints.xcodeproj"; sourceTree = ""; }; 11BCE9367A626F89920B14671B71FF07 /* WCDB.swift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = WCDB.swift; path = WCDB.swift.xcodeproj; sourceTree = ""; }; 1BB93459E605BC8D21A4FE01CCCA1C0C /* Pods-FY-IMChat-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-FY-IMChat-frameworks.sh"; sourceTree = ""; }; 1D9901AF1E23811CFB54AEA4ECD942BF /* UITableView+FDTemplateLayoutCell */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "UITableView+FDTemplateLayoutCell"; path = "UITableView+FDTemplateLayoutCell.xcodeproj"; sourceTree = ""; }; 2ACBC9CE9A3FA36127A8E2A104F3FDFB /* RxCocoa */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RxCocoa; path = RxCocoa.xcodeproj; sourceTree = ""; }; 374284FE69290B23B7ED62FF180A1C71 /* HandyJSON */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = HandyJSON; path = HandyJSON.xcodeproj; sourceTree = ""; }; 46C770FB65D3D309792127CFD56FD9B8 /* LookinServer */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = LookinServer; path = LookinServer.xcodeproj; sourceTree = ""; }; 499E620EA4999D9FF1E3234277C2CF2A /* MBProgressHUD */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MBProgressHUD; path = MBProgressHUD.xcodeproj; sourceTree = ""; }; 514DEE95CF54879F0F065FD68F04AF6F /* R.swift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = R.swift; path = R.swift.xcodeproj; sourceTree = ""; }; 561081F080F0DA8ABA0C1588011B8FDF /* Pods-FY-IMChat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-FY-IMChat.release.xcconfig"; sourceTree = ""; }; 581C8733EC6B49617C9E986DEC414849 /* Kingfisher */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Kingfisher; path = Kingfisher.xcodeproj; sourceTree = ""; }; 607F4C3B2B91E2F189EAE1547F5860ED /* Pods-FY-IMChat-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-FY-IMChat-Info.plist"; sourceTree = ""; }; 6C40E1643E4BD3784E41115E09146137 /* Pods-FY-IMChat.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-FY-IMChat.modulemap"; sourceTree = ""; }; 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 73510C8E62D698B63C49990AA2405BE8 /* Pods-FY-IMChat-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-FY-IMChat-acknowledgements.markdown"; sourceTree = ""; }; 7C3E7FE59E7584693361B440BF0409A8 /* Pods-FY-IMChat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-FY-IMChat.debug.xcconfig"; sourceTree = ""; }; 7D092B25BF1B9DB2B5DECE236780D189 /* Moya */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Moya; path = Moya.xcodeproj; sourceTree = ""; }; 80768DA22DD40A31E5CBFB00A0C907E3 /* RxRelay */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RxRelay; path = RxRelay.xcodeproj; sourceTree = ""; }; 80F2A5AD23DCFB1AF4994707D2649ED2 /* NSObject+Rx */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "NSObject+Rx"; path = "NSObject+Rx.xcodeproj"; sourceTree = ""; }; 857C0E24CF40B7B3DA2BD6FA5647577B /* RxSwift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RxSwift; path = RxSwift.xcodeproj; sourceTree = ""; }; 98FC81A49A355E0476EF6F20062425D1 /* Pods-FY-IMChat-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-FY-IMChat-umbrella.h"; sourceTree = ""; }; 9B99FEFC29E8CDDB98C169ACEEA30F06 /* MJRefresh */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MJRefresh; path = MJRefresh.xcodeproj; sourceTree = ""; }; 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; A50B9332B0F888D2D5A5DA753F626655 /* YYText */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = YYText; path = YYText.xcodeproj; sourceTree = ""; }; AA88F6F88491C368B6255613C73CE925 /* Pods-FY-IMChat-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-FY-IMChat-acknowledgements.plist"; sourceTree = ""; }; AF90DD2CA388D1789BAC140781F36FA1 /* SwiftDate */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SwiftDate; path = SwiftDate.xcodeproj; sourceTree = ""; }; B17FBD4BAE6C8F56AF2C17EBA67635D2 /* R.swift.Library */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = R.swift.Library; path = R.swift.Library.xcodeproj; sourceTree = ""; }; B2822B632C9B73C1341CAE612EFB90CC /* Pods-FY-IMChat */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-FY-IMChat"; path = Pods_FY_IMChat.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BF81471569EFE4A56814D0A7D1C24EC3 /* Localize-Swift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "Localize-Swift"; path = "Localize-Swift.xcodeproj"; sourceTree = ""; }; C52B01BAA3BA7AD7306D321A5D0CA43E /* YYImage */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = YYImage; path = YYImage.xcodeproj; sourceTree = ""; }; CA7DE2133F5A597608F2965178D3A58B /* SwifterSwift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SwifterSwift; path = SwifterSwift.xcodeproj; sourceTree = ""; }; CF2B2063A9DEB48D730473BC03C4B9F6 /* WCDBOptimizedSQLCipher */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = WCDBOptimizedSQLCipher; path = WCDBOptimizedSQLCipher.xcodeproj; sourceTree = ""; }; D128140CC9E76B5402DC0A1A6F50EAFB /* SwiftyJSON */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SwiftyJSON; path = SwiftyJSON.xcodeproj; sourceTree = ""; }; D36D5E926088FE66266C81634BF0A649 /* TZImagePickerController */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = TZImagePickerController; path = TZImagePickerController.xcodeproj; sourceTree = ""; }; D9A216D78F694506A7F33ADF7C6AB76B /* FDFullscreenPopGesture */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FDFullscreenPopGesture; path = FDFullscreenPopGesture.xcodeproj; sourceTree = ""; }; E13BAFCAD1DCB54A76BAB39718907ED5 /* IGListKit */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = IGListKit; path = IGListKit.xcodeproj; sourceTree = ""; }; E19F7217AA5D518ED2D03F5DBD9318DE /* SnapKit */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SnapKit; path = SnapKit.xcodeproj; sourceTree = ""; }; E5D2CCA1CBD9E9DBABE5E4AF29D0256B /* RxTheme */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RxTheme; path = RxTheme.xcodeproj; sourceTree = ""; }; E74166EC76A656C80377A9C7631815CC /* YBImageBrowser */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = YBImageBrowser; path = YBImageBrowser.xcodeproj; sourceTree = ""; }; EAF7D9CF742B5974293716634CFEB658 /* Alamofire */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Alamofire; path = Alamofire.xcodeproj; sourceTree = ""; }; FA089FB2D3E623384DBE92B4E7C61590 /* SDWebImage */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SDWebImage; path = SDWebImage.xcodeproj; sourceTree = ""; }; FBAF30105A714565F2E375AAC158147A /* SQLiteRepairKit */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SQLiteRepairKit; path = SQLiteRepairKit.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 9A5A975A9130CA8F6A446E120E4B844F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( AB889899087A022F9CDC807FD8FDD58E /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0C39A08F310CC034632690BEE4EB14EF /* Products */ = { isa = PBXGroup; children = ( B2822B632C9B73C1341CAE612EFB90CC /* Pods-FY-IMChat */, ); name = Products; sourceTree = ""; }; 35829EBE3BB4C12753FC61D4001AACDA /* Targets Support Files */ = { isa = PBXGroup; children = ( 8E65A30BE3E971541F6A5A9409FBBFA5 /* Pods-FY-IMChat */, ); name = "Targets Support Files"; sourceTree = ""; }; 53B0E9973AA05354C2BB6F89AD290C1E /* Pods */ = { isa = PBXGroup; children = ( EAF7D9CF742B5974293716634CFEB658 /* Alamofire */, D9A216D78F694506A7F33ADF7C6AB76B /* FDFullscreenPopGesture */, 374284FE69290B23B7ED62FF180A1C71 /* HandyJSON */, 0438DBF2C110FB4EE71FC78F51DDDF6A /* IGListDiffKit */, E13BAFCAD1DCB54A76BAB39718907ED5 /* IGListKit */, 0A3C1132C414C38CFDBF4556F1A58678 /* IQKeyboardManagerSwift */, 581C8733EC6B49617C9E986DEC414849 /* Kingfisher */, BF81471569EFE4A56814D0A7D1C24EC3 /* Localize-Swift */, 46C770FB65D3D309792127CFD56FD9B8 /* LookinServer */, 499E620EA4999D9FF1E3234277C2CF2A /* MBProgressHUD */, 9B99FEFC29E8CDDB98C169ACEEA30F06 /* MJRefresh */, 7D092B25BF1B9DB2B5DECE236780D189 /* Moya */, 80F2A5AD23DCFB1AF4994707D2649ED2 /* NSObject+Rx */, 514DEE95CF54879F0F065FD68F04AF6F /* R.swift */, B17FBD4BAE6C8F56AF2C17EBA67635D2 /* R.swift.Library */, 09DDDFAE034F2B7BEBFD89569E59AFFB /* ReachabilitySwift */, 2ACBC9CE9A3FA36127A8E2A104F3FDFB /* RxCocoa */, 80768DA22DD40A31E5CBFB00A0C907E3 /* RxRelay */, 857C0E24CF40B7B3DA2BD6FA5647577B /* RxSwift */, E5D2CCA1CBD9E9DBABE5E4AF29D0256B /* RxTheme */, FA089FB2D3E623384DBE92B4E7C61590 /* SDWebImage */, E19F7217AA5D518ED2D03F5DBD9318DE /* SnapKit */, FBAF30105A714565F2E375AAC158147A /* SQLiteRepairKit */, AF90DD2CA388D1789BAC140781F36FA1 /* SwiftDate */, CA7DE2133F5A597608F2965178D3A58B /* SwifterSwift */, D128140CC9E76B5402DC0A1A6F50EAFB /* SwiftyJSON */, D36D5E926088FE66266C81634BF0A649 /* TZImagePickerController */, 1D9901AF1E23811CFB54AEA4ECD942BF /* UITableView+FDTemplateLayoutCell */, 10543E34CEFC1213E9B0EB7AA4DF255E /* UIView+FDCollapsibleConstraints */, 11BCE9367A626F89920B14671B71FF07 /* WCDB.swift */, CF2B2063A9DEB48D730473BC03C4B9F6 /* WCDBOptimizedSQLCipher */, E74166EC76A656C80377A9C7631815CC /* YBImageBrowser */, C52B01BAA3BA7AD7306D321A5D0CA43E /* YYImage */, A50B9332B0F888D2D5A5DA753F626655 /* YYText */, ); name = Pods; sourceTree = ""; }; 578452D2E740E91742655AC8F1636D1F /* iOS */ = { isa = PBXGroup; children = ( 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; 8E65A30BE3E971541F6A5A9409FBBFA5 /* Pods-FY-IMChat */ = { isa = PBXGroup; children = ( 6C40E1643E4BD3784E41115E09146137 /* Pods-FY-IMChat.modulemap */, 73510C8E62D698B63C49990AA2405BE8 /* Pods-FY-IMChat-acknowledgements.markdown */, AA88F6F88491C368B6255613C73CE925 /* Pods-FY-IMChat-acknowledgements.plist */, 0C61FD2BA6F5AA699823B33298FC60CB /* Pods-FY-IMChat-dummy.m */, 1BB93459E605BC8D21A4FE01CCCA1C0C /* Pods-FY-IMChat-frameworks.sh */, 607F4C3B2B91E2F189EAE1547F5860ED /* Pods-FY-IMChat-Info.plist */, 98FC81A49A355E0476EF6F20062425D1 /* Pods-FY-IMChat-umbrella.h */, 7C3E7FE59E7584693361B440BF0409A8 /* Pods-FY-IMChat.debug.xcconfig */, 561081F080F0DA8ABA0C1588011B8FDF /* Pods-FY-IMChat.release.xcconfig */, ); name = "Pods-FY-IMChat"; path = "Target Support Files/Pods-FY-IMChat"; sourceTree = ""; }; CF1408CF629C7361332E53B88F7BD30C = { isa = PBXGroup; children = ( 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */, 53B0E9973AA05354C2BB6F89AD290C1E /* Pods */, 0C39A08F310CC034632690BEE4EB14EF /* Products */, 35829EBE3BB4C12753FC61D4001AACDA /* Targets Support Files */, ); sourceTree = ""; }; D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */ = { isa = PBXGroup; children = ( 578452D2E740E91742655AC8F1636D1F /* iOS */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 7BF3C67E7CE338E84571CCC495C056D3 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( F45523F3401BB4CA0CB62234B6205D3A /* Pods-FY-IMChat-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 7394B44837E9030821120642706837CD /* Pods-FY-IMChat */ = { isa = PBXNativeTarget; buildConfigurationList = E885209C405A81CB7F3B05CEED29662E /* Build configuration list for PBXNativeTarget "Pods-FY-IMChat" */; buildPhases = ( 7BF3C67E7CE338E84571CCC495C056D3 /* Headers */, 7A47863E30B7CCED86A168314FF71521 /* Sources */, 9A5A975A9130CA8F6A446E120E4B844F /* Frameworks */, D27ACF4C42F532800C4161C08388526A /* Resources */, ); buildRules = ( ); dependencies = ( 4ABD7FE1017F402FD2489137159A4919 /* PBXTargetDependency */, A5DF7A3B8C7FE020FF127BA5F0EBFF77 /* PBXTargetDependency */, C319839666DE0DAC31D627A95874EC14 /* PBXTargetDependency */, 837433F707F97A0E488D560144C59412 /* PBXTargetDependency */, CEE4D8572E1848FEED3FD8AE36934ABA /* PBXTargetDependency */, 2A71AEFDC8C50A610ACD43F383DCB162 /* PBXTargetDependency */, C60C0B6E4EEABCA7F78AEF0E8EE8B052 /* PBXTargetDependency */, 74890EBBDA8BF24181A654E540E2CB31 /* PBXTargetDependency */, E0195A72AA40DA30C91C7B637ABE0C82 /* PBXTargetDependency */, 6C877032B4FF9440AE9EA9554A10E4EE /* PBXTargetDependency */, 7B34F4F8BF792B699A636594A1F2ED8C /* PBXTargetDependency */, 70306294EE6F0F32D999B8CF86C34E78 /* PBXTargetDependency */, 8ABCF92BB74D229B3FD6B5D97AFD5936 /* PBXTargetDependency */, 2A158B5869F39181E0AD6808311AF40C /* PBXTargetDependency */, C42C582C55336AB8D3FAC94BFEC3C469 /* PBXTargetDependency */, 58322AA353B72A30C74C3930A97C1C75 /* PBXTargetDependency */, DD818123A4461F7628D1266E9F85A40B /* PBXTargetDependency */, 381D9D805D4EC8B97ABA0B1A00E0FF2E /* PBXTargetDependency */, 83C987B3610025033541CDF14C0D9623 /* PBXTargetDependency */, 3944C46835A0A64B5D07415E6C3BC503 /* PBXTargetDependency */, 2F1FDC8D9F185F6420C7420607259EBE /* PBXTargetDependency */, B9D96AAC085423C5A960C025DC9C56FA /* PBXTargetDependency */, 00FC4471B64A3AAA7F20B084271FAD2F /* PBXTargetDependency */, C46A1F437E0E06DFC05C76A2217ACA85 /* PBXTargetDependency */, 437C95191B6121D44F33352D78506090 /* PBXTargetDependency */, 8D3AE9674B22F455C8DA6644BB9D73E0 /* PBXTargetDependency */, 0929409EEF98DEF97A2DCADAB5CB6A1A /* PBXTargetDependency */, 8CAC0DC656DEDC926C69CEE8B14074A6 /* PBXTargetDependency */, D3F6FBF11E7136888E9C42DA1D5BE7DB /* PBXTargetDependency */, 0C7AE0F2D7942D11D9D8DA15A18C9F50 /* PBXTargetDependency */, 458E77858CB03B88A00B12BD69B8EC21 /* PBXTargetDependency */, 0C7F2C4FE236F9D69728EB2B8F373783 /* PBXTargetDependency */, A0D3AB8ADFF5FA70CD19CB5205A98645 /* PBXTargetDependency */, C3D84EDE1633317CB1A5805352FD2F7E /* PBXTargetDependency */, ); name = "Pods-FY-IMChat"; productName = Pods_FY_IMChat; productReference = B2822B632C9B73C1341CAE612EFB90CC /* Pods-FY-IMChat */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ BFDFE7DC352907FC980B868725387E98 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = CF1408CF629C7361332E53B88F7BD30C; productRefGroup = 0C39A08F310CC034632690BEE4EB14EF /* Products */; projectDirPath = ""; projectReferences = ( { ProjectRef = EAF7D9CF742B5974293716634CFEB658 /* Alamofire */; }, { ProjectRef = D9A216D78F694506A7F33ADF7C6AB76B /* FDFullscreenPopGesture */; }, { ProjectRef = 374284FE69290B23B7ED62FF180A1C71 /* HandyJSON */; }, { ProjectRef = 0438DBF2C110FB4EE71FC78F51DDDF6A /* IGListDiffKit */; }, { ProjectRef = E13BAFCAD1DCB54A76BAB39718907ED5 /* IGListKit */; }, { ProjectRef = 0A3C1132C414C38CFDBF4556F1A58678 /* IQKeyboardManagerSwift */; }, { ProjectRef = 581C8733EC6B49617C9E986DEC414849 /* Kingfisher */; }, { ProjectRef = BF81471569EFE4A56814D0A7D1C24EC3 /* Localize-Swift */; }, { ProjectRef = 46C770FB65D3D309792127CFD56FD9B8 /* LookinServer */; }, { ProjectRef = 499E620EA4999D9FF1E3234277C2CF2A /* MBProgressHUD */; }, { ProjectRef = 9B99FEFC29E8CDDB98C169ACEEA30F06 /* MJRefresh */; }, { ProjectRef = 7D092B25BF1B9DB2B5DECE236780D189 /* Moya */; }, { ProjectRef = 80F2A5AD23DCFB1AF4994707D2649ED2 /* NSObject+Rx */; }, { ProjectRef = 514DEE95CF54879F0F065FD68F04AF6F /* R.swift */; }, { ProjectRef = B17FBD4BAE6C8F56AF2C17EBA67635D2 /* R.swift.Library */; }, { ProjectRef = 09DDDFAE034F2B7BEBFD89569E59AFFB /* ReachabilitySwift */; }, { ProjectRef = 2ACBC9CE9A3FA36127A8E2A104F3FDFB /* RxCocoa */; }, { ProjectRef = 80768DA22DD40A31E5CBFB00A0C907E3 /* RxRelay */; }, { ProjectRef = 857C0E24CF40B7B3DA2BD6FA5647577B /* RxSwift */; }, { ProjectRef = E5D2CCA1CBD9E9DBABE5E4AF29D0256B /* RxTheme */; }, { ProjectRef = FA089FB2D3E623384DBE92B4E7C61590 /* SDWebImage */; }, { ProjectRef = FBAF30105A714565F2E375AAC158147A /* SQLiteRepairKit */; }, { ProjectRef = E19F7217AA5D518ED2D03F5DBD9318DE /* SnapKit */; }, { ProjectRef = AF90DD2CA388D1789BAC140781F36FA1 /* SwiftDate */; }, { ProjectRef = CA7DE2133F5A597608F2965178D3A58B /* SwifterSwift */; }, { ProjectRef = D128140CC9E76B5402DC0A1A6F50EAFB /* SwiftyJSON */; }, { ProjectRef = D36D5E926088FE66266C81634BF0A649 /* TZImagePickerController */; }, { ProjectRef = 1D9901AF1E23811CFB54AEA4ECD942BF /* UITableView+FDTemplateLayoutCell */; }, { ProjectRef = 10543E34CEFC1213E9B0EB7AA4DF255E /* UIView+FDCollapsibleConstraints */; }, { ProjectRef = 11BCE9367A626F89920B14671B71FF07 /* WCDB.swift */; }, { ProjectRef = CF2B2063A9DEB48D730473BC03C4B9F6 /* WCDBOptimizedSQLCipher */; }, { ProjectRef = E74166EC76A656C80377A9C7631815CC /* YBImageBrowser */; }, { ProjectRef = C52B01BAA3BA7AD7306D321A5D0CA43E /* YYImage */; }, { ProjectRef = A50B9332B0F888D2D5A5DA753F626655 /* YYText */; }, ); projectRoot = ""; targets = ( 7394B44837E9030821120642706837CD /* Pods-FY-IMChat */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ D27ACF4C42F532800C4161C08388526A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 7A47863E30B7CCED86A168314FF71521 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 59659CE0366CC5E00255421847A4D9E5 /* Pods-FY-IMChat-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 00FC4471B64A3AAA7F20B084271FAD2F /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SnapKit; targetProxy = 515CA3968C8D75CEE2C174CF752AA174 /* PBXContainerItemProxy */; }; 0929409EEF98DEF97A2DCADAB5CB6A1A /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = TZImagePickerController; targetProxy = FFA99A43553494A820BCD3EC55843B14 /* PBXContainerItemProxy */; }; 0C7AE0F2D7942D11D9D8DA15A18C9F50 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = WCDB.swift; targetProxy = 3E9D67A07F0A886FF6004CA2144BF4DF /* PBXContainerItemProxy */; }; 0C7F2C4FE236F9D69728EB2B8F373783 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = YBImageBrowser; targetProxy = 78550796E8A69A97F34F540E81E61AEB /* PBXContainerItemProxy */; }; 2A158B5869F39181E0AD6808311AF40C /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = R.swift; targetProxy = 5C992C3ACA732AEBCFBFF28EB96BFC16 /* PBXContainerItemProxy */; }; 2A71AEFDC8C50A610ACD43F383DCB162 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = IQKeyboardManagerSwift; targetProxy = F913E5D1C9AEE1E2A723435FC667138F /* PBXContainerItemProxy */; }; 2F1FDC8D9F185F6420C7420607259EBE /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SDWebImage; targetProxy = 4730D86AD92E3133810CDADE979D323E /* PBXContainerItemProxy */; }; 381D9D805D4EC8B97ABA0B1A00E0FF2E /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RxRelay; targetProxy = 38C8E84E0C6BC9A02E472775186B8674 /* PBXContainerItemProxy */; }; 3944C46835A0A64B5D07415E6C3BC503 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RxTheme; targetProxy = 20E4979136D7BEF8A2A2246AF45C3095 /* PBXContainerItemProxy */; }; 437C95191B6121D44F33352D78506090 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SwifterSwift; targetProxy = 179EE28B34EF9DFDDE20E7557E721E93 /* PBXContainerItemProxy */; }; 458E77858CB03B88A00B12BD69B8EC21 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = WCDBOptimizedSQLCipher; targetProxy = 5D0925AC06BF098BA82FACCBEEC72D21 /* PBXContainerItemProxy */; }; 4ABD7FE1017F402FD2489137159A4919 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Alamofire; targetProxy = 6D11C13A84D5B7C485A3851E27D6C31B /* PBXContainerItemProxy */; }; 58322AA353B72A30C74C3930A97C1C75 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = ReachabilitySwift; targetProxy = EF09737501CBDA7963CF2A90C03DB14D /* PBXContainerItemProxy */; }; 6C877032B4FF9440AE9EA9554A10E4EE /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = MBProgressHUD; targetProxy = 82FDE8BA4002CA5EA9A25C40FCABCF79 /* PBXContainerItemProxy */; }; 70306294EE6F0F32D999B8CF86C34E78 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Moya; targetProxy = 6E93F34FB70EAEDF8C5B59AC54221BA6 /* PBXContainerItemProxy */; }; 74890EBBDA8BF24181A654E540E2CB31 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "Localize-Swift"; targetProxy = D5EAA56F1E50EE55FD279F29B2B59634 /* PBXContainerItemProxy */; }; 7B34F4F8BF792B699A636594A1F2ED8C /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = MJRefresh; targetProxy = B116771450CA88BD525EECBFB296E62D /* PBXContainerItemProxy */; }; 837433F707F97A0E488D560144C59412 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = IGListDiffKit; targetProxy = B3FA85EF66D6498A6F10E79323E5ABCA /* PBXContainerItemProxy */; }; 83C987B3610025033541CDF14C0D9623 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RxSwift; targetProxy = 2729CC999F664F831C5427C679442B70 /* PBXContainerItemProxy */; }; 8ABCF92BB74D229B3FD6B5D97AFD5936 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "NSObject+Rx"; targetProxy = E93988DDA9311B8385BB8950396A866B /* PBXContainerItemProxy */; }; 8CAC0DC656DEDC926C69CEE8B14074A6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "UITableView+FDTemplateLayoutCell"; targetProxy = B9D62E0742962486B4247FDE82DED067 /* PBXContainerItemProxy */; }; 8D3AE9674B22F455C8DA6644BB9D73E0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SwiftyJSON; targetProxy = 8D0C130FFDEECBB71CA8FA4FAC8DB832 /* PBXContainerItemProxy */; }; A0D3AB8ADFF5FA70CD19CB5205A98645 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = YYImage; targetProxy = 34A4839299095B0C0EC28D32E1EA8631 /* PBXContainerItemProxy */; }; A5DF7A3B8C7FE020FF127BA5F0EBFF77 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = FDFullscreenPopGesture; targetProxy = 89DB88F1490B277041804DF46504434E /* PBXContainerItemProxy */; }; B9D96AAC085423C5A960C025DC9C56FA /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SQLiteRepairKit; targetProxy = 556A154513D26F324D5B6296BC0BCF04 /* PBXContainerItemProxy */; }; C319839666DE0DAC31D627A95874EC14 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = HandyJSON; targetProxy = 70EC061318C69C673D7B3745D2B4DCDE /* PBXContainerItemProxy */; }; C3D84EDE1633317CB1A5805352FD2F7E /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = YYText; targetProxy = 9E3434EE805C22588545C3FDE0209F81 /* PBXContainerItemProxy */; }; C42C582C55336AB8D3FAC94BFEC3C469 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = R.swift.Library; targetProxy = 27A7FA3CF9A7F44C66A4CBA3B3BABEA1 /* PBXContainerItemProxy */; }; C46A1F437E0E06DFC05C76A2217ACA85 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SwiftDate; targetProxy = E617B6696E88F6BF79E54EE80D8CA84F /* PBXContainerItemProxy */; }; C60C0B6E4EEABCA7F78AEF0E8EE8B052 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Kingfisher; targetProxy = 2BD3BFA0F60EC170B8BD78DF4A02BF73 /* PBXContainerItemProxy */; }; CEE4D8572E1848FEED3FD8AE36934ABA /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = IGListKit; targetProxy = C7C6BBCA61141E4541F59E23A3E90255 /* PBXContainerItemProxy */; }; D3F6FBF11E7136888E9C42DA1D5BE7DB /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "UIView+FDCollapsibleConstraints"; targetProxy = 8D1DAC5546C4AB3C02A4050206695DA5 /* PBXContainerItemProxy */; }; DD818123A4461F7628D1266E9F85A40B /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RxCocoa; targetProxy = F8A3327D7F399134FE2388E99FAE6543 /* PBXContainerItemProxy */; }; E0195A72AA40DA30C91C7B637ABE0C82 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = LookinServer; targetProxy = DF5B3C1CAE37761B2C05A85CAE10DAB2 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 2B9E26EAE2CD392AD762421F663075A1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 63FAF33E1C55B71A5F5A8B3CC8749F99 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; D888A6EFDDE7368AD5C0AE529C8B26B3 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 561081F080F0DA8ABA0C1588011B8FDF /* Pods-FY-IMChat.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat.modulemap"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; EC829709B58FDA79E74C857A0A3AC401 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7C3E7FE59E7584693361B440BF0409A8 /* Pods-FY-IMChat.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat.modulemap"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( 2B9E26EAE2CD392AD762421F663075A1 /* Debug */, 63FAF33E1C55B71A5F5A8B3CC8749F99 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; E885209C405A81CB7F3B05CEED29662E /* Build configuration list for PBXNativeTarget "Pods-FY-IMChat" */ = { isa = XCConfigurationList; buildConfigurations = ( EC829709B58FDA79E74C857A0A3AC401 /* Debug */, D888A6EFDDE7368AD5C0AE529C8B26B3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; } ================================================ FILE: JetChat/Pods/R.swift/License ================================================ The MIT License (MIT) Copyright (c) 2014-2020 Mathijs Kadijk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/ColorResource.swift ================================================ // // ColorResource.swift // R.swift.Library // // Created by Tom Lokhorst on 2016-03-13. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation public protocol ColorResourceType { /// Bundle this color is in var bundle: Bundle { get } /// Name of the color var name: String { get } } public struct ColorResource: ColorResourceType { /// Bundle this color is in public let bundle: Bundle /// Name of the color public let name: String public init(bundle: Bundle, name: String) { self.bundle = bundle self.name = name } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/FileResource.swift ================================================ // // FileResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 06-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation public protocol FileResourceType { /// Bundle this file is in var bundle: Bundle { get } /// Name of the file file on disk var name: String { get } /// Extension of the file on disk var pathExtension: String { get } } public extension FileResourceType { /// Name of the file on disk with the pathExtension var fullName: String { return [name, pathExtension].joined(separator: ".") } /** Returns the full pathname for this resource. - returns: The full pathname for this resource or nil if the file could not be located. */ func path() -> String? { return bundle.path(forResource: self) } /** Returns the file URL for this resource. - returns: The file URL for this resource or nil if the file could not be located. */ func url() -> URL? { return bundle.url(forResource: self) } } public struct FileResource: FileResourceType { /// Bundle this file is in public let bundle: Bundle /// Name of the file on disk, without the pathExtension public let name: String /// Extension of the file on disk public let pathExtension: String public init(bundle: Bundle, name: String, pathExtension: String) { self.bundle = bundle self.name = name self.pathExtension = pathExtension } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/FontResource.swift ================================================ // // FontResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 06-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation public protocol FontResourceType { /// Name of the font var fontName: String { get } } public struct FontResource: FontResourceType { /// Name of the font public let fontName: String public init(fontName: String) { self.fontName = fontName } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/Identifier.swift ================================================ // // Identifier.swift // R.swift Library // // Created by Mathijs Kadijk on 06-12-15. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation /// Base protocol for all identifiers public protocol IdentifierType: CustomStringConvertible { /// Identifier string var identifier: String { get } } extension IdentifierType { /// CustomStringConvertible implementation, returns the identifier public var description: String { return identifier } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/ImageResource.swift ================================================ // // ImageResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 11-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation public protocol ImageResourceType { /// Bundle this image is in var bundle: Bundle { get } /// Name of the image var name: String { get } } public struct ImageResource: ImageResourceType { /// Bundle this image is in public let bundle: Bundle /// Name of the image public let name: String public init(bundle: Bundle, name: String) { self.bundle = bundle self.name = name } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/NibResource.swift ================================================ // // NibResource.swift // R.swift Library // // Created by Mathijs Kadijk on 06-12-15. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation /// Represents a nib file on disk public protocol NibResourceType { /// Bundle this nib is in or nil for main bundle var bundle: Bundle { get } /// Name of the nib file on disk var name: String { get } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/ReuseIdentifierProtocol.swift ================================================ // // ReuseIdentifierProtocol.swift // R.swift Library // // Created by Mathijs Kadijk on 06-12-15. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation /// Reuse identifier protocol public protocol ReuseIdentifierType: IdentifierType { /// Type of this reuseable associatedtype ReusableType } /// Reuse identifier public struct ReuseIdentifier: ReuseIdentifierType { /// Type of this reuseable public typealias ReusableType = Reusable /// String identifier of this reusable public let identifier: String /** Create a new ReuseIdentifier based on the string identifier - parameter identifier: The string identifier for this reusable - returns: A new ReuseIdentifier */ public init(identifier: String) { self.identifier = identifier } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/StoryboardResource.swift ================================================ // // StoryboardResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 07-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation public protocol StoryboardResourceType { /// Bundle this storyboard is in var bundle: Bundle { get } /// Name of the storyboard file on disk var name: String { get } } public protocol StoryboardResourceWithInitialControllerType: StoryboardResourceType { /// Type of the inital controller associatedtype InitialController } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/StoryboardSegueIdentifierProtocol.swift ================================================ // // StoryboardSegueIdentifierProtocol.swift // R.swift Library // // Created by Mathijs Kadijk on 06-12-15. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation /// Segue identifier protocol public protocol StoryboardSegueIdentifierType: IdentifierType { /// Type of the segue itself associatedtype SegueType /// Type of the source view controller associatedtype SourceType /// Type of the destination view controller associatedtype DestinationType } /// Segue identifier public struct StoryboardSegueIdentifier: StoryboardSegueIdentifierType { /// Type of the segue itself public typealias SegueType = Segue /// Type of the source view controller public typealias SourceType = Source /// Type of the destination view controller public typealias DestinationType = Destination /// Identifier string of this segue public let identifier: String /** Create a new identifier based on the identifier string - returns: A new StoryboardSegueIdentifier */ public init(identifier: String) { self.identifier = identifier } /// Create a new StoryboardSegue based on the identifier and source view controller public func storyboardSegue(withSource source: Source) -> StoryboardSegue { return StoryboardSegue(identifier: self, source: source) } } /// Typed segue information public struct TypedStoryboardSegueInfo: StoryboardSegueIdentifierType { /// Type of the segue itself public typealias SegueType = Segue /// Type of the source view controller public typealias SourceType = Source /// Type of the destination view controller public typealias DestinationType = Destination /// Segue destination view controller public let destination: Destination /// Segue identifier public let identifier: String /// The original segue public let segue: Segue /// Segue source view controller public let source: Source } /// Segue with identifier and source view controller public struct StoryboardSegue { /// Identifier of this segue public let identifier: StoryboardSegueIdentifier /// Segue source view controller public let source: Source /** Create a new segue based on the identifier and source view controller - returns: A new StoryboardSegue */ public init(identifier: StoryboardSegueIdentifier, source: Source) { self.identifier = identifier self.source = source } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/StoryboardViewControllerResource.swift ================================================ // // StoryboardViewControllerResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 13-03-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation public protocol StoryboardViewControllerResourceType: IdentifierType { associatedtype ViewControllerType } public struct StoryboardViewControllerResource: StoryboardViewControllerResourceType { public typealias ViewControllerType = ViewController /// Storyboard identifier of this view controller public let identifier: String public init(identifier: String) { self.identifier = identifier } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/StringResource.swift ================================================ // // StringResource.swift // R.swift.Library // // Created by Tom Lokhorst on 2016-04-23. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation public protocol StringResourceType { /// Key for the string var key: String { get } /// File in containing the string var tableName: String { get } /// Bundle this string is in var bundle: Bundle { get } /// Locales of the a localizable string var locales: [String] { get } /// Comment directly before and/or after the string, if any var comment: String? { get } } public struct StringResource: StringResourceType { /// Key for the string public let key: String /// File in containing the string public let tableName: String /// Bundle this string is in public let bundle: Bundle /// Locales of the a localizable string public let locales: [String] /// Comment directly before and/or after the string, if any public let comment: String? public init(key: String, tableName: String, bundle: Bundle, locales: [String], comment: String?) { self.key = key self.tableName = tableName self.bundle = bundle self.locales = locales self.comment = comment } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Core/Validatable.swift ================================================ // // Validatable.swift // R.swift.Library // // Created by Mathijs Kadijk on 17-12-15. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation /// Error thrown during validation public struct ValidationError: Error, CustomStringConvertible { /// Human readable description public let description: String public init(description: String) { self.description = description } } public protocol Validatable { /** Validates this entity and throws if it encounters an invalid situation, a validatable should also validate it sub-validatables if it has any. - throws: If there the configuration error a ValidationError is thrown */ static func validate() throws } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Foundation/Bundle+FileResource.swift ================================================ // // Bundle+FileResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 10-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation public extension Bundle { /** Returns the file URL for the given resource (R.file.*). - parameter resource: The resource to get the file URL for (R.file.*). - returns: The file URL for the resource file (R.file.*) or nil if the file could not be located. */ func url(forResource resource: FileResourceType) -> URL? { return url(forResource: resource.name, withExtension: resource.pathExtension) } /** Returns the full pathname for the resource (R.file.*). - parameter resource: The resource file to get the path for (R.file.*). - returns: The full pathname for the resource file (R.file.*) or nil if the file could not be located. */ func path(forResource resource: FileResourceType) -> String? { return path(forResource: resource.name, ofType: resource.pathExtension) } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/Foundation/Data+FileResource.swift ================================================ // // Data+FileResource.swift // R.swift.Library // // Created by Tom Lokhorst on 2016-03-11. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation public struct NoUrlForResourceError: Error {} public extension Data { /** Creates and returns NSData with the contents of the specified file resource (R.file.*). - parameter resource: The file resource (R.file.*) - returns: A NSData object with the contents of the specified file. */ init(resource: FileResourceType) throws { guard let url = resource.url() else { throw NoUrlForResourceError() } try self.init(contentsOf: url) } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/NibResource+UIKit.swift ================================================ // // NibResource+UIKit.swift // R.swift.Library // // Created by Mathijs Kadijk on 06-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation import UIKit public extension NibResourceType { /** Instantiate the nib to get the top-level objects from this nib - parameter ownerOrNil: The owner, if the owner parameter is nil, connections to File's Owner are not permitted. - parameter options: Options are identical to the options specified with -[NSBundle loadNibNamed:owner:options:] - returns: An array containing the top-level objects from the NIB */ func instantiate(withOwner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = [:]) -> [Any] { return UINib(resource: self).instantiate(withOwner: ownerOrNil, options: optionsOrNil) } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/StoryboardResourceWithInitialController+UIKit.swift ================================================ // // StoryboardResourceWithInitialController+UIKit.swift // R.swift.Library // // Created by Mathijs Kadijk on 07-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation import UIKit public extension StoryboardResourceWithInitialControllerType { /** Instantiates and returns the initial view controller in the view controller graph. - returns: The initial view controller in the storyboard. */ func instantiateInitialViewController() -> InitialController? { return UIStoryboard(resource: self).instantiateInitialViewController() as? InitialController } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/TypedStoryboardSegueInfo+UIStoryboardSegue.swift ================================================ // // TypedStoryboardSegueInfo+UIStoryboardSegue.swift // R.swift Library // // Created by Mathijs Kadijk on 06-12-15. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation import UIKit extension TypedStoryboardSegueInfo { /** Returns typed information about the given segue, fails if the segue types don't exactly match types. - returns: A newly initialized TypedStoryboardSegueInfo object or nil. */ public init?(segueIdentifier: SegueIdentifier, segue: UIStoryboardSegue) where SegueIdentifier.SegueType == Segue, SegueIdentifier.SourceType == Source, SegueIdentifier.DestinationType == Destination { guard let identifier = segue.identifier, let source = segue.source as? SegueIdentifier.SourceType, let destination = segue.destination as? SegueIdentifier.DestinationType, let segue = segue as? SegueIdentifier.SegueType, identifier == segueIdentifier.identifier else { return nil } self.segue = segue self.identifier = identifier self.source = source self.destination = destination } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/UICollectionView+ReuseIdentifierProtocol.swift ================================================ // // UICollectionView+ReuseIdentifierProtocol.swift // R.swift Library // // Created by Mathijs Kadijk on 06-12-15. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation import UIKit public extension UICollectionView { /** Returns a typed reusable cell object located by its identifier - parameter identifier: The R.reuseIdentifier.* value for the specified cell. - parameter indexPath: The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the collection view. - returns: A subclass of UICollectionReusableView or nil if the cast fails. */ func dequeueReusableCell(withReuseIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.ReusableType? where Identifier.ReusableType: UICollectionReusableView { return dequeueReusableCell(withReuseIdentifier: identifier.identifier, for: indexPath) as? Identifier.ReusableType } /** Returns a typed reusable supplementary view located by its identifier and kind. - parameter elementKind: The kind of supplementary view to retrieve. This value is defined by the layout object. - parameter identifier: The R.reuseIdentifier.* value for the specified view. - parameter indexPath: The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the collection view. - returns: A subclass of UICollectionReusableView or nil if the cast fails. */ func dequeueReusableSupplementaryView(ofKind elementKind: String, withReuseIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.ReusableType? where Identifier.ReusableType: UICollectionReusableView { return dequeueReusableSupplementaryView(ofKind: elementKind, withReuseIdentifier: identifier.identifier, for: indexPath) as? Identifier.ReusableType } /** Register a R.nib.* for use in creating new collection view cells. - parameter nibResource: A nib resource (R.nib.*) containing a object of type UICollectionViewCell that has a reuse identifier */ func register(_ nibResource: Resource) where Resource.ReusableType: UICollectionViewCell { register(UINib(resource: nibResource), forCellWithReuseIdentifier: nibResource.identifier) } /** Register a R.nib.* for use in creating supplementary views for the collection view. - parameter nibResource: A nib resource (R.nib.*) containing a object of type UICollectionReusableView. that has a reuse identifier */ func register(_ nibResource: Resource, forSupplementaryViewOfKind kind: String) where Resource.ReusableType: UICollectionReusableView { register(UINib(resource: nibResource), forSupplementaryViewOfKind: kind, withReuseIdentifier: nibResource.identifier) } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/UIColor+ColorResource.swift ================================================ // // UIColor+ColorResource.swift // R.swift.Library // // Created by Tom Lokhorst on 2017-06-06. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import UIKit @available(iOS 11.0, *) @available(tvOS 11.0, *) public extension UIColor { #if os(iOS) || os(tvOS) /** Returns the color from this resource (R.color.*) that is compatible with the trait collection. - parameter resource: The resource you want the image of (R.color.*) - parameter traitCollection: Traits that describe the desired color to retrieve, pass nil to use traits that describe the main screen. - returns: A color that exactly or best matches the desired traits with the given resource (R.color.*), or nil if no suitable color was found. */ convenience init?(resource: ColorResourceType, compatibleWith traitCollection: UITraitCollection? = nil) { self.init(named: resource.name, in: resource.bundle, compatibleWith: traitCollection) } #endif #if os(watchOS) /** Returns the color from this resource (R.color.*) that is compatible with the trait collection. - parameter resource: The resource you want the image of (R.color.*) - returns: A color that exactly or best matches the desired traits with the given resource (R.color.*), or nil if no suitable color was found. */ @available(watchOSApplicationExtension 4.0, *) convenience init?(resource: ColorResourceType) { self.init(named: resource.name) } #endif } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/UIFont+FontResource.swift ================================================ // // UIFont+FontResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 06-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation import UIKit public extension UIFont { /** Creates and returns a font object for the specified font resource (R.font.*) and size. - parameter resource: The font resource (R.font.*) for the specific font to load - parameter size: The size (in points) to which the font is scaled. This value must be greater than 0.0. - returns: A font object of the specified font resource and size. */ convenience init?(resource: FontResourceType, size: CGFloat) { self.init(name: resource.fontName, size: size) } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/UIImage+ImageResource.swift ================================================ // // UIImage+ImageResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 11-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import UIKit public extension UIImage { #if os(iOS) || os(tvOS) /** Returns the image from this resource (R.image.*) that is compatible with the trait collection. - parameter resource: The resource you want the image of (R.image.*) - parameter traitCollection: Traits that describe the desired image to retrieve, pass nil to use traits that describe the main screen. - returns: An image that exactly or best matches the desired traits with the given resource (R.image.*), or nil if no suitable image was found. */ convenience init?(resource: ImageResourceType, compatibleWith traitCollection: UITraitCollection? = nil) { self.init(named: resource.name, in: resource.bundle, compatibleWith: traitCollection) } #endif #if os(watchOS) /** Returns the image from this resource (R.image.*) that is compatible with the trait collection. - parameter resource: The resource you want the image of (R.image.*) - returns: An image that exactly or best matches the desired traits with the given resource (R.image.*), or nil if no suitable image was found. */ convenience init?(resource: ImageResourceType) { self.init(named: resource.name) } #endif } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/UINib+NibResource.swift ================================================ // // UINib+NibResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 08-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import UIKit public extension UINib { /** Returns a UINib object initialized to the nib file of the specified resource (R.nib.*). - parameter resource: The resource (R.nib.*) to load - returns: The initialized UINib object. An exception is thrown if there were errors during initialization or the nib file could not be located. */ convenience init(resource: NibResourceType) { self.init(nibName: resource.name, bundle: resource.bundle) } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/UIStoryboard+StoryboardResource.swift ================================================ // // UIStoryboard+StoryboardResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 07-01-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import UIKit public extension UIStoryboard { /** Creates and returns a storyboard object for the specified storyboard resource (R.storyboard.*) file. - parameter resource: The storyboard resource (R.storyboard.*) for the specific storyboard to load - returns: A storyboard object for the specified file. If no storyboard resource file matching name exists, an exception is thrown with description: `Could not find a storyboard named 'XXXXXX' in bundle....` */ convenience init(resource: StoryboardResourceType) { self.init(name: resource.name, bundle: resource.bundle) } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/UIStoryboard+StoryboardViewControllerResource.swift ================================================ // // UIViewController+StoryboardViewControllerResource.swift // R.swift.Library // // Created by Mathijs Kadijk on 13-03-16. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation import UIKit public extension UIStoryboard { /** Instantiates and returns the view controller with the specified resource (R.storyboard.*.*). - parameter resource: An resource (R.storyboard.*.*) that uniquely identifies the view controller in the storyboard file. If the specified resource does not exist in the storyboard file, this method raises an exception. - returns: The view controller corresponding to the specified resource (R.storyboard.*.*). If no view controller is associated, this method throws an exception. */ func instantiateViewController(withResource resource: ViewControllerResource) -> ViewControllerResource.ViewControllerType? { return self.instantiateViewController(withIdentifier: resource.identifier) as? ViewControllerResource.ViewControllerType } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/UITableView+ReuseIdentifierProtocol.swift ================================================ // // UITableView+ReuseIdentifierProtocol.swift // R.swift Library // // Created by Mathijs Kadijk on 06-12-15. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation import UIKit public extension UITableView { /** Returns a typed reusable table-view cell object for the specified reuse identifier and adds it to the table. - parameter identifier: A R.reuseIdentifier.* value identifying the cell object to be reused. - parameter indexPath: The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the table view. - returns: The UITableViewCell subclass with the associated reuse identifier or nil if it couldn't be casted correctly. - precondition: You must register a class or nib file using the registerNib: or registerClass:forCellReuseIdentifier: method before calling this method. */ func dequeueReusableCell(withIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.ReusableType? where Identifier.ReusableType: UITableViewCell { return dequeueReusableCell(withIdentifier: identifier.identifier, for: indexPath) as? Identifier.ReusableType } @available(*, unavailable, message: "Use dequeueReusableCell(withIdentifier:for:) instead") func dequeueReusableCell(withIdentifier identifier: Identifier) -> Identifier.ReusableType? where Identifier.ReusableType: UITableViewCell { fatalError() } /** Returns a typed reusable header or footer view located by its identifier. - parameter identifier: A R.reuseIdentifier.* value identifying the header or footer view to be reused. - returns: A UITableViewHeaderFooterView object with the associated identifier or nil if no such object exists in the reusable view queue or if it couldn't be cast correctly. */ func dequeueReusableHeaderFooterView(withIdentifier identifier: Identifier) -> Identifier.ReusableType? where Identifier.ReusableType: UITableViewHeaderFooterView { return dequeueReusableHeaderFooterView(withIdentifier: identifier.identifier) as? Identifier.ReusableType } /** Register a R.nib.* containing a cell with the table view under it's contained identifier. - parameter nibResource: A nib resource (R.nib.*) containing a table view cell that has a reuse identifier */ func register(_ nibResource: Resource) where Resource.ReusableType: UITableViewCell { register(UINib(resource: nibResource), forCellReuseIdentifier: nibResource.identifier) } /** Register a R.nib.* containing a header or footer with the table view under it's contained identifier. - parameter nibResource: A nib resource (R.nib.*) containing a view that has a reuse identifier */ func registerHeaderFooterView(_ nibResource: Resource) where Resource: ReuseIdentifierType, Resource.ReusableType: UIView { register(UINib(resource: nibResource), forHeaderFooterViewReuseIdentifier: nibResource.identifier) } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/UIViewController+NibResource.swift ================================================ // // UIViewController+NibResource.swift // R.swift Library // // Created by Mathijs Kadijk on 06-12-15. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation import UIKit public extension UIViewController { /** Returns a newly initialized view controller with the nib resource (R.nib.*). - parameter nib: The nib resource (R.nib.*) to associate with the view controller. - returns: A newly initialized UIViewController object. */ convenience init(nib: NibResourceType) { self.init(nibName: nib.name, bundle: nib.bundle) } } ================================================ FILE: JetChat/Pods/R.swift.Library/Library/UIKit/UIViewController+StoryboardSegueIdentifierProtocol.swift ================================================ // // UIViewController+StoryboardSegueIdentifierProtocol.swift // R.swift Library // // Created by Mathijs Kadijk on 06-12-15. // From: https://github.com/mac-cain13/R.swift.Library // License: MIT License // import Foundation import UIKit public protocol SeguePerformerType { func performSegue(withIdentifier identifier: String, sender: Any?) } extension UIViewController: SeguePerformerType {} public extension SeguePerformerType { /** Initiates the segue with the specified identifier (R.segue.*) from the current view controller's storyboard file. - parameter identifier: The R.segue.* that identifies the triggered segue. - parameter sender: The object that you want to use to initiate the segue. This object is made available for informational purposes during the actual segue. - SeeAlso: Library for typed block based segues: [tomlokhorst/SegueManager](https://github.com/tomlokhorst/SegueManager) */ func performSegue(withIdentifier identifier: StoryboardSegueIdentifier, sender: Any?) { performSegue(withIdentifier: identifier.identifier, sender: sender) } } public extension StoryboardSegue where Source : UIViewController { /** Performs this segue on the source view controller - parameter sender: The object that you want to use to initiate the segue. This object is made available for informational purposes during the actual segue. */ func performSegue(sender: Any? = nil) { source.performSegue(withIdentifier: identifier.identifier, sender: sender) } } ================================================ FILE: JetChat/Pods/R.swift.Library/License ================================================ The MIT License (MIT) Copyright (c) 2015 Mathijs Kadijk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/R.swift.Library/Readme.md ================================================ # R.swift.Library [![Version](https://img.shields.io/cocoapods/v/R.swift.Library.svg?style=flat)](https://cocoapods.org/pods/R.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![License](https://img.shields.io/cocoapods/l/R.swift.Library.svg?style=flat)](blob/master/License) ![Platform](https://img.shields.io/cocoapods/p/R.swift.Library.svg?style=flat) _Library containing types supporting code generated by [R.swift](https://github.com/mac-cain13/R.swift)_ ## Why use this? Regular users probably want to include this library to use [R.swift](https://github.com/mac-cain13/R.swift). Developers of other libraries can use this library to extend upon the types and code R.swift generates and uses. ## Installation ### CocoaPods (recommended) _**Be aware:** If you just want to use R.swift follow the [installation instructions for R.swift](https://github.com/mac-cain13/R.swift#Installation)._ 1. Add `pod 'R.swift.Library'` to your [Podfile](http://cocoapods.org/#get_started) 2. Run `pod install` ### Carthage 1. Add `github "mac-cain13/R.swift.Library"` to your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile) 2. Run `carthage` ### Swift Package Manager (Requires Xcode 11) 1. Open your Xcode project. 2. Select `File > Swift Packages > Add Package Dependency...` 3. Paste `https://github.com/mac-cain13/R.swift.Library` to the text field and click on the `Next` button. 4. Choose appropriate version and click on the `Next` button. (If you need latest one, just click on the `Next` button.) 5. Confirm that `Rswift` in the Package Product column is checked and your app's name is selected in the Add to Target column. 6. Click on the `Next` button. ### Manually _As an embedded framework using git submodules._ 0. If your project is not yet a git repository, run `git init` 1. Add R.swift.Library as a submodule by running: `git submodule add https://github.com/mac-cain13/R.swift.Library.git` 3. Open the new `R.swift.Library` folder, and drag the `R.swift.Library.xcodeproj` into the Project Navigator of your application's Xcode project. 4. Select the `R.swift.Library.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target. 5. Select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar. 6. In the tab bar at the top of that window, open the "General" panel. 7. Click on the `+` button under the "Embedded Binaries" section. 8. Choose the `Rswift.framework` > The `Rswift.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device. ## License [R.swift](https://github.com/mac-cain13/R.swift) and [R.swift.Library](https://github.com/mac-cain13/R.swift.Library) are created by [Mathijs Kadijk](https://github.com/mac-cain13) and released under a [MIT License](License). ================================================ FILE: JetChat/Pods/R.swift.Library.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 0BBF1068BB3B41A7FFF880B05795E07B /* UIColor+ColorResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2628B93B8AFA10A0EC55DA4CE2BCBB /* UIColor+ColorResource.swift */; }; 0C4F5BFE6D00473694426D27ABC11A28 /* FontResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB623CD96D0DA2D748EB71A1086CA2A /* FontResource.swift */; }; 0E95BD610EF45AE5B515DE2AD5F7C823 /* NibResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2669DF823AC7148ACC6995A6BC2CAD5D /* NibResource.swift */; }; 0FA820A1092C3DB17A3F2C82FD4AFB84 /* Data+FileResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82A0D6FE6B14AEC444352733B74E7620 /* Data+FileResource.swift */; }; 1B200E79A464C91233A77074C903F8FD /* UIImage+ImageResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916EEF0BE2CEE80B92E1DEC528ABE162 /* UIImage+ImageResource.swift */; }; 2411AE419A24BE790B3B65B0196FB594 /* StoryboardResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5EFD9BAE662D567150C610DB558709 /* StoryboardResource.swift */; }; 244D3CDB2966D6AE7DE44051806017AC /* UICollectionView+ReuseIdentifierProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF2A8F8DBE5CCC0E46AE3DCCC8F6E0 /* UICollectionView+ReuseIdentifierProtocol.swift */; }; 2B29A243062C9F66198908EBDB0017B5 /* Bundle+FileResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AA3E281812FBE3BCAE65081B7F1C59 /* Bundle+FileResource.swift */; }; 2D5B0E39AB3DE7F5D3F4F2A8A1C1D4C5 /* R.swift.Library-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5863C260841B827CD6DAF3EFBDF63C78 /* R.swift.Library-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 56AF519A31089CF99A2DD85043ACA13D /* ReuseIdentifierProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4FFF574DA865FFE4A38141EA3C0552 /* ReuseIdentifierProtocol.swift */; }; 6BD87E05828E5C090225001E9B8EB67A /* UITableView+ReuseIdentifierProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0EEB92F64982D56632206A7D19F4C2F /* UITableView+ReuseIdentifierProtocol.swift */; }; 6F40C3134AE58DD8AE0406CF871AFB95 /* UIViewController+StoryboardSegueIdentifierProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9D902025BE9976A24616DBA7D08F3A /* UIViewController+StoryboardSegueIdentifierProtocol.swift */; }; 79502ECF650046C5EBE8CF980A04ACDC /* TypedStoryboardSegueInfo+UIStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F17BB1DD05264547C1615E7CCC84BA /* TypedStoryboardSegueInfo+UIStoryboardSegue.swift */; }; 8008F98F930079E0F9F24D9E85BDA499 /* StringResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE830BC62069D8306382C4B4EC279834 /* StringResource.swift */; }; 81A621AEE3152C6832D13641CDE233FF /* UIViewController+NibResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F3AE537895379CBE833189C9A4FA127 /* UIViewController+NibResource.swift */; }; 957CDE1D86CFE23AEDB433A90BCAE96E /* StoryboardSegueIdentifierProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D868A4318827ECCF8582C4F549BE0472 /* StoryboardSegueIdentifierProtocol.swift */; }; 993EA04440B376A09BCD2126F1E771EE /* NibResource+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2543FB18DD737F2D7DEAAF3DB67F582E /* NibResource+UIKit.swift */; }; 9C7E9D17673F02C3A73662BB6FA5F1CC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBE17BA66F3F0333EE62DFAA8B512C9C /* Foundation.framework */; }; 9CD6C8AA7FE288C087651EAC8F86E0D4 /* ImageResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05D35D43CE7071E6A0413834D9A02964 /* ImageResource.swift */; }; 9D42E7890E9FBCC3B108880C280764A5 /* Validatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E86EFB28CE1FDAC538CC87BA85AFE0 /* Validatable.swift */; }; A125B00D13C3964FA4FDB94F684D1E8E /* StoryboardResourceWithInitialController+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 266E5651CC84B1071D707AEDB5DB1945 /* StoryboardResourceWithInitialController+UIKit.swift */; }; A82DA5F7B9ED116C03CD180DBDE21433 /* UIStoryboard+StoryboardViewControllerResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2985B8304D310B87F3A02632595E69A /* UIStoryboard+StoryboardViewControllerResource.swift */; }; A89B8795FA80A6C452B134AC46853AB3 /* FileResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A082CC0C4276FC2C5302AD09AB6700BD /* FileResource.swift */; }; AB019015428DF9B980679F1AD4C5C8B9 /* UINib+NibResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACAA2DF16AF41EEAF3D192317F4CB8FD /* UINib+NibResource.swift */; }; B70B88A4F9B8B1F30813E3EA2651FCD6 /* Identifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6345E58BA358AE234137F8649BB47E0 /* Identifier.swift */; }; BB05F14C113E866C307E21CF6E827315 /* ColorResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C3AF7420033891FE325E22BF08CF46 /* ColorResource.swift */; }; C974C044A5122FEE8B85026C2358DF28 /* UIStoryboard+StoryboardResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C411A1FD05163B4A9F300AC428C26CF2 /* UIStoryboard+StoryboardResource.swift */; }; D337D77F3D3A6FFE2E8A2041FE4175D0 /* R.swift.Library-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 241C64CE32764417C660E090E4681BE7 /* R.swift.Library-dummy.m */; }; DAF38AE55A5BC5B9399D2A7FDCB3D558 /* StoryboardViewControllerResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BDBE4BAE53D934DB55228EDC721FF79 /* StoryboardViewControllerResource.swift */; }; DC00B4068B6F85EBB1FD9E01F93921AD /* UIFont+FontResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB5C39B2E711E276B2D126EF242DD982 /* UIFont+FontResource.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 05D35D43CE7071E6A0413834D9A02964 /* ImageResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageResource.swift; path = Library/Core/ImageResource.swift; sourceTree = ""; }; 06A4C248292207C3A8654DF0F5819F5B /* R.swift.Library.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = R.swift.Library.release.xcconfig; sourceTree = ""; }; 0BDBE4BAE53D934DB55228EDC721FF79 /* StoryboardViewControllerResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StoryboardViewControllerResource.swift; path = Library/Core/StoryboardViewControllerResource.swift; sourceTree = ""; }; 18EF2A8F8DBE5CCC0E46AE3DCCC8F6E0 /* UICollectionView+ReuseIdentifierProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UICollectionView+ReuseIdentifierProtocol.swift"; path = "Library/UIKit/UICollectionView+ReuseIdentifierProtocol.swift"; sourceTree = ""; }; 241C64CE32764417C660E090E4681BE7 /* R.swift.Library-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "R.swift.Library-dummy.m"; sourceTree = ""; }; 2543FB18DD737F2D7DEAAF3DB67F582E /* NibResource+UIKit.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NibResource+UIKit.swift"; path = "Library/UIKit/NibResource+UIKit.swift"; sourceTree = ""; }; 2669DF823AC7148ACC6995A6BC2CAD5D /* NibResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NibResource.swift; path = Library/Core/NibResource.swift; sourceTree = ""; }; 266E5651CC84B1071D707AEDB5DB1945 /* StoryboardResourceWithInitialController+UIKit.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "StoryboardResourceWithInitialController+UIKit.swift"; path = "Library/UIKit/StoryboardResourceWithInitialController+UIKit.swift"; sourceTree = ""; }; 27E86EFB28CE1FDAC538CC87BA85AFE0 /* Validatable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Validatable.swift; path = Library/Core/Validatable.swift; sourceTree = ""; }; 35C1286245B5987BF5C5B2207FC517AC /* R.swift.Library.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = R.swift.Library.modulemap; sourceTree = ""; }; 37C3AF7420033891FE325E22BF08CF46 /* ColorResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorResource.swift; path = Library/Core/ColorResource.swift; sourceTree = ""; }; 53F17BB1DD05264547C1615E7CCC84BA /* TypedStoryboardSegueInfo+UIStoryboardSegue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TypedStoryboardSegueInfo+UIStoryboardSegue.swift"; path = "Library/UIKit/TypedStoryboardSegueInfo+UIStoryboardSegue.swift"; sourceTree = ""; }; 5863C260841B827CD6DAF3EFBDF63C78 /* R.swift.Library-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "R.swift.Library-umbrella.h"; sourceTree = ""; }; 5F3AE537895379CBE833189C9A4FA127 /* UIViewController+NibResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIViewController+NibResource.swift"; path = "Library/UIKit/UIViewController+NibResource.swift"; sourceTree = ""; }; 6AB623CD96D0DA2D748EB71A1086CA2A /* FontResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FontResource.swift; path = Library/Core/FontResource.swift; sourceTree = ""; }; 6E2628B93B8AFA10A0EC55DA4CE2BCBB /* UIColor+ColorResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIColor+ColorResource.swift"; path = "Library/UIKit/UIColor+ColorResource.swift"; sourceTree = ""; }; 82A0D6FE6B14AEC444352733B74E7620 /* Data+FileResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Data+FileResource.swift"; path = "Library/Foundation/Data+FileResource.swift"; sourceTree = ""; }; 916EEF0BE2CEE80B92E1DEC528ABE162 /* UIImage+ImageResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIImage+ImageResource.swift"; path = "Library/UIKit/UIImage+ImageResource.swift"; sourceTree = ""; }; 9E9D902025BE9976A24616DBA7D08F3A /* UIViewController+StoryboardSegueIdentifierProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIViewController+StoryboardSegueIdentifierProtocol.swift"; path = "Library/UIKit/UIViewController+StoryboardSegueIdentifierProtocol.swift"; sourceTree = ""; }; A082CC0C4276FC2C5302AD09AB6700BD /* FileResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FileResource.swift; path = Library/Core/FileResource.swift; sourceTree = ""; }; ACAA2DF16AF41EEAF3D192317F4CB8FD /* UINib+NibResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UINib+NibResource.swift"; path = "Library/UIKit/UINib+NibResource.swift"; sourceTree = ""; }; AE5EFD9BAE662D567150C610DB558709 /* StoryboardResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StoryboardResource.swift; path = Library/Core/StoryboardResource.swift; sourceTree = ""; }; AE830BC62069D8306382C4B4EC279834 /* StringResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StringResource.swift; path = Library/Core/StringResource.swift; sourceTree = ""; }; AF7D442E40E485397B9B0ADCFE3200AB /* R.swift.Library-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "R.swift.Library-prefix.pch"; sourceTree = ""; }; B0EEB92F64982D56632206A7D19F4C2F /* UITableView+ReuseIdentifierProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UITableView+ReuseIdentifierProtocol.swift"; path = "Library/UIKit/UITableView+ReuseIdentifierProtocol.swift"; sourceTree = ""; }; BA4FFF574DA865FFE4A38141EA3C0552 /* ReuseIdentifierProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ReuseIdentifierProtocol.swift; path = Library/Core/ReuseIdentifierProtocol.swift; sourceTree = ""; }; BA7C479A8FB69F0F707E933476011DBB /* R.swift.Library.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = R.swift.Library.debug.xcconfig; sourceTree = ""; }; C411A1FD05163B4A9F300AC428C26CF2 /* UIStoryboard+StoryboardResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIStoryboard+StoryboardResource.swift"; path = "Library/UIKit/UIStoryboard+StoryboardResource.swift"; sourceTree = ""; }; CB5C39B2E711E276B2D126EF242DD982 /* UIFont+FontResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIFont+FontResource.swift"; path = "Library/UIKit/UIFont+FontResource.swift"; sourceTree = ""; }; D47EE99420FF1B9743FE39A55976A653 /* R.swift.Library-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "R.swift.Library-Info.plist"; sourceTree = ""; }; D5AA3E281812FBE3BCAE65081B7F1C59 /* Bundle+FileResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Bundle+FileResource.swift"; path = "Library/Foundation/Bundle+FileResource.swift"; sourceTree = ""; }; D6345E58BA358AE234137F8649BB47E0 /* Identifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Identifier.swift; path = Library/Core/Identifier.swift; sourceTree = ""; }; D868A4318827ECCF8582C4F549BE0472 /* StoryboardSegueIdentifierProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StoryboardSegueIdentifierProtocol.swift; path = Library/Core/StoryboardSegueIdentifierProtocol.swift; sourceTree = ""; }; DA372BB34DA343967CD088D04B5B527C /* R.swift.Library */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = R.swift.Library; path = Rswift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DBE17BA66F3F0333EE62DFAA8B512C9C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; E2985B8304D310B87F3A02632595E69A /* UIStoryboard+StoryboardViewControllerResource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIStoryboard+StoryboardViewControllerResource.swift"; path = "Library/UIKit/UIStoryboard+StoryboardViewControllerResource.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 78DBDA50406DF789F1E142DA9A8CCCF2 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 9C7E9D17673F02C3A73662BB6FA5F1CC /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 06B46FBFEC8FEE4E57B5B1EDA5809371 /* R.swift.Library */ = { isa = PBXGroup; children = ( D5AA3E281812FBE3BCAE65081B7F1C59 /* Bundle+FileResource.swift */, 37C3AF7420033891FE325E22BF08CF46 /* ColorResource.swift */, 82A0D6FE6B14AEC444352733B74E7620 /* Data+FileResource.swift */, A082CC0C4276FC2C5302AD09AB6700BD /* FileResource.swift */, 6AB623CD96D0DA2D748EB71A1086CA2A /* FontResource.swift */, D6345E58BA358AE234137F8649BB47E0 /* Identifier.swift */, 05D35D43CE7071E6A0413834D9A02964 /* ImageResource.swift */, 2669DF823AC7148ACC6995A6BC2CAD5D /* NibResource.swift */, 2543FB18DD737F2D7DEAAF3DB67F582E /* NibResource+UIKit.swift */, BA4FFF574DA865FFE4A38141EA3C0552 /* ReuseIdentifierProtocol.swift */, AE5EFD9BAE662D567150C610DB558709 /* StoryboardResource.swift */, 266E5651CC84B1071D707AEDB5DB1945 /* StoryboardResourceWithInitialController+UIKit.swift */, D868A4318827ECCF8582C4F549BE0472 /* StoryboardSegueIdentifierProtocol.swift */, 0BDBE4BAE53D934DB55228EDC721FF79 /* StoryboardViewControllerResource.swift */, AE830BC62069D8306382C4B4EC279834 /* StringResource.swift */, 53F17BB1DD05264547C1615E7CCC84BA /* TypedStoryboardSegueInfo+UIStoryboardSegue.swift */, 18EF2A8F8DBE5CCC0E46AE3DCCC8F6E0 /* UICollectionView+ReuseIdentifierProtocol.swift */, 6E2628B93B8AFA10A0EC55DA4CE2BCBB /* UIColor+ColorResource.swift */, CB5C39B2E711E276B2D126EF242DD982 /* UIFont+FontResource.swift */, 916EEF0BE2CEE80B92E1DEC528ABE162 /* UIImage+ImageResource.swift */, ACAA2DF16AF41EEAF3D192317F4CB8FD /* UINib+NibResource.swift */, C411A1FD05163B4A9F300AC428C26CF2 /* UIStoryboard+StoryboardResource.swift */, E2985B8304D310B87F3A02632595E69A /* UIStoryboard+StoryboardViewControllerResource.swift */, B0EEB92F64982D56632206A7D19F4C2F /* UITableView+ReuseIdentifierProtocol.swift */, 5F3AE537895379CBE833189C9A4FA127 /* UIViewController+NibResource.swift */, 9E9D902025BE9976A24616DBA7D08F3A /* UIViewController+StoryboardSegueIdentifierProtocol.swift */, 27E86EFB28CE1FDAC538CC87BA85AFE0 /* Validatable.swift */, BFA565699E9B4AA9429146592B2A8F11 /* Support Files */, ); name = R.swift.Library; path = R.swift.Library; sourceTree = ""; }; 0B7ADC8C6AC5A3271201CB8102BAF2DD /* Products */ = { isa = PBXGroup; children = ( DA372BB34DA343967CD088D04B5B527C /* R.swift.Library */, ); name = Products; sourceTree = ""; }; 2893F64AED50DBE6DB16812B556C2820 = { isa = PBXGroup; children = ( 28FF557BD0499D81261BE99BB5A0B2DF /* Frameworks */, 0B7ADC8C6AC5A3271201CB8102BAF2DD /* Products */, 06B46FBFEC8FEE4E57B5B1EDA5809371 /* R.swift.Library */, ); sourceTree = ""; }; 28FF557BD0499D81261BE99BB5A0B2DF /* Frameworks */ = { isa = PBXGroup; children = ( A3AFFD767F2F438CA4D7383747D6F9E3 /* iOS */, ); name = Frameworks; sourceTree = ""; }; A3AFFD767F2F438CA4D7383747D6F9E3 /* iOS */ = { isa = PBXGroup; children = ( DBE17BA66F3F0333EE62DFAA8B512C9C /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; BFA565699E9B4AA9429146592B2A8F11 /* Support Files */ = { isa = PBXGroup; children = ( 35C1286245B5987BF5C5B2207FC517AC /* R.swift.Library.modulemap */, 241C64CE32764417C660E090E4681BE7 /* R.swift.Library-dummy.m */, D47EE99420FF1B9743FE39A55976A653 /* R.swift.Library-Info.plist */, AF7D442E40E485397B9B0ADCFE3200AB /* R.swift.Library-prefix.pch */, 5863C260841B827CD6DAF3EFBDF63C78 /* R.swift.Library-umbrella.h */, BA7C479A8FB69F0F707E933476011DBB /* R.swift.Library.debug.xcconfig */, 06A4C248292207C3A8654DF0F5819F5B /* R.swift.Library.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/R.swift.Library"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ C1C6706916C3FE78C93E163A222397A8 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 2D5B0E39AB3DE7F5D3F4F2A8A1C1D4C5 /* R.swift.Library-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 61AC8FF22886EA444144B44F9AC733B8 /* R.swift.Library */ = { isa = PBXNativeTarget; buildConfigurationList = A77F7EE736F0593FC072E7F26B6D964F /* Build configuration list for PBXNativeTarget "R.swift.Library" */; buildPhases = ( C1C6706916C3FE78C93E163A222397A8 /* Headers */, 3B716177F69D55261B51559C3E80BF6D /* Sources */, 78DBDA50406DF789F1E142DA9A8CCCF2 /* Frameworks */, BAC153C1A57131A33DF20ECA5D6BCDD3 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = R.swift.Library; productName = Rswift; productReference = DA372BB34DA343967CD088D04B5B527C /* R.swift.Library */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ ABB5B165FDD7803A1A5EECC1198E8E0C /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = DDE7173D388F6F202D31CE4ECC5F20F7 /* Build configuration list for PBXProject "R.swift.Library" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 2893F64AED50DBE6DB16812B556C2820; productRefGroup = 0B7ADC8C6AC5A3271201CB8102BAF2DD /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 61AC8FF22886EA444144B44F9AC733B8 /* R.swift.Library */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ BAC153C1A57131A33DF20ECA5D6BCDD3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 3B716177F69D55261B51559C3E80BF6D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2B29A243062C9F66198908EBDB0017B5 /* Bundle+FileResource.swift in Sources */, BB05F14C113E866C307E21CF6E827315 /* ColorResource.swift in Sources */, 0FA820A1092C3DB17A3F2C82FD4AFB84 /* Data+FileResource.swift in Sources */, A89B8795FA80A6C452B134AC46853AB3 /* FileResource.swift in Sources */, 0C4F5BFE6D00473694426D27ABC11A28 /* FontResource.swift in Sources */, B70B88A4F9B8B1F30813E3EA2651FCD6 /* Identifier.swift in Sources */, 9CD6C8AA7FE288C087651EAC8F86E0D4 /* ImageResource.swift in Sources */, 0E95BD610EF45AE5B515DE2AD5F7C823 /* NibResource.swift in Sources */, 993EA04440B376A09BCD2126F1E771EE /* NibResource+UIKit.swift in Sources */, D337D77F3D3A6FFE2E8A2041FE4175D0 /* R.swift.Library-dummy.m in Sources */, 56AF519A31089CF99A2DD85043ACA13D /* ReuseIdentifierProtocol.swift in Sources */, 2411AE419A24BE790B3B65B0196FB594 /* StoryboardResource.swift in Sources */, A125B00D13C3964FA4FDB94F684D1E8E /* StoryboardResourceWithInitialController+UIKit.swift in Sources */, 957CDE1D86CFE23AEDB433A90BCAE96E /* StoryboardSegueIdentifierProtocol.swift in Sources */, DAF38AE55A5BC5B9399D2A7FDCB3D558 /* StoryboardViewControllerResource.swift in Sources */, 8008F98F930079E0F9F24D9E85BDA499 /* StringResource.swift in Sources */, 79502ECF650046C5EBE8CF980A04ACDC /* TypedStoryboardSegueInfo+UIStoryboardSegue.swift in Sources */, 244D3CDB2966D6AE7DE44051806017AC /* UICollectionView+ReuseIdentifierProtocol.swift in Sources */, 0BBF1068BB3B41A7FFF880B05795E07B /* UIColor+ColorResource.swift in Sources */, DC00B4068B6F85EBB1FD9E01F93921AD /* UIFont+FontResource.swift in Sources */, 1B200E79A464C91233A77074C903F8FD /* UIImage+ImageResource.swift in Sources */, AB019015428DF9B980679F1AD4C5C8B9 /* UINib+NibResource.swift in Sources */, C974C044A5122FEE8B85026C2358DF28 /* UIStoryboard+StoryboardResource.swift in Sources */, A82DA5F7B9ED116C03CD180DBDE21433 /* UIStoryboard+StoryboardViewControllerResource.swift in Sources */, 6BD87E05828E5C090225001E9B8EB67A /* UITableView+ReuseIdentifierProtocol.swift in Sources */, 81A621AEE3152C6832D13641CDE233FF /* UIViewController+NibResource.swift in Sources */, 6F40C3134AE58DD8AE0406CF871AFB95 /* UIViewController+StoryboardSegueIdentifierProtocol.swift in Sources */, 9D42E7890E9FBCC3B108880C280764A5 /* Validatable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 33FFA6B6349F346422F1FDA57C5D7714 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = BA7C479A8FB69F0F707E933476011DBB /* R.swift.Library.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/R.swift.Library/R.swift.Library-prefix.pch"; INFOPLIST_FILE = "Target Support Files/R.swift.Library/R.swift.Library-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/R.swift.Library/R.swift.Library.modulemap"; PRODUCT_MODULE_NAME = Rswift; PRODUCT_NAME = Rswift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.1; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 61BBA37DE0995CA27F7F5A0AF2CF3ADE /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 06A4C248292207C3A8654DF0F5819F5B /* R.swift.Library.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/R.swift.Library/R.swift.Library-prefix.pch"; INFOPLIST_FILE = "Target Support Files/R.swift.Library/R.swift.Library-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/R.swift.Library/R.swift.Library.modulemap"; PRODUCT_MODULE_NAME = Rswift; PRODUCT_NAME = Rswift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.1; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 823476EC2DC66913C28D30D9370F91E6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 9720202A3553F6A7C9E53D981C25096B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ A77F7EE736F0593FC072E7F26B6D964F /* Build configuration list for PBXNativeTarget "R.swift.Library" */ = { isa = XCConfigurationList; buildConfigurations = ( 33FFA6B6349F346422F1FDA57C5D7714 /* Debug */, 61BBA37DE0995CA27F7F5A0AF2CF3ADE /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; DDE7173D388F6F202D31CE4ECC5F20F7 /* Build configuration list for PBXProject "R.swift.Library" */ = { isa = XCConfigurationList; buildConfigurations = ( 823476EC2DC66913C28D30D9370F91E6 /* Debug */, 9720202A3553F6A7C9E53D981C25096B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = ABB5B165FDD7803A1A5EECC1198E8E0C /* Project object */; } ================================================ FILE: JetChat/Pods/R.swift.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXAggregateTarget section */ FF6ED22E1CDAF83ADE98360601DC4DE5 /* R.swift */ = { isa = PBXAggregateTarget; buildConfigurationList = 448BEE38A7C2EB9792FC71C13C333B25 /* Build configuration list for PBXAggregateTarget "R.swift" */; buildPhases = ( ); dependencies = ( F31639E6F1AFA516CA853F5C385B62B6 /* PBXTargetDependency */, ); name = R.swift; }; /* End PBXAggregateTarget section */ /* Begin PBXContainerItemProxy section */ D1DEDFB1F2644C4E7A1B8588E2C33CE9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = EA0D0955105E64F697BFE3333E95EB01 /* R.swift.Library.xcodeproj */; proxyType = 1; remoteGlobalIDString = 61AC8FF22886EA444144B44F9AC733B8; remoteInfo = R.swift.Library; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 0ACB86BA17B7AF5C7475F48C47EBCBA4 /* R.swift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = R.swift.release.xcconfig; sourceTree = ""; }; A7FE24C116568454DDF763AAE78B510D /* R.swift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = R.swift.debug.xcconfig; sourceTree = ""; }; EA0D0955105E64F697BFE3333E95EB01 /* R.swift.Library */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = R.swift.Library; path = R.swift.Library.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXGroup section */ 013C9EA4ED630FBD546ECCA8A8E24FCD /* Support Files */ = { isa = PBXGroup; children = ( A7FE24C116568454DDF763AAE78B510D /* R.swift.debug.xcconfig */, 0ACB86BA17B7AF5C7475F48C47EBCBA4 /* R.swift.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/R.swift"; sourceTree = ""; }; 377F583B1B038B3E26B9E10CBA580814 /* Dependencies */ = { isa = PBXGroup; children = ( EA0D0955105E64F697BFE3333E95EB01 /* R.swift.Library */, ); name = Dependencies; sourceTree = ""; }; 955BBF8B76891C1979D0E584713B1394 /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; A227BCAB473C80EF4383E1AC6529B361 /* R.swift */ = { isa = PBXGroup; children = ( 013C9EA4ED630FBD546ECCA8A8E24FCD /* Support Files */, ); name = R.swift; path = R.swift; sourceTree = ""; }; CE8978BB2824B28944F03B530D10726E /* Products */ = { isa = PBXGroup; children = ( ); name = Products; sourceTree = ""; }; F2E3357DA00E89877D9F37B70946C357 = { isa = PBXGroup; children = ( 377F583B1B038B3E26B9E10CBA580814 /* Dependencies */, 955BBF8B76891C1979D0E584713B1394 /* Frameworks */, CE8978BB2824B28944F03B530D10726E /* Products */, A227BCAB473C80EF4383E1AC6529B361 /* R.swift */, ); sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXProject section */ B90D616832CAB42E650F12B59C2EA780 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 8A41CD8E4C679AC7247793EA23D1CDD6 /* Build configuration list for PBXProject "R.swift" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = F2E3357DA00E89877D9F37B70946C357; productRefGroup = CE8978BB2824B28944F03B530D10726E /* Products */; projectDirPath = ""; projectReferences = ( { ProjectRef = EA0D0955105E64F697BFE3333E95EB01 /* R.swift.Library */; }, ); projectRoot = ""; targets = ( FF6ED22E1CDAF83ADE98360601DC4DE5 /* R.swift */, ); }; /* End PBXProject section */ /* Begin PBXTargetDependency section */ F31639E6F1AFA516CA853F5C385B62B6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = R.swift.Library; targetProxy = D1DEDFB1F2644C4E7A1B8588E2C33CE9 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 00AB0073A7B305943362DA7D21E24B75 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 0ACB86BA17B7AF5C7475F48C47EBCBA4 /* R.swift.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 379EF0260A372F3690E8F6DD7A9F6022 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 4DD87E629DB5CBE6E957B37E993B79A2 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; EA13933170815E225ECE50BDA2D9B730 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = A7FE24C116568454DDF763AAE78B510D /* R.swift.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 448BEE38A7C2EB9792FC71C13C333B25 /* Build configuration list for PBXAggregateTarget "R.swift" */ = { isa = XCConfigurationList; buildConfigurations = ( EA13933170815E225ECE50BDA2D9B730 /* Debug */, 00AB0073A7B305943362DA7D21E24B75 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 8A41CD8E4C679AC7247793EA23D1CDD6 /* Build configuration list for PBXProject "R.swift" */ = { isa = XCConfigurationList; buildConfigurations = ( 379EF0260A372F3690E8F6DD7A9F6022 /* Debug */, 4DD87E629DB5CBE6E957B37E993B79A2 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = B90D616832CAB42E650F12B59C2EA780 /* Project object */; } ================================================ FILE: JetChat/Pods/ReachabilitySwift/LICENSE ================================================ Copyright (c) 2016 Ashley Mills Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/ReachabilitySwift/README.md ================================================ # Reachability.swift Reachability.swift is a replacement for Apple's Reachability sample, re-written in Swift with closures. It is compatible with **iOS** (8.0 - 12.0), **OSX** (10.9 - 10.14) and **tvOS** (9.0 - 12.0) Inspired by https://github.com/tonymillion/Reachability ## Supporting **Reachability.swift** Keeping **Reachability.swift** up-to-date is a time consuming task. Making updates, reviewing pull requests, responding to issues and answering emails all take time. If you're an iOS developer who's looking for a quick and easy way to create App Store screenshots, please try out my app [Screenshot Producer](https://itunes.apple.com/app/apple-store/id1252374855?pt=215893&ct=reachability&mt=8)… Devices | Layout | Copy | Localize | Export       :------:|:------:|:------:|:------:|:------: ![](http://is2.mzstatic.com/image/thumb/Purple118/v4/64/af/55/64af55bc-2ef0-691c-f5f3-4963685f7f63/source/552x414bb.jpg) | ![](http://is4.mzstatic.com/image/thumb/Purple128/v4/fb/4c/bd/fb4cbd2f-dd04-22ba-4fdf-5ac652693fb8/source/552x414bb.jpg) | ![](http://is1.mzstatic.com/image/thumb/Purple118/v4/5a/4f/cf/5a4fcfdf-ca04-0307-9f2e-83178e8ad90d/source/552x414bb.jpg) | ![](http://is4.mzstatic.com/image/thumb/Purple128/v4/17/ea/56/17ea562e-e045-96e7-fcac-cfaaf4f499fd/source/552x414bb.jpg) | ![](http://is4.mzstatic.com/image/thumb/Purple118/v4/59/9e/dd/599edd50-f05c-f413-8e88-e614731fd828/source/552x414bb.jpg) And don't forget to **★** the repo. This increases its visibility and encourages others to contribute. Thanks Ash ## Got a problem? Please read https://github.com/ashleymills/Reachability.swift/blob/master/CONTRIBUTING.md before raising an issue. ## Installation ### Manual Just drop the **Reachability.swift** file into your project. That's it! ### CocoaPods [CocoaPods][] is a dependency manager for Cocoa projects. To install Reachability.swift with CocoaPods: 1. Make sure CocoaPods is [installed][CocoaPods Installation]. 2. Update your Podfile to include the following: ``` ruby use_frameworks! pod 'ReachabilitySwift' ``` 3. Run `pod install`. [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started 4. In your code import Reachability like so: `import Reachability` ### Carthage [Carthage][] is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To install Reachability.swift with Carthage: 1. Install Carthage via [Homebrew][] ```bash $ brew update $ brew install carthage ``` 2. Add `github "ashleymills/Reachability.swift"` to your Cartfile. 3. Run `carthage update`. 4. Drag `Reachability.framework` from the `Carthage/Build/iOS/` directory to the `Linked Frameworks and Libraries` section of your Xcode project’s `General` settings. 5. Add `$(SRCROOT)/Carthage/Build/iOS/Reachability.framework` to `Input Files` of Run Script Phase for Carthage. 6. In your code import Reachability like so: `import Reachability` [Carthage]: https://github.com/Carthage/Carthage [Homebrew]: http://brew.sh [Photo Flipper]: https://itunes.apple.com/app/apple-store/id749627884?pt=215893&ct=GitHubReachability&mt=8 ## Example - closures NOTE: All closures are run on the **main queue**. ```swift //declare this property where it won't go out of scope relative to your listener let reachability = Reachability()! reachability.whenReachable = { reachability in if reachability.connection == .wifi { print("Reachable via WiFi") } else { print("Reachable via Cellular") } } reachability.whenUnreachable = { _ in print("Not reachable") } do { try reachability.startNotifier() } catch { print("Unable to start notifier") } ``` and for stopping notifications ```swift reachability.stopNotifier() ``` ## Example - notifications NOTE: All notifications are delivered on the **main queue**. ```swift //declare this property where it won't go out of scope relative to your listener let reachability = Reachability()! //declare this inside of viewWillAppear NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: reachability) do{ try reachability.startNotifier() }catch{ print("could not start reachability notifier") } ``` and ```swift @objc func reachabilityChanged(note: Notification) { let reachability = note.object as! Reachability switch reachability.connection { case .wifi: print("Reachable via WiFi") case .cellular: print("Reachable via Cellular") case .unavailable: print("Network not reachable") } } ``` and for stopping notifications ```swift reachability.stopNotifier() NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability) ``` ## Want to help? Got a bug fix, or a new feature? Create a pull request and go for it! ## Let me know! If you use **Reachability.swift**, please let me know about your app and I'll put a link [here…](https://github.com/ashleymills/Reachability.swift/wiki/Apps-using-Reachability.swift) and tell your friends! Cheers, Ash ================================================ FILE: JetChat/Pods/ReachabilitySwift/Sources/Reachability.swift ================================================ /* Copyright (c) 2014, Ashley Mills All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import SystemConfiguration import Foundation public enum ReachabilityError: Error { case failedToCreateWithAddress(sockaddr, Int32) case failedToCreateWithHostname(String, Int32) case unableToSetCallback(Int32) case unableToSetDispatchQueue(Int32) case unableToGetFlags(Int32) } @available(*, unavailable, renamed: "Notification.Name.reachabilityChanged") public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") public extension Notification.Name { static let reachabilityChanged = Notification.Name("reachabilityChanged") } public class Reachability { public typealias NetworkReachable = (Reachability) -> () public typealias NetworkUnreachable = (Reachability) -> () @available(*, unavailable, renamed: "Connection") public enum NetworkStatus: CustomStringConvertible { case notReachable, reachableViaWiFi, reachableViaWWAN public var description: String { switch self { case .reachableViaWWAN: return "Cellular" case .reachableViaWiFi: return "WiFi" case .notReachable: return "No Connection" } } } public enum Connection: CustomStringConvertible { @available(*, deprecated, renamed: "unavailable") case none case unavailable, wifi, cellular public var description: String { switch self { case .cellular: return "Cellular" case .wifi: return "WiFi" case .unavailable: return "No Connection" case .none: return "unavailable" } } } public var whenReachable: NetworkReachable? public var whenUnreachable: NetworkUnreachable? @available(*, deprecated, renamed: "allowsCellularConnection") public let reachableOnWWAN: Bool = true /// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`) public var allowsCellularConnection: Bool // The notification center on which "reachability changed" events are being posted public var notificationCenter: NotificationCenter = NotificationCenter.default @available(*, deprecated, renamed: "connection.description") public var currentReachabilityString: String { return "\(connection)" } @available(*, unavailable, renamed: "connection") public var currentReachabilityStatus: Connection { return connection } public var connection: Connection { if flags == nil { try? setReachabilityFlags() } switch flags?.connection { case .unavailable?, nil: return .unavailable case .none?: return .unavailable case .cellular?: return allowsCellularConnection ? .cellular : .unavailable case .wifi?: return .wifi } } fileprivate var isRunningOnDevice: Bool = { #if targetEnvironment(simulator) return false #else return true #endif }() fileprivate(set) var notifierRunning = false fileprivate let reachabilityRef: SCNetworkReachability fileprivate let reachabilitySerialQueue: DispatchQueue fileprivate let notificationQueue: DispatchQueue? fileprivate(set) var flags: SCNetworkReachabilityFlags? { didSet { guard flags != oldValue else { return } notifyReachabilityChanged() } } required public init(reachabilityRef: SCNetworkReachability, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil, notificationQueue: DispatchQueue? = .main) { self.allowsCellularConnection = true self.reachabilityRef = reachabilityRef self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue) self.notificationQueue = notificationQueue } public convenience init(hostname: String, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil, notificationQueue: DispatchQueue? = .main) throws { guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { throw ReachabilityError.failedToCreateWithHostname(hostname, SCError()) } self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue) } public convenience init(queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil, notificationQueue: DispatchQueue? = .main) throws { var zeroAddress = sockaddr() zeroAddress.sa_len = UInt8(MemoryLayout.size) zeroAddress.sa_family = sa_family_t(AF_INET) guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { throw ReachabilityError.failedToCreateWithAddress(zeroAddress, SCError()) } self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue) } deinit { stopNotifier() } } public extension Reachability { // MARK: - *** Notifier methods *** func startNotifier() throws { guard !notifierRunning else { return } let callback: SCNetworkReachabilityCallBack = { (reachability, flags, info) in guard let info = info else { return } // `weakifiedReachability` is guaranteed to exist by virtue of our // retain/release callbacks which we provided to the `SCNetworkReachabilityContext`. let weakifiedReachability = Unmanaged.fromOpaque(info).takeUnretainedValue() // The weak `reachability` _may_ no longer exist if the `Reachability` // object has since been deallocated but a callback was already in flight. weakifiedReachability.reachability?.flags = flags } let weakifiedReachability = ReachabilityWeakifier(reachability: self) let opaqueWeakifiedReachability = Unmanaged.passUnretained(weakifiedReachability).toOpaque() var context = SCNetworkReachabilityContext( version: 0, info: UnsafeMutableRawPointer(opaqueWeakifiedReachability), retain: { (info: UnsafeRawPointer) -> UnsafeRawPointer in let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) _ = unmanagedWeakifiedReachability.retain() return UnsafeRawPointer(unmanagedWeakifiedReachability.toOpaque()) }, release: { (info: UnsafeRawPointer) -> Void in let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) unmanagedWeakifiedReachability.release() }, copyDescription: { (info: UnsafeRawPointer) -> Unmanaged in let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) let weakifiedReachability = unmanagedWeakifiedReachability.takeUnretainedValue() let description = weakifiedReachability.reachability?.description ?? "nil" return Unmanaged.passRetained(description as CFString) } ) if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) { stopNotifier() throw ReachabilityError.unableToSetCallback(SCError()) } if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) { stopNotifier() throw ReachabilityError.unableToSetDispatchQueue(SCError()) } // Perform an initial check try setReachabilityFlags() notifierRunning = true } func stopNotifier() { defer { notifierRunning = false } SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) } // MARK: - *** Connection test methods *** @available(*, deprecated, message: "Please use `connection != .none`") var isReachable: Bool { return connection != .unavailable } @available(*, deprecated, message: "Please use `connection == .cellular`") var isReachableViaWWAN: Bool { // Check we're not on the simulator, we're REACHABLE and check we're on WWAN return connection == .cellular } @available(*, deprecated, message: "Please use `connection == .wifi`") var isReachableViaWiFi: Bool { return connection == .wifi } var description: String { return flags?.description ?? "unavailable flags" } } fileprivate extension Reachability { func setReachabilityFlags() throws { try reachabilitySerialQueue.sync { [unowned self] in var flags = SCNetworkReachabilityFlags() if !SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags) { self.stopNotifier() throw ReachabilityError.unableToGetFlags(SCError()) } self.flags = flags } } func notifyReachabilityChanged() { let notify = { [weak self] in guard let self = self else { return } self.connection != .unavailable ? self.whenReachable?(self) : self.whenUnreachable?(self) self.notificationCenter.post(name: .reachabilityChanged, object: self) } // notify on the configured `notificationQueue`, or the caller's (i.e. `reachabilitySerialQueue`) notificationQueue?.async(execute: notify) ?? notify() } } extension SCNetworkReachabilityFlags { typealias Connection = Reachability.Connection var connection: Connection { guard isReachableFlagSet else { return .unavailable } // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi #if targetEnvironment(simulator) return .wifi #else var connection = Connection.unavailable if !isConnectionRequiredFlagSet { connection = .wifi } if isConnectionOnTrafficOrDemandFlagSet { if !isInterventionRequiredFlagSet { connection = .wifi } } if isOnWWANFlagSet { connection = .cellular } return connection #endif } var isOnWWANFlagSet: Bool { #if os(iOS) return contains(.isWWAN) #else return false #endif } var isReachableFlagSet: Bool { return contains(.reachable) } var isConnectionRequiredFlagSet: Bool { return contains(.connectionRequired) } var isInterventionRequiredFlagSet: Bool { return contains(.interventionRequired) } var isConnectionOnTrafficFlagSet: Bool { return contains(.connectionOnTraffic) } var isConnectionOnDemandFlagSet: Bool { return contains(.connectionOnDemand) } var isConnectionOnTrafficOrDemandFlagSet: Bool { return !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty } var isTransientConnectionFlagSet: Bool { return contains(.transientConnection) } var isLocalAddressFlagSet: Bool { return contains(.isLocalAddress) } var isDirectFlagSet: Bool { return contains(.isDirect) } var isConnectionRequiredAndTransientFlagSet: Bool { return intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection] } var description: String { let W = isOnWWANFlagSet ? "W" : "-" let R = isReachableFlagSet ? "R" : "-" let c = isConnectionRequiredFlagSet ? "c" : "-" let t = isTransientConnectionFlagSet ? "t" : "-" let i = isInterventionRequiredFlagSet ? "i" : "-" let C = isConnectionOnTrafficFlagSet ? "C" : "-" let D = isConnectionOnDemandFlagSet ? "D" : "-" let l = isLocalAddressFlagSet ? "l" : "-" let d = isDirectFlagSet ? "d" : "-" return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" } } /** `ReachabilityWeakifier` weakly wraps the `Reachability` class in order to break retain cycles when interacting with CoreFoundation. CoreFoundation callbacks expect a pair of retain/release whenever an opaque `info` parameter is provided. These callbacks exist to guard against memory management race conditions when invoking the callbacks. #### Race Condition If we passed `SCNetworkReachabilitySetCallback` a direct reference to our `Reachability` class without also providing corresponding retain/release callbacks, then a race condition can lead to crashes when: - `Reachability` is deallocated on thread X - A `SCNetworkReachability` callback(s) is already in flight on thread Y #### Retain Cycle If we pass `Reachability` to CoreFoundtion while also providing retain/ release callbacks, we would create a retain cycle once CoreFoundation retains our `Reachability` class. This fixes the crashes and his how CoreFoundation expects the API to be used, but doesn't play nicely with Swift/ARC. This cycle would only be broken after manually calling `stopNotifier()` — `deinit` would never be called. #### ReachabilityWeakifier By providing both retain/release callbacks and wrapping `Reachability` in a weak wrapper, we: - interact correctly with CoreFoundation, thereby avoiding a crash. See "Memory Management Programming Guide for Core Foundation". - don't alter the public API of `Reachability.swift` in any way - still allow for automatic stopping of the notifier on `deinit`. */ private class ReachabilityWeakifier { weak var reachability: Reachability? init(reachability: Reachability) { self.reachability = reachability } } ================================================ FILE: JetChat/Pods/ReachabilitySwift.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 01CD1EDA7E57C97906ADE5964B0910F6 /* ReachabilitySwift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = AC6C6CFA3000B2D74897C5D2DD471762 /* ReachabilitySwift-dummy.m */; }; 84F68FC89F423DBC6C21B0F84BB5AEDF /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA696B5637595F81FD13D907D4F78F7 /* Reachability.swift */; }; 8931A2D6A5F9BED6C18623CFF4BEE7B6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D32CCE574946C4E7500B01AF98B4252 /* Foundation.framework */; }; 8D68BEE088299DCD3EB7FAD69915D2EB /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD0A9A85A3FCE0671509CC9AE79B6823 /* SystemConfiguration.framework */; }; 8E05891A4FE392B3B8930AA82032447B /* ReachabilitySwift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 110FFED695F4EA03F5BC65A329FE8FE8 /* ReachabilitySwift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; B0431E9C78BFE6CAB37E09BB3A06A69B /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFEA48ECD4F43A2D24DEB0757BF87FB3 /* CoreTelephony.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 110FFED695F4EA03F5BC65A329FE8FE8 /* ReachabilitySwift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "ReachabilitySwift-umbrella.h"; sourceTree = ""; }; 185105C5AD318DA1B77778AB3DA8FF2C /* ReachabilitySwift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = ReachabilitySwift.modulemap; sourceTree = ""; }; 1C88F27212A758844644A2D33A27E0C4 /* ReachabilitySwift */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = ReachabilitySwift; path = Reachability.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5D32CCE574946C4E7500B01AF98B4252 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 687776155C4F788053D389E9E8EB6377 /* ReachabilitySwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = ReachabilitySwift.release.xcconfig; sourceTree = ""; }; AC6C6CFA3000B2D74897C5D2DD471762 /* ReachabilitySwift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "ReachabilitySwift-dummy.m"; sourceTree = ""; }; BD0A9A85A3FCE0671509CC9AE79B6823 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; BFEA48ECD4F43A2D24DEB0757BF87FB3 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CoreTelephony.framework; sourceTree = DEVELOPER_DIR; }; C59285C666B48ACBBD00314517E2A88E /* ReachabilitySwift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "ReachabilitySwift-prefix.pch"; sourceTree = ""; }; CEA696B5637595F81FD13D907D4F78F7 /* Reachability.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Reachability.swift; path = Sources/Reachability.swift; sourceTree = ""; }; D4A0922B45BB3F8071269ED265846AA5 /* ReachabilitySwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = ReachabilitySwift.debug.xcconfig; sourceTree = ""; }; D93A25026F8B01BA041F5A3814703E11 /* ReachabilitySwift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ReachabilitySwift-Info.plist"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 3C0640714DE05F6D3C72B7DD4C9F9CFB /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B0431E9C78BFE6CAB37E09BB3A06A69B /* CoreTelephony.framework in Frameworks */, 8931A2D6A5F9BED6C18623CFF4BEE7B6 /* Foundation.framework in Frameworks */, 8D68BEE088299DCD3EB7FAD69915D2EB /* SystemConfiguration.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 177DC929419C3ECD41CE7A7BC8AE7298 /* Support Files */ = { isa = PBXGroup; children = ( 185105C5AD318DA1B77778AB3DA8FF2C /* ReachabilitySwift.modulemap */, AC6C6CFA3000B2D74897C5D2DD471762 /* ReachabilitySwift-dummy.m */, D93A25026F8B01BA041F5A3814703E11 /* ReachabilitySwift-Info.plist */, C59285C666B48ACBBD00314517E2A88E /* ReachabilitySwift-prefix.pch */, 110FFED695F4EA03F5BC65A329FE8FE8 /* ReachabilitySwift-umbrella.h */, D4A0922B45BB3F8071269ED265846AA5 /* ReachabilitySwift.debug.xcconfig */, 687776155C4F788053D389E9E8EB6377 /* ReachabilitySwift.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/ReachabilitySwift"; sourceTree = ""; }; 1B7F44A38EBDA5628B0C2C85A433808A = { isa = PBXGroup; children = ( E5D78BB6C542A315DB038ED0EC7A0E5F /* Frameworks */, DB7FCBACC30A5F71DBC3E29843AA5E69 /* Products */, F7EC0502B22FA2221FDFECD065685EA7 /* ReachabilitySwift */, ); sourceTree = ""; }; 80DDF05F30B8ACBF29668CCCCBCB85FD /* iOS */ = { isa = PBXGroup; children = ( BFEA48ECD4F43A2D24DEB0757BF87FB3 /* CoreTelephony.framework */, 5D32CCE574946C4E7500B01AF98B4252 /* Foundation.framework */, BD0A9A85A3FCE0671509CC9AE79B6823 /* SystemConfiguration.framework */, ); name = iOS; sourceTree = ""; }; DB7FCBACC30A5F71DBC3E29843AA5E69 /* Products */ = { isa = PBXGroup; children = ( 1C88F27212A758844644A2D33A27E0C4 /* ReachabilitySwift */, ); name = Products; sourceTree = ""; }; E5D78BB6C542A315DB038ED0EC7A0E5F /* Frameworks */ = { isa = PBXGroup; children = ( 80DDF05F30B8ACBF29668CCCCBCB85FD /* iOS */, ); name = Frameworks; sourceTree = ""; }; F7EC0502B22FA2221FDFECD065685EA7 /* ReachabilitySwift */ = { isa = PBXGroup; children = ( CEA696B5637595F81FD13D907D4F78F7 /* Reachability.swift */, 177DC929419C3ECD41CE7A7BC8AE7298 /* Support Files */, ); name = ReachabilitySwift; path = ReachabilitySwift; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ E2C80CCE896005BA72B96A51772F457B /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 8E05891A4FE392B3B8930AA82032447B /* ReachabilitySwift-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 02AAC45BCBE8F4C196016B6D96C7A992 /* ReachabilitySwift */ = { isa = PBXNativeTarget; buildConfigurationList = 0494A6CB46A5367AEE58E0611EAB1599 /* Build configuration list for PBXNativeTarget "ReachabilitySwift" */; buildPhases = ( E2C80CCE896005BA72B96A51772F457B /* Headers */, C85BE21108523B3A772BDD91A4862CFF /* Sources */, 3C0640714DE05F6D3C72B7DD4C9F9CFB /* Frameworks */, 984495E9FEB5E24CE92D8043B7CD9126 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = ReachabilitySwift; productName = Reachability; productReference = 1C88F27212A758844644A2D33A27E0C4 /* ReachabilitySwift */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 1708B285359A2088AFDF81A03009D27F /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = A51B311C4D5D3C71C913DE9FF391CD1C /* Build configuration list for PBXProject "ReachabilitySwift" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 1B7F44A38EBDA5628B0C2C85A433808A; productRefGroup = DB7FCBACC30A5F71DBC3E29843AA5E69 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 02AAC45BCBE8F4C196016B6D96C7A992 /* ReachabilitySwift */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 984495E9FEB5E24CE92D8043B7CD9126 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ C85BE21108523B3A772BDD91A4862CFF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 84F68FC89F423DBC6C21B0F84BB5AEDF /* Reachability.swift in Sources */, 01CD1EDA7E57C97906ADE5964B0910F6 /* ReachabilitySwift-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 1E65F0B2CD0AF3B39460442A45347031 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = D4A0922B45BB3F8071269ED265846AA5 /* ReachabilitySwift.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/ReachabilitySwift/ReachabilitySwift-prefix.pch"; INFOPLIST_FILE = "Target Support Files/ReachabilitySwift/ReachabilitySwift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/ReachabilitySwift/ReachabilitySwift.modulemap"; PRODUCT_MODULE_NAME = Reachability; PRODUCT_NAME = Reachability; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 227C0C4BEE65FC0E2B4F4AD78F11E93B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; D4EA4F9E0AF98D6AD03F3B5DD49859EB /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; DC8A54872B99A2914754A6D4E5E5AC0A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 687776155C4F788053D389E9E8EB6377 /* ReachabilitySwift.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/ReachabilitySwift/ReachabilitySwift-prefix.pch"; INFOPLIST_FILE = "Target Support Files/ReachabilitySwift/ReachabilitySwift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/ReachabilitySwift/ReachabilitySwift.modulemap"; PRODUCT_MODULE_NAME = Reachability; PRODUCT_NAME = Reachability; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 0494A6CB46A5367AEE58E0611EAB1599 /* Build configuration list for PBXNativeTarget "ReachabilitySwift" */ = { isa = XCConfigurationList; buildConfigurations = ( 1E65F0B2CD0AF3B39460442A45347031 /* Debug */, DC8A54872B99A2914754A6D4E5E5AC0A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; A51B311C4D5D3C71C913DE9FF391CD1C /* Build configuration list for PBXProject "ReachabilitySwift" */ = { isa = XCConfigurationList; buildConfigurations = ( D4EA4F9E0AF98D6AD03F3B5DD49859EB /* Debug */, 227C0C4BEE65FC0E2B4F4AD78F11E93B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 1708B285359A2088AFDF81A03009D27F /* Project object */; } ================================================ FILE: JetChat/Pods/RxCocoa/LICENSE.md ================================================ **The MIT License** **Copyright © 2015 Krunoslav Zaher, Shai Mishali** **All rights reserved.** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/RxCocoa/Platform/DataStructures/Bag.swift ================================================ // // Bag.swift // Platform // // Created by Krunoslav Zaher on 2/28/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Swift let arrayDictionaryMaxSize = 30 struct BagKey { /** Unique identifier for object added to `Bag`. It's underlying type is UInt64. If we assume there in an idealized CPU that works at 4GHz, it would take ~150 years of continuous running time for it to overflow. */ fileprivate let rawValue: UInt64 } /** Data structure that represents a bag of elements typed `T`. Single element can be stored multiple times. Time and space complexity of insertion and deletion is O(n). It is suitable for storing small number of elements. */ struct Bag : CustomDebugStringConvertible { /// Type of identifier for inserted elements. typealias KeyType = BagKey typealias Entry = (key: BagKey, value: T) private var _nextKey: BagKey = BagKey(rawValue: 0) // data // first fill inline variables var _key0: BagKey? var _value0: T? // then fill "array dictionary" var _pairs = ContiguousArray() // last is sparse dictionary var _dictionary: [BagKey: T]? var _onlyFastPath = true /// Creates new empty `Bag`. init() { } /** Inserts `value` into bag. - parameter element: Element to insert. - returns: Key that can be used to remove element from bag. */ mutating func insert(_ element: T) -> BagKey { let key = _nextKey _nextKey = BagKey(rawValue: _nextKey.rawValue &+ 1) if _key0 == nil { _key0 = key _value0 = element return key } _onlyFastPath = false if _dictionary != nil { _dictionary![key] = element return key } if _pairs.count < arrayDictionaryMaxSize { _pairs.append((key: key, value: element)) return key } _dictionary = [key: element] return key } /// - returns: Number of elements in bag. var count: Int { let dictionaryCount: Int = _dictionary?.count ?? 0 return (_value0 != nil ? 1 : 0) + _pairs.count + dictionaryCount } /// Removes all elements from bag and clears capacity. mutating func removeAll() { _key0 = nil _value0 = nil _pairs.removeAll(keepingCapacity: false) _dictionary?.removeAll(keepingCapacity: false) } /** Removes element with a specific `key` from bag. - parameter key: Key that identifies element to remove from bag. - returns: Element that bag contained, or nil in case element was already removed. */ mutating func removeKey(_ key: BagKey) -> T? { if _key0 == key { _key0 = nil let value = _value0! _value0 = nil return value } if let existingObject = _dictionary?.removeValue(forKey: key) { return existingObject } for i in 0 ..< _pairs.count where _pairs[i].key == key { let value = _pairs[i].value _pairs.remove(at: i) return value } return nil } } extension Bag { /// A textual representation of `self`, suitable for debugging. var debugDescription : String { "\(self.count) elements in Bag" } } extension Bag { /// Enumerates elements inside the bag. /// /// - parameter action: Enumeration closure. func forEach(_ action: (T) -> Void) { if _onlyFastPath { if let value0 = _value0 { action(value0) } return } let value0 = _value0 let dictionary = _dictionary if let value0 = value0 { action(value0) } for i in 0 ..< _pairs.count { action(_pairs[i].value) } if dictionary?.count ?? 0 > 0 { for element in dictionary!.values { action(element) } } } } extension BagKey: Hashable { func hash(into hasher: inout Hasher) { hasher.combine(rawValue) } } func ==(lhs: BagKey, rhs: BagKey) -> Bool { lhs.rawValue == rhs.rawValue } ================================================ FILE: JetChat/Pods/RxCocoa/Platform/DataStructures/InfiniteSequence.swift ================================================ // // InfiniteSequence.swift // Platform // // Created by Krunoslav Zaher on 6/13/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Sequence that repeats `repeatedValue` infinite number of times. struct InfiniteSequence : Sequence { typealias Iterator = AnyIterator private let repeatedValue: Element init(repeatedValue: Element) { self.repeatedValue = repeatedValue } func makeIterator() -> Iterator { let repeatedValue = self.repeatedValue return AnyIterator { repeatedValue } } } ================================================ FILE: JetChat/Pods/RxCocoa/Platform/DataStructures/PriorityQueue.swift ================================================ // // PriorityQueue.swift // Platform // // Created by Krunoslav Zaher on 12/27/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // struct PriorityQueue { private let hasHigherPriority: (Element, Element) -> Bool private let isEqual: (Element, Element) -> Bool private var elements = [Element]() init(hasHigherPriority: @escaping (Element, Element) -> Bool, isEqual: @escaping (Element, Element) -> Bool) { self.hasHigherPriority = hasHigherPriority self.isEqual = isEqual } mutating func enqueue(_ element: Element) { elements.append(element) bubbleToHigherPriority(elements.count - 1) } func peek() -> Element? { elements.first } var isEmpty: Bool { elements.count == 0 } mutating func dequeue() -> Element? { guard let front = peek() else { return nil } removeAt(0) return front } mutating func remove(_ element: Element) { for i in 0 ..< elements.count { if self.isEqual(elements[i], element) { removeAt(i) return } } } private mutating func removeAt(_ index: Int) { let removingLast = index == elements.count - 1 if !removingLast { elements.swapAt(index, elements.count - 1) } _ = elements.popLast() if !removingLast { bubbleToHigherPriority(index) bubbleToLowerPriority(index) } } private mutating func bubbleToHigherPriority(_ initialUnbalancedIndex: Int) { precondition(initialUnbalancedIndex >= 0) precondition(initialUnbalancedIndex < elements.count) var unbalancedIndex = initialUnbalancedIndex while unbalancedIndex > 0 { let parentIndex = (unbalancedIndex - 1) / 2 guard self.hasHigherPriority(elements[unbalancedIndex], elements[parentIndex]) else { break } elements.swapAt(unbalancedIndex, parentIndex) unbalancedIndex = parentIndex } } private mutating func bubbleToLowerPriority(_ initialUnbalancedIndex: Int) { precondition(initialUnbalancedIndex >= 0) precondition(initialUnbalancedIndex < elements.count) var unbalancedIndex = initialUnbalancedIndex while true { let leftChildIndex = unbalancedIndex * 2 + 1 let rightChildIndex = unbalancedIndex * 2 + 2 var highestPriorityIndex = unbalancedIndex if leftChildIndex < elements.count && self.hasHigherPriority(elements[leftChildIndex], elements[highestPriorityIndex]) { highestPriorityIndex = leftChildIndex } if rightChildIndex < elements.count && self.hasHigherPriority(elements[rightChildIndex], elements[highestPriorityIndex]) { highestPriorityIndex = rightChildIndex } guard highestPriorityIndex != unbalancedIndex else { break } elements.swapAt(highestPriorityIndex, unbalancedIndex) unbalancedIndex = highestPriorityIndex } } } extension PriorityQueue : CustomDebugStringConvertible { var debugDescription: String { elements.debugDescription } } ================================================ FILE: JetChat/Pods/RxCocoa/Platform/DataStructures/Queue.swift ================================================ // // Queue.swift // Platform // // Created by Krunoslav Zaher on 3/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /** Data structure that represents queue. Complexity of `enqueue`, `dequeue` is O(1) when number of operations is averaged over N operations. Complexity of `peek` is O(1). */ struct Queue: Sequence { /// Type of generator. typealias Generator = AnyIterator private let resizeFactor = 2 private var storage: ContiguousArray private var innerCount = 0 private var pushNextIndex = 0 private let initialCapacity: Int /** Creates new queue. - parameter capacity: Capacity of newly created queue. */ init(capacity: Int) { initialCapacity = capacity storage = ContiguousArray(repeating: nil, count: capacity) } private var dequeueIndex: Int { let index = pushNextIndex - count return index < 0 ? index + storage.count : index } /// - returns: Is queue empty. var isEmpty: Bool { count == 0 } /// - returns: Number of elements inside queue. var count: Int { innerCount } /// - returns: Element in front of a list of elements to `dequeue`. func peek() -> T { precondition(count > 0) return storage[dequeueIndex]! } mutating private func resizeTo(_ size: Int) { var newStorage = ContiguousArray(repeating: nil, count: size) let count = self.count let dequeueIndex = self.dequeueIndex let spaceToEndOfQueue = storage.count - dequeueIndex // first batch is from dequeue index to end of array let countElementsInFirstBatch = Swift.min(count, spaceToEndOfQueue) // second batch is wrapped from start of array to end of queue let numberOfElementsInSecondBatch = count - countElementsInFirstBatch newStorage[0 ..< countElementsInFirstBatch] = storage[dequeueIndex ..< (dequeueIndex + countElementsInFirstBatch)] newStorage[countElementsInFirstBatch ..< (countElementsInFirstBatch + numberOfElementsInSecondBatch)] = storage[0 ..< numberOfElementsInSecondBatch] self.innerCount = count pushNextIndex = count storage = newStorage } /// Enqueues `element`. /// /// - parameter element: Element to enqueue. mutating func enqueue(_ element: T) { if count == storage.count { resizeTo(Swift.max(storage.count, 1) * resizeFactor) } storage[pushNextIndex] = element pushNextIndex += 1 innerCount += 1 if pushNextIndex >= storage.count { pushNextIndex -= storage.count } } private mutating func dequeueElementOnly() -> T { precondition(count > 0) let index = dequeueIndex defer { storage[index] = nil innerCount -= 1 } return storage[index]! } /// Dequeues element or throws an exception in case queue is empty. /// /// - returns: Dequeued element. mutating func dequeue() -> T? { if self.count == 0 { return nil } defer { let downsizeLimit = storage.count / (resizeFactor * resizeFactor) if count < downsizeLimit && downsizeLimit >= initialCapacity { resizeTo(storage.count / resizeFactor) } } return dequeueElementOnly() } /// - returns: Generator of contained elements. func makeIterator() -> AnyIterator { var i = dequeueIndex var innerCount = count return AnyIterator { if innerCount == 0 { return nil } defer { innerCount -= 1 i += 1 } if i >= self.storage.count { i -= self.storage.count } return self.storage[i] } } } ================================================ FILE: JetChat/Pods/RxCocoa/Platform/DispatchQueue+Extensions.swift ================================================ // // DispatchQueue+Extensions.swift // Platform // // Created by Krunoslav Zaher on 10/22/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // import Dispatch extension DispatchQueue { private static var token: DispatchSpecificKey<()> = { let key = DispatchSpecificKey<()>() DispatchQueue.main.setSpecific(key: key, value: ()) return key }() static var isMain: Bool { DispatchQueue.getSpecific(key: token) != nil } } ================================================ FILE: JetChat/Pods/RxCocoa/Platform/Platform.Darwin.swift ================================================ // // Platform.Darwin.swift // Platform // // Created by Krunoslav Zaher on 12/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) import Darwin import Foundation extension Thread { static func setThreadLocalStorageValue(_ value: T?, forKey key: NSCopying) { let currentThread = Thread.current let threadDictionary = currentThread.threadDictionary if let newValue = value { threadDictionary[key] = newValue } else { threadDictionary[key] = nil } } static func getThreadLocalStorageValueForKey(_ key: NSCopying) -> T? { let currentThread = Thread.current let threadDictionary = currentThread.threadDictionary return threadDictionary[key] as? T } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/Platform/Platform.Linux.swift ================================================ // // Platform.Linux.swift // Platform // // Created by Krunoslav Zaher on 12/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(Linux) import Foundation extension Thread { static func setThreadLocalStorageValue(_ value: T?, forKey key: String) { if let newValue = value { Thread.current.threadDictionary[key] = newValue } else { Thread.current.threadDictionary[key] = nil } } static func getThreadLocalStorageValueForKey(_ key: String) -> T? { let currentThread = Thread.current let threadDictionary = currentThread.threadDictionary return threadDictionary[key] as? T } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/Platform/RecursiveLock.swift ================================================ // // RecursiveLock.swift // Platform // // Created by Krunoslav Zaher on 12/18/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // import Foundation #if TRACE_RESOURCES class RecursiveLock: NSRecursiveLock { override init() { _ = Resources.incrementTotal() super.init() } override func lock() { super.lock() _ = Resources.incrementTotal() } override func unlock() { super.unlock() _ = Resources.decrementTotal() } deinit { _ = Resources.decrementTotal() } } #else typealias RecursiveLock = NSRecursiveLock #endif ================================================ FILE: JetChat/Pods/RxCocoa/README.md ================================================

RxSwift Logo
Build Status Supported Platforms: iOS, macOS, tvOS, watchOS & Linux

Rx is a [generic abstraction of computation](https://youtu.be/looJcaeboBY) expressed through `Observable` interface, which lets you broadcast and subscribe to values and other events from an `Observable` stream. RxSwift is the Swift-specific implementation of the [Reactive Extensions](http://reactivex.io) standard.

RxSwift Observable Example of a price constantly changing and updating the app's UI

While this version aims to stay true to the original spirit and naming conventions of Rx, this projects also aims to provide a true Swift-first API for Rx APIs. Cross platform documentation can be found on [ReactiveX.io](http://reactivex.io/). Like other Rx implementation, RxSwift's intention is to enable easy composition of asynchronous operations and streams of data in the form of `Observable` objects and a suite of methods to transform and compose these pieces of asynchronous work. KVO observation, async operations, UI Events and other streams of data are all unified under [abstraction of sequence](Documentation/GettingStarted.md#observables-aka-sequences). This is the reason why Rx is so simple, elegant and powerful. ## I came here because I want to ... ###### ... understand * [why use rx?](Documentation/Why.md) * [the basics, getting started with RxSwift](Documentation/GettingStarted.md) * [traits](Documentation/Traits.md) - what are `Single`, `Completable`, `Maybe`, `Driver`, and `ControlProperty` ... and why do they exist? * [testing](Documentation/UnitTests.md) * [tips and common errors](Documentation/Tips.md) * [debugging](Documentation/GettingStarted.md#debugging) * [the math behind Rx](Documentation/MathBehindRx.md) * [what are hot and cold observable sequences?](Documentation/HotAndColdObservables.md) ###### ... install * Integrate RxSwift/RxCocoa with my app. [Installation Guide](#installation) ###### ... hack around * with the example app. [Running Example App](Documentation/ExampleApp.md) * with operators in playgrounds. [Playgrounds](Documentation/Playgrounds.md) ###### ... interact * All of this is great, but it would be nice to talk with other people using RxSwift and exchange experiences.
[Join Slack Channel](http://slack.rxswift.org) * Report a problem using the library. [Open an Issue With Bug Template](.github/ISSUE_TEMPLATE.md) * Request a new feature. [Open an Issue With Feature Request Template](Documentation/NewFeatureRequestTemplate.md) * Help out [Check out contribution guide](CONTRIBUTING.md) ###### ... compare * [with Combine and ReactiveSwift](Documentation/ComparisonWithOtherLibraries.md). ###### ... understand the structure RxSwift is as compositional as the asynchronous work it drives. The core unit is RxSwift itself, while other dependencies can be added for UI Work, testing, and more. It comprises five separate components depending on each other in the following way: ```none ┌──────────────┐ ┌──────────────┐ │ RxCocoa ├────▶ RxRelay │ └───────┬──────┘ └──────┬───────┘ │ │ ┌───────▼──────────────────▼───────┐ │ RxSwift │ └───────▲──────────────────▲───────┘ │ │ ┌───────┴──────┐ ┌──────┴───────┐ │ RxTest │ │ RxBlocking │ └──────────────┘ └──────────────┘ ``` * **RxSwift**: The core of RxSwift, providing the Rx standard as (mostly) defined by [ReactiveX](https://reactivex.io). It has no other dependencies. * **RxCocoa**: Provides Cocoa-specific capabilities for general iOS/macOS/watchOS & tvOS app development, such as Shared Sequences, Traits, and much more. It depends on both `RxSwift` and `RxRelay`. * **RxRelay**: Provides `PublishRelay`, `BehaviorRelay` and `ReplayRelay`, three [simple wrappers around Subjects](https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Subjects.md#relays). It depends on `RxSwift`. * **RxTest** and **RxBlocking**: Provides testing capabilities for Rx-based systems. It depends on `RxSwift`. ## Usage
Here's an example In Action
Define search for GitHub repositories ...
let searchResults = searchBar.rx.text.orEmpty
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest { query -> Observable<[Repository]> in
        if query.isEmpty {
            return .just([])
        }
        return searchGitHub(query)
            .catchAndReturn([])
    }
    .observe(on: MainScheduler.instance)
... then bind the results to your tableview
searchResults
    .bind(to: tableView.rx.items(cellIdentifier: "Cell")) {
        (index, repository: Repository, cell) in
        cell.textLabel?.text = repository.name
        cell.detailTextLabel?.text = repository.url
    }
    .disposed(by: disposeBag)
## Requirements * Xcode 12.x * Swift 5.x For Xcode 11 and below, [use RxSwift 5.x](https://github.com/ReactiveX/RxSwift/releases/tag/5.1.1). ## Installation RxSwift doesn't contain any external dependencies. These are currently the supported installation options: ### Manual Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run the sample app ### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html) ```ruby # Podfile use_frameworks! target 'YOUR_TARGET_NAME' do pod 'RxSwift', '6.5.0' pod 'RxCocoa', '6.5.0' end # RxTest and RxBlocking make the most sense in the context of unit/integration tests target 'YOUR_TESTING_TARGET' do pod 'RxBlocking', '6.5.0' pod 'RxTest', '6.5.0' end ``` Replace `YOUR_TARGET_NAME` and then, in the `Podfile` directory, type: ```bash $ pod install ``` ### XCFrameworks Each release starting with RxSwift 6 includes `*.xcframework` framework binaries. Simply drag the needed framework binaries to your **Frameworks, Libraries, and Embedded Content** section under your target's **General** tab. > **Note**: If you're using `RxCocoa`, be sure to also drag **RxCocoaRuntime.xcframework** before importing `RxCocoa`. XCFrameworks instructions ### [Carthage](https://github.com/Carthage/Carthage) Add this to `Cartfile` ``` github "ReactiveX/RxSwift" "6.5.0" ``` ```bash $ carthage update ``` #### Carthage as a Static Library Carthage defaults to building RxSwift as a Dynamic Library. If you wish to build RxSwift as a Static Library using Carthage you may use the script below to manually modify the framework type before building with Carthage: ```bash carthage update RxSwift --platform iOS --no-build sed -i -e 's/MACH_O_TYPE = mh_dylib/MACH_O_TYPE = staticlib/g' Carthage/Checkouts/RxSwift/Rx.xcodeproj/project.pbxproj carthage build RxSwift --platform iOS ``` ### [Swift Package Manager](https://github.com/apple/swift-package-manager) > **Note**: There is a critical cross-dependency bug affecting many projects including RxSwift in Swift Package Manager. We've [filed a bug (SR-12303)](https://bugs.swift.org/browse/SR-12303) in early 2020 but have no answer yet. Your mileage may vary. A partial workaround can be found [here](https://github.com/ReactiveX/RxSwift/issues/2127#issuecomment-717830502). Create a `Package.swift` file. ```swift // swift-tools-version:5.0 import PackageDescription let package = Package( name: "RxTestProject", dependencies: [ .package(url: "https://github.com/ReactiveX/RxSwift.git", .exact("6.5.0")) ], targets: [ .target(name: "RxTestProject", dependencies: ["RxSwift", "RxCocoa"]) ] ) ``` ```bash $ swift build ``` To build or test a module with RxTest dependency, set `TEST=1`. ```bash $ TEST=1 swift test ``` ### Manually using git submodules * Add RxSwift as a submodule ```bash $ git submodule add git@github.com:ReactiveX/RxSwift.git ``` * Drag `Rx.xcodeproj` into Project Navigator * Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxSwift`, `RxCocoa` and `RxRelay` targets ## References * [http://reactivex.io/](http://reactivex.io/) * [Reactive Extensions GitHub (GitHub)](https://github.com/Reactive-Extensions) * [RxSwift RayWenderlich.com Book](https://store.raywenderlich.com/products/rxswift-reactive-programming-with-swift) * [RxSwift: Debunking the myth of hard (YouTube)](https://www.youtube.com/watch?v=GdvLP0ZAhhc) * [Boxue.io RxSwift Online Course](https://boxueio.com/series/rxswift-101) (Chinese 🇨🇳) * [Expert to Expert: Brian Beckman and Erik Meijer - Inside the .NET Reactive Framework (Rx) (video)](https://youtu.be/looJcaeboBY) * [Reactive Programming Overview (Jafar Husain from Netflix)](https://youtu.be/-8Y1-lE6NSA) * [Subject/Observer is Dual to Iterator (paper)](http://csl.stanford.edu/~christos/pldi2010.fit/meijer.duality.pdf) * [Rx standard sequence operators visualized (visualization tool)](http://rxmarbles.com/) * [Haskell](https://www.haskell.org/) ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Common/ControlTarget.swift ================================================ // // ControlTarget.swift // RxCocoa // // Created by Krunoslav Zaher on 2/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) || os(macOS) import RxSwift #if os(iOS) || os(tvOS) import UIKit typealias Control = UIKit.UIControl #elseif os(macOS) import Cocoa typealias Control = Cocoa.NSControl #endif // This should be only used from `MainScheduler` final class ControlTarget: RxTarget { typealias Callback = (Control) -> Void let selector: Selector = #selector(ControlTarget.eventHandler(_:)) weak var control: Control? #if os(iOS) || os(tvOS) let controlEvents: UIControl.Event #endif var callback: Callback? #if os(iOS) || os(tvOS) init(control: Control, controlEvents: UIControl.Event, callback: @escaping Callback) { MainScheduler.ensureRunningOnMainThread() self.control = control self.controlEvents = controlEvents self.callback = callback super.init() control.addTarget(self, action: selector, for: controlEvents) let method = self.method(for: selector) if method == nil { rxFatalError("Can't find method") } } #elseif os(macOS) init(control: Control, callback: @escaping Callback) { MainScheduler.ensureRunningOnMainThread() self.control = control self.callback = callback super.init() control.target = self control.action = self.selector let method = self.method(for: self.selector) if method == nil { rxFatalError("Can't find method") } } #endif @objc func eventHandler(_ sender: Control!) { if let callback = self.callback, let control = self.control { callback(control) } } override func dispose() { super.dispose() #if os(iOS) || os(tvOS) self.control?.removeTarget(self, action: self.selector, for: self.controlEvents) #elseif os(macOS) self.control?.target = nil self.control?.action = nil #endif self.callback = nil } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Common/DelegateProxy.swift ================================================ // // DelegateProxy.swift // RxCocoa // // Created by Krunoslav Zaher on 6/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if !os(Linux) import RxSwift #if SWIFT_PACKAGE && !os(Linux) import RxCocoaRuntime #endif /// Base class for `DelegateProxyType` protocol. /// /// This implementation is not thread safe and can be used only from one thread (Main thread). open class DelegateProxy: _RXDelegateProxy { public typealias ParentObject = P public typealias Delegate = D private var _sentMessageForSelector = [Selector: MessageDispatcher]() private var _methodInvokedForSelector = [Selector: MessageDispatcher]() /// Parent object associated with delegate proxy. private weak var _parentObject: ParentObject? private let _currentDelegateFor: (ParentObject) -> AnyObject? private let _setCurrentDelegateTo: (AnyObject?, ParentObject) -> Void /// Initializes new instance. /// /// - parameter parentObject: Optional parent object that owns `DelegateProxy` as associated object. public init(parentObject: ParentObject, delegateProxy: Proxy.Type) where Proxy: DelegateProxy, Proxy.ParentObject == ParentObject, Proxy.Delegate == Delegate { self._parentObject = parentObject self._currentDelegateFor = delegateProxy._currentDelegate self._setCurrentDelegateTo = delegateProxy._setCurrentDelegate MainScheduler.ensureRunningOnMainThread() #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif super.init() } /** Returns observable sequence of invocations of delegate methods. Elements are sent *before method is invoked*. Only methods that have `void` return value can be observed using this method because those methods are used as a notification mechanism. It doesn't matter if they are optional or not. Observing is performed by installing a hidden associated `PublishSubject` that is used to dispatch messages to observers. Delegate methods that have non `void` return value can't be observed directly using this method because: * those methods are not intended to be used as a notification mechanism, but as a behavior customization mechanism * there is no sensible automatic way to determine a default return value In case observing of delegate methods that have return type is required, it can be done by manually installing a `PublishSubject` or `BehaviorSubject` and implementing delegate method. e.g. // delegate proxy part (RxScrollViewDelegateProxy) let internalSubject = PublishSubject public func requiredDelegateMethod(scrollView: UIScrollView, arg1: CGPoint) -> Bool { internalSubject.on(.next(arg1)) return self._forwardToDelegate?.requiredDelegateMethod?(scrollView, arg1: arg1) ?? defaultReturnValue } .... // reactive property implementation in a real class (`UIScrollView`) public var property: Observable { let proxy = RxScrollViewDelegateProxy.proxy(for: base) return proxy.internalSubject.asObservable() } **In case calling this method prints "Delegate proxy is already implementing `\(selector)`, a more performant way of registering might exist.", that means that manual observing method is required analog to the example above because delegate method has already been implemented.** - parameter selector: Selector used to filter observed invocations of delegate methods. - returns: Observable sequence of arguments passed to `selector` method. */ open func sentMessage(_ selector: Selector) -> Observable<[Any]> { MainScheduler.ensureRunningOnMainThread() let subject = self._sentMessageForSelector[selector] if let subject = subject { return subject.asObservable() } else { let subject = MessageDispatcher(selector: selector, delegateProxy: self) self._sentMessageForSelector[selector] = subject return subject.asObservable() } } /** Returns observable sequence of invoked delegate methods. Elements are sent *after method is invoked*. Only methods that have `void` return value can be observed using this method because those methods are used as a notification mechanism. It doesn't matter if they are optional or not. Observing is performed by installing a hidden associated `PublishSubject` that is used to dispatch messages to observers. Delegate methods that have non `void` return value can't be observed directly using this method because: * those methods are not intended to be used as a notification mechanism, but as a behavior customization mechanism * there is no sensible automatic way to determine a default return value In case observing of delegate methods that have return type is required, it can be done by manually installing a `PublishSubject` or `BehaviorSubject` and implementing delegate method. e.g. // delegate proxy part (RxScrollViewDelegateProxy) let internalSubject = PublishSubject public func requiredDelegateMethod(scrollView: UIScrollView, arg1: CGPoint) -> Bool { internalSubject.on(.next(arg1)) return self._forwardToDelegate?.requiredDelegateMethod?(scrollView, arg1: arg1) ?? defaultReturnValue } .... // reactive property implementation in a real class (`UIScrollView`) public var property: Observable { let proxy = RxScrollViewDelegateProxy.proxy(for: base) return proxy.internalSubject.asObservable() } **In case calling this method prints "Delegate proxy is already implementing `\(selector)`, a more performant way of registering might exist.", that means that manual observing method is required analog to the example above because delegate method has already been implemented.** - parameter selector: Selector used to filter observed invocations of delegate methods. - returns: Observable sequence of arguments passed to `selector` method. */ open func methodInvoked(_ selector: Selector) -> Observable<[Any]> { MainScheduler.ensureRunningOnMainThread() let subject = self._methodInvokedForSelector[selector] if let subject = subject { return subject.asObservable() } else { let subject = MessageDispatcher(selector: selector, delegateProxy: self) self._methodInvokedForSelector[selector] = subject return subject.asObservable() } } fileprivate func checkSelectorIsObservable(_ selector: Selector) { MainScheduler.ensureRunningOnMainThread() if self.hasWiredImplementation(for: selector) { print("⚠️ Delegate proxy is already implementing `\(selector)`, a more performant way of registering might exist.") return } if self.voidDelegateMethodsContain(selector) { return } // In case `_forwardToDelegate` is `nil`, it is assumed the check is being done prematurely. if !(self._forwardToDelegate?.responds(to: selector) ?? true) { print("⚠️ Using delegate proxy dynamic interception method but the target delegate object doesn't respond to the requested selector. " + "In case pure Swift delegate proxy is being used please use manual observing method by using`PublishSubject`s. " + " (selector: `\(selector)`, forwardToDelegate: `\(self._forwardToDelegate ?? self)`)") } } // proxy open override func _sentMessage(_ selector: Selector, withArguments arguments: [Any]) { self._sentMessageForSelector[selector]?.on(.next(arguments)) } open override func _methodInvoked(_ selector: Selector, withArguments arguments: [Any]) { self._methodInvokedForSelector[selector]?.on(.next(arguments)) } /// Returns reference of normal delegate that receives all forwarded messages /// through `self`. /// /// - returns: Value of reference if set or nil. open func forwardToDelegate() -> Delegate? { return castOptionalOrFatalError(self._forwardToDelegate) } /// Sets reference of normal delegate that receives all forwarded messages /// through `self`. /// /// - parameter delegate: Reference of delegate that receives all messages through `self`. /// - parameter retainDelegate: Should `self` retain `forwardToDelegate`. open func setForwardToDelegate(_ delegate: Delegate?, retainDelegate: Bool) { #if DEBUG // 4.0 all configurations MainScheduler.ensureRunningOnMainThread() #endif self._setForwardToDelegate(delegate, retainDelegate: retainDelegate) let sentSelectors: [Selector] = self._sentMessageForSelector.values.filter { $0.hasObservers }.map { $0.selector } let invokedSelectors: [Selector] = self._methodInvokedForSelector.values.filter { $0.hasObservers }.map { $0.selector } let allUsedSelectors = sentSelectors + invokedSelectors for selector in Set(allUsedSelectors) { self.checkSelectorIsObservable(selector) } self.reset() } private func hasObservers(selector: Selector) -> Bool { return (self._sentMessageForSelector[selector]?.hasObservers ?? false) || (self._methodInvokedForSelector[selector]?.hasObservers ?? false) } override open func responds(to aSelector: Selector!) -> Bool { guard let aSelector = aSelector else { return false } return super.responds(to: aSelector) || (self._forwardToDelegate?.responds(to: aSelector) ?? false) || (self.voidDelegateMethodsContain(aSelector) && self.hasObservers(selector: aSelector)) } fileprivate func reset() { guard let parentObject = self._parentObject else { return } let maybeCurrentDelegate = self._currentDelegateFor(parentObject) if maybeCurrentDelegate === self { self._setCurrentDelegateTo(nil, parentObject) self._setCurrentDelegateTo(castOrFatalError(self), parentObject) } } deinit { for v in self._sentMessageForSelector.values { v.on(.completed) } for v in self._methodInvokedForSelector.values { v.on(.completed) } #if TRACE_RESOURCES _ = Resources.decrementTotal() #endif } } private let mainScheduler = MainScheduler() private final class MessageDispatcher { private let dispatcher: PublishSubject<[Any]> private let result: Observable<[Any]> fileprivate let selector: Selector init(selector: Selector, delegateProxy _delegateProxy: DelegateProxy) { weak var weakDelegateProxy = _delegateProxy let dispatcher = PublishSubject<[Any]>() self.dispatcher = dispatcher self.selector = selector self.result = dispatcher .do(onSubscribed: { weakDelegateProxy?.checkSelectorIsObservable(selector); weakDelegateProxy?.reset() }, onDispose: { weakDelegateProxy?.reset() }) .share() .subscribe(on: mainScheduler) } var on: (Event<[Any]>) -> Void { return self.dispatcher.on } var hasObservers: Bool { return self.dispatcher.hasObservers } func asObservable() -> Observable<[Any]> { return self.result } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Common/DelegateProxyType.swift ================================================ // // DelegateProxyType.swift // RxCocoa // // Created by Krunoslav Zaher on 6/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if !os(Linux) import Foundation import RxSwift /** `DelegateProxyType` protocol enables using both normal delegates and Rx observable sequences with views that can have only one delegate/datasource registered. `Proxies` store information about observers, subscriptions and delegates for specific views. Type implementing `DelegateProxyType` should never be initialized directly. To fetch initialized instance of type implementing `DelegateProxyType`, `proxy` method should be used. This is more or less how it works. +-------------------------------------------+ | | | UIView subclass (UIScrollView) | | | +-----------+-------------------------------+ | | Delegate | | +-----------v-------------------------------+ | | | Delegate proxy : DelegateProxyType +-----+----> Observable | , UIScrollViewDelegate | | +-----------+-------------------------------+ +----> Observable | | | +----> Observable | | | forwards events | | to custom delegate | | v +-----------v-------------------------------+ | | | Custom delegate (UIScrollViewDelegate) | | | +-------------------------------------------+ Since RxCocoa needs to automagically create those Proxies and because views that have delegates can be hierarchical UITableView : UIScrollView : UIView .. and corresponding delegates are also hierarchical UITableViewDelegate : UIScrollViewDelegate : NSObject ... this mechanism can be extended by using the following snippet in `registerKnownImplementations` or in some other part of your app that executes before using `rx.*` (e.g. appDidFinishLaunching). RxScrollViewDelegateProxy.register { RxTableViewDelegateProxy(parentObject: $0) } */ public protocol DelegateProxyType: AnyObject { associatedtype ParentObject: AnyObject associatedtype Delegate /// It is require that enumerate call `register` of the extended DelegateProxy subclasses here. static func registerKnownImplementations() /// Unique identifier for delegate static var identifier: UnsafeRawPointer { get } /// Returns designated delegate property for object. /// /// Objects can have multiple delegate properties. /// /// Each delegate property needs to have it's own type implementing `DelegateProxyType`. /// /// It's abstract method. /// /// - parameter object: Object that has delegate property. /// - returns: Value of delegate property. static func currentDelegate(for object: ParentObject) -> Delegate? /// Sets designated delegate property for object. /// /// Objects can have multiple delegate properties. /// /// Each delegate property needs to have it's own type implementing `DelegateProxyType`. /// /// It's abstract method. /// /// - parameter delegate: Delegate value. /// - parameter object: Object that has delegate property. static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) /// Returns reference of normal delegate that receives all forwarded messages /// through `self`. /// /// - returns: Value of reference if set or nil. func forwardToDelegate() -> Delegate? /// Sets reference of normal delegate that receives all forwarded messages /// through `self`. /// /// - parameter forwardToDelegate: Reference of delegate that receives all messages through `self`. /// - parameter retainDelegate: Should `self` retain `forwardToDelegate`. func setForwardToDelegate(_ forwardToDelegate: Delegate?, retainDelegate: Bool) } // default implementations extension DelegateProxyType { /// Unique identifier for delegate public static var identifier: UnsafeRawPointer { let delegateIdentifier = ObjectIdentifier(Delegate.self) let integerIdentifier = Int(bitPattern: delegateIdentifier) return UnsafeRawPointer(bitPattern: integerIdentifier)! } } // workaround of Delegate: class extension DelegateProxyType { static func _currentDelegate(for object: ParentObject) -> AnyObject? { currentDelegate(for: object).map { $0 as AnyObject } } static func _setCurrentDelegate(_ delegate: AnyObject?, to object: ParentObject) { setCurrentDelegate(castOptionalOrFatalError(delegate), to: object) } func _forwardToDelegate() -> AnyObject? { self.forwardToDelegate().map { $0 as AnyObject } } func _setForwardToDelegate(_ forwardToDelegate: AnyObject?, retainDelegate: Bool) { self.setForwardToDelegate(castOptionalOrFatalError(forwardToDelegate), retainDelegate: retainDelegate) } } extension DelegateProxyType { /// Store DelegateProxy subclass to factory. /// When make 'Rx*DelegateProxy' subclass, call 'Rx*DelegateProxySubclass.register(for:_)' 1 time, or use it in DelegateProxyFactory /// 'Rx*DelegateProxy' can have one subclass implementation per concrete ParentObject type. /// Should call it from concrete DelegateProxy type, not generic. public static func register(make: @escaping (Parent) -> Self) { self.factory.extend(make: make) } /// Creates new proxy for target object. /// Should not call this function directory, use 'DelegateProxy.proxy(for:)' public static func createProxy(for object: AnyObject) -> Self { castOrFatalError(factory.createProxy(for: object)) } /// Returns existing proxy for object or installs new instance of delegate proxy. /// /// - parameter object: Target object on which to install delegate proxy. /// - returns: Installed instance of delegate proxy. /// /// /// extension Reactive where Base: UISearchBar { /// /// public var delegate: DelegateProxy { /// return RxSearchBarDelegateProxy.proxy(for: base) /// } /// /// public var text: ControlProperty { /// let source: Observable = self.delegate.observe(#selector(UISearchBarDelegate.searchBar(_:textDidChange:))) /// ... /// } /// } public static func proxy(for object: ParentObject) -> Self { MainScheduler.ensureRunningOnMainThread() let maybeProxy = self.assignedProxy(for: object) let proxy: AnyObject if let existingProxy = maybeProxy { proxy = existingProxy } else { proxy = castOrFatalError(self.createProxy(for: object)) self.assignProxy(proxy, toObject: object) assert(self.assignedProxy(for: object) === proxy) } let currentDelegate = self._currentDelegate(for: object) let delegateProxy: Self = castOrFatalError(proxy) if currentDelegate !== delegateProxy { delegateProxy._setForwardToDelegate(currentDelegate, retainDelegate: false) assert(delegateProxy._forwardToDelegate() === currentDelegate) self._setCurrentDelegate(proxy, to: object) assert(self._currentDelegate(for: object) === proxy) assert(delegateProxy._forwardToDelegate() === currentDelegate) } return delegateProxy } /// Sets forward delegate for `DelegateProxyType` associated with a specific object and return disposable that can be used to unset the forward to delegate. /// Using this method will also make sure that potential original object cached selectors are cleared and will report any accidental forward delegate mutations. /// /// - parameter forwardDelegate: Delegate object to set. /// - parameter retainDelegate: Retain `forwardDelegate` while it's being set. /// - parameter onProxyForObject: Object that has `delegate` property. /// - returns: Disposable object that can be used to clear forward delegate. public static func installForwardDelegate(_ forwardDelegate: Delegate, retainDelegate: Bool, onProxyForObject object: ParentObject) -> Disposable { weak var weakForwardDelegate: AnyObject? = forwardDelegate as AnyObject let proxy = self.proxy(for: object) assert(proxy._forwardToDelegate() === nil, "This is a feature to warn you that there is already a delegate (or data source) set somewhere previously. The action you are trying to perform will clear that delegate (data source) and that means that some of your features that depend on that delegate (data source) being set will likely stop working.\n" + "If you are ok with this, try to set delegate (data source) to `nil` in front of this operation.\n" + " This is the source object value: \(object)\n" + " This is the original delegate (data source) value: \(proxy.forwardToDelegate()!)\n" + "Hint: Maybe delegate was already set in xib or storyboard and now it's being overwritten in code.\n") proxy.setForwardToDelegate(forwardDelegate, retainDelegate: retainDelegate) return Disposables.create { MainScheduler.ensureRunningOnMainThread() let delegate: AnyObject? = weakForwardDelegate assert(delegate == nil || proxy._forwardToDelegate() === delegate, "Delegate was changed from time it was first set. Current \(String(describing: proxy.forwardToDelegate())), and it should have been \(proxy)") proxy.setForwardToDelegate(nil, retainDelegate: retainDelegate) } } } // private extensions extension DelegateProxyType { private static var factory: DelegateProxyFactory { DelegateProxyFactory.sharedFactory(for: self) } private static func assignedProxy(for object: ParentObject) -> AnyObject? { let maybeDelegate = objc_getAssociatedObject(object, self.identifier) return castOptionalOrFatalError(maybeDelegate) } private static func assignProxy(_ proxy: AnyObject, toObject object: ParentObject) { objc_setAssociatedObject(object, self.identifier, proxy, .OBJC_ASSOCIATION_RETAIN) } } /// Describes an object that has a delegate. public protocol HasDelegate: AnyObject { /// Delegate type associatedtype Delegate /// Delegate var delegate: Delegate? { get set } } extension DelegateProxyType where ParentObject: HasDelegate, Self.Delegate == ParentObject.Delegate { public static func currentDelegate(for object: ParentObject) -> Delegate? { object.delegate } public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) { object.delegate = delegate } } /// Describes an object that has a data source. public protocol HasDataSource: AnyObject { /// Data source type associatedtype DataSource /// Data source var dataSource: DataSource? { get set } } extension DelegateProxyType where ParentObject: HasDataSource, Self.Delegate == ParentObject.DataSource { public static func currentDelegate(for object: ParentObject) -> Delegate? { object.dataSource } public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) { object.dataSource = delegate } } /// Describes an object that has a prefetch data source. @available(iOS 10.0, tvOS 10.0, *) public protocol HasPrefetchDataSource: AnyObject { /// Prefetch data source type associatedtype PrefetchDataSource /// Prefetch data source var prefetchDataSource: PrefetchDataSource? { get set } } @available(iOS 10.0, tvOS 10.0, *) extension DelegateProxyType where ParentObject: HasPrefetchDataSource, Self.Delegate == ParentObject.PrefetchDataSource { public static func currentDelegate(for object: ParentObject) -> Delegate? { object.prefetchDataSource } public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) { object.prefetchDataSource = delegate } } #if os(iOS) || os(tvOS) import UIKit extension ObservableType { func subscribeProxyDataSource(ofObject object: DelegateProxy.ParentObject, dataSource: DelegateProxy.Delegate, retainDataSource: Bool, binding: @escaping (DelegateProxy, Event) -> Void) -> Disposable where DelegateProxy.ParentObject: UIView , DelegateProxy.Delegate: AnyObject { let proxy = DelegateProxy.proxy(for: object) let unregisterDelegate = DelegateProxy.installForwardDelegate(dataSource, retainDelegate: retainDataSource, onProxyForObject: object) // Do not perform layoutIfNeeded if the object is still not in the view hierarchy if object.window != nil { // this is needed to flush any delayed old state (https://github.com/RxSwiftCommunity/RxDataSources/pull/75) object.layoutIfNeeded() } let subscription = self.asObservable() .observe(on:MainScheduler()) .catch { error in bindingError(error) return Observable.empty() } // source can never end, otherwise it would release the subscriber, and deallocate the data source .concat(Observable.never()) .take(until: object.rx.deallocated) .subscribe { [weak object] (event: Event) in if let object = object { assert(proxy === DelegateProxy.currentDelegate(for: object), "Proxy changed from the time it was first set.\nOriginal: \(proxy)\nExisting: \(String(describing: DelegateProxy.currentDelegate(for: object)))") } binding(proxy, event) switch event { case .error(let error): bindingError(error) unregisterDelegate.dispose() case .completed: unregisterDelegate.dispose() default: break } } return Disposables.create { [weak object] in subscription.dispose() if object?.window != nil { object?.layoutIfNeeded() } unregisterDelegate.dispose() } } } #endif /** To add delegate proxy subclasses call `DelegateProxySubclass.register()` in `registerKnownImplementations` or in some other part of your app that executes before using `rx.*` (e.g. appDidFinishLaunching). class RxScrollViewDelegateProxy: DelegateProxy { public static func registerKnownImplementations() { self.register { RxTableViewDelegateProxy(parentObject: $0) } } ... */ private class DelegateProxyFactory { private static var _sharedFactories: [UnsafeRawPointer: DelegateProxyFactory] = [:] fileprivate static func sharedFactory(for proxyType: DelegateProxy.Type) -> DelegateProxyFactory { MainScheduler.ensureRunningOnMainThread() let identifier = DelegateProxy.identifier if let factory = _sharedFactories[identifier] { return factory } let factory = DelegateProxyFactory(for: proxyType) _sharedFactories[identifier] = factory DelegateProxy.registerKnownImplementations() return factory } private var _factories: [ObjectIdentifier: ((AnyObject) -> AnyObject)] private var _delegateProxyType: Any.Type private var _identifier: UnsafeRawPointer private init(for proxyType: DelegateProxy.Type) { self._factories = [:] self._delegateProxyType = proxyType self._identifier = proxyType.identifier } fileprivate func extend(make: @escaping (ParentObject) -> DelegateProxy) { MainScheduler.ensureRunningOnMainThread() precondition(self._identifier == DelegateProxy.identifier, "Delegate proxy has inconsistent identifier") guard self._factories[ObjectIdentifier(ParentObject.self)] == nil else { rxFatalError("The factory of \(ParentObject.self) is duplicated. DelegateProxy is not allowed of duplicated base object type.") } self._factories[ObjectIdentifier(ParentObject.self)] = { make(castOrFatalError($0)) } } fileprivate func createProxy(for object: AnyObject) -> AnyObject { MainScheduler.ensureRunningOnMainThread() var maybeMirror: Mirror? = Mirror(reflecting: object) while let mirror = maybeMirror { if let factory = self._factories[ObjectIdentifier(mirror.subjectType)] { return factory(object) } maybeMirror = mirror.superclassMirror } rxFatalError("DelegateProxy has no factory of \(object). Implement DelegateProxy subclass for \(object) first.") } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Common/Infallible+Bind.swift ================================================ // // Infallible+Bind.swift // RxCocoa // // Created by Shai Mishali on 27/08/2020. // Copyright © 2020 Krunoslav Zaher. All rights reserved. // import RxSwift extension InfallibleType { /** Creates new subscription and sends elements to observer(s). In this form, it's equivalent to the `subscribe` method, but it better conveys intent, and enables writing more consistent binding code. - parameter observers: Observers to receives events. - returns: Disposable object that can be used to unsubscribe the observers. */ public func bind(to observers: Observer...) -> Disposable where Observer.Element == Element { self.subscribe { event in observers.forEach { $0.on(event) } } } /** Creates new subscription and sends elements to observer(s). In this form, it's equivalent to the `subscribe` method, but it better conveys intent, and enables writing more consistent binding code. - parameter observers: Observers to receives events. - returns: Disposable object that can be used to unsubscribe the observers. */ public func bind(to observers: Observer...) -> Disposable where Observer.Element == Element? { self.map { $0 as Element? } .subscribe { event in observers.forEach { $0.on(event) } } } /** Subscribes to observable sequence using custom binder function. - parameter binder: Function used to bind elements from `self`. - returns: Object representing subscription. */ public func bind(to binder: (Self) -> Result) -> Result { binder(self) } /** Subscribes to observable sequence using custom binder function and final parameter passed to binder function after `self` is passed. public func bind(to binder: Self -> R1 -> R2, curriedArgument: R1) -> R2 { return binder(self)(curriedArgument) } - parameter binder: Function used to bind elements from `self`. - parameter curriedArgument: Final argument passed to `binder` to finish binding process. - returns: Object representing subscription. */ public func bind(to binder: (Self) -> (R1) -> R2, curriedArgument: R1) -> R2 { binder(self)(curriedArgument) } /** Subscribes an element handler to an observable sequence. In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter onNext: Action to invoke for each element in the observable sequence. - returns: Subscription object used to unsubscribe from the observable sequence. */ public func bind(onNext: @escaping (Element) -> Void) -> Disposable { self.subscribe(onNext: onNext) } /** Creates new subscription and sends elements to `BehaviorRelay`. - parameter relays: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func bind(to relays: BehaviorRelay...) -> Disposable { return self.subscribe(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `BehaviorRelay`. - parameter relays: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func bind(to relays: BehaviorRelay...) -> Disposable { return self.subscribe(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `PublishRelay`. - parameter relays: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func bind(to relays: PublishRelay...) -> Disposable { return self.subscribe(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `PublishRelay`. - parameter relays: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func bind(to relays: PublishRelay...) -> Disposable { return self.subscribe(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `ReplayRelay`. - parameter relays: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func bind(to relays: ReplayRelay...) -> Disposable { return self.subscribe(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `ReplayRelay`. - parameter relays: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func bind(to relays: ReplayRelay...) -> Disposable { return self.subscribe(onNext: { e in relays.forEach { $0.accept(e) } }) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Common/Observable+Bind.swift ================================================ // // Observable+Bind.swift // RxCocoa // // Created by Krunoslav Zaher on 8/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift extension ObservableType { /** Creates new subscription and sends elements to observer(s). In this form, it's equivalent to the `subscribe` method, but it better conveys intent, and enables writing more consistent binding code. - parameter observers: Observers to receives events. - returns: Disposable object that can be used to unsubscribe the observers. */ public func bind(to observers: Observer...) -> Disposable where Observer.Element == Element { self.subscribe { event in observers.forEach { $0.on(event) } } } /** Creates new subscription and sends elements to observer(s). In this form, it's equivalent to the `subscribe` method, but it better conveys intent, and enables writing more consistent binding code. - parameter observers: Observers to receives events. - returns: Disposable object that can be used to unsubscribe the observers. */ public func bind(to observers: Observer...) -> Disposable where Observer.Element == Element? { self.map { $0 as Element? } .subscribe { event in observers.forEach { $0.on(event) } } } /** Subscribes to observable sequence using custom binder function. - parameter binder: Function used to bind elements from `self`. - returns: Object representing subscription. */ public func bind(to binder: (Self) -> Result) -> Result { binder(self) } /** Subscribes to observable sequence using custom binder function and final parameter passed to binder function after `self` is passed. public func bind(to binder: Self -> R1 -> R2, curriedArgument: R1) -> R2 { return binder(self)(curriedArgument) } - parameter binder: Function used to bind elements from `self`. - parameter curriedArgument: Final argument passed to `binder` to finish binding process. - returns: Object representing subscription. */ public func bind(to binder: (Self) -> (R1) -> R2, curriedArgument: R1) -> R2 { binder(self)(curriedArgument) } /** Subscribes an element handler to an observable sequence. In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - Note: If `object` can't be retained, none of the other closures will be invoked. - parameter object: The object to provide an unretained reference on. - parameter onNext: Action to invoke for each element in the observable sequence. - returns: Subscription object used to unsubscribe from the observable sequence. */ public func bind( with object: Object, onNext: @escaping (Object, Element) -> Void ) -> Disposable { self.subscribe(onNext: { [weak object] in guard let object = object else { return } onNext(object, $0) }, onError: { error in rxFatalErrorInDebug("Binding error: \(error)") }) } /** Subscribes an element handler to an observable sequence. In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter onNext: Action to invoke for each element in the observable sequence. - returns: Subscription object used to unsubscribe from the observable sequence. */ public func bind(onNext: @escaping (Element) -> Void) -> Disposable { self.subscribe(onNext: onNext, onError: { error in rxFatalErrorInDebug("Binding error: \(error)") }) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Common/RxCocoaObjCRuntimeError+Extensions.swift ================================================ // // RxCocoaObjCRuntimeError+Extensions.swift // RxCocoa // // Created by Krunoslav Zaher on 10/9/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if SWIFT_PACKAGE && !DISABLE_SWIZZLING && !os(Linux) import RxCocoaRuntime #endif #if !DISABLE_SWIZZLING && !os(Linux) /// RxCocoa ObjC runtime interception mechanism. public enum RxCocoaInterceptionMechanism { /// Unknown message interception mechanism. case unknown /// Key value observing interception mechanism. case kvo } /// RxCocoa ObjC runtime modification errors. public enum RxCocoaObjCRuntimeError : Swift.Error , CustomDebugStringConvertible { /// Unknown error has occurred. case unknown(target: AnyObject) /** If the object is reporting a different class then it's real class, that means that there is probably already some interception mechanism in place or something weird is happening. The most common case when this would happen is when using a combination of KVO (`observe`) and `sentMessage`. This error is easily resolved by just using `sentMessage` observing before `observe`. The reason why the other way around could create issues is because KVO will unregister it's interceptor class and restore original class. Unfortunately that will happen no matter was there another interceptor subclass registered in hierarchy or not. Failure scenario: * KVO sets class to be `__KVO__OriginalClass` (subclass of `OriginalClass`) * `sentMessage` sets object class to be `_RX_namespace___KVO__OriginalClass` (subclass of `__KVO__OriginalClass`) * then unobserving with KVO will restore class to be `OriginalClass` -> failure point (possibly a bug in KVO) The reason why changing order of observing works is because any interception method on unregistration should return object's original real class (if that doesn't happen then it's really easy to argue that's a bug in that interception mechanism). This library won't remove registered interceptor even if there aren't any observers left because it's highly unlikely it would have any benefit in real world use cases, and it's even more dangerous. */ case objectMessagesAlreadyBeingIntercepted(target: AnyObject, interceptionMechanism: RxCocoaInterceptionMechanism) /// Trying to observe messages for selector that isn't implemented. case selectorNotImplemented(target: AnyObject) /// Core Foundation classes are usually toll free bridged. Those classes crash the program in case /// `object_setClass` is performed on them. /// /// There is a possibility to just swizzle methods on original object, but since those won't be usual use /// cases for this library, then an error will just be reported for now. case cantInterceptCoreFoundationTollFreeBridgedObjects(target: AnyObject) /// Two libraries have simultaneously tried to modify ObjC runtime and that was detected. This can only /// happen in scenarios where multiple interception libraries are used. /// /// To synchronize other libraries intercepting messages for an object, use `synchronized` on target object and /// it's meta-class. case threadingCollisionWithOtherInterceptionMechanism(target: AnyObject) /// For some reason saving original method implementation under RX namespace failed. case savingOriginalForwardingMethodFailed(target: AnyObject) /// Intercepting a sent message by replacing a method implementation with `_objc_msgForward` failed for some reason. case replacingMethodWithForwardingImplementation(target: AnyObject) /// Attempt to intercept one of the performance sensitive methods: /// * class /// * respondsToSelector: /// * methodSignatureForSelector: /// * forwardingTargetForSelector: case observingPerformanceSensitiveMessages(target: AnyObject) /// Message implementation has unsupported return type (for example large struct). The reason why this is a error /// is because in some cases intercepting sent messages requires replacing implementation with `_objc_msgForward_stret` /// instead of `_objc_msgForward`. /// /// The unsupported cases should be fairly uncommon. case observingMessagesWithUnsupportedReturnType(target: AnyObject) } extension RxCocoaObjCRuntimeError { /// A textual representation of `self`, suitable for debugging. public var debugDescription: String { switch self { case let .unknown(target): return "Unknown error occurred.\nTarget: `\(target)`" case let .objectMessagesAlreadyBeingIntercepted(target, interceptionMechanism): let interceptionMechanismDescription = interceptionMechanism == .kvo ? "KVO" : "other interception mechanism" return "Collision between RxCocoa interception mechanism and \(interceptionMechanismDescription)." + " To resolve this conflict please use this interception mechanism first.\nTarget: \(target)" case let .selectorNotImplemented(target): return "Trying to observe messages for selector that isn't implemented.\nTarget: \(target)" case let .cantInterceptCoreFoundationTollFreeBridgedObjects(target): return "Interception of messages sent to Core Foundation isn't supported.\nTarget: \(target)" case let .threadingCollisionWithOtherInterceptionMechanism(target): return "Detected a conflict while modifying ObjC runtime.\nTarget: \(target)" case let .savingOriginalForwardingMethodFailed(target): return "Saving original method implementation failed.\nTarget: \(target)" case let .replacingMethodWithForwardingImplementation(target): return "Intercepting a sent message by replacing a method implementation with `_objc_msgForward` failed for some reason.\nTarget: \(target)" case let .observingPerformanceSensitiveMessages(target): return "Attempt to intercept one of the performance sensitive methods. \nTarget: \(target)" case let .observingMessagesWithUnsupportedReturnType(target): return "Attempt to intercept a method with unsupported return type. \nTarget: \(target)" } } } // MARK: Conversions `NSError` > `RxCocoaObjCRuntimeError` extension Error { func rxCocoaErrorForTarget(_ target: AnyObject) -> RxCocoaObjCRuntimeError { let error = self as NSError if error.domain == RXObjCRuntimeErrorDomain { let errorCode = RXObjCRuntimeError(rawValue: error.code) ?? .unknown switch errorCode { case .unknown: return .unknown(target: target) case .objectMessagesAlreadyBeingIntercepted: let isKVO = (error.userInfo[RXObjCRuntimeErrorIsKVOKey] as? NSNumber)?.boolValue ?? false return .objectMessagesAlreadyBeingIntercepted(target: target, interceptionMechanism: isKVO ? .kvo : .unknown) case .selectorNotImplemented: return .selectorNotImplemented(target: target) case .cantInterceptCoreFoundationTollFreeBridgedObjects: return .cantInterceptCoreFoundationTollFreeBridgedObjects(target: target) case .threadingCollisionWithOtherInterceptionMechanism: return .threadingCollisionWithOtherInterceptionMechanism(target: target) case .savingOriginalForwardingMethodFailed: return .savingOriginalForwardingMethodFailed(target: target) case .replacingMethodWithForwardingImplementation: return .replacingMethodWithForwardingImplementation(target: target) case .observingPerformanceSensitiveMessages: return .observingPerformanceSensitiveMessages(target: target) case .observingMessagesWithUnsupportedReturnType: return .observingMessagesWithUnsupportedReturnType(target: target) @unknown default: fatalError("Unhandled Objective C Runtime Error") } } return RxCocoaObjCRuntimeError.unknown(target: target) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Common/RxTarget.swift ================================================ // // RxTarget.swift // RxCocoa // // Created by Krunoslav Zaher on 7/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Foundation import RxSwift class RxTarget : NSObject , Disposable { private var retainSelf: RxTarget? override init() { super.init() self.retainSelf = self #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif #if DEBUG MainScheduler.ensureRunningOnMainThread() #endif } func dispose() { #if DEBUG MainScheduler.ensureRunningOnMainThread() #endif self.retainSelf = nil } #if TRACE_RESOURCES deinit { _ = Resources.decrementTotal() } #endif } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Common/SectionedViewDataSourceType.swift ================================================ // // SectionedViewDataSourceType.swift // RxCocoa // // Created by Krunoslav Zaher on 1/10/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // import Foundation /// Data source with access to underlying sectioned model. public protocol SectionedViewDataSourceType { /// Returns model at index path. /// /// In case data source doesn't contain any sections when this method is being called, `RxCocoaError.ItemsNotYetBound(object: self)` is thrown. /// - parameter indexPath: Model index path /// - returns: Model at index path. func model(at indexPath: IndexPath) throws -> Any } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Common/TextInput.swift ================================================ // // TextInput.swift // RxCocoa // // Created by Krunoslav Zaher on 5/12/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // import RxSwift #if os(iOS) || os(tvOS) import UIKit /// Represents text input with reactive extensions. public struct TextInput { /// Base text input to extend. public let base: Base /// Reactive wrapper for `text` property. public let text: ControlProperty /// Initializes new text input. /// /// - parameter base: Base object. /// - parameter text: Textual control property. public init(base: Base, text: ControlProperty) { self.base = base self.text = text } } extension Reactive where Base: UITextField { /// Reactive text input. public var textInput: TextInput { return TextInput(base: base, text: self.text) } } extension Reactive where Base: UITextView { /// Reactive text input. public var textInput: TextInput { return TextInput(base: base, text: self.text) } } #endif #if os(macOS) import Cocoa /// Represents text input with reactive extensions. public struct TextInput { /// Base text input to extend. public let base: Base /// Reactive wrapper for `text` property. public let text: ControlProperty /// Initializes new text input. /// /// - parameter base: Base object. /// - parameter text: Textual control property. public init(base: Base, text: ControlProperty) { self.base = base self.text = text } } extension Reactive where Base: NSTextField, Base: NSTextInputClient { /// Reactive text input. public var textInput: TextInput { return TextInput(base: self.base, text: self.text) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Foundation/KVORepresentable+CoreGraphics.swift ================================================ // // KVORepresentable+CoreGraphics.swift // RxCocoa // // Created by Krunoslav Zaher on 11/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if !os(Linux) import RxSwift import CoreGraphics import Foundation #if arch(x86_64) || arch(arm64) let CGRectType = "{CGRect={CGPoint=dd}{CGSize=dd}}" let CGSizeType = "{CGSize=dd}" let CGPointType = "{CGPoint=dd}" #elseif arch(i386) || arch(arm) || os(watchOS) let CGRectType = "{CGRect={CGPoint=ff}{CGSize=ff}}" let CGSizeType = "{CGSize=ff}" let CGPointType = "{CGPoint=ff}" #endif extension CGRect : KVORepresentable { public typealias KVOType = NSValue /// Constructs self from `NSValue`. public init?(KVOValue: KVOType) { if strcmp(KVOValue.objCType, CGRectType) != 0 { return nil } var typedValue = CGRect(x: 0, y: 0, width: 0, height: 0) KVOValue.getValue(&typedValue) self = typedValue } } extension CGPoint : KVORepresentable { public typealias KVOType = NSValue /// Constructs self from `NSValue`. public init?(KVOValue: KVOType) { if strcmp(KVOValue.objCType, CGPointType) != 0 { return nil } var typedValue = CGPoint(x: 0, y: 0) KVOValue.getValue(&typedValue) self = typedValue } } extension CGSize : KVORepresentable { public typealias KVOType = NSValue /// Constructs self from `NSValue`. public init?(KVOValue: KVOType) { if strcmp(KVOValue.objCType, CGSizeType) != 0 { return nil } var typedValue = CGSize(width: 0, height: 0) KVOValue.getValue(&typedValue) self = typedValue } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Foundation/KVORepresentable+Swift.swift ================================================ // // KVORepresentable+Swift.swift // RxCocoa // // Created by Krunoslav Zaher on 11/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Foundation extension Int : KVORepresentable { public typealias KVOType = NSNumber /// Constructs `Self` using KVO value. public init?(KVOValue: KVOType) { self.init(KVOValue.int32Value) } } extension Int32 : KVORepresentable { public typealias KVOType = NSNumber /// Constructs `Self` using KVO value. public init?(KVOValue: KVOType) { self.init(KVOValue.int32Value) } } extension Int64 : KVORepresentable { public typealias KVOType = NSNumber /// Constructs `Self` using KVO value. public init?(KVOValue: KVOType) { self.init(KVOValue.int64Value) } } extension UInt : KVORepresentable { public typealias KVOType = NSNumber /// Constructs `Self` using KVO value. public init?(KVOValue: KVOType) { self.init(KVOValue.uintValue) } } extension UInt32 : KVORepresentable { public typealias KVOType = NSNumber /// Constructs `Self` using KVO value. public init?(KVOValue: KVOType) { self.init(KVOValue.uint32Value) } } extension UInt64 : KVORepresentable { public typealias KVOType = NSNumber /// Constructs `Self` using KVO value. public init?(KVOValue: KVOType) { self.init(KVOValue.uint64Value) } } extension Bool : KVORepresentable { public typealias KVOType = NSNumber /// Constructs `Self` using KVO value. public init?(KVOValue: KVOType) { self.init(KVOValue.boolValue) } } extension RawRepresentable where RawValue: KVORepresentable { /// Constructs `Self` using optional KVO value. init?(KVOValue: RawValue.KVOType?) { guard let KVOValue = KVOValue else { return nil } guard let rawValue = RawValue(KVOValue: KVOValue) else { return nil } self.init(rawValue: rawValue) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Foundation/KVORepresentable.swift ================================================ // // KVORepresentable.swift // RxCocoa // // Created by Krunoslav Zaher on 11/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Type that is KVO representable (KVO mechanism can be used to observe it). public protocol KVORepresentable { /// Associated KVO type. associatedtype KVOType /// Constructs `Self` using KVO value. init?(KVOValue: KVOType) } extension KVORepresentable { /// Initializes `KVORepresentable` with optional value. init?(KVOValue: KVOType?) { guard let KVOValue = KVOValue else { return nil } self.init(KVOValue: KVOValue) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Foundation/NSObject+Rx+KVORepresentable.swift ================================================ // // NSObject+Rx+KVORepresentable.swift // RxCocoa // // Created by Krunoslav Zaher on 11/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if !os(Linux) import Foundation import RxSwift /// Key value observing options public struct KeyValueObservingOptions: OptionSet { /// Raw value public let rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue } /// Whether a sequence element should be sent to the observer immediately, before the subscribe method even returns. public static let initial = KeyValueObservingOptions(rawValue: 1 << 0) /// Whether to send updated values. public static let new = KeyValueObservingOptions(rawValue: 1 << 1) } extension Reactive where Base: NSObject { /** Specialization of generic `observe` method. This is a special overload because to observe values of some type (for example `Int`), first values of KVO type need to be observed (`NSNumber`), and then converted to result type. For more information take a look at `observe` method. */ public func observe(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial], retainSelf: Bool = true) -> Observable { return self.observe(Element.KVOType.self, keyPath, options: options, retainSelf: retainSelf) .map(Element.init) } } #if !DISABLE_SWIZZLING && !os(Linux) // KVO extension Reactive where Base: NSObject { /** Specialization of generic `observeWeakly` method. For more information take a look at `observeWeakly` method. */ public func observeWeakly(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial]) -> Observable { return self.observeWeakly(Element.KVOType.self, keyPath, options: options) .map(Element.init) } } #endif #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Foundation/NSObject+Rx+RawRepresentable.swift ================================================ // // NSObject+Rx+RawRepresentable.swift // RxCocoa // // Created by Krunoslav Zaher on 11/9/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if !os(Linux) import RxSwift import Foundation extension Reactive where Base: NSObject { /** Specialization of generic `observe` method. This specialization first observes `KVORepresentable` value and then converts it to `RawRepresentable` value. It is useful for observing bridged ObjC enum values. For more information take a look at `observe` method. */ public func observe(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial], retainSelf: Bool = true) -> Observable where Element.RawValue: KVORepresentable { return self.observe(Element.RawValue.KVOType.self, keyPath, options: options, retainSelf: retainSelf) .map(Element.init) } } #if !DISABLE_SWIZZLING // observeWeakly + RawRepresentable extension Reactive where Base: NSObject { /** Specialization of generic `observeWeakly` method. This specialization first observes `KVORepresentable` value and then converts it to `RawRepresentable` value. It is useful for observing bridged ObjC enum values. For more information take a look at `observeWeakly` method. */ public func observeWeakly(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial]) -> Observable where Element.RawValue: KVORepresentable { return self.observeWeakly(Element.RawValue.KVOType.self, keyPath, options: options) .map(Element.init) } } #endif #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Foundation/NSObject+Rx.swift ================================================ // // NSObject+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 2/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if !os(Linux) import Foundation import RxSwift #if SWIFT_PACKAGE && !DISABLE_SWIZZLING && !os(Linux) import RxCocoaRuntime #endif #if !DISABLE_SWIZZLING && !os(Linux) private var deallocatingSubjectTriggerContext: UInt8 = 0 private var deallocatingSubjectContext: UInt8 = 0 #endif private var deallocatedSubjectTriggerContext: UInt8 = 0 private var deallocatedSubjectContext: UInt8 = 0 #if !os(Linux) /** KVO is a tricky mechanism. When observing child in a ownership hierarchy, usually retaining observing target is wanted behavior. When observing parent in a ownership hierarchy, usually retaining target isn't wanter behavior. KVO with weak references is especially tricky. For it to work, some kind of swizzling is required. That can be done by * replacing object class dynamically (like KVO does) * by swizzling `dealloc` method on all instances for a class. * some third method ... Both approaches can fail in certain scenarios: * problems arise when swizzlers return original object class (like KVO does when nobody is observing) * Problems can arise because replacing dealloc method isn't atomic operation (get implementation, set implementation). Second approach is chosen. It can fail in case there are multiple libraries dynamically trying to replace dealloc method. In case that isn't the case, it should be ok. */ extension Reactive where Base: NSObject { /** Observes values on `keyPath` starting from `self` with `options` and retains `self` if `retainSelf` is set. `observe` is just a simple and performant wrapper around KVO mechanism. * it can be used to observe paths starting from `self` or from ancestors in ownership graph (`retainSelf = false`) * it can be used to observe paths starting from descendants in ownership graph (`retainSelf = true`) * the paths have to consist only of `strong` properties, otherwise you are risking crashing the system by not unregistering KVO observer before dealloc. If support for weak properties is needed or observing arbitrary or unknown relationships in the ownership tree, `observeWeakly` is the preferred option. - parameter type: Optional type hint of the observed sequence elements. - parameter keyPath: Key path of property names to observe. - parameter options: KVO mechanism notification options. - parameter retainSelf: Retains self during observation if set `true`. - returns: Observable sequence of objects on `keyPath`. */ public func observe(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial], retainSelf: Bool = true) -> Observable { KVOObservable(object: self.base, keyPath: keyPath, options: options, retainTarget: retainSelf).asObservable() } /** Observes values at the provided key path using the provided options. - parameter keyPath: A key path between the object and one of its properties. - parameter options: Key-value observation options, defaults to `.new` and `.initial`. - note: When the object is deallocated, a completion event is emitted. - returns: An observable emitting value changes at the provided key path. */ public func observe(_ keyPath: KeyPath, options: NSKeyValueObservingOptions = [.new, .initial]) -> Observable { Observable.create { [weak base] observer in let observation = base?.observe(keyPath, options: options) { obj, _ in observer.on(.next(obj[keyPath: keyPath])) } return Disposables.create { observation?.invalidate() } } .take(until: base.rx.deallocated) } } #endif #if !DISABLE_SWIZZLING && !os(Linux) // KVO extension Reactive where Base: NSObject { /** Observes values on `keyPath` starting from `self` with `options` and doesn't retain `self`. It can be used in all cases where `observe` can be used and additionally * because it won't retain observed target, it can be used to observe arbitrary object graph whose ownership relation is unknown * it can be used to observe `weak` properties **Since it needs to intercept object deallocation process it needs to perform swizzling of `dealloc` method on observed object.** - parameter type: Optional type hint of the observed sequence elements. - parameter keyPath: Key path of property names to observe. - parameter options: KVO mechanism notification options. - returns: Observable sequence of objects on `keyPath`. */ public func observeWeakly(_ type: Element.Type, _ keyPath: String, options: KeyValueObservingOptions = [.new, .initial]) -> Observable { return observeWeaklyKeyPathFor(self.base, keyPath: keyPath, options: options) .map { n in return n as? Element } } } #endif // Dealloc extension Reactive where Base: AnyObject { /** Observable sequence of object deallocated events. After object is deallocated one `()` element will be produced and sequence will immediately complete. - returns: Observable sequence of object deallocated events. */ public var deallocated: Observable { return self.synchronized { if let deallocObservable = objc_getAssociatedObject(self.base, &deallocatedSubjectContext) as? DeallocObservable { return deallocObservable.subject } let deallocObservable = DeallocObservable() objc_setAssociatedObject(self.base, &deallocatedSubjectContext, deallocObservable, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return deallocObservable.subject } } #if !DISABLE_SWIZZLING && !os(Linux) /** Observable sequence of message arguments that completes when object is deallocated. Each element is produced before message is invoked on target object. `methodInvoked` exists in case observing of invoked messages is needed. In case an error occurs sequence will fail with `RxCocoaObjCRuntimeError`. In case some argument is `nil`, instance of `NSNull()` will be sent. - returns: Observable sequence of arguments passed to `selector` method. */ public func sentMessage(_ selector: Selector) -> Observable<[Any]> { return self.synchronized { // in case of dealloc selector replay subject behavior needs to be used if selector == deallocSelector { return self.deallocating.map { _ in [] } } do { let proxy: MessageSentProxy = try self.registerMessageInterceptor(selector) return proxy.messageSent.asObservable() } catch let e { return Observable.error(e) } } } /** Observable sequence of message arguments that completes when object is deallocated. Each element is produced after message is invoked on target object. `sentMessage` exists in case interception of sent messages before they were invoked is needed. In case an error occurs sequence will fail with `RxCocoaObjCRuntimeError`. In case some argument is `nil`, instance of `NSNull()` will be sent. - returns: Observable sequence of arguments passed to `selector` method. */ public func methodInvoked(_ selector: Selector) -> Observable<[Any]> { return self.synchronized { // in case of dealloc selector replay subject behavior needs to be used if selector == deallocSelector { return self.deallocated.map { _ in [] } } do { let proxy: MessageSentProxy = try self.registerMessageInterceptor(selector) return proxy.methodInvoked.asObservable() } catch let e { return Observable.error(e) } } } /** Observable sequence of object deallocating events. When `dealloc` message is sent to `self` one `()` element will be produced and after object is deallocated sequence will immediately complete. In case an error occurs sequence will fail with `RxCocoaObjCRuntimeError`. - returns: Observable sequence of object deallocating events. */ public var deallocating: Observable<()> { return self.synchronized { do { let proxy: DeallocatingProxy = try self.registerMessageInterceptor(deallocSelector) return proxy.messageSent.asObservable() } catch let e { return Observable.error(e) } } } private func registerMessageInterceptor(_ selector: Selector) throws -> T { let rxSelector = RX_selector(selector) let selectorReference = RX_reference_from_selector(rxSelector) let subject: T if let existingSubject = objc_getAssociatedObject(self.base, selectorReference) as? T { subject = existingSubject } else { subject = T() objc_setAssociatedObject( self.base, selectorReference, subject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC ) } if subject.isActive { return subject } var error: NSError? let targetImplementation = RX_ensure_observing(self.base, selector, &error) if targetImplementation == nil { throw error?.rxCocoaErrorForTarget(self.base) ?? RxCocoaError.unknown } subject.targetImplementation = targetImplementation! return subject } #endif } // MARK: Message interceptors #if !DISABLE_SWIZZLING && !os(Linux) private protocol MessageInterceptorSubject: AnyObject { init() var isActive: Bool { get } var targetImplementation: IMP { get set } } private final class DeallocatingProxy : MessageInterceptorSubject , RXDeallocatingObserver { typealias Element = () let messageSent = ReplaySubject<()>.create(bufferSize: 1) @objc var targetImplementation: IMP = RX_default_target_implementation() var isActive: Bool { return self.targetImplementation != RX_default_target_implementation() } init() { } @objc func deallocating() { self.messageSent.on(.next(())) } deinit { self.messageSent.on(.completed) } } private final class MessageSentProxy : MessageInterceptorSubject , RXMessageSentObserver { typealias Element = [AnyObject] let messageSent = PublishSubject<[Any]>() let methodInvoked = PublishSubject<[Any]>() @objc var targetImplementation: IMP = RX_default_target_implementation() var isActive: Bool { return self.targetImplementation != RX_default_target_implementation() } init() { } @objc func messageSent(withArguments arguments: [Any]) { self.messageSent.on(.next(arguments)) } @objc func methodInvoked(withArguments arguments: [Any]) { self.methodInvoked.on(.next(arguments)) } deinit { self.messageSent.on(.completed) self.methodInvoked.on(.completed) } } #endif private final class DeallocObservable { let subject = ReplaySubject.create(bufferSize:1) init() { } deinit { self.subject.on(.next(())) self.subject.on(.completed) } } // MARK: KVO #if !os(Linux) private protocol KVOObservableProtocol { var target: AnyObject { get } var keyPath: String { get } var retainTarget: Bool { get } var options: KeyValueObservingOptions { get } } private final class KVOObserver : _RXKVOObserver , Disposable { typealias Callback = (Any?) -> Void var retainSelf: KVOObserver? init(parent: KVOObservableProtocol, callback: @escaping Callback) { #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif super.init(target: parent.target, retainTarget: parent.retainTarget, keyPath: parent.keyPath, options: parent.options.nsOptions, callback: callback) self.retainSelf = self } override func dispose() { super.dispose() self.retainSelf = nil } deinit { #if TRACE_RESOURCES _ = Resources.decrementTotal() #endif } } private final class KVOObservable : ObservableType , KVOObservableProtocol { typealias Element = Element? unowned var target: AnyObject var strongTarget: AnyObject? var keyPath: String var options: KeyValueObservingOptions var retainTarget: Bool init(object: AnyObject, keyPath: String, options: KeyValueObservingOptions, retainTarget: Bool) { self.target = object self.keyPath = keyPath self.options = options self.retainTarget = retainTarget if retainTarget { self.strongTarget = object } } func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element? { let observer = KVOObserver(parent: self) { value in if value as? NSNull != nil { observer.on(.next(nil)) return } observer.on(.next(value as? Element)) } return Disposables.create(with: observer.dispose) } } private extension KeyValueObservingOptions { var nsOptions: NSKeyValueObservingOptions { var result: UInt = 0 if self.contains(.new) { result |= NSKeyValueObservingOptions.new.rawValue } if self.contains(.initial) { result |= NSKeyValueObservingOptions.initial.rawValue } return NSKeyValueObservingOptions(rawValue: result) } } #endif #if !DISABLE_SWIZZLING && !os(Linux) private func observeWeaklyKeyPathFor(_ target: NSObject, keyPath: String, options: KeyValueObservingOptions) -> Observable { let components = keyPath.components(separatedBy: ".").filter { $0 != "self" } let observable = observeWeaklyKeyPathFor(target, keyPathSections: components, options: options) .finishWithNilWhenDealloc(target) if !options.isDisjoint(with: .initial) { return observable } else { return observable .skip(1) } } // This should work correctly // Identifiers can't contain `,`, so the only place where `,` can appear // is as a delimiter. // This means there is `W` as element in an array of property attributes. private func isWeakProperty(_ properyRuntimeInfo: String) -> Bool { properyRuntimeInfo.range(of: ",W,") != nil } private extension ObservableType where Element == AnyObject? { func finishWithNilWhenDealloc(_ target: NSObject) -> Observable { let deallocating = target.rx.deallocating return deallocating .map { _ in return Observable.just(nil) } .startWith(self.asObservable()) .switchLatest() } } private func observeWeaklyKeyPathFor( _ target: NSObject, keyPathSections: [String], options: KeyValueObservingOptions ) -> Observable { weak var weakTarget: AnyObject? = target let propertyName = keyPathSections[0] let remainingPaths = Array(keyPathSections[1.. // KVO recursion for value changes return propertyObservable .flatMapLatest { (nextTarget: AnyObject?) -> Observable in if nextTarget == nil { return Observable.just(nil) } let nextObject = nextTarget! as? NSObject let strongTarget: AnyObject? = weakTarget if nextObject == nil { return Observable.error(RxCocoaError.invalidObjectOnKeyPath(object: nextTarget!, sourceObject: strongTarget ?? NSNull(), propertyName: propertyName)) } // if target is alive, then send change // if it's deallocated, don't send anything if strongTarget == nil { return Observable.empty() } let nextElementsObservable = keyPathSections.count == 1 ? Observable.just(nextTarget) : observeWeaklyKeyPathFor(nextObject!, keyPathSections: remainingPaths, options: options) if isWeak { return nextElementsObservable .finishWithNilWhenDealloc(nextObject!) } else { return nextElementsObservable } } } #endif // MARK: Constants private let deallocSelector = NSSelectorFromString("dealloc") // MARK: AnyObject + Reactive extension Reactive where Base: AnyObject { func synchronized( _ action: () -> T) -> T { objc_sync_enter(self.base) let result = action() objc_sync_exit(self.base) return result } } extension Reactive where Base: AnyObject { /** Helper to make sure that `Observable` returned from `createCachedObservable` is only created once. This is important because there is only one `target` and `action` properties on `NSControl` or `UIBarButtonItem`. */ func lazyInstanceObservable(_ key: UnsafeRawPointer, createCachedObservable: () -> T) -> T { if let value = objc_getAssociatedObject(self.base, key) { return value as! T } let observable = createCachedObservable() objc_setAssociatedObject(self.base, key, observable, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return observable } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Foundation/NotificationCenter+Rx.swift ================================================ // // NotificationCenter+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 5/2/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Foundation import RxSwift extension Reactive where Base: NotificationCenter { /** Transforms notifications posted to notification center to observable sequence of notifications. - parameter name: Optional name used to filter notifications. - parameter object: Optional object used to filter notifications. - returns: Observable sequence of posted notifications. */ public func notification(_ name: Notification.Name?, object: AnyObject? = nil) -> Observable { return Observable.create { [weak object] observer in let nsObserver = self.base.addObserver(forName: name, object: object, queue: nil) { notification in observer.on(.next(notification)) } return Disposables.create { self.base.removeObserver(nsObserver) } } } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Foundation/URLSession+Rx.swift ================================================ // // URLSession+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 3/23/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Foundation import RxSwift #if canImport(FoundationNetworking) import FoundationNetworking #endif /// RxCocoa URL errors. public enum RxCocoaURLError : Swift.Error { /// Unknown error occurred. case unknown /// Response is not NSHTTPURLResponse case nonHTTPResponse(response: URLResponse) /// Response is not successful. (not in `200 ..< 300` range) case httpRequestFailed(response: HTTPURLResponse, data: Data?) /// Deserialization error. case deserializationError(error: Swift.Error) } extension RxCocoaURLError : CustomDebugStringConvertible { /// A textual representation of `self`, suitable for debugging. public var debugDescription: String { switch self { case .unknown: return "Unknown error has occurred." case let .nonHTTPResponse(response): return "Response is not NSHTTPURLResponse `\(response)`." case let .httpRequestFailed(response, _): return "HTTP request failed with `\(response.statusCode)`." case let .deserializationError(error): return "Error during deserialization of the response: \(error)" } } } private func escapeTerminalString(_ value: String) -> String { return value.replacingOccurrences(of: "\"", with: "\\\"", options:[], range: nil) } private func convertURLRequestToCurlCommand(_ request: URLRequest) -> String { let method = request.httpMethod ?? "GET" var returnValue = "curl -X \(method) " if let httpBody = request.httpBody { let maybeBody = String(data: httpBody, encoding: String.Encoding.utf8) if let body = maybeBody { returnValue += "-d \"\(escapeTerminalString(body))\" " } } for (key, value) in request.allHTTPHeaderFields ?? [:] { let escapedKey = escapeTerminalString(key as String) let escapedValue = escapeTerminalString(value as String) returnValue += "\n -H \"\(escapedKey): \(escapedValue)\" " } let URLString = request.url?.absoluteString ?? "" returnValue += "\n\"\(escapeTerminalString(URLString))\"" returnValue += " -i -v" return returnValue } private func convertResponseToString(_ response: URLResponse?, _ error: NSError?, _ interval: TimeInterval) -> String { let ms = Int(interval * 1000) if let response = response as? HTTPURLResponse { if 200 ..< 300 ~= response.statusCode { return "Success (\(ms)ms): Status \(response.statusCode)" } else { return "Failure (\(ms)ms): Status \(response.statusCode)" } } if let error = error { if error.domain == NSURLErrorDomain && error.code == NSURLErrorCancelled { return "Canceled (\(ms)ms)" } return "Failure (\(ms)ms): NSError > \(error)" } return "" } extension Reactive where Base: URLSession { /** Observable sequence of responses for URL request. Performing of request starts after observer is subscribed and not after invoking this method. **URL requests will be performed per subscribed observer.** Any error during fetching of the response will cause observed sequence to terminate with error. - parameter request: URL request. - returns: Observable sequence of URL responses. */ public func response(request: URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> { return Observable.create { observer in // smart compiler should be able to optimize this out let d: Date? if URLSession.rx.shouldLogRequest(request) { d = Date() } else { d = nil } let task = self.base.dataTask(with: request) { data, response, error in if URLSession.rx.shouldLogRequest(request) { let interval = Date().timeIntervalSince(d ?? Date()) print(convertURLRequestToCurlCommand(request)) #if os(Linux) print(convertResponseToString(response, error.flatMap { $0 as NSError }, interval)) #else print(convertResponseToString(response, error.map { $0 as NSError }, interval)) #endif } guard let response = response, let data = data else { observer.on(.error(error ?? RxCocoaURLError.unknown)) return } guard let httpResponse = response as? HTTPURLResponse else { observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response))) return } observer.on(.next((httpResponse, data))) observer.on(.completed) } task.resume() return Disposables.create(with: task.cancel) } } /** Observable sequence of response data for URL request. Performing of request starts after observer is subscribed and not after invoking this method. **URL requests will be performed per subscribed observer.** Any error during fetching of the response will cause observed sequence to terminate with error. If response is not HTTP response with status code in the range of `200 ..< 300`, sequence will terminate with `(RxCocoaErrorDomain, RxCocoaError.NetworkError)`. - parameter request: URL request. - returns: Observable sequence of response data. */ public func data(request: URLRequest) -> Observable { return self.response(request: request).map { pair -> Data in if 200 ..< 300 ~= pair.0.statusCode { return pair.1 } else { throw RxCocoaURLError.httpRequestFailed(response: pair.0, data: pair.1) } } } /** Observable sequence of response JSON for URL request. Performing of request starts after observer is subscribed and not after invoking this method. **URL requests will be performed per subscribed observer.** Any error during fetching of the response will cause observed sequence to terminate with error. If response is not HTTP response with status code in the range of `200 ..< 300`, sequence will terminate with `(RxCocoaErrorDomain, RxCocoaError.NetworkError)`. If there is an error during JSON deserialization observable sequence will fail with that error. - parameter request: URL request. - returns: Observable sequence of response JSON. */ public func json(request: URLRequest, options: JSONSerialization.ReadingOptions = []) -> Observable { return self.data(request: request).map { data -> Any in do { return try JSONSerialization.jsonObject(with: data, options: options) } catch let error { throw RxCocoaURLError.deserializationError(error: error) } } } /** Observable sequence of response JSON for GET request with `URL`. Performing of request starts after observer is subscribed and not after invoking this method. **URL requests will be performed per subscribed observer.** Any error during fetching of the response will cause observed sequence to terminate with error. If response is not HTTP response with status code in the range of `200 ..< 300`, sequence will terminate with `(RxCocoaErrorDomain, RxCocoaError.NetworkError)`. If there is an error during JSON deserialization observable sequence will fail with that error. - parameter url: URL of `NSURLRequest` request. - returns: Observable sequence of response JSON. */ public func json(url: Foundation.URL) -> Observable { self.json(request: URLRequest(url: url)) } } extension Reactive where Base == URLSession { /// Log URL requests to standard output in curl format. public static var shouldLogRequest: (URLRequest) -> Bool = { _ in #if DEBUG return true #else return false #endif } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Runtime/_RX.m ================================================ // // _RX.m // RxCocoa // // Created by Krunoslav Zaher on 7/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #import "include/_RX.h" ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Runtime/_RXDelegateProxy.m ================================================ // // _RXDelegateProxy.m // RxCocoa // // Created by Krunoslav Zaher on 7/4/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #import "include/_RXDelegateProxy.h" #import "include/_RX.h" #import "include/_RXObjCRuntime.h" @interface _RXDelegateProxy () { id __weak __forwardToDelegate; } @property (nonatomic, strong) id strongForwardDelegate; @end static NSMutableDictionary *voidSelectorsPerClass = nil; @implementation _RXDelegateProxy +(NSSet*)collectVoidSelectorsForProtocol:(Protocol *)protocol { NSMutableSet *selectors = [NSMutableSet set]; unsigned int protocolMethodCount = 0; struct objc_method_description *pMethods = protocol_copyMethodDescriptionList(protocol, NO, YES, &protocolMethodCount); for (unsigned int i = 0; i < protocolMethodCount; ++i) { struct objc_method_description method = pMethods[i]; if (RX_is_method_with_description_void(method)) { [selectors addObject:SEL_VALUE(method.name)]; } } free(pMethods); unsigned int numberOfBaseProtocols = 0; Protocol * __unsafe_unretained * pSubprotocols = protocol_copyProtocolList(protocol, &numberOfBaseProtocols); for (unsigned int i = 0; i < numberOfBaseProtocols; ++i) { [selectors unionSet:[self collectVoidSelectorsForProtocol:pSubprotocols[i]]]; } free(pSubprotocols); return selectors; } +(void)initialize { @synchronized (_RXDelegateProxy.class) { if (voidSelectorsPerClass == nil) { voidSelectorsPerClass = [[NSMutableDictionary alloc] init]; } NSMutableSet *voidSelectors = [NSMutableSet set]; #define CLASS_HIERARCHY_MAX_DEPTH 100 NSInteger classHierarchyDepth = 0; Class targetClass = NULL; for (classHierarchyDepth = 0, targetClass = self; classHierarchyDepth < CLASS_HIERARCHY_MAX_DEPTH && targetClass != nil; ++classHierarchyDepth, targetClass = class_getSuperclass(targetClass) ) { unsigned int count; Protocol *__unsafe_unretained *pProtocols = class_copyProtocolList(targetClass, &count); for (unsigned int i = 0; i < count; i++) { NSSet *selectorsForProtocol = [self collectVoidSelectorsForProtocol:pProtocols[i]]; [voidSelectors unionSet:selectorsForProtocol]; } free(pProtocols); } if (classHierarchyDepth == CLASS_HIERARCHY_MAX_DEPTH) { NSLog(@"Detected weird class hierarchy with depth over %d. Starting with this class -> %@", CLASS_HIERARCHY_MAX_DEPTH, self); #if DEBUG abort(); #endif } voidSelectorsPerClass[CLASS_VALUE(self)] = voidSelectors; } } -(id)_forwardToDelegate { return __forwardToDelegate; } -(void)_setForwardToDelegate:(id __nullable)forwardToDelegate retainDelegate:(BOOL)retainDelegate { __forwardToDelegate = forwardToDelegate; if (retainDelegate) { self.strongForwardDelegate = forwardToDelegate; } else { self.strongForwardDelegate = nil; } } -(BOOL)hasWiredImplementationForSelector:(SEL)selector { return [super respondsToSelector:selector]; } -(BOOL)voidDelegateMethodsContain:(SEL)selector { @synchronized(_RXDelegateProxy.class) { NSSet *voidSelectors = voidSelectorsPerClass[CLASS_VALUE(self.class)]; NSAssert(voidSelectors != nil, @"Set of allowed methods not initialized"); return [voidSelectors containsObject:SEL_VALUE(selector)]; } } -(void)forwardInvocation:(NSInvocation *)anInvocation { BOOL isVoid = RX_is_method_signature_void(anInvocation.methodSignature); NSArray *arguments = nil; if (isVoid) { arguments = RX_extract_arguments(anInvocation); [self _sentMessage:anInvocation.selector withArguments:arguments]; } if (self._forwardToDelegate && [self._forwardToDelegate respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:self._forwardToDelegate]; } if (isVoid) { [self _methodInvoked:anInvocation.selector withArguments:arguments]; } } // abstract method -(void)_sentMessage:(SEL)selector withArguments:(NSArray *)arguments { } // abstract method -(void)_methodInvoked:(SEL)selector withArguments:(NSArray *)arguments { } -(void)dealloc { } @end ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Runtime/_RXKVOObserver.m ================================================ // // _RXKVOObserver.m // RxCocoa // // Created by Krunoslav Zaher on 7/11/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #import "include/_RXKVOObserver.h" @interface _RXKVOObserver () @property (nonatomic, unsafe_unretained) id target; @property (nonatomic, strong ) id retainedTarget; @property (nonatomic, copy ) NSString *keyPath; @property (nonatomic, copy ) void (^callback)(id); @end @implementation _RXKVOObserver -(instancetype)initWithTarget:(id)target retainTarget:(BOOL)retainTarget keyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options callback:(void (^)(id))callback { self = [super init]; if (!self) return nil; self.target = target; if (retainTarget) { self.retainedTarget = target; } self.keyPath = keyPath; self.callback = callback; [self.target addObserver:self forKeyPath:self.keyPath options:options context:nil]; return self; } -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { @synchronized(self) { self.callback(change[NSKeyValueChangeNewKey]); } } -(void)dispose { [self.target removeObserver:self forKeyPath:self.keyPath context:nil]; self.target = nil; self.retainedTarget = nil; } @end ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Runtime/_RXObjCRuntime.m ================================================ // // _RXObjCRuntime.m // RxCocoa // // Created by Krunoslav Zaher on 7/11/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #import #import #import #import #import #import #import "include/_RX.h" #import "include/_RXObjCRuntime.h" // self + cmd #define HIDDEN_ARGUMENT_COUNT 2 #if !DISABLE_SWIZZLING #define NSErrorParam NSError *__autoreleasing __nullable * __nullable @class RXObjCRuntime; BOOL RXAbortOnThreadingHazard = NO; typedef NSInvocation *NSInvocationRef; typedef NSMethodSignature *NSMethodSignatureRef; typedef unsigned char rx_uchar; typedef unsigned short rx_ushort; typedef unsigned int rx_uint; typedef unsigned long rx_ulong; typedef id (^rx_block)(id); typedef BOOL (^RXInterceptWithOptimizedObserver)(RXObjCRuntime * __nonnull self, Class __nonnull class, SEL __nonnull selector, NSErrorParam error); static CFTypeID defaultTypeID; static SEL deallocSelector; static int RxSwizzlingTargetClassKey = 0; #if TRACE_RESOURCES _Atomic static int32_t numberOInterceptedMethods = 0; _Atomic static int32_t numberOfForwardedMethods = 0; #endif #define THREADING_HAZARD(class) \ NSLog(@"There was a problem swizzling on `%@`.\nYou have probably two libraries performing swizzling in runtime.\nWe didn't want to crash your program, but this is not good ...\nYou an solve this problem by either not using swizzling in this library, removing one of those other libraries, or making sure that swizzling parts are synchronized (only perform them on main thread).\nAnd yes, this message will self destruct when you clear the console, and since it's non deterministic, the problem could still exist and it will be hard for you to reproduce it.", NSStringFromClass(class)); ABORT_IN_DEBUG if (RXAbortOnThreadingHazard) { abort(); } #define ALWAYS(condition, message) if (!(condition)) { [NSException raise:@"RX Invalid Operator" format:@"%@", message]; } #define ALWAYS_WITH_INFO(condition, message) NSAssert((condition), @"%@ [%@] > %@", NSStringFromClass(class), NSStringFromSelector(selector), (message)) #define C_ALWAYS(condition, message) NSCAssert((condition), @"%@ [%@] > %@", NSStringFromClass(class), NSStringFromSelector(selector), (message)) #define RX_PREFIX @"_RX_namespace_" #define RX_ARG_id(value) ((value) ?: [NSNull null]) #define RX_ARG_char(value) [NSNumber numberWithChar:value] #define RX_ARG_short(value) [NSNumber numberWithShort:value] #define RX_ARG_int(value) [NSNumber numberWithInt:value] #define RX_ARG_long(value) [NSNumber numberWithLong:value] #define RX_ARG_BOOL(value) [NSNumber numberWithBool:value] #define RX_ARG_SEL(value) [NSNumber valueWithPointer:value] #define RX_ARG_rx_uchar(value) [NSNumber numberWithUnsignedInt:value] #define RX_ARG_rx_ushort(value) [NSNumber numberWithUnsignedInt:value] #define RX_ARG_rx_uint(value) [NSNumber numberWithUnsignedInt:value] #define RX_ARG_rx_ulong(value) [NSNumber numberWithUnsignedLong:value] #define RX_ARG_rx_block(value) ((id)(value) ?: [NSNull null]) #define RX_ARG_float(value) [NSNumber numberWithFloat:value] #define RX_ARG_double(value) [NSNumber numberWithDouble:value] typedef struct supported_type { const char *encoding; } supported_type_t; static supported_type_t supported_types[] = { { .encoding = @encode(void)}, { .encoding = @encode(id)}, { .encoding = @encode(Class)}, { .encoding = @encode(void (^)(void))}, { .encoding = @encode(char)}, { .encoding = @encode(short)}, { .encoding = @encode(int)}, { .encoding = @encode(long)}, { .encoding = @encode(long long)}, { .encoding = @encode(unsigned char)}, { .encoding = @encode(unsigned short)}, { .encoding = @encode(unsigned int)}, { .encoding = @encode(unsigned long)}, { .encoding = @encode(unsigned long long)}, { .encoding = @encode(float)}, { .encoding = @encode(double)}, { .encoding = @encode(BOOL)}, { .encoding = @encode(const char*)}, }; NSString * __nonnull const RXObjCRuntimeErrorDomain = @"RXObjCRuntimeErrorDomain"; NSString * __nonnull const RXObjCRuntimeErrorIsKVOKey = @"RXObjCRuntimeErrorIsKVOKey"; BOOL RX_return_type_is_supported(const char *type) { if (type == nil) { return NO; } for (int i = 0; i < sizeof(supported_types) / sizeof(supported_type_t); ++i) { if (supported_types[i].encoding[0] != type[0]) { continue; } if (strcmp(supported_types[i].encoding, type) == 0) { return YES; } } return NO; } static BOOL RX_method_has_supported_return_type(Method method) { const char *rawEncoding = method_getTypeEncoding(method); ALWAYS(rawEncoding != nil, @"Example encoding method is nil."); NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:rawEncoding]; ALWAYS(methodSignature != nil, @"Method signature method is nil."); return RX_return_type_is_supported(methodSignature.methodReturnType); } SEL __nonnull RX_selector(SEL __nonnull selector) { NSString *selectorString = NSStringFromSelector(selector); return NSSelectorFromString([RX_PREFIX stringByAppendingString:selectorString]); } #endif BOOL RX_is_method_signature_void(NSMethodSignature * __nonnull methodSignature) { const char *methodReturnType = methodSignature.methodReturnType; return strcmp(methodReturnType, @encode(void)) == 0; } BOOL RX_is_method_with_description_void(struct objc_method_description method) { return strncmp(method.types, @encode(void), 1) == 0; } id __nonnull RX_extract_argument_at_index(NSInvocation * __nonnull invocation, NSUInteger index) { const char *argumentType = [invocation.methodSignature getArgumentTypeAtIndex:index]; #define RETURN_VALUE(type) \ else if (strcmp(argumentType, @encode(type)) == 0) {\ type val = 0; \ [invocation getArgument:&val atIndex:index]; \ return @(val); \ } // Skip const type qualifier. if (argumentType[0] == 'r') { argumentType++; } if (strcmp(argumentType, @encode(id)) == 0 || strcmp(argumentType, @encode(Class)) == 0 || strcmp(argumentType, @encode(void (^)(void))) == 0 ) { __unsafe_unretained id argument = nil; [invocation getArgument:&argument atIndex:index]; return argument; } RETURN_VALUE(char) RETURN_VALUE(short) RETURN_VALUE(int) RETURN_VALUE(long) RETURN_VALUE(long long) RETURN_VALUE(unsigned char) RETURN_VALUE(unsigned short) RETURN_VALUE(unsigned int) RETURN_VALUE(unsigned long) RETURN_VALUE(unsigned long long) RETURN_VALUE(float) RETURN_VALUE(double) RETURN_VALUE(BOOL) RETURN_VALUE(const char *) else { NSUInteger size = 0; NSGetSizeAndAlignment(argumentType, &size, NULL); NSCParameterAssert(size > 0); uint8_t data[size]; [invocation getArgument:&data atIndex:index]; return [NSValue valueWithBytes:&data objCType:argumentType]; } } NSArray *RX_extract_arguments(NSInvocation *invocation) { NSUInteger numberOfArguments = invocation.methodSignature.numberOfArguments; NSUInteger numberOfVisibleArguments = numberOfArguments - HIDDEN_ARGUMENT_COUNT; NSCParameterAssert(numberOfVisibleArguments >= 0); NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:numberOfVisibleArguments]; for (NSUInteger index = HIDDEN_ARGUMENT_COUNT; index < numberOfArguments; ++index) { [arguments addObject:RX_extract_argument_at_index(invocation, index) ?: [NSNull null]]; } return arguments; } IMP __nonnull RX_default_target_implementation(void) { return _objc_msgForward; } #if !DISABLE_SWIZZLING void * __nonnull RX_reference_from_selector(SEL __nonnull selector) { return selector; } static BOOL RX_forward_invocation(id __nonnull __unsafe_unretained self, NSInvocation *invocation) { SEL originalSelector = RX_selector(invocation.selector); id messageSentObserver = objc_getAssociatedObject(self, originalSelector); if (messageSentObserver != nil) { NSArray *arguments = RX_extract_arguments(invocation); [messageSentObserver messageSentWithArguments:arguments]; } if ([self respondsToSelector:originalSelector]) { invocation.selector = originalSelector; [invocation invokeWithTarget:self]; if (messageSentObserver != nil) { NSArray *arguments = RX_extract_arguments(invocation); [messageSentObserver methodInvokedWithArguments:arguments]; } return YES; } return NO; } static BOOL RX_responds_to_selector(id __nonnull __unsafe_unretained self, SEL selector) { Class class = object_getClass(self); if (class == nil) { return NO; } Method m = class_getInstanceMethod(class, selector); return m != nil; } static NSMethodSignatureRef RX_method_signature(id __nonnull __unsafe_unretained self, SEL selector) { Class class = object_getClass(self); if (class == nil) { return nil; } Method method = class_getInstanceMethod(class, selector); if (method == nil) { return nil; } const char *encoding = method_getTypeEncoding(method); if (encoding == nil) { return nil; } return [NSMethodSignature signatureWithObjCTypes:encoding]; } static NSString * __nonnull RX_method_encoding(Method __nonnull method) { const char *typeEncoding = method_getTypeEncoding(method); ALWAYS(typeEncoding != nil, @"Method encoding is nil."); NSString *encoding = [NSString stringWithCString:typeEncoding encoding:NSASCIIStringEncoding]; ALWAYS(encoding != nil, @"Can't convert encoding to NSString."); return encoding; } @interface RXObjCRuntime: NSObject @property (nonatomic, assign) pthread_mutex_t lock; @property (nonatomic, strong) NSMutableSet *classesThatSupportObservingByForwarding; @property (nonatomic, strong) NSMutableDictionary *> *forwardedSelectorsByClass; @property (nonatomic, strong) NSMutableDictionary *dynamicSubclassByRealClass; @property (nonatomic, strong) NSMutableDictionary*> *interceptorIMPbySelectorsByClass; +(RXObjCRuntime*)instance; -(void)performLocked:(void (^)(RXObjCRuntime* __nonnull))action; -(IMP __nullable)ensurePrepared:(id __nonnull)target forObserving:(SEL __nonnull)selector error:(NSErrorParam)error; -(BOOL)ensureSwizzledSelector:(SEL __nonnull)selector ofClass:(Class __nonnull)class newImplementationGenerator:(IMP(^)(void))newImplementationGenerator replacementImplementationGenerator:(IMP (^)(IMP originalImplementation))replacementImplementationGenerator error:(NSErrorParam)error; +(void)registerOptimizedObserver:(RXInterceptWithOptimizedObserver)registration encodedAs:(SEL)selector; @end /** All API methods perform work on locked instance of `RXObjCRuntime`. In that way it's easy to prove that every action is properly locked. */ IMP __nullable RX_ensure_observing(id __nonnull target, SEL __nonnull selector, NSErrorParam error) { __block IMP targetImplementation = nil; // Target is the second object that needs to be synchronized to TRY to make sure other swizzling framework // won't do something in parallel. // Even though this is too fine grained locking and more coarse grained locks should exist, this is just in case // someone calls this method directly without any external lock. @synchronized(target) { // The only other resource that all other swizzling libraries have in common without introducing external // dependencies is class object. // // It is polite to try to synchronize it in hope other unknown entities will also attempt to do so. // It's like trying to figure out how to communicate with aliens without actually communicating, // save for the fact that aliens are people, programmers, authors of swizzling libraries. @synchronized([target class]) { [[RXObjCRuntime instance] performLocked:^(RXObjCRuntime * __nonnull self) { targetImplementation = [self ensurePrepared:target forObserving:selector error:error]; }]; } } return targetImplementation; } // bodies #define FORWARD_BODY(invocation) if (RX_forward_invocation(self, NAME_CAT(_, 0, invocation))) { return; } #define RESPONDS_TO_SELECTOR_BODY(selector) if (RX_responds_to_selector(self, NAME_CAT(_, 0, selector))) return YES; #define CLASS_BODY(...) return actAsClass; #define METHOD_SIGNATURE_FOR_SELECTOR_BODY(selector) \ NSMethodSignatureRef methodSignature = RX_method_signature(self, NAME_CAT(_, 0, selector)); \ if (methodSignature != nil) { \ return methodSignature; \ } #define DEALLOCATING_BODY(...) \ id observer = objc_getAssociatedObject(self, rxSelector); \ if (observer != nil && observer.targetImplementation == thisIMP) { \ [observer deallocating]; \ } #define OBSERVE_BODY(...) \ id observer = objc_getAssociatedObject(self, rxSelector); \ \ if (observer != nil && observer.targetImplementation == thisIMP) { \ [observer messageSentWithArguments:@[COMMA_DELIMITED_ARGUMENTS(__VA_ARGS__)]]; \ } \ #define OBSERVE_INVOKED_BODY(...) \ if (observer != nil && observer.targetImplementation == thisIMP) { \ [observer methodInvokedWithArguments:@[COMMA_DELIMITED_ARGUMENTS(__VA_ARGS__)]]; \ } \ #define BUILD_ARG_WRAPPER(type) RX_ARG_ ## type //RX_ARG_ ## type #define CAT(_1, _2, head, tail) RX_CAT2(head, tail) #define SEPARATE_BY_COMMA(_1, _2, head, tail) head, tail #define SEPARATE_BY_SPACE(_1, _2, head, tail) head tail #define SEPARATE_BY_UNDERSCORE(head, tail) RX_CAT2(RX_CAT2(head, _), tail) #define UNDERSCORE_TYPE_CAT(_1, index, type) RX_CAT2(_, type) // generates -> _type #define NAME_CAT(_1, index, type) SEPARATE_BY_UNDERSCORE(type, index) // generates -> type_0 #define TYPE_AND_NAME_CAT(_1, index, type) type SEPARATE_BY_UNDERSCORE(type, index) // generates -> type type_0 #define NOT_NULL_ARGUMENT_CAT(_1, index, type) BUILD_ARG_WRAPPER(type)(NAME_CAT(_1, index, type)) // generates -> ((id)(type_0) ?: [NSNull null]) #define EXAMPLE_PARAMETER(_1, index, type) RX_CAT2(_, type):(type)SEPARATE_BY_UNDERSCORE(type, index) // generates -> _type:(type)type_0 #define SELECTOR_PART(_1, index, type) RX_CAT2(_, type:) // generates -> _type: #define COMMA_DELIMITED_ARGUMENTS(...) RX_FOREACH(_, SEPARATE_BY_COMMA, NOT_NULL_ARGUMENT_CAT, ## __VA_ARGS__) #define ARGUMENTS(...) RX_FOREACH_COMMA(_, NAME_CAT, ## __VA_ARGS__) #define DECLARE_ARGUMENTS(...) RX_FOREACH_COMMA(_, TYPE_AND_NAME_CAT, ## __VA_ARGS__) // optimized observe methods #define GENERATE_SELECTOR_IDENTIFIER(...) RX_CAT2(exampleSelector, RX_FOREACH(_, CAT, UNDERSCORE_TYPE_CAT, ## __VA_ARGS__)) #define GENERATE_METHOD_IDENTIFIER(...) RX_CAT2(swizzle, RX_FOREACH(_, CAT, UNDERSCORE_TYPE_CAT, ## __VA_ARGS__)) #define GENERATE_OBSERVE_METHOD_DECLARATION(...) \ -(BOOL)GENERATE_METHOD_IDENTIFIER(__VA_ARGS__):(Class __nonnull)class \ selector:(SEL)selector \ error:(NSErrorParam)error { \ #define BUILD_EXAMPLE_METHOD(return_value, ...) \ +(return_value)RX_CAT2(RX_CAT2(example_, return_value), RX_FOREACH(_, SEPARATE_BY_SPACE, EXAMPLE_PARAMETER, ## __VA_ARGS__)) {} #define BUILD_EXAMPLE_METHOD_SELECTOR(return_value, ...) \ RX_CAT2(RX_CAT2(example_, return_value), RX_FOREACH(_, SEPARATE_BY_SPACE, SELECTOR_PART, ## __VA_ARGS__)) #define SWIZZLE_OBSERVE_METHOD_DEFINITIONS(return_value, ...) \ BUILD_EXAMPLE_METHOD(return_value, ## __VA_ARGS__) \ SWIZZLE_METHOD(return_value, GENERATE_OBSERVE_METHOD_DECLARATION(return_value, ## __VA_ARGS__), OBSERVE_BODY, OBSERVE_INVOKED_BODY, ## __VA_ARGS__) \ #define SWIZZLE_OBSERVE_METHOD_BODY(return_value, ...) \ __unused SEL GENERATE_SELECTOR_IDENTIFIER(return_value, ## __VA_ARGS__) = @selector(BUILD_EXAMPLE_METHOD_SELECTOR(return_value, ## __VA_ARGS__)); \ [self registerOptimizedObserver:^BOOL(RXObjCRuntime * __nonnull self, Class __nonnull class, \ SEL __nonnull selector, NSErrorParam error) { \ return [self GENERATE_METHOD_IDENTIFIER(return_value, ## __VA_ARGS__):class selector:selector error:error]; \ } encodedAs:GENERATE_SELECTOR_IDENTIFIER(return_value, ## __VA_ARGS__)]; \ // infrastructure method #define NO_BODY(...) #define SWIZZLE_INFRASTRUCTURE_METHOD(return_value, method_name, parameters, method_selector, body, ...) \ SWIZZLE_METHOD(return_value, -(BOOL)method_name:(Class __nonnull)class parameters error:(NSErrorParam)error \ { \ SEL selector = method_selector; , body, NO_BODY, __VA_ARGS__) \ // common base #define SWIZZLE_METHOD(return_value, method_prototype, body, invoked_body, ...) \ method_prototype \ __unused SEL rxSelector = RX_selector(selector); \ IMP (^newImplementationGenerator)(void) = ^() { \ __block IMP thisIMP = nil; \ id newImplementation = ^return_value(__unsafe_unretained id self DECLARE_ARGUMENTS(__VA_ARGS__)) { \ body(__VA_ARGS__) \ \ struct objc_super superInfo = { \ .receiver = self, \ .super_class = class_getSuperclass(class) \ }; \ \ return_value (*msgSend)(struct objc_super *, SEL DECLARE_ARGUMENTS(__VA_ARGS__)) \ = (__typeof__(msgSend))objc_msgSendSuper; \ @try { \ return msgSend(&superInfo, selector ARGUMENTS(__VA_ARGS__)); \ } \ @finally { invoked_body(__VA_ARGS__) } \ }; \ \ thisIMP = imp_implementationWithBlock(newImplementation); \ return thisIMP; \ }; \ \ IMP (^replacementImplementationGenerator)(IMP) = ^(IMP originalImplementation) { \ __block return_value (*originalImplementationTyped)(__unsafe_unretained id, SEL DECLARE_ARGUMENTS(__VA_ARGS__) ) \ = (__typeof__(originalImplementationTyped))(originalImplementation); \ \ __block IMP thisIMP = nil; \ id implementationReplacement = ^return_value(__unsafe_unretained id self DECLARE_ARGUMENTS(__VA_ARGS__) ) { \ body(__VA_ARGS__) \ @try { \ return originalImplementationTyped(self, selector ARGUMENTS(__VA_ARGS__)); \ } \ @finally { invoked_body(__VA_ARGS__) } \ }; \ \ thisIMP = imp_implementationWithBlock(implementationReplacement); \ return thisIMP; \ }; \ \ return [self ensureSwizzledSelector:selector \ ofClass:class \ newImplementationGenerator:newImplementationGenerator \ replacementImplementationGenerator:replacementImplementationGenerator \ error:error]; \ } \ @interface RXObjCRuntime (InfrastructureMethods) @end // MARK: Infrastructure Methods @implementation RXObjCRuntime (InfrastructureMethods) SWIZZLE_INFRASTRUCTURE_METHOD( void, swizzleForwardInvocation, , @selector(forwardInvocation:), FORWARD_BODY, NSInvocationRef ) SWIZZLE_INFRASTRUCTURE_METHOD( BOOL, swizzleRespondsToSelector, , @selector(respondsToSelector:), RESPONDS_TO_SELECTOR_BODY, SEL ) SWIZZLE_INFRASTRUCTURE_METHOD( Class __nonnull, swizzleClass, toActAs:(Class)actAsClass, @selector(class), CLASS_BODY ) SWIZZLE_INFRASTRUCTURE_METHOD( NSMethodSignatureRef, swizzleMethodSignatureForSelector, , @selector(methodSignatureForSelector:), METHOD_SIGNATURE_FOR_SELECTOR_BODY, SEL ) SWIZZLE_INFRASTRUCTURE_METHOD( void, swizzleDeallocating, , deallocSelector, DEALLOCATING_BODY ) @end // MARK: Optimized intercepting methods for specific combination of parameter types @interface RXObjCRuntime (swizzle) @end @implementation RXObjCRuntime(swizzle) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, char) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, short) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, int) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, long) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, rx_uchar) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, rx_ushort) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, rx_uint) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, rx_ulong) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, rx_block) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, float) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, double) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, SEL) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, id) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, char) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, short) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, int) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, long) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, rx_uchar) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, rx_ushort) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, rx_uint) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, rx_ulong) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, rx_block) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, float) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, double) SWIZZLE_OBSERVE_METHOD_DEFINITIONS(void, id, SEL) +(void)load { SWIZZLE_OBSERVE_METHOD_BODY(void) SWIZZLE_OBSERVE_METHOD_BODY(void, id) SWIZZLE_OBSERVE_METHOD_BODY(void, char) SWIZZLE_OBSERVE_METHOD_BODY(void, short) SWIZZLE_OBSERVE_METHOD_BODY(void, int) SWIZZLE_OBSERVE_METHOD_BODY(void, long) SWIZZLE_OBSERVE_METHOD_BODY(void, rx_uchar) SWIZZLE_OBSERVE_METHOD_BODY(void, rx_ushort) SWIZZLE_OBSERVE_METHOD_BODY(void, rx_uint) SWIZZLE_OBSERVE_METHOD_BODY(void, rx_ulong) SWIZZLE_OBSERVE_METHOD_BODY(void, rx_block) SWIZZLE_OBSERVE_METHOD_BODY(void, float) SWIZZLE_OBSERVE_METHOD_BODY(void, double) SWIZZLE_OBSERVE_METHOD_BODY(void, SEL) SWIZZLE_OBSERVE_METHOD_BODY(void, id, id) SWIZZLE_OBSERVE_METHOD_BODY(void, id, char) SWIZZLE_OBSERVE_METHOD_BODY(void, id, short) SWIZZLE_OBSERVE_METHOD_BODY(void, id, int) SWIZZLE_OBSERVE_METHOD_BODY(void, id, long) SWIZZLE_OBSERVE_METHOD_BODY(void, id, rx_uchar) SWIZZLE_OBSERVE_METHOD_BODY(void, id, rx_ushort) SWIZZLE_OBSERVE_METHOD_BODY(void, id, rx_uint) SWIZZLE_OBSERVE_METHOD_BODY(void, id, rx_ulong) SWIZZLE_OBSERVE_METHOD_BODY(void, id, rx_block) SWIZZLE_OBSERVE_METHOD_BODY(void, id, float) SWIZZLE_OBSERVE_METHOD_BODY(void, id, double) SWIZZLE_OBSERVE_METHOD_BODY(void, id, SEL) } @end // MARK: RXObjCRuntime @implementation RXObjCRuntime static RXObjCRuntime *_instance = nil; static NSMutableDictionary *optimizedObserversByMethodEncoding = nil; +(RXObjCRuntime*)instance { return _instance; } +(void)initialize { _instance = [[RXObjCRuntime alloc] init]; defaultTypeID = CFGetTypeID((CFTypeRef)RXObjCRuntime.class); // just need a reference of some object not from CF deallocSelector = NSSelectorFromString(@"dealloc"); NSAssert(_instance != nil, @"Failed to initialize swizzling"); } -(instancetype)init { self = [super init]; if (!self) return nil; self.classesThatSupportObservingByForwarding = [NSMutableSet set]; self.forwardedSelectorsByClass = [NSMutableDictionary dictionary]; self.dynamicSubclassByRealClass = [NSMutableDictionary dictionary]; self.interceptorIMPbySelectorsByClass = [NSMutableDictionary dictionary]; pthread_mutexattr_t lock_attr; pthread_mutexattr_init(&lock_attr); pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&_lock, &lock_attr); pthread_mutexattr_destroy(&lock_attr); return self; } -(void)performLocked:(void (^)(RXObjCRuntime* __nonnull))action { pthread_mutex_lock(&_lock); action(self); pthread_mutex_unlock(&_lock); } +(void)registerOptimizedObserver:(RXInterceptWithOptimizedObserver)registration encodedAs:(SEL)selector { Method exampleEncodingMethod = class_getClassMethod(self, selector); ALWAYS(exampleEncodingMethod != nil, @"Example encoding method is nil."); NSString *methodEncoding = RX_method_encoding(exampleEncodingMethod); if (optimizedObserversByMethodEncoding == nil) { optimizedObserversByMethodEncoding = [NSMutableDictionary dictionary]; } DLOG(@"Added optimized method: %@ (%@)", methodEncoding, NSStringFromSelector(selector)); ALWAYS(optimizedObserversByMethodEncoding[methodEncoding] == nil, @"Optimized observer already registered") optimizedObserversByMethodEncoding[methodEncoding] = registration; } /** This is the main entry point for observing messages sent to arbitrary objects. */ -(IMP __nullable)ensurePrepared:(id __nonnull)target forObserving:(SEL __nonnull)selector error:(NSErrorParam)error { Method instanceMethod = class_getInstanceMethod([target class], selector); if (instanceMethod == nil) { RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain code:RXObjCRuntimeErrorSelectorNotImplemented userInfo:nil], nil); } if (selector == @selector(class) || selector == @selector(forwardingTargetForSelector:) || selector == @selector(methodSignatureForSelector:) || selector == @selector(respondsToSelector:)) { RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain code:RXObjCRuntimeErrorObservingPerformanceSensitiveMessages userInfo:nil], nil); } // For `dealloc` message, original implementation will be swizzled. // This is a special case because observing `dealloc` message is performed when `observeWeakly` is used. // // Some toll free bridged classes don't handle `object_setClass` well and cause crashes. // // To make `deallocating` as robust as possible, original implementation will be replaced. if (selector == deallocSelector) { Class __nonnull deallocSwizzingTarget = [target class]; IMP interceptorIMPForSelector = [self interceptorImplementationForSelector:selector forClass:deallocSwizzingTarget]; if (interceptorIMPForSelector != nil) { return interceptorIMPForSelector; } if (![self swizzleDeallocating:deallocSwizzingTarget error:error]) { return nil; } interceptorIMPForSelector = [self interceptorImplementationForSelector:selector forClass:deallocSwizzingTarget]; if (interceptorIMPForSelector != nil) { return interceptorIMPForSelector; } } else { Class __nullable swizzlingImplementorClass = [self prepareTargetClassForObserving:target error:error]; if (swizzlingImplementorClass == nil) { return nil; } NSString *methodEncoding = RX_method_encoding(instanceMethod); RXInterceptWithOptimizedObserver optimizedIntercept = optimizedObserversByMethodEncoding[methodEncoding]; if (!RX_method_has_supported_return_type(instanceMethod)) { RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain code:RXObjCRuntimeErrorObservingMessagesWithUnsupportedReturnType userInfo:nil], nil); } // optimized interception method if (optimizedIntercept != nil) { IMP interceptorIMPForSelector = [self interceptorImplementationForSelector:selector forClass:swizzlingImplementorClass]; if (interceptorIMPForSelector != nil) { return interceptorIMPForSelector; } if (!optimizedIntercept(self, swizzlingImplementorClass, selector, error)) { return nil; } interceptorIMPForSelector = [self interceptorImplementationForSelector:selector forClass:swizzlingImplementorClass]; if (interceptorIMPForSelector != nil) { return interceptorIMPForSelector; } } // default fallback to observing by forwarding messages else { if ([self forwardingSelector:selector forClass:swizzlingImplementorClass]) { return RX_default_target_implementation(); } if (![self observeByForwardingMessages:swizzlingImplementorClass selector:selector target:target error:error]) { return nil; } if ([self forwardingSelector:selector forClass:swizzlingImplementorClass]) { return RX_default_target_implementation(); } } } RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain code:RXObjCRuntimeErrorUnknown userInfo:nil], nil); } -(Class __nullable)prepareTargetClassForObserving:(id __nonnull)target error:(NSErrorParam)error { Class swizzlingClass = objc_getAssociatedObject(target, &RxSwizzlingTargetClassKey); if (swizzlingClass != nil) { return swizzlingClass; } Class __nonnull wannaBeClass = [target class]; /** Core Foundation classes are usually toll free bridged. Those classes crash the program in case `object_setClass` is performed on them. There is a possibility to just swizzle methods on original object, but since those won't be usual use cases for this library, then an error will just be reported for now. */ BOOL isThisTollFreeFoundationClass = CFGetTypeID((CFTypeRef)target) != defaultTypeID; if (isThisTollFreeFoundationClass) { RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain code:RXObjCRuntimeErrorCantInterceptCoreFoundationTollFreeBridgedObjects userInfo:nil], nil); } /** If the object is reporting a different class then what it's real class, that means that there is probably already some interception mechanism in place or something weird is happening. Most common case when this would happen is when using KVO (`observe`) and `sentMessage`. This error is easily resolved by just using `sentMessage` observing before `observe`. The reason why other way around could create issues is because KVO will unregister it's interceptor class and restore original class. Unfortunately that will happen no matter was there another interceptor subclass registered in hierarchy or not. Failure scenario: * KVO sets class to be `__KVO__OriginalClass` (subclass of `OriginalClass`) * `sentMessage` sets object class to be `_RX_namespace___KVO__OriginalClass` (subclass of `__KVO__OriginalClass`) * then unobserving with KVO will restore class to be `OriginalClass` -> failure point The reason why changing order of observing works is because any interception method should return object's original real class (if that doesn't happen then it's really easy to argue that's a bug in that other library). This library won't remove registered interceptor even if there aren't any observers left because it's highly unlikely it would have any benefit in real world use cases, and it's even more dangerous. */ if ([target class] != object_getClass(target)) { BOOL isKVO = [target respondsToSelector:NSSelectorFromString(@"_isKVOA")]; RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain code:RXObjCRuntimeErrorObjectMessagesAlreadyBeingIntercepted userInfo:@{ RXObjCRuntimeErrorIsKVOKey : @(isKVO) }], nil); } Class __nullable dynamicFakeSubclass = [self ensureHasDynamicFakeSubclass:wannaBeClass error:error]; if (dynamicFakeSubclass == nil) { return nil; } Class previousClass = object_setClass(target, dynamicFakeSubclass); if (previousClass != wannaBeClass) { THREADING_HAZARD(wannaBeClass); RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain code:RXObjCRuntimeErrorThreadingCollisionWithOtherInterceptionMechanism userInfo:nil], nil); } objc_setAssociatedObject(target, &RxSwizzlingTargetClassKey, dynamicFakeSubclass, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return dynamicFakeSubclass; } -(BOOL)forwardingSelector:(SEL)selector forClass:(Class __nonnull)class { return [self.forwardedSelectorsByClass[CLASS_VALUE(class)] containsObject:SEL_VALUE(selector)]; } -(void)registerForwardedSelector:(SEL)selector forClass:(Class __nonnull)class { NSValue *classValue = CLASS_VALUE(class); NSMutableSet *forwardedSelectors = self.forwardedSelectorsByClass[classValue]; if (forwardedSelectors == nil) { forwardedSelectors = [NSMutableSet set]; self.forwardedSelectorsByClass[classValue] = forwardedSelectors; } [forwardedSelectors addObject:SEL_VALUE(selector)]; } -(BOOL)observeByForwardingMessages:(Class __nonnull)swizzlingImplementorClass selector:(SEL)selector target:(id __nonnull)target error:(NSErrorParam)error { if (![self ensureForwardingMethodsAreSwizzled:swizzlingImplementorClass error:error]) { return NO; } ALWAYS(![self forwardingSelector:selector forClass:swizzlingImplementorClass], @"Already observing selector for class"); #if TRACE_RESOURCES atomic_fetch_add(&numberOfForwardedMethods, 1); #endif SEL rxSelector = RX_selector(selector); Method instanceMethod = class_getInstanceMethod(swizzlingImplementorClass, selector); ALWAYS(instanceMethod != nil, @"Instance method is nil"); const char* methodEncoding = method_getTypeEncoding(instanceMethod); ALWAYS(methodEncoding != nil, @"Method encoding is nil."); NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:methodEncoding]; ALWAYS(methodSignature != nil, @"Method signature is invalid."); IMP implementation = method_getImplementation(instanceMethod); if (implementation == nil) { RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain code:RXObjCRuntimeErrorSelectorNotImplemented userInfo:nil], NO); } if (!class_addMethod(swizzlingImplementorClass, rxSelector, implementation, methodEncoding)) { RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain code:RXObjCRuntimeErrorSavingOriginalForwardingMethodFailed userInfo:nil], NO); } if (!class_addMethod(swizzlingImplementorClass, selector, _objc_msgForward, methodEncoding)) { if (implementation != method_setImplementation(instanceMethod, _objc_msgForward)) { THREADING_HAZARD(swizzlingImplementorClass); RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain code:RXObjCRuntimeErrorReplacingMethodWithForwardingImplementation userInfo:nil], NO); } } DLOG(@"Rx uses forwarding to observe `%@` for `%@`.", NSStringFromSelector(selector), swizzlingImplementorClass); [self registerForwardedSelector:selector forClass:swizzlingImplementorClass]; return YES; } /** If object don't have some weird behavior, claims it's the same class that runtime shows, then dynamic subclass is created (only this instance will have performance hit). In case something weird is detected, then original base class is being swizzled and all instances will have somewhat reduced performance. This is especially handy optimization for weak KVO. Nobody will swizzle for example `NSString`, but to know when instance of a `NSString` was deallocated, performance hit will be only felt on a single instance of `NSString`, not all instances of `NSString`s. */ -(Class __nullable)ensureHasDynamicFakeSubclass:(Class __nonnull)class error:(NSErrorParam)error { Class dynamicFakeSubclass = self.dynamicSubclassByRealClass[CLASS_VALUE(class)]; if (dynamicFakeSubclass != nil) { return dynamicFakeSubclass; } NSString *dynamicFakeSubclassName = [RX_PREFIX stringByAppendingString:NSStringFromClass(class)]; const char *dynamicFakeSubclassNameRaw = dynamicFakeSubclassName.UTF8String; dynamicFakeSubclass = objc_allocateClassPair(class, dynamicFakeSubclassNameRaw, 0); ALWAYS(dynamicFakeSubclass != nil, @"Class not generated"); if (![self swizzleClass:dynamicFakeSubclass toActAs:class error:error]) { return nil; } objc_registerClassPair(dynamicFakeSubclass); [self.dynamicSubclassByRealClass setObject:dynamicFakeSubclass forKey:CLASS_VALUE(class)]; ALWAYS(self.dynamicSubclassByRealClass[CLASS_VALUE(class)] != nil, @"Class not registered"); return dynamicFakeSubclass; } -(BOOL)ensureForwardingMethodsAreSwizzled:(Class __nonnull)class error:(NSErrorParam)error { NSValue *classValue = CLASS_VALUE(class); if ([self.classesThatSupportObservingByForwarding containsObject:classValue]) { return YES; } if (![self swizzleForwardInvocation:class error:error]) { return NO; } if (![self swizzleMethodSignatureForSelector:class error:error]) { return NO; } if (![self swizzleRespondsToSelector:class error:error]) { return NO; } [self.classesThatSupportObservingByForwarding addObject:classValue]; return YES; } -(void)registerInterceptedSelector:(SEL)selector implementation:(IMP)implementation forClass:(Class)class { NSValue * __nonnull classValue = CLASS_VALUE(class); NSValue * __nonnull selectorValue = SEL_VALUE(selector); NSMutableDictionary *swizzledIMPBySelectorsForClass = self.interceptorIMPbySelectorsByClass[classValue]; if (swizzledIMPBySelectorsForClass == nil) { swizzledIMPBySelectorsForClass = [NSMutableDictionary dictionary]; self.interceptorIMPbySelectorsByClass[classValue] = swizzledIMPBySelectorsForClass; } swizzledIMPBySelectorsForClass[selectorValue] = IMP_VALUE(implementation); ALWAYS([self interceptorImplementationForSelector:selector forClass:class] != nil, @"Class should have been swizzled"); } -(IMP)interceptorImplementationForSelector:(SEL)selector forClass:(Class)class { NSValue * __nonnull classValue = CLASS_VALUE(class); NSValue * __nonnull selectorValue = SEL_VALUE(selector); NSMutableDictionary *swizzledIMPBySelectorForClass = self.interceptorIMPbySelectorsByClass[classValue]; NSValue *impValue = swizzledIMPBySelectorForClass[selectorValue]; return impValue.pointerValue; } -(BOOL)ensureSwizzledSelector:(SEL __nonnull)selector ofClass:(Class __nonnull)class newImplementationGenerator:(IMP(^)(void))newImplementationGenerator replacementImplementationGenerator:(IMP (^)(IMP originalImplementation))replacementImplementationGenerator error:(NSErrorParam)error { if ([self interceptorImplementationForSelector:selector forClass:class] != nil) { DLOG(@"Trying to register same intercept at least once, this sounds like a possible bug"); return YES; } #if TRACE_RESOURCES atomic_fetch_add(&numberOInterceptedMethods, 1); #endif DLOG(@"Rx is swizzling `%@` for `%@`", NSStringFromSelector(selector), class); Method existingMethod = class_getInstanceMethod(class, selector); ALWAYS(existingMethod != nil, @"Method doesn't exist"); const char *encoding = method_getTypeEncoding(existingMethod); ALWAYS(encoding != nil, @"Encoding is nil"); IMP newImplementation = newImplementationGenerator(); if (class_addMethod(class, selector, newImplementation, encoding)) { // new method added, job done [self registerInterceptedSelector:selector implementation:newImplementation forClass:class]; return YES; } imp_removeBlock(newImplementation); // if add fails, that means that method already exists on targetClass Method existingMethodOnTargetClass = existingMethod; IMP originalImplementation = method_getImplementation(existingMethodOnTargetClass); ALWAYS(originalImplementation != nil, @"Method must exist."); IMP implementationReplacementIMP = replacementImplementationGenerator(originalImplementation); ALWAYS(implementationReplacementIMP != nil, @"Method must exist."); IMP originalImplementationAfterChange = method_setImplementation(existingMethodOnTargetClass, implementationReplacementIMP); ALWAYS(originalImplementation != nil, @"Method must exist."); // If method replacing failed, who knows what happened, better not trying again, otherwise program can get // corrupted. [self registerInterceptedSelector:selector implementation:implementationReplacementIMP forClass:class]; // ¯\_(ツ)_/¯ if (originalImplementationAfterChange != originalImplementation) { THREADING_HAZARD(class); return NO; } return YES; } @end #if TRACE_RESOURCES NSInteger RX_number_of_dynamic_subclasses(void) { __block NSInteger count = 0; [[RXObjCRuntime instance] performLocked:^(RXObjCRuntime * __nonnull self) { count = self.dynamicSubclassByRealClass.count; }]; return count; } NSInteger RX_number_of_forwarding_enabled_classes(void) { __block NSInteger count = 0; [[RXObjCRuntime instance] performLocked:^(RXObjCRuntime * __nonnull self) { count = self.classesThatSupportObservingByForwarding.count; }]; return count; } NSInteger RX_number_of_intercepting_classes(void) { __block NSInteger count = 0; [[RXObjCRuntime instance] performLocked:^(RXObjCRuntime * __nonnull self) { count = self.interceptorIMPbySelectorsByClass.count; }]; return count; } NSInteger RX_number_of_forwarded_methods(void) { return numberOfForwardedMethods; } NSInteger RX_number_of_swizzled_methods(void) { return numberOInterceptedMethods; } #endif #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Runtime/include/RxCocoaRuntime.h ================================================ // // RxCocoaRuntime.h // RxCocoa // // Created by Krunoslav Zaher on 2/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #import #import "_RX.h" #import "_RXDelegateProxy.h" #import "_RXKVOObserver.h" #import "_RXObjCRuntime.h" //! Project version number for RxCocoa. FOUNDATION_EXPORT double RxCocoaVersionNumber; //! Project version string for RxCocoa. FOUNDATION_EXPORT const unsigned char RxCocoaVersionString[]; ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Runtime/include/_RX.h ================================================ // // _RX.h // RxCocoa // // Created by Krunoslav Zaher on 7/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #import #import /** ################################################################################ This file is part of RX private API ################################################################################ */ #if TRACE_RESOURCES >= 2 # define DLOG(...) NSLog(__VA_ARGS__) #else # define DLOG(...) #endif #if DEBUG # define ABORT_IN_DEBUG abort(); #else # define ABORT_IN_DEBUG #endif #define SEL_VALUE(x) [NSValue valueWithPointer:(x)] #define CLASS_VALUE(x) [NSValue valueWithNonretainedObject:(x)] #define IMP_VALUE(x) [NSValue valueWithPointer:(x)] /** Checks that the local `error` instance exists before assigning it's value by reference. This macro exists to work around static analysis warnings — `NSError` is always assumed to be `nullable`, even though we explicitly define the method parameter as `nonnull`. See http://www.openradar.me/21766176 for more details. */ #define RX_THROW_ERROR(errorValue, returnValue) if (error != nil) { *error = (errorValue); } return (returnValue); #define RX_CAT2(_1, _2) _RX_CAT2(_1, _2) #define _RX_CAT2(_1, _2) _1 ## _2 #define RX_ELEMENT_AT(n, ...) RX_CAT2(_RX_ELEMENT_AT_, n)(__VA_ARGS__) #define _RX_ELEMENT_AT_0(x, ...) x #define _RX_ELEMENT_AT_1(_0, x, ...) x #define _RX_ELEMENT_AT_2(_0, _1, x, ...) x #define _RX_ELEMENT_AT_3(_0, _1, _2, x, ...) x #define _RX_ELEMENT_AT_4(_0, _1, _2, _3, x, ...) x #define _RX_ELEMENT_AT_5(_0, _1, _2, _3, _4, x, ...) x #define _RX_ELEMENT_AT_6(_0, _1, _2, _3, _4, _5, x, ...) x #define RX_COUNT(...) RX_ELEMENT_AT(6, ## __VA_ARGS__, 6, 5, 4, 3, 2, 1, 0) #define RX_EMPTY(...) RX_ELEMENT_AT(6, ## __VA_ARGS__, 0, 0, 0, 0, 0, 0, 1) /** #define SUM(context, index, head, tail) head + tail #define MAP(context, index, element) (context)[index] * (element) RX_FOR(numbers, SUM, MAP, b0, b1, b2); (numbers)[0] * (b0) + (numbers)[1] * (b1) + (numbers[2]) * (b2) */ #define RX_FOREACH(context, concat, map, ...) RX_FOR_MAX(RX_COUNT(__VA_ARGS__), _RX_FOREACH_CONCAT, _RX_FOREACH_MAP, context, concat, map, __VA_ARGS__) #define _RX_FOREACH_CONCAT(index, head, tail, context, concat, map, ...) concat(context, index, head, tail) #define _RX_FOREACH_MAP(index, context, concat, map, ...) map(context, index, RX_ELEMENT_AT(index, __VA_ARGS__)) /** #define MAP(context, index, item) (context)[index] * (item) RX_FOR_COMMA(numbers, MAP, b0, b1); ,(numbers)[0] * b0, (numbers)[1] * b1 */ #define RX_FOREACH_COMMA(context, map, ...) RX_CAT2(_RX_FOREACH_COMMA_EMPTY_, RX_EMPTY(__VA_ARGS__))(context, map, ## __VA_ARGS__) #define _RX_FOREACH_COMMA_EMPTY_1(context, map, ...) #define _RX_FOREACH_COMMA_EMPTY_0(context, map, ...) , RX_FOR_MAX(RX_COUNT(__VA_ARGS__), _RX_FOREACH_COMMA_CONCAT, _RX_FOREACH_COMMA_MAP, context, map, __VA_ARGS__) #define _RX_FOREACH_COMMA_CONCAT(index, head, tail, context, map, ...) head, tail #define _RX_FOREACH_COMMA_MAP(index, context, map, ...) map(context, index, RX_ELEMENT_AT(index, __VA_ARGS__)) // rx for #define RX_FOR_MAX(max, concat, map, ...) RX_CAT2(RX_FOR_, max)(concat, map, ## __VA_ARGS__) #define RX_FOR_0(concat, map, ...) #define RX_FOR_1(concat, map, ...) map(0, __VA_ARGS__) #define RX_FOR_2(concat, map, ...) concat(1, RX_FOR_1(concat, map, ## __VA_ARGS__), map(1, __VA_ARGS__), __VA_ARGS__) #define RX_FOR_3(concat, map, ...) concat(2, RX_FOR_2(concat, map, ## __VA_ARGS__), map(2, __VA_ARGS__), __VA_ARGS__) #define RX_FOR_4(concat, map, ...) concat(3, RX_FOR_3(concat, map, ## __VA_ARGS__), map(3, __VA_ARGS__), __VA_ARGS__) #define RX_FOR_5(concat, map, ...) concat(4, RX_FOR_4(concat, map, ## __VA_ARGS__), map(4, __VA_ARGS__), __VA_ARGS__) #define RX_FOR_6(concat, map, ...) concat(5, RX_FOR_5(concat, map, ## __VA_ARGS__), map(5, __VA_ARGS__), __VA_ARGS__) ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Runtime/include/_RXDelegateProxy.h ================================================ // // _RXDelegateProxy.h // RxCocoa // // Created by Krunoslav Zaher on 7/4/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface _RXDelegateProxy : NSObject @property (nonatomic, weak, readonly) id _forwardToDelegate; -(void)_setForwardToDelegate:(id __nullable)forwardToDelegate retainDelegate:(BOOL)retainDelegate NS_SWIFT_NAME(_setForwardToDelegate(_:retainDelegate:)) ; -(BOOL)hasWiredImplementationForSelector:(SEL)selector; -(BOOL)voidDelegateMethodsContain:(SEL)selector; -(void)_sentMessage:(SEL)selector withArguments:(NSArray*)arguments; -(void)_methodInvoked:(SEL)selector withArguments:(NSArray*)arguments; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Runtime/include/_RXKVOObserver.h ================================================ // // _RXKVOObserver.h // RxCocoa // // Created by Krunoslav Zaher on 7/11/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #import /** ################################################################################ This file is part of RX private API ################################################################################ */ // Exists because if written in Swift, reading unowned is disabled during dealloc process @interface _RXKVOObserver : NSObject -(instancetype)initWithTarget:(id)target retainTarget:(BOOL)retainTarget keyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options callback:(void (^)(id))callback; -(void)dispose; @end ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Runtime/include/_RXObjCRuntime.h ================================================ // // _RXObjCRuntime.h // RxCocoa // // Created by Krunoslav Zaher on 7/11/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #import #if !DISABLE_SWIZZLING /** ################################################################################ This file is part of RX private API ################################################################################ */ /** This flag controls `RELEASE` configuration behavior in case race was detecting while modifying ObjC runtime. In case this value is set to `YES`, after runtime race is detected, `abort()` will be called. Otherwise, only error will be reported using normal error reporting mechanism. In `DEBUG` mode `abort` will be always called in case race is detected. Races can't happen in case this is the only library modifying ObjC runtime, but in case there are multiple libraries changing ObjC runtime, race conditions can occur because there is no way to synchronize multiple libraries unaware of each other. To help remedy this situation this library will use `synchronized` on target object and it's meta-class, but there aren't any guarantees of how other libraries will behave. Default value is `NO`. */ extern BOOL RXAbortOnThreadingHazard; /// Error domain for RXObjCRuntime. extern NSString * __nonnull const RXObjCRuntimeErrorDomain; /// `userInfo` key with additional information is interceptor probably KVO. extern NSString * __nonnull const RXObjCRuntimeErrorIsKVOKey; typedef NS_ENUM(NSInteger, RXObjCRuntimeError) { RXObjCRuntimeErrorUnknown = 1, RXObjCRuntimeErrorObjectMessagesAlreadyBeingIntercepted = 2, RXObjCRuntimeErrorSelectorNotImplemented = 3, RXObjCRuntimeErrorCantInterceptCoreFoundationTollFreeBridgedObjects = 4, RXObjCRuntimeErrorThreadingCollisionWithOtherInterceptionMechanism = 5, RXObjCRuntimeErrorSavingOriginalForwardingMethodFailed = 6, RXObjCRuntimeErrorReplacingMethodWithForwardingImplementation = 7, RXObjCRuntimeErrorObservingPerformanceSensitiveMessages = 8, RXObjCRuntimeErrorObservingMessagesWithUnsupportedReturnType = 9, }; /// Transforms normal selector into a selector with RX prefix. SEL _Nonnull RX_selector(SEL _Nonnull selector); /// Transforms selector into a unique pointer (because of Swift conversion rules) void * __nonnull RX_reference_from_selector(SEL __nonnull selector); /// Protocol that interception observers must implement. @protocol RXMessageSentObserver /// In case the same selector is being intercepted for a pair of base/sub classes, /// this property will differentiate between interceptors that need to fire. @property (nonatomic, assign, readonly) IMP __nonnull targetImplementation; -(void)messageSentWithArguments:(NSArray* __nonnull)arguments; -(void)methodInvokedWithArguments:(NSArray* __nonnull)arguments; @end /// Protocol that deallocating observer must implement. @protocol RXDeallocatingObserver /// In case the same selector is being intercepted for a pair of base/sub classes, /// this property will differentiate between interceptors that need to fire. @property (nonatomic, assign, readonly) IMP __nonnull targetImplementation; -(void)deallocating; @end /// Ensures interceptor is installed on target object. IMP __nullable RX_ensure_observing(id __nonnull target, SEL __nonnull selector, NSError *__autoreleasing __nullable * __nullable error); #endif /// Extracts arguments for `invocation`. NSArray * __nonnull RX_extract_arguments(NSInvocation * __nonnull invocation); /// Returns `YES` in case method has `void` return type. BOOL RX_is_method_with_description_void(struct objc_method_description method); /// Returns `YES` in case methodSignature has `void` return type. BOOL RX_is_method_signature_void(NSMethodSignature * __nonnull methodSignature); /// Default value for `RXInterceptionObserver.targetImplementation`. IMP __nonnull RX_default_target_implementation(void); ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/RxCocoa.h ================================================ // // RxCocoa.h // RxCocoa // // Created by Krunoslav Zaher on 2/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #import #import #import #import #import //! Project version number for RxCocoa. FOUNDATION_EXPORT double RxCocoaVersionNumber; //! Project version string for RxCocoa. FOUNDATION_EXPORT const unsigned char RxCocoaVersionString[]; ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/RxCocoa.swift ================================================ // // RxCocoa.swift // RxCocoa // // Created by Krunoslav Zaher on 2/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Foundation // Importing RxCocoa also imports RxRelay @_exported import RxRelay import RxSwift #if os(iOS) import UIKit #endif /// RxCocoa errors. public enum RxCocoaError : Swift.Error , CustomDebugStringConvertible { /// Unknown error has occurred. case unknown /// Invalid operation was attempted. case invalidOperation(object: Any) /// Items are not yet bound to user interface but have been requested. case itemsNotYetBound(object: Any) /// Invalid KVO Path. case invalidPropertyName(object: Any, propertyName: String) /// Invalid object on key path. case invalidObjectOnKeyPath(object: Any, sourceObject: AnyObject, propertyName: String) /// Error during swizzling. case errorDuringSwizzling /// Casting error. case castingError(object: Any, targetType: Any.Type) } // MARK: Debug descriptions extension RxCocoaError { /// A textual representation of `self`, suitable for debugging. public var debugDescription: String { switch self { case .unknown: return "Unknown error occurred." case let .invalidOperation(object): return "Invalid operation was attempted on `\(object)`." case let .itemsNotYetBound(object): return "Data source is set, but items are not yet bound to user interface for `\(object)`." case let .invalidPropertyName(object, propertyName): return "Object `\(object)` doesn't have a property named `\(propertyName)`." case let .invalidObjectOnKeyPath(object, sourceObject, propertyName): return "Unobservable object `\(object)` was observed as `\(propertyName)` of `\(sourceObject)`." case .errorDuringSwizzling: return "Error during swizzling." case let .castingError(object, targetType): return "Error casting `\(object)` to `\(targetType)`" } } } // MARK: Error binding policies func bindingError(_ error: Swift.Error) { let error = "Binding error: \(error)" #if DEBUG rxFatalError(error) #else print(error) #endif } /// Swift does not implement abstract methods. This method is used as a runtime check to ensure that methods which intended to be abstract (i.e., they should be implemented in subclasses) are not called directly on the superclass. func rxAbstractMethod(message: String = "Abstract method", file: StaticString = #file, line: UInt = #line) -> Swift.Never { rxFatalError(message, file: file, line: line) } func rxFatalError(_ lastMessage: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) -> Swift.Never { // The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours. fatalError(lastMessage(), file: file, line: line) } func rxFatalErrorInDebug(_ lastMessage: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) { #if DEBUG fatalError(lastMessage(), file: file, line: line) #else print("\(file):\(line): \(lastMessage())") #endif } // MARK: casts or fatal error // workaround for Swift compiler bug, cheers compiler team :) func castOptionalOrFatalError(_ value: Any?) -> T? { if value == nil { return nil } let v: T = castOrFatalError(value) return v } func castOrThrow(_ resultType: T.Type, _ object: Any) throws -> T { guard let returnValue = object as? T else { throw RxCocoaError.castingError(object: object, targetType: resultType) } return returnValue } func castOptionalOrThrow(_ resultType: T.Type, _ object: AnyObject) throws -> T? { if NSNull().isEqual(object) { return nil } guard let returnValue = object as? T else { throw RxCocoaError.castingError(object: object, targetType: resultType) } return returnValue } func castOrFatalError(_ value: AnyObject!, message: String) -> T { let maybeResult: T? = value as? T guard let result = maybeResult else { rxFatalError(message) } return result } func castOrFatalError(_ value: Any!) -> T { let maybeResult: T? = value as? T guard let result = maybeResult else { rxFatalError("Failure converting from \(String(describing: value)) to \(T.self)") } return result } // MARK: Error messages let dataSourceNotSet = "DataSource not set" let delegateNotSet = "Delegate not set" // MARK: Shared with RxSwift func rxFatalError(_ lastMessage: String) -> Never { // The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours. fatalError(lastMessage) } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/ControlEvent.swift ================================================ // // ControlEvent.swift // RxCocoa // // Created by Krunoslav Zaher on 8/28/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift /// A protocol that extends `ControlEvent`. public protocol ControlEventType : ObservableType { /// - returns: `ControlEvent` interface func asControlEvent() -> ControlEvent } /** A trait for `Observable`/`ObservableType` that represents an event on a UI element. Properties: - it doesn’t send any initial value on subscription, - it `Complete`s the sequence when the control deallocates, - it never errors out - it delivers events on `MainScheduler.instance`. **The implementation of `ControlEvent` will ensure that sequence of events is being subscribed on main scheduler (`subscribe(on: ConcurrentMainScheduler.instance)` behavior).** **It is the implementor’s responsibility to make sure that all other properties enumerated above are satisfied.** **If they aren’t, using this trait will communicate wrong properties, and could potentially break someone’s code.** **If the `events` observable sequence passed into the initializer doesn’t satisfy all enumerated properties, don’t use this trait.** */ public struct ControlEvent : ControlEventType { public typealias Element = PropertyType let events: Observable /// Initializes control event with a observable sequence that represents events. /// /// - parameter events: Observable sequence that represents events. /// - returns: Control event created with a observable sequence of events. public init(events: Ev) where Ev.Element == Element { self.events = events.subscribe(on: ConcurrentMainScheduler.instance) } /// Subscribes an observer to control events. /// /// - parameter observer: Observer to subscribe to events. /// - returns: Disposable object that can be used to unsubscribe the observer from receiving control events. public func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { self.events.subscribe(observer) } /// - returns: `Observable` interface. public func asObservable() -> Observable { self.events } /// - returns: `ControlEvent` interface. public func asControlEvent() -> ControlEvent { self } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/ControlProperty.swift ================================================ // // ControlProperty.swift // RxCocoa // // Created by Krunoslav Zaher on 8/28/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift /// Protocol that enables extension of `ControlProperty`. public protocol ControlPropertyType : ObservableType, ObserverType { /// - returns: `ControlProperty` interface func asControlProperty() -> ControlProperty } /** Trait for `Observable`/`ObservableType` that represents property of UI element. Sequence of values only represents initial control value and user initiated value changes. Programmatic value changes won't be reported. It's properties are: - `shareReplay(1)` behavior - it's stateful, upon subscription (calling subscribe) last element is immediately replayed if it was produced - it will `Complete` sequence on control being deallocated - it never errors out - it delivers events on `MainScheduler.instance` **The implementation of `ControlProperty` will ensure that sequence of values is being subscribed on main scheduler (`subscribe(on: ConcurrentMainScheduler.instance)` behavior).** **It is implementor's responsibility to make sure that that all other properties enumerated above are satisfied.** **If they aren't, then using this trait communicates wrong properties and could potentially break someone's code.** **In case `values` observable sequence that is being passed into initializer doesn't satisfy all enumerated properties, please don't use this trait.** */ public struct ControlProperty : ControlPropertyType { public typealias Element = PropertyType let values: Observable let valueSink: AnyObserver /// Initializes control property with a observable sequence that represents property values and observer that enables /// binding values to property. /// /// - parameter values: Observable sequence that represents property values. /// - parameter valueSink: Observer that enables binding values to control property. /// - returns: Control property created with a observable sequence of values and an observer that enables binding values /// to property. public init(values: Values, valueSink: Sink) where Element == Values.Element, Element == Sink.Element { self.values = values.subscribe(on: ConcurrentMainScheduler.instance) self.valueSink = valueSink.asObserver() } /// Subscribes an observer to control property values. /// /// - parameter observer: Observer to subscribe to property values. /// - returns: Disposable object that can be used to unsubscribe the observer from receiving control property values. public func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { self.values.subscribe(observer) } /// `ControlEvent` of user initiated value changes. Every time user updates control value change event /// will be emitted from `changed` event. /// /// Programmatic changes to control value won't be reported. /// /// It contains all control property values except for first one. /// /// The name only implies that sequence element will be generated once user changes a value and not that /// adjacent sequence values need to be different (e.g. because of interaction between programmatic and user updates, /// or for any other reason). public var changed: ControlEvent { ControlEvent(events: self.values.skip(1)) } /// - returns: `Observable` interface. public func asObservable() -> Observable { self.values } /// - returns: `ControlProperty` interface. public func asControlProperty() -> ControlProperty { self } /// Binds event to user interface. /// /// - In case next element is received, it is being set to control value. /// - In case error is received, DEBUG builds raise fatal error, RELEASE builds log event to standard output. /// - In case sequence completes, nothing happens. public func on(_ event: Event) { switch event { case .error(let error): bindingError(error) case .next: self.valueSink.on(event) case .completed: self.valueSink.on(event) } } } extension ControlPropertyType where Element == String? { /// Transforms control property of type `String?` into control property of type `String`. public var orEmpty: ControlProperty { let original: ControlProperty = self.asControlProperty() let values: Observable = original.values.map { $0 ?? "" } let valueSink: AnyObserver = original.valueSink.mapObserver { $0 } return ControlProperty(values: values, valueSink: valueSink) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Driver/BehaviorRelay+Driver.swift ================================================ // // BehaviorRelay+Driver.swift // RxCocoa // // Created by Krunoslav Zaher on 10/7/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // import RxSwift import RxRelay extension BehaviorRelay { /// Converts `BehaviorRelay` to `Driver`. /// /// - returns: Observable sequence. public func asDriver() -> Driver { let source = self.asObservable() .observe(on:DriverSharingStrategy.scheduler) return SharedSequence(source) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Driver/ControlEvent+Driver.swift ================================================ // // ControlEvent+Driver.swift // RxCocoa // // Created by Krunoslav Zaher on 9/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift extension ControlEvent { /// Converts `ControlEvent` to `Driver` trait. /// /// `ControlEvent` already can't fail, so no special case needs to be handled. public func asDriver() -> Driver { return self.asDriver { _ -> Driver in #if DEBUG rxFatalError("Somehow driver received error from a source that shouldn't fail.") #else return Driver.empty() #endif } } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Driver/ControlProperty+Driver.swift ================================================ // // ControlProperty+Driver.swift // RxCocoa // // Created by Krunoslav Zaher on 9/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift extension ControlProperty { /// Converts `ControlProperty` to `Driver` trait. /// /// `ControlProperty` already can't fail, so no special case needs to be handled. public func asDriver() -> Driver { return self.asDriver { _ -> Driver in #if DEBUG rxFatalError("Somehow driver received error from a source that shouldn't fail.") #else return Driver.empty() #endif } } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Driver/Driver+Subscription.swift ================================================ // // Driver+Subscription.swift // RxCocoa // // Created by Krunoslav Zaher on 9/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift import RxRelay private let errorMessage = "`drive*` family of methods can be only called from `MainThread`.\n" + "This is required to ensure that the last replayed `Driver` element is delivered on `MainThread`.\n" extension SharedSequenceConvertibleType where SharingStrategy == DriverSharingStrategy { /** Creates new subscription and sends elements to observer. This method can be only called from `MainThread`. In this form it's equivalent to `subscribe` method, but it communicates intent better. - parameter observers: Observers that receives events. - returns: Disposable object that can be used to unsubscribe the observer from the subject. */ public func drive(_ observers: Observer...) -> Disposable where Observer.Element == Element { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return self.asSharedSequence() .asObservable() .subscribe { e in observers.forEach { $0.on(e) } } } /** Creates new subscription and sends elements to observer. This method can be only called from `MainThread`. In this form it's equivalent to `subscribe` method, but it communicates intent better. - parameter observers: Observers that receives events. - returns: Disposable object that can be used to unsubscribe the observer from the subject. */ public func drive(_ observers: Observer...) -> Disposable where Observer.Element == Element? { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return self.asSharedSequence() .asObservable() .map { $0 as Element? } .subscribe { e in observers.forEach { $0.on(e) } } } /** Creates new subscription and sends elements to `BehaviorRelay`. This method can be only called from `MainThread`. - parameter relays: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func drive(_ relays: BehaviorRelay...) -> Disposable { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return self.drive(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `BehaviorRelay`. This method can be only called from `MainThread`. - parameter relays: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func drive(_ relays: BehaviorRelay...) -> Disposable { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return self.drive(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `ReplayRelay`. This method can be only called from `MainThread`. - parameter relays: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func drive(_ relays: ReplayRelay...) -> Disposable { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return self.drive(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `ReplayRelay`. This method can be only called from `MainThread`. - parameter relays: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func drive(_ relays: ReplayRelay...) -> Disposable { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return self.drive(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Subscribes to observable sequence using custom binder function. This method can be only called from `MainThread`. - parameter transformation: Function used to bind elements from `self`. - returns: Object representing subscription. */ public func drive(_ transformation: (Observable) -> Result) -> Result { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return transformation(self.asObservable()) } /** Subscribes to observable sequence using custom binder function and final parameter passed to binder function after `self` is passed. public func drive(with: Self -> R1 -> R2, curriedArgument: R1) -> R2 { return with(self)(curriedArgument) } This method can be only called from `MainThread`. - parameter with: Function used to bind elements from `self`. - parameter curriedArgument: Final argument passed to `binder` to finish binding process. - returns: Object representing subscription. */ public func drive(_ with: (Observable) -> (R1) -> R2, curriedArgument: R1) -> R2 { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return with(self.asObservable())(curriedArgument) } /** Subscribes an element handler, a completion handler and disposed handler to an observable sequence. This method can be only called from `MainThread`. Also, take in an object and provide an unretained, safe to use (i.e. not implicitly unwrapped), reference to it along with the events emitted by the sequence. Error callback is not exposed because `Driver` can't error out. - Note: If `object` can't be retained, none of the other closures will be invoked. - parameter object: The object to provide an unretained reference on. - parameter onNext: Action to invoke for each element in the observable sequence. - parameter onCompleted: Action to invoke upon graceful termination of the observable sequence. gracefully completed, errored, or if the generation is canceled by disposing subscription) - parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has gracefully completed, errored, or if the generation is canceled by disposing subscription) - returns: Subscription object used to unsubscribe from the observable sequence. */ public func drive( with object: Object, onNext: ((Object, Element) -> Void)? = nil, onCompleted: ((Object) -> Void)? = nil, onDisposed: ((Object) -> Void)? = nil ) -> Disposable { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return self.asObservable().subscribe(with: object, onNext: onNext, onCompleted: onCompleted, onDisposed: onDisposed) } /** Subscribes an element handler, a completion handler and disposed handler to an observable sequence. This method can be only called from `MainThread`. Error callback is not exposed because `Driver` can't error out. - parameter onNext: Action to invoke for each element in the observable sequence. - parameter onCompleted: Action to invoke upon graceful termination of the observable sequence. gracefully completed, errored, or if the generation is canceled by disposing subscription) - parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has gracefully completed, errored, or if the generation is canceled by disposing subscription) - returns: Subscription object used to unsubscribe from the observable sequence. */ public func drive( onNext: ((Element) -> Void)? = nil, onCompleted: (() -> Void)? = nil, onDisposed: (() -> Void)? = nil ) -> Disposable { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return self.asObservable().subscribe(onNext: onNext, onCompleted: onCompleted, onDisposed: onDisposed) } /** Subscribes to this `Driver` with a no-op. This method can be only called from `MainThread`. - note: This is an alias of `drive(onNext: nil, onCompleted: nil, onDisposed: nil)` used to fix an ambiguity bug in Swift: https://bugs.swift.org/browse/SR-13657 - returns: Subscription object used to unsubscribe from the observable sequence. */ public func drive() -> Disposable { drive(onNext: nil, onCompleted: nil, onDisposed: nil) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Driver/Driver.swift ================================================ // // Driver.swift // RxCocoa // // Created by Krunoslav Zaher on 9/26/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // import RxSwift /** Trait that represents observable sequence with following properties: - it never fails - it delivers events on `MainScheduler.instance` - `share(replay: 1, scope: .whileConnected)` sharing strategy Additional explanation: - all observers share sequence computation resources - it's stateful, upon subscription (calling subscribe) last element is immediately replayed if it was produced - computation of elements is reference counted with respect to the number of observers - if there are no subscribers, it will release sequence computation resources In case trait that models event bus is required, please check `Signal`. `Driver` can be considered a builder pattern for observable sequences that drive the application. If observable sequence has produced at least one element, after new subscription is made last produced element will be immediately replayed on the same thread on which the subscription was made. When using `drive*`, `subscribe*` and `bind*` family of methods, they should always be called from main thread. If `drive*`, `subscribe*` and `bind*` are called from background thread, it is possible that initial replay will happen on background thread, and subsequent events will arrive on main thread. To find out more about traits and how to use them, please visit `Documentation/Traits.md`. */ public typealias Driver = SharedSequence public struct DriverSharingStrategy: SharingStrategyProtocol { public static var scheduler: SchedulerType { SharingScheduler.make() } public static func share(_ source: Observable) -> Observable { source.share(replay: 1, scope: .whileConnected) } } extension SharedSequenceConvertibleType where SharingStrategy == DriverSharingStrategy { /// Adds `asDriver` to `SharingSequence` with `DriverSharingStrategy`. public func asDriver() -> Driver { self.asSharedSequence() } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Driver/ObservableConvertibleType+Driver.swift ================================================ // // ObservableConvertibleType+Driver.swift // RxCocoa // // Created by Krunoslav Zaher on 9/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift extension ObservableConvertibleType { /** Converts observable sequence to `Driver` trait. - parameter onErrorJustReturn: Element to return in case of error and after that complete the sequence. - returns: Driver trait. */ public func asDriver(onErrorJustReturn: Element) -> Driver { let source = self .asObservable() .observe(on:DriverSharingStrategy.scheduler) .catchAndReturn(onErrorJustReturn) return Driver(source) } /** Converts observable sequence to `Driver` trait. - parameter onErrorDriveWith: Driver that continues to drive the sequence in case of error. - returns: Driver trait. */ public func asDriver(onErrorDriveWith: Driver) -> Driver { let source = self .asObservable() .observe(on:DriverSharingStrategy.scheduler) .catch { _ in onErrorDriveWith.asObservable() } return Driver(source) } /** Converts observable sequence to `Driver` trait. - parameter onErrorRecover: Calculates driver that continues to drive the sequence in case of error. - returns: Driver trait. */ public func asDriver(onErrorRecover: @escaping (_ error: Swift.Error) -> Driver) -> Driver { let source = self .asObservable() .observe(on:DriverSharingStrategy.scheduler) .catch { error in onErrorRecover(error).asObservable() } return Driver(source) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/SharedSequence/ObservableConvertibleType+SharedSequence.swift ================================================ // // ObservableConvertibleType+SharedSequence.swift // RxCocoa // // Created by Krunoslav Zaher on 11/1/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // import RxSwift extension ObservableConvertibleType { /** Converts anything convertible to `Observable` to `SharedSequence` unit. - parameter onErrorJustReturn: Element to return in case of error and after that complete the sequence. - returns: Driving observable sequence. */ public func asSharedSequence(sharingStrategy: S.Type = S.self, onErrorJustReturn: Element) -> SharedSequence { let source = self .asObservable() .observe(on:S.scheduler) .catchAndReturn(onErrorJustReturn) return SharedSequence(source) } /** Converts anything convertible to `Observable` to `SharedSequence` unit. - parameter onErrorDriveWith: SharedSequence that provides elements of the sequence in case of error. - returns: Driving observable sequence. */ public func asSharedSequence(sharingStrategy: S.Type = S.self, onErrorDriveWith: SharedSequence) -> SharedSequence { let source = self .asObservable() .observe(on:S.scheduler) .catch { _ in onErrorDriveWith.asObservable() } return SharedSequence(source) } /** Converts anything convertible to `Observable` to `SharedSequence` unit. - parameter onErrorRecover: Calculates driver that continues to drive the sequence in case of error. - returns: Driving observable sequence. */ public func asSharedSequence(sharingStrategy: S.Type = S.self, onErrorRecover: @escaping (_ error: Swift.Error) -> SharedSequence) -> SharedSequence { let source = self .asObservable() .observe(on:S.scheduler) .catch { error in onErrorRecover(error).asObservable() } return SharedSequence(source) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/SharedSequence/SchedulerType+SharedSequence.swift ================================================ // // SchedulerType+SharedSequence.swift // RxCocoa // // Created by Krunoslav Zaher on 8/27/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // import RxSwift public enum SharingScheduler { /// Default scheduler used in SharedSequence based traits. public private(set) static var make: () -> SchedulerType = { MainScheduler() } /** This method can be used in unit tests to ensure that built in shared sequences are using mock schedulers instead of main schedulers. **This shouldn't be used in normal release builds.** */ static public func mock(scheduler: SchedulerType, action: () throws -> Void) rethrows { return try mock(makeScheduler: { scheduler }, action: action) } /** This method can be used in unit tests to ensure that built in shared sequences are using mock schedulers instead of main schedulers. **This shouldn't be used in normal release builds.** */ static public func mock(makeScheduler: @escaping () -> SchedulerType, action: () throws -> Void) rethrows { let originalMake = make make = makeScheduler defer { make = originalMake } try action() // If you remove this line , compiler buggy optimizations will change behavior of this code _forceCompilerToStopDoingInsaneOptimizationsThatBreakCode(makeScheduler) // Scary, I know } } #if os(Linux) import Glibc #else import Foundation #endif func _forceCompilerToStopDoingInsaneOptimizationsThatBreakCode(_ scheduler: () -> SchedulerType) { let a: Int32 = 1 #if os(Linux) let b = 314 + Int32(Glibc.random() & 1) #else let b = 314 + Int32(arc4random() & 1) #endif if a == b { print(scheduler()) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/SharedSequence/SharedSequence+Concurrency.swift ================================================ // // SharedSequence+Concurrency.swift // RxCocoa // // Created by Shai Mishali on 22/09/2021. // Copyright © 2021 Krunoslav Zaher. All rights reserved. // #if swift(>=5.5.2) && canImport(_Concurrency) && !os(Linux) import Foundation // MARK: - Shared Sequence @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public extension SharedSequence { /// Allows iterating over the values of this Shared Sequence /// asynchronously via Swift's concurrency features (`async/await`) /// /// A sample usage would look like so: /// /// ```swift /// for await value in driver.values { /// // Handle emitted values /// } /// ``` @MainActor var values: AsyncStream { AsyncStream { continuation in // It is safe to ignore the `onError` closure here since // Shared Sequences (`Driver` and `Signal`) cannot fail let disposable = self.asObservable() .subscribe( onNext: { value in continuation.yield(value) }, onCompleted: { continuation.finish() }, onDisposed: { continuation.onTermination?(.cancelled) } ) continuation.onTermination = { @Sendable _ in disposable.dispose() } } } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/SharedSequence/SharedSequence+Operators+arity.swift ================================================ // This file is autogenerated. Take a look at `Preprocessor` target in RxSwift project // // SharedSequence+Operators+arity.swift // RxCocoa // // Created by Krunoslav Zaher on 10/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift // 2 extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, resultSelector: @escaping (O1.Element, O2.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever all of the observable sequences have produced an element at a corresponding index. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable() ) return SharedSequence(source) } } extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, resultSelector: @escaping (O1.Element, O2.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever any of the observable sequences produces an element. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable() ) return SharedSequence(source) } } // 3 extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, resultSelector: @escaping (O1.Element, O2.Element, O3.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever all of the observable sequences have produced an element at a corresponding index. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable() ) return SharedSequence(source) } } extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, resultSelector: @escaping (O1.Element, O2.Element, O3.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever any of the observable sequences produces an element. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable() ) return SharedSequence(source) } } // 4 extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever all of the observable sequences have produced an element at a corresponding index. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable() ) return SharedSequence(source) } } extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever any of the observable sequences produces an element. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable() ) return SharedSequence(source) } } // 5 extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever all of the observable sequences have produced an element at a corresponding index. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable() ) return SharedSequence(source) } } extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever any of the observable sequences produces an element. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable() ) return SharedSequence(source) } } // 6 extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever all of the observable sequences have produced an element at a corresponding index. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable() ) return SharedSequence(source) } } extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever any of the observable sequences produces an element. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable() ) return SharedSequence(source) } } // 7 extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy, SharingStrategy == O7.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable(), source7.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever all of the observable sequences have produced an element at a corresponding index. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy, SharingStrategy == O7.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable(), source7.asSharedSequence().asObservable() ) return SharedSequence(source) } } extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy, SharingStrategy == O7.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable(), source7.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever any of the observable sequences produces an element. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy, SharingStrategy == O7.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable(), source7.asSharedSequence().asObservable() ) return SharedSequence(source) } } // 8 extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element, O8.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy, SharingStrategy == O7.SharingStrategy, SharingStrategy == O8.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable(), source7.asSharedSequence().asObservable(), source8.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever all of the observable sequences have produced an element at a corresponding index. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy, SharingStrategy == O7.SharingStrategy, SharingStrategy == O8.SharingStrategy { let source = Observable.zip( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable(), source7.asSharedSequence().asObservable(), source8.asSharedSequence().asObservable() ) return SharedSequence(source) } } extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element, O8.Element) throws -> Element) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy, SharingStrategy == O7.SharingStrategy, SharingStrategy == O8.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable(), source7.asSharedSequence().asObservable(), source8.asSharedSequence().asObservable(), resultSelector: resultSelector ) return SharedSequence(source) } } extension SharedSequenceConvertibleType where Element == Any { /** Merges the specified observable sequences into one observable sequence of element tuples whenever any of the observable sequences produces an element. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8) -> SharedSequence where SharingStrategy == O1.SharingStrategy, SharingStrategy == O2.SharingStrategy, SharingStrategy == O3.SharingStrategy, SharingStrategy == O4.SharingStrategy, SharingStrategy == O5.SharingStrategy, SharingStrategy == O6.SharingStrategy, SharingStrategy == O7.SharingStrategy, SharingStrategy == O8.SharingStrategy { let source = Observable.combineLatest( source1.asSharedSequence().asObservable(), source2.asSharedSequence().asObservable(), source3.asSharedSequence().asObservable(), source4.asSharedSequence().asObservable(), source5.asSharedSequence().asObservable(), source6.asSharedSequence().asObservable(), source7.asSharedSequence().asObservable(), source8.asSharedSequence().asObservable() ) return SharedSequence(source) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/SharedSequence/SharedSequence+Operators.swift ================================================ // // SharedSequence+Operators.swift // RxCocoa // // Created by Krunoslav Zaher on 9/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift // MARK: map extension SharedSequenceConvertibleType { /** Projects each element of an observable sequence into a new form. - parameter selector: A transform function to apply to each source element. - returns: An observable sequence whose elements are the result of invoking the transform function on each element of source. */ public func map(_ selector: @escaping (Element) -> Result) -> SharedSequence { let source = self .asObservable() .map(selector) return SharedSequence(source) } } // MARK: compactMap extension SharedSequenceConvertibleType { /** Projects each element of an observable sequence into an optional form and filters all optional results. - parameter selector: A transform function to apply to each source element and which returns an element or nil. - returns: An observable sequence whose elements are the result of filtering the transform function for each element of the source. */ public func compactMap(_ selector: @escaping (Element) -> Result?) -> SharedSequence { let source = self .asObservable() .compactMap(selector) return SharedSequence(source) } } // MARK: filter extension SharedSequenceConvertibleType { /** Filters the elements of an observable sequence based on a predicate. - parameter predicate: A function to test each source element for a condition. - returns: An observable sequence that contains elements from the input sequence that satisfy the condition. */ public func filter(_ predicate: @escaping (Element) -> Bool) -> SharedSequence { let source = self .asObservable() .filter(predicate) return SharedSequence(source) } } // MARK: switchLatest extension SharedSequenceConvertibleType where Element: SharedSequenceConvertibleType { /** Transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. Each time a new inner observable sequence is received, unsubscribe from the previous inner observable sequence. - returns: The observable sequence that at any point in time produces the elements of the most recent inner observable sequence that has been received. */ public func switchLatest() -> SharedSequence { let source: Observable = self .asObservable() .map { $0.asSharedSequence() } .switchLatest() return SharedSequence(source) } } // MARK: flatMapLatest extension SharedSequenceConvertibleType { /** Projects each element of an observable sequence into a new sequence of observable sequences and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. It is a combination of `map` + `switchLatest` operator - parameter selector: A transform function to apply to each element. - returns: An observable sequence whose elements are the result of invoking the transform function on each element of source producing an Observable of Observable sequences and that at any point in time produces the elements of the most recent inner observable sequence that has been received. */ public func flatMapLatest(_ selector: @escaping (Element) -> SharedSequence) -> SharedSequence { let source: Observable = self .asObservable() .flatMapLatest(selector) return SharedSequence(source) } } // MARK: flatMapFirst extension SharedSequenceConvertibleType { /** Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. If element is received while there is some projected observable sequence being merged it will simply be ignored. - parameter selector: A transform function to apply to element that was observed while no observable is executing in parallel. - returns: An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence that was received while no other sequence was being calculated. */ public func flatMapFirst(_ selector: @escaping (Element) -> SharedSequence) -> SharedSequence { let source: Observable = self .asObservable() .flatMapFirst(selector) return SharedSequence(source) } } // MARK: do extension SharedSequenceConvertibleType { /** Invokes an action for each event in the observable sequence, and propagates all observer messages through the result sequence. - parameter onNext: Action to invoke for each element in the observable sequence. - parameter afterNext: Action to invoke for each element after the observable has passed an onNext event along to its downstream. - parameter onCompleted: Action to invoke upon graceful termination of the observable sequence. - parameter afterCompleted: Action to invoke after graceful termination of the observable sequence. - parameter onSubscribe: Action to invoke before subscribing to source observable sequence. - parameter onSubscribed: Action to invoke after subscribing to source observable sequence. - parameter onDispose: Action to invoke after subscription to source observable has been disposed for any reason. It can be either because sequence terminates for some reason or observer subscription being disposed. - returns: The source sequence with the side-effecting behavior applied. */ public func `do`(onNext: ((Element) -> Void)? = nil, afterNext: ((Element) -> Void)? = nil, onCompleted: (() -> Void)? = nil, afterCompleted: (() -> Void)? = nil, onSubscribe: (() -> Void)? = nil, onSubscribed: (() -> Void)? = nil, onDispose: (() -> Void)? = nil) -> SharedSequence { let source = self.asObservable() .do(onNext: onNext, afterNext: afterNext, onCompleted: onCompleted, afterCompleted: afterCompleted, onSubscribe: onSubscribe, onSubscribed: onSubscribed, onDispose: onDispose) return SharedSequence(source) } } // MARK: debug extension SharedSequenceConvertibleType { /** Prints received events for all observers on standard output. - parameter identifier: Identifier that is printed together with event description to standard output. - returns: An observable sequence whose events are printed to standard output. */ public func debug(_ identifier: String? = nil, trimOutput: Bool = false, file: String = #file, line: UInt = #line, function: String = #function) -> SharedSequence { let source = self.asObservable() .debug(identifier, trimOutput: trimOutput, file: file, line: line, function: function) return SharedSequence(source) } } // MARK: distinctUntilChanged extension SharedSequenceConvertibleType where Element: Equatable { /** Returns an observable sequence that contains only distinct contiguous elements according to equality operator. - returns: An observable sequence only containing the distinct contiguous elements, based on equality operator, from the source sequence. */ public func distinctUntilChanged() -> SharedSequence { let source = self.asObservable() .distinctUntilChanged({ $0 }, comparer: { ($0 == $1) }) return SharedSequence(source) } } extension SharedSequenceConvertibleType { /** Returns an observable sequence that contains only distinct contiguous elements according to the `keySelector`. - parameter keySelector: A function to compute the comparison key for each element. - returns: An observable sequence only containing the distinct contiguous elements, based on a computed key value, from the source sequence. */ public func distinctUntilChanged(_ keySelector: @escaping (Element) -> Key) -> SharedSequence { let source = self.asObservable() .distinctUntilChanged(keySelector, comparer: { $0 == $1 }) return SharedSequence(source) } /** Returns an observable sequence that contains only distinct contiguous elements according to the `comparer`. - parameter comparer: Equality comparer for computed key values. - returns: An observable sequence only containing the distinct contiguous elements, based on `comparer`, from the source sequence. */ public func distinctUntilChanged(_ comparer: @escaping (Element, Element) -> Bool) -> SharedSequence { let source = self.asObservable() .distinctUntilChanged({ $0 }, comparer: comparer) return SharedSequence(source) } /** Returns an observable sequence that contains only distinct contiguous elements according to the keySelector and the comparer. - parameter keySelector: A function to compute the comparison key for each element. - parameter comparer: Equality comparer for computed key values. - returns: An observable sequence only containing the distinct contiguous elements, based on a computed key value and the comparer, from the source sequence. */ public func distinctUntilChanged(_ keySelector: @escaping (Element) -> K, comparer: @escaping (K, K) -> Bool) -> SharedSequence { let source = self.asObservable() .distinctUntilChanged(keySelector, comparer: comparer) return SharedSequence(source) } } // MARK: flatMap extension SharedSequenceConvertibleType { /** Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. - parameter selector: A transform function to apply to each element. - returns: An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. */ public func flatMap(_ selector: @escaping (Element) -> SharedSequence) -> SharedSequence { let source = self.asObservable() .flatMap(selector) return SharedSequence(source) } } // MARK: merge extension SharedSequenceConvertibleType { /** Merges elements from all observable sequences from collection into a single observable sequence. - seealso: [merge operator on reactivex.io](http://reactivex.io/documentation/operators/merge.html) - parameter sources: Collection of observable sequences to merge. - returns: The observable sequence that merges the elements of the observable sequences. */ public static func merge(_ sources: Collection) -> SharedSequence where Collection.Element == SharedSequence { let source = Observable.merge(sources.map { $0.asObservable() }) return SharedSequence(source) } /** Merges elements from all observable sequences from array into a single observable sequence. - seealso: [merge operator on reactivex.io](http://reactivex.io/documentation/operators/merge.html) - parameter sources: Array of observable sequences to merge. - returns: The observable sequence that merges the elements of the observable sequences. */ public static func merge(_ sources: [SharedSequence]) -> SharedSequence { let source = Observable.merge(sources.map { $0.asObservable() }) return SharedSequence(source) } /** Merges elements from all observable sequences into a single observable sequence. - seealso: [merge operator on reactivex.io](http://reactivex.io/documentation/operators/merge.html) - parameter sources: Collection of observable sequences to merge. - returns: The observable sequence that merges the elements of the observable sequences. */ public static func merge(_ sources: SharedSequence...) -> SharedSequence { let source = Observable.merge(sources.map { $0.asObservable() }) return SharedSequence(source) } } // MARK: merge extension SharedSequenceConvertibleType where Element: SharedSequenceConvertibleType { /** Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence. - returns: The observable sequence that merges the elements of the observable sequences. */ public func merge() -> SharedSequence { let source = self.asObservable() .map { $0.asSharedSequence() } .merge() return SharedSequence(source) } /** Merges elements from all inner observable sequences into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences. - parameter maxConcurrent: Maximum number of inner observable sequences being subscribed to concurrently. - returns: The observable sequence that merges the elements of the inner sequences. */ public func merge(maxConcurrent: Int) -> SharedSequence { let source = self.asObservable() .map { $0.asSharedSequence() } .merge(maxConcurrent: maxConcurrent) return SharedSequence(source) } } // MARK: throttle extension SharedSequenceConvertibleType { /** Returns an Observable that emits the first and the latest item emitted by the source Observable during sequential time windows of a specified duration. This operator makes sure that no two elements are emitted in less then dueTime. - seealso: [debounce operator on reactivex.io](http://reactivex.io/documentation/operators/debounce.html) - parameter dueTime: Throttling duration for each element. - parameter latest: Should latest element received in a dueTime wide time window since last element emission be emitted. - returns: The throttled sequence. */ public func throttle(_ dueTime: RxTimeInterval, latest: Bool = true) -> SharedSequence { let source = self.asObservable() .throttle(dueTime, latest: latest, scheduler: SharingStrategy.scheduler) return SharedSequence(source) } /** Ignores elements from an observable sequence which are followed by another element within a specified relative time duration, using the specified scheduler to run throttling timers. - parameter dueTime: Throttling duration for each element. - returns: The throttled sequence. */ public func debounce(_ dueTime: RxTimeInterval) -> SharedSequence { let source = self.asObservable() .debounce(dueTime, scheduler: SharingStrategy.scheduler) return SharedSequence(source) } } // MARK: scan extension SharedSequenceConvertibleType { /** Applies an accumulator function over an observable sequence and returns each intermediate result. The specified seed value is used as the initial accumulator value. For aggregation behavior with no intermediate results, see `reduce`. - parameter seed: The initial accumulator value. - parameter accumulator: An accumulator function to be invoked on each element. - returns: An observable sequence containing the accumulated values. */ public func scan(_ seed: A, accumulator: @escaping (A, Element) -> A) -> SharedSequence { let source = self.asObservable() .scan(seed, accumulator: accumulator) return SharedSequence(source) } } // MARK: concat extension SharedSequence { /** Concatenates all observable sequences in the given sequence, as long as the previous observable sequence terminated successfully. - returns: An observable sequence that contains the elements of each given sequence, in sequential order. */ public static func concat(_ sequence: Sequence) -> SharedSequence where Sequence.Element == SharedSequence { let source = Observable.concat(sequence.lazy.map { $0.asObservable() }) return SharedSequence(source) } /** Concatenates all observable sequences in the given sequence, as long as the previous observable sequence terminated successfully. - returns: An observable sequence that contains the elements of each given sequence, in sequential order. */ public static func concat(_ collection: Collection) -> SharedSequence where Collection.Element == SharedSequence { let source = Observable.concat(collection.map { $0.asObservable() }) return SharedSequence(source) } } // MARK: zip extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip(_ collection: Collection, resultSelector: @escaping ([Element]) throws -> Result) -> SharedSequence where Collection.Element == SharedSequence { let source = Observable.zip(collection.map { $0.asSharedSequence().asObservable() }, resultSelector: resultSelector) return SharedSequence(source) } /** Merges the specified observable sequences into one observable sequence all of the observable sequences have produced an element at a corresponding index. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip(_ collection: Collection) -> SharedSequence where Collection.Element == SharedSequence { let source = Observable.zip(collection.map { $0.asSharedSequence().asObservable() }) return SharedSequence(source) } } // MARK: combineLatest extension SharedSequence { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest(_ collection: Collection, resultSelector: @escaping ([Element]) throws -> Result) -> SharedSequence where Collection.Element == SharedSequence { let source = Observable.combineLatest(collection.map { $0.asObservable() }, resultSelector: resultSelector) return SharedSequence(source) } /** Merges the specified observable sequences into one observable sequence whenever any of the observable sequences produces an element. - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest(_ collection: Collection) -> SharedSequence where Collection.Element == SharedSequence { let source = Observable.combineLatest(collection.map { $0.asObservable() }) return SharedSequence(source) } } // MARK: - withUnretained extension SharedSequenceConvertibleType where SharingStrategy == SignalSharingStrategy { /** Provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events emitted by the sequence. In the case the provided object cannot be retained successfully, the sequence will complete. - note: Be careful when using this operator in a sequence that has a buffer or replay, for example `share(replay: 1)`, as the sharing buffer will also include the provided object, which could potentially cause a retain cycle. - parameter obj: The object to provide an unretained reference on. - parameter resultSelector: A function to combine the unretained referenced on `obj` and the value of the observable sequence. - returns: An observable sequence that contains the result of `resultSelector` being called with an unretained reference on `obj` and the values of the original sequence. */ public func withUnretained( _ obj: Object, resultSelector: @escaping (Object, Element) -> Out ) -> SharedSequence { SharedSequence(self.asObservable().withUnretained(obj, resultSelector: resultSelector)) } /** Provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events emitted by the sequence. In the case the provided object cannot be retained successfully, the sequence will complete. - note: Be careful when using this operator in a sequence that has a buffer or replay, for example `share(replay: 1)`, as the sharing buffer will also include the provided object, which could potentially cause a retain cycle. - parameter obj: The object to provide an unretained reference on. - returns: An observable sequence of tuples that contains both an unretained reference on `obj` and the values of the original sequence. */ public func withUnretained(_ obj: Object) -> SharedSequence { withUnretained(obj) { ($0, $1) } } } extension SharedSequenceConvertibleType where SharingStrategy == DriverSharingStrategy { @available(*, message: "withUnretained has been deprecated for Driver. Consider using `drive(with:onNext:onCompleted:onDisposed:)`, instead", unavailable) public func withUnretained( _ obj: Object, resultSelector: @escaping (Object, Element) -> Out ) -> SharedSequence { SharedSequence(self.asObservable().withUnretained(obj, resultSelector: resultSelector)) } @available(*, message: "withUnretained has been deprecated for Driver. Consider using `drive(with:onNext:onCompleted:onDisposed:)`, instead", unavailable) public func withUnretained(_ obj: Object) -> SharedSequence { SharedSequence(self.asObservable().withUnretained(obj) { ($0, $1) }) } } // MARK: withLatestFrom extension SharedSequenceConvertibleType { /** Merges two observable sequences into one observable sequence by combining each element from self with the latest element from the second source, if any. - parameter second: Second observable source. - parameter resultSelector: Function to invoke for each element from the self combined with the latest element from the second source, if any. - returns: An observable sequence containing the result of combining each element of the self with the latest element from the second source, if any, using the specified result selector function. */ public func withLatestFrom(_ second: SecondO, resultSelector: @escaping (Element, SecondO.Element) -> ResultType) -> SharedSequence where SecondO.SharingStrategy == SharingStrategy { let source = self.asObservable() .withLatestFrom(second.asSharedSequence(), resultSelector: resultSelector) return SharedSequence(source) } /** Merges two observable sequences into one observable sequence by using latest element from the second sequence every time when `self` emits an element. - parameter second: Second observable source. - returns: An observable sequence containing the result of combining each element of the self with the latest element from the second source, if any, using the specified result selector function. */ public func withLatestFrom(_ second: SecondO) -> SharedSequence { let source = self.asObservable() .withLatestFrom(second.asSharedSequence()) return SharedSequence(source) } } // MARK: skip extension SharedSequenceConvertibleType { /** Bypasses a specified number of elements in an observable sequence and then returns the remaining elements. - seealso: [skip operator on reactivex.io](http://reactivex.io/documentation/operators/skip.html) - parameter count: The number of elements to skip before returning the remaining elements. - returns: An observable sequence that contains the elements that occur after the specified index in the input sequence. */ public func skip(_ count: Int) -> SharedSequence { let source = self.asObservable() .skip(count) return SharedSequence(source) } } // MARK: startWith extension SharedSequenceConvertibleType { /** Prepends a value to an observable sequence. - seealso: [startWith operator on reactivex.io](http://reactivex.io/documentation/operators/startwith.html) - parameter element: Element to prepend to the specified sequence. - returns: The source sequence prepended with the specified values. */ public func startWith(_ element: Element) -> SharedSequence { let source = self.asObservable() .startWith(element) return SharedSequence(source) } } // MARK: delay extension SharedSequenceConvertibleType { /** Returns an observable sequence by the source observable sequence shifted forward in time by a specified delay. Error events from the source observable sequence are not delayed. - seealso: [delay operator on reactivex.io](http://reactivex.io/documentation/operators/delay.html) - parameter dueTime: Relative time shift of the source by. - returns: the source Observable shifted in time by the specified delay. */ public func delay(_ dueTime: RxTimeInterval) -> SharedSequence { let source = self.asObservable() .delay(dueTime, scheduler: SharingStrategy.scheduler) return SharedSequence(source) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/SharedSequence/SharedSequence.swift ================================================ // // SharedSequence.swift // RxCocoa // // Created by Krunoslav Zaher on 8/27/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift /** Trait that represents observable sequence that shares computation resources with following properties: - it never fails - it delivers events on `SharingStrategy.scheduler` - sharing strategy is customizable using `SharingStrategy.share` behavior `SharedSequence` can be considered a builder pattern for observable sequences that share computation resources. To find out more about units and how to use them, please visit `Documentation/Traits.md`. */ public struct SharedSequence : SharedSequenceConvertibleType, ObservableConvertibleType { let source: Observable init(_ source: Observable) { self.source = SharingStrategy.share(source) } init(raw: Observable) { self.source = raw } #if EXPANDABLE_SHARED_SEQUENCE /** This method is extension hook in case this unit needs to extended from outside the library. By defining `EXPANDABLE_SHARED_SEQUENCE` one agrees that it's up to them to ensure shared sequence properties are preserved after extension. */ public static func createUnsafe(source: Source) -> SharedSequence { SharedSequence(raw: source.asObservable()) } #endif /** - returns: Built observable sequence. */ public func asObservable() -> Observable { self.source } /** - returns: `self` */ public func asSharedSequence() -> SharedSequence { self } } /** Different `SharedSequence` sharing strategies must conform to this protocol. */ public protocol SharingStrategyProtocol { /** Scheduled on which all sequence events will be delivered. */ static var scheduler: SchedulerType { get } /** Computation resources sharing strategy for multiple sequence observers. E.g. One can choose `share(replay:scope:)` as sequence event sharing strategies, but also do something more exotic, like implementing promises or lazy loading chains. */ static func share(_ source: Observable) -> Observable } /** A type that can be converted to `SharedSequence`. */ public protocol SharedSequenceConvertibleType : ObservableConvertibleType { associatedtype SharingStrategy: SharingStrategyProtocol /** Converts self to `SharedSequence`. */ func asSharedSequence() -> SharedSequence } extension SharedSequenceConvertibleType { public func asObservable() -> Observable { self.asSharedSequence().asObservable() } } extension SharedSequence { /** Returns an empty observable sequence, using the specified scheduler to send out the single `Completed` message. - returns: An observable sequence with no elements. */ public static func empty() -> SharedSequence { SharedSequence(raw: Observable.empty().subscribe(on: SharingStrategy.scheduler)) } /** Returns a non-terminating observable sequence, which can be used to denote an infinite duration. - returns: An observable sequence whose observers will never get called. */ public static func never() -> SharedSequence { SharedSequence(raw: Observable.never()) } /** Returns an observable sequence that contains a single element. - parameter element: Single element in the resulting observable sequence. - returns: An observable sequence containing the single specified element. */ public static func just(_ element: Element) -> SharedSequence { SharedSequence(raw: Observable.just(element).subscribe(on: SharingStrategy.scheduler)) } /** Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes. - parameter observableFactory: Observable factory function to invoke for each observer that subscribes to the resulting sequence. - returns: An observable sequence whose observers trigger an invocation of the given observable factory function. */ public static func deferred(_ observableFactory: @escaping () -> SharedSequence) -> SharedSequence { SharedSequence(Observable.deferred { observableFactory().asObservable() }) } /** This method creates a new Observable instance with a variable number of elements. - seealso: [from operator on reactivex.io](http://reactivex.io/documentation/operators/from.html) - parameter elements: Elements to generate. - returns: The observable sequence whose elements are pulled from the given arguments. */ public static func of(_ elements: Element ...) -> SharedSequence { let source = Observable.from(elements, scheduler: SharingStrategy.scheduler) return SharedSequence(raw: source) } } extension SharedSequence { /** This method converts an array to an observable sequence. - seealso: [from operator on reactivex.io](http://reactivex.io/documentation/operators/from.html) - returns: The observable sequence whose elements are pulled from the given enumerable sequence. */ public static func from(_ array: [Element]) -> SharedSequence { let source = Observable.from(array, scheduler: SharingStrategy.scheduler) return SharedSequence(raw: source) } /** This method converts a sequence to an observable sequence. - seealso: [from operator on reactivex.io](http://reactivex.io/documentation/operators/from.html) - returns: The observable sequence whose elements are pulled from the given enumerable sequence. */ public static func from(_ sequence: Sequence) -> SharedSequence where Sequence.Element == Element { let source = Observable.from(sequence, scheduler: SharingStrategy.scheduler) return SharedSequence(raw: source) } /** This method converts a optional to an observable sequence. - seealso: [from operator on reactivex.io](http://reactivex.io/documentation/operators/from.html) - parameter optional: Optional element in the resulting observable sequence. - returns: An observable sequence containing the wrapped value or not from given optional. */ public static func from(optional: Element?) -> SharedSequence { let source = Observable.from(optional: optional, scheduler: SharingStrategy.scheduler) return SharedSequence(raw: source) } } extension SharedSequence where Element: RxAbstractInteger { /** Returns an observable sequence that produces a value after each period, using the specified scheduler to run timers and to send out observer messages. - seealso: [interval operator on reactivex.io](http://reactivex.io/documentation/operators/interval.html) - parameter period: Period for producing the values in the resulting sequence. - returns: An observable sequence that produces a value after each period. */ public static func interval(_ period: RxTimeInterval) -> SharedSequence { SharedSequence(Observable.interval(period, scheduler: SharingStrategy.scheduler)) } } // MARK: timer extension SharedSequence where Element: RxAbstractInteger { /** Returns an observable sequence that periodically produces a value after the specified initial relative due time has elapsed, using the specified scheduler to run timers. - seealso: [timer operator on reactivex.io](http://reactivex.io/documentation/operators/timer.html) - parameter dueTime: Relative time at which to produce the first value. - parameter period: Period to produce subsequent values. - returns: An observable sequence that produces a value after due time has elapsed and then each period. */ public static func timer(_ dueTime: RxTimeInterval, period: RxTimeInterval) -> SharedSequence { SharedSequence(Observable.timer(dueTime, period: period, scheduler: SharingStrategy.scheduler)) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Signal/ControlEvent+Signal.swift ================================================ // // ControlEvent+Signal.swift // RxCocoa // // Created by Krunoslav Zaher on 11/1/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // import RxSwift extension ControlEvent { /// Converts `ControlEvent` to `Signal` trait. /// /// `ControlEvent` already can't fail, so no special case needs to be handled. public func asSignal() -> Signal { return self.asSignal { _ -> Signal in #if DEBUG rxFatalError("Somehow signal received error from a source that shouldn't fail.") #else return Signal.empty() #endif } } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Signal/ObservableConvertibleType+Signal.swift ================================================ // // ObservableConvertibleType+Signal.swift // RxCocoa // // Created by Krunoslav Zaher on 9/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift extension ObservableConvertibleType { /** Converts observable sequence to `Signal` trait. - parameter onErrorJustReturn: Element to return in case of error and after that complete the sequence. - returns: Signal trait. */ public func asSignal(onErrorJustReturn: Element) -> Signal { let source = self .asObservable() .observe(on: SignalSharingStrategy.scheduler) .catchAndReturn(onErrorJustReturn) return Signal(source) } /** Converts observable sequence to `Signal` trait. - parameter onErrorSignalWith: Signal that continues to emit the sequence in case of error. - returns: Signal trait. */ public func asSignal(onErrorSignalWith: Signal) -> Signal { let source = self .asObservable() .observe(on: SignalSharingStrategy.scheduler) .catch { _ in onErrorSignalWith.asObservable() } return Signal(source) } /** Converts observable sequence to `Signal` trait. - parameter onErrorRecover: Calculates signal that continues to emit the sequence in case of error. - returns: Signal trait. */ public func asSignal(onErrorRecover: @escaping (_ error: Swift.Error) -> Signal) -> Signal { let source = self .asObservable() .observe(on: SignalSharingStrategy.scheduler) .catch { error in onErrorRecover(error).asObservable() } return Signal(source) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Signal/PublishRelay+Signal.swift ================================================ // // PublishRelay+Signal.swift // RxCocoa // // Created by Krunoslav Zaher on 12/28/15. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // import RxSwift import RxRelay extension PublishRelay { /// Converts `PublishRelay` to `Signal`. /// /// - returns: Observable sequence. public func asSignal() -> Signal { let source = self.asObservable() .observe(on:SignalSharingStrategy.scheduler) return SharedSequence(source) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Signal/Signal+Subscription.swift ================================================ // // Signal+Subscription.swift // RxCocoa // // Created by Krunoslav Zaher on 9/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift import RxRelay extension SharedSequenceConvertibleType where SharingStrategy == SignalSharingStrategy { /** Creates new subscription and sends elements to observer. In this form it's equivalent to `subscribe` method, but it communicates intent better. - parameter observers: Observers that receives events. - returns: Disposable object that can be used to unsubscribe the observer from the subject. */ public func emit(to observers: Observer...) -> Disposable where Observer.Element == Element { return self.asSharedSequence() .asObservable() .subscribe { event in observers.forEach { $0.on(event) } } } /** Creates new subscription and sends elements to observer. In this form it's equivalent to `subscribe` method, but it communicates intent better. - parameter observers: Observers that receives events. - returns: Disposable object that can be used to unsubscribe the observer from the subject. */ public func emit(to observers: Observer...) -> Disposable where Observer.Element == Element? { return self.asSharedSequence() .asObservable() .map { $0 as Element? } .subscribe { event in observers.forEach { $0.on(event) } } } /** Creates new subscription and sends elements to `BehaviorRelay`. - parameter relays: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func emit(to relays: BehaviorRelay...) -> Disposable { return self.emit(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `BehaviorRelay`. - parameter relays: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func emit(to relays: BehaviorRelay...) -> Disposable { return self.emit(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `PublishRelay`. - parameter relays: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func emit(to relays: PublishRelay...) -> Disposable { return self.emit(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `PublishRelay`. - parameter relays: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func emit(to relays: PublishRelay...) -> Disposable { return self.emit(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `ReplayRelay`. - parameter relays: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func emit(to relays: ReplayRelay...) -> Disposable { return self.emit(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `ReplayRelay`. - parameter relays: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ public func emit(to relays: ReplayRelay...) -> Disposable { return self.emit(onNext: { e in relays.forEach { $0.accept(e) } }) } /** Subscribes an element handler, a completion handler and disposed handler to an observable sequence. Also, take in an object and provide an unretained, safe to use (i.e. not implicitly unwrapped), reference to it along with the events emitted by the sequence. Error callback is not exposed because `Signal` can't error out. - Note: If `object` can't be retained, none of the other closures will be invoked. - parameter object: The object to provide an unretained reference on. - parameter onNext: Action to invoke for each element in the observable sequence. - parameter onCompleted: Action to invoke upon graceful termination of the observable sequence. gracefully completed, errored, or if the generation is canceled by disposing subscription) - parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has gracefully completed, errored, or if the generation is canceled by disposing subscription) - returns: Subscription object used to unsubscribe from the observable sequence. */ public func emit( with object: Object, onNext: ((Object, Element) -> Void)? = nil, onCompleted: ((Object) -> Void)? = nil, onDisposed: ((Object) -> Void)? = nil ) -> Disposable { self.asObservable().subscribe( with: object, onNext: onNext, onCompleted: onCompleted, onDisposed: onDisposed ) } /** Subscribes an element handler, a completion handler and disposed handler to an observable sequence. Error callback is not exposed because `Signal` can't error out. - parameter onNext: Action to invoke for each element in the observable sequence. - parameter onCompleted: Action to invoke upon graceful termination of the observable sequence. gracefully completed, errored, or if the generation is canceled by disposing subscription) - parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has gracefully completed, errored, or if the generation is canceled by disposing subscription) - returns: Subscription object used to unsubscribe from the observable sequence. */ public func emit( onNext: ((Element) -> Void)? = nil, onCompleted: (() -> Void)? = nil, onDisposed: (() -> Void)? = nil ) -> Disposable { self.asObservable().subscribe(onNext: onNext, onCompleted: onCompleted, onDisposed: onDisposed) } /** Subscribes to this `Signal` with a no-op. This method can be only called from `MainThread`. - note: This is an alias of `emit(onNext: nil, onCompleted: nil, onDisposed: nil)` used to fix an ambiguity bug in Swift: https://bugs.swift.org/browse/SR-13657 - returns: Subscription object used to unsubscribe from the observable sequence. */ public func emit() -> Disposable { emit(onNext: nil, onCompleted: nil, onDisposed: nil) } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/Traits/Signal/Signal.swift ================================================ // // Signal.swift // RxCocoa // // Created by Krunoslav Zaher on 9/26/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // import RxSwift /** Trait that represents observable sequence with following properties: - it never fails - it delivers events on `MainScheduler.instance` - `share(scope: .whileConnected)` sharing strategy Additional explanation: - all observers share sequence computation resources - there is no replaying of sequence elements on new observer subscription - computation of elements is reference counted with respect to the number of observers - if there are no subscribers, it will release sequence computation resources In case trait that models state propagation is required, please check `Driver`. `Signal` can be considered a builder pattern for observable sequences that model imperative events part of the application. To find out more about units and how to use them, please visit `Documentation/Traits.md`. */ public typealias Signal = SharedSequence public struct SignalSharingStrategy: SharingStrategyProtocol { public static var scheduler: SchedulerType { SharingScheduler.make() } public static func share(_ source: Observable) -> Observable { source.share(scope: .whileConnected) } } extension SharedSequenceConvertibleType where SharingStrategy == SignalSharingStrategy { /// Adds `asPublisher` to `SharingSequence` with `PublishSharingStrategy`. public func asSignal() -> Signal { self.asSharedSequence() } } ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift ================================================ // // RxCollectionViewReactiveArrayDataSource.swift // RxCocoa // // Created by Krunoslav Zaher on 6/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift // objc monkey business class _RxCollectionViewReactiveArrayDataSource : NSObject , UICollectionViewDataSource { @objc(numberOfSectionsInCollectionView:) func numberOfSections(in: UICollectionView) -> Int { 1 } func _collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 0 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { _collectionView(collectionView, numberOfItemsInSection: section) } fileprivate func _collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { rxAbstractMethod() } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { _collectionView(collectionView, cellForItemAt: indexPath) } } class RxCollectionViewReactiveArrayDataSourceSequenceWrapper : RxCollectionViewReactiveArrayDataSource , RxCollectionViewDataSourceType { typealias Element = Sequence override init(cellFactory: @escaping CellFactory) { super.init(cellFactory: cellFactory) } func collectionView(_ collectionView: UICollectionView, observedEvent: Event) { Binder(self) { collectionViewDataSource, sectionModels in let sections = Array(sectionModels) collectionViewDataSource.collectionView(collectionView, observedElements: sections) }.on(observedEvent) } } // Please take a look at `DelegateProxyType.swift` class RxCollectionViewReactiveArrayDataSource : _RxCollectionViewReactiveArrayDataSource , SectionedViewDataSourceType { typealias CellFactory = (UICollectionView, Int, Element) -> UICollectionViewCell var itemModels: [Element]? func modelAtIndex(_ index: Int) -> Element? { itemModels?[index] } func model(at indexPath: IndexPath) throws -> Any { precondition(indexPath.section == 0) guard let item = itemModels?[indexPath.item] else { throw RxCocoaError.itemsNotYetBound(object: self) } return item } var cellFactory: CellFactory init(cellFactory: @escaping CellFactory) { self.cellFactory = cellFactory } // data source override func _collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { itemModels?.count ?? 0 } override func _collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { cellFactory(collectionView, indexPath.item, itemModels![indexPath.item]) } // reactive func collectionView(_ collectionView: UICollectionView, observedElements: [Element]) { self.itemModels = observedElements collectionView.reloadData() // workaround for http://stackoverflow.com/questions/39867325/ios-10-bug-uicollectionview-received-layout-attributes-for-a-cell-with-an-index collectionView.collectionViewLayout.invalidateLayout() } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/DataSources/RxPickerViewAdapter.swift ================================================ // // RxPickerViewAdapter.swift // RxCocoa // // Created by Sergey Shulga on 12/07/2017. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // #if os(iOS) import UIKit import RxSwift class RxPickerViewArrayDataSource: NSObject, UIPickerViewDataSource, SectionedViewDataSourceType { fileprivate var items: [T] = [] func model(at indexPath: IndexPath) throws -> Any { guard items.indices ~= indexPath.row else { throw RxCocoaError.itemsNotYetBound(object: self) } return items[indexPath.row] } func numberOfComponents(in pickerView: UIPickerView) -> Int { 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { items.count } } class RxPickerViewSequenceDataSource : RxPickerViewArrayDataSource , RxPickerViewDataSourceType { typealias Element = Sequence func pickerView(_ pickerView: UIPickerView, observedEvent: Event) { Binder(self) { dataSource, items in dataSource.items = items pickerView.reloadAllComponents() } .on(observedEvent.map(Array.init)) } } final class RxStringPickerViewAdapter : RxPickerViewSequenceDataSource , UIPickerViewDelegate { typealias TitleForRow = (Int, Sequence.Element) -> String? private let titleForRow: TitleForRow init(titleForRow: @escaping TitleForRow) { self.titleForRow = titleForRow super.init() } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { titleForRow(row, items[row]) } } final class RxAttributedStringPickerViewAdapter: RxPickerViewSequenceDataSource, UIPickerViewDelegate { typealias AttributedTitleForRow = (Int, Sequence.Element) -> NSAttributedString? private let attributedTitleForRow: AttributedTitleForRow init(attributedTitleForRow: @escaping AttributedTitleForRow) { self.attributedTitleForRow = attributedTitleForRow super.init() } func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { attributedTitleForRow(row, items[row]) } } final class RxPickerViewAdapter: RxPickerViewSequenceDataSource, UIPickerViewDelegate { typealias ViewForRow = (Int, Sequence.Element, UIView?) -> UIView private let viewForRow: ViewForRow init(viewForRow: @escaping ViewForRow) { self.viewForRow = viewForRow super.init() } func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { viewForRow(row, items[row], view) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift ================================================ // // RxTableViewReactiveArrayDataSource.swift // RxCocoa // // Created by Krunoslav Zaher on 6/26/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift // objc monkey business class _RxTableViewReactiveArrayDataSource : NSObject , UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { 1 } func _tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 0 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { _tableView(tableView, numberOfRowsInSection: section) } fileprivate func _tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { rxAbstractMethod() } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { _tableView(tableView, cellForRowAt: indexPath) } } class RxTableViewReactiveArrayDataSourceSequenceWrapper : RxTableViewReactiveArrayDataSource , RxTableViewDataSourceType { typealias Element = Sequence override init(cellFactory: @escaping CellFactory) { super.init(cellFactory: cellFactory) } func tableView(_ tableView: UITableView, observedEvent: Event) { Binder(self) { tableViewDataSource, sectionModels in let sections = Array(sectionModels) tableViewDataSource.tableView(tableView, observedElements: sections) }.on(observedEvent) } } // Please take a look at `DelegateProxyType.swift` class RxTableViewReactiveArrayDataSource : _RxTableViewReactiveArrayDataSource , SectionedViewDataSourceType { typealias CellFactory = (UITableView, Int, Element) -> UITableViewCell var itemModels: [Element]? func modelAtIndex(_ index: Int) -> Element? { itemModels?[index] } func model(at indexPath: IndexPath) throws -> Any { precondition(indexPath.section == 0) guard let item = itemModels?[indexPath.item] else { throw RxCocoaError.itemsNotYetBound(object: self) } return item } let cellFactory: CellFactory init(cellFactory: @escaping CellFactory) { self.cellFactory = cellFactory } override func _tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { itemModels?.count ?? 0 } override func _tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { cellFactory(tableView, indexPath.item, itemModels![indexPath.row]) } // reactive func tableView(_ tableView: UITableView, observedElements: [Element]) { self.itemModels = observedElements tableView.reloadData() } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Events/ItemEvents.swift ================================================ // // ItemEvents.swift // RxCocoa // // Created by Krunoslav Zaher on 6/20/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit public typealias ItemMovedEvent = (sourceIndex: IndexPath, destinationIndex: IndexPath) public typealias WillDisplayCellEvent = (cell: UITableViewCell, indexPath: IndexPath) public typealias DidEndDisplayingCellEvent = (cell: UITableViewCell, indexPath: IndexPath) #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/NSTextStorage+Rx.swift ================================================ // // NSTextStorage+Rx.swift // RxCocoa // // Created by Segii Shulga on 12/30/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import RxSwift import UIKit extension Reactive where Base: NSTextStorage { /// Reactive wrapper for `delegate`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var delegate: DelegateProxy { return RxTextStorageDelegateProxy.proxy(for: base) } /// Reactive wrapper for `delegate` message. public var didProcessEditingRangeChangeInLength: Observable<(editedMask: NSTextStorage.EditActions, editedRange: NSRange, delta: Int)> { return delegate .methodInvoked(#selector(NSTextStorageDelegate.textStorage(_:didProcessEditing:range:changeInLength:))) .map { a in let editedMask = NSTextStorage.EditActions(rawValue: try castOrThrow(UInt.self, a[1]) ) let editedRange = try castOrThrow(NSValue.self, a[2]).rangeValue let delta = try castOrThrow(Int.self, a[3]) return (editedMask, editedRange, delta) } } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Protocols/RxCollectionViewDataSourceType.swift ================================================ // // RxCollectionViewDataSourceType.swift // RxCocoa // // Created by Krunoslav Zaher on 6/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift /// Marks data source as `UICollectionView` reactive data source enabling it to be used with one of the `bindTo` methods. public protocol RxCollectionViewDataSourceType /*: UICollectionViewDataSource*/ { /// Type of elements that can be bound to collection view. associatedtype Element /// New observable sequence event observed. /// /// - parameter collectionView: Bound collection view. /// - parameter observedEvent: Event func collectionView(_ collectionView: UICollectionView, observedEvent: Event) } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Protocols/RxPickerViewDataSourceType.swift ================================================ // // RxPickerViewDataSourceType.swift // RxCocoa // // Created by Sergey Shulga on 05/07/2017. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // #if os(iOS) import UIKit import RxSwift /// Marks data source as `UIPickerView` reactive data source enabling it to be used with one of the `bindTo` methods. public protocol RxPickerViewDataSourceType { /// Type of elements that can be bound to picker view. associatedtype Element /// New observable sequence event observed. /// /// - parameter pickerView: Bound picker view. /// - parameter observedEvent: Event func pickerView(_ pickerView: UIPickerView, observedEvent: Event) } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Protocols/RxTableViewDataSourceType.swift ================================================ // // RxTableViewDataSourceType.swift // RxCocoa // // Created by Krunoslav Zaher on 6/26/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift /// Marks data source as `UITableView` reactive data source enabling it to be used with one of the `bindTo` methods. public protocol RxTableViewDataSourceType /*: UITableViewDataSource*/ { /// Type of elements that can be bound to table view. associatedtype Element /// New observable sequence event observed. /// /// - parameter tableView: Bound table view. /// - parameter observedEvent: Event func tableView(_ tableView: UITableView, observedEvent: Event) } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxCollectionViewDataSourcePrefetchingProxy.swift ================================================ // // RxCollectionViewDataSourcePrefetchingProxy.swift // RxCocoa // // Created by Rowan Livingstone on 2/15/18. // Copyright © 2018 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift @available(iOS 10.0, tvOS 10.0, *) extension UICollectionView: HasPrefetchDataSource { public typealias PrefetchDataSource = UICollectionViewDataSourcePrefetching } @available(iOS 10.0, tvOS 10.0, *) private let collectionViewPrefetchDataSourceNotSet = CollectionViewPrefetchDataSourceNotSet() @available(iOS 10.0, tvOS 10.0, *) private final class CollectionViewPrefetchDataSourceNotSet : NSObject , UICollectionViewDataSourcePrefetching { func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {} } @available(iOS 10.0, tvOS 10.0, *) open class RxCollectionViewDataSourcePrefetchingProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var collectionView: UICollectionView? /// - parameter collectionView: Parent object for delegate proxy. public init(collectionView: ParentObject) { self.collectionView = collectionView super.init(parentObject: collectionView, delegateProxy: RxCollectionViewDataSourcePrefetchingProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxCollectionViewDataSourcePrefetchingProxy(collectionView: $0) } } private var _prefetchItemsPublishSubject: PublishSubject<[IndexPath]>? /// Optimized version used for observing prefetch items callbacks. internal var prefetchItemsPublishSubject: PublishSubject<[IndexPath]> { if let subject = _prefetchItemsPublishSubject { return subject } let subject = PublishSubject<[IndexPath]>() _prefetchItemsPublishSubject = subject return subject } private weak var _requiredMethodsPrefetchDataSource: UICollectionViewDataSourcePrefetching? = collectionViewPrefetchDataSourceNotSet /// For more information take a look at `DelegateProxyType`. open override func setForwardToDelegate(_ forwardToDelegate: UICollectionViewDataSourcePrefetching?, retainDelegate: Bool) { _requiredMethodsPrefetchDataSource = forwardToDelegate ?? collectionViewPrefetchDataSourceNotSet super.setForwardToDelegate(forwardToDelegate, retainDelegate: retainDelegate) } deinit { if let subject = _prefetchItemsPublishSubject { subject.on(.completed) } } } @available(iOS 10.0, tvOS 10.0, *) extension RxCollectionViewDataSourcePrefetchingProxy: UICollectionViewDataSourcePrefetching { /// Required delegate method implementation. public func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { if let subject = _prefetchItemsPublishSubject { subject.on(.next(indexPaths)) } (_requiredMethodsPrefetchDataSource ?? collectionViewPrefetchDataSourceNotSet).collectionView(collectionView, prefetchItemsAt: indexPaths) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxCollectionViewDataSourceProxy.swift ================================================ // // RxCollectionViewDataSourceProxy.swift // RxCocoa // // Created by Krunoslav Zaher on 6/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift extension UICollectionView: HasDataSource { public typealias DataSource = UICollectionViewDataSource } private let collectionViewDataSourceNotSet = CollectionViewDataSourceNotSet() private final class CollectionViewDataSourceNotSet : NSObject , UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 0 } // The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath: func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { rxAbstractMethod(message: dataSourceNotSet) } } /// For more information take a look at `DelegateProxyType`. open class RxCollectionViewDataSourceProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var collectionView: UICollectionView? /// - parameter collectionView: Parent object for delegate proxy. public init(collectionView: ParentObject) { self.collectionView = collectionView super.init(parentObject: collectionView, delegateProxy: RxCollectionViewDataSourceProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxCollectionViewDataSourceProxy(collectionView: $0) } } private weak var _requiredMethodsDataSource: UICollectionViewDataSource? = collectionViewDataSourceNotSet /// For more information take a look at `DelegateProxyType`. open override func setForwardToDelegate(_ forwardToDelegate: UICollectionViewDataSource?, retainDelegate: Bool) { _requiredMethodsDataSource = forwardToDelegate ?? collectionViewDataSourceNotSet super.setForwardToDelegate(forwardToDelegate, retainDelegate: retainDelegate) } } extension RxCollectionViewDataSourceProxy: UICollectionViewDataSource { /// Required delegate method implementation. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { (_requiredMethodsDataSource ?? collectionViewDataSourceNotSet).collectionView(collectionView, numberOfItemsInSection: section) } /// Required delegate method implementation. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { (_requiredMethodsDataSource ?? collectionViewDataSourceNotSet).collectionView(collectionView, cellForItemAt: indexPath) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxCollectionViewDelegateProxy.swift ================================================ // // RxCollectionViewDelegateProxy.swift // RxCocoa // // Created by Krunoslav Zaher on 6/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift /// For more information take a look at `DelegateProxyType`. open class RxCollectionViewDelegateProxy : RxScrollViewDelegateProxy { /// Typed parent object. public weak private(set) var collectionView: UICollectionView? /// Initializes `RxCollectionViewDelegateProxy` /// /// - parameter collectionView: Parent object for delegate proxy. public init(collectionView: UICollectionView) { self.collectionView = collectionView super.init(scrollView: collectionView) } } extension RxCollectionViewDelegateProxy: UICollectionViewDelegateFlowLayout {} #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxNavigationControllerDelegateProxy.swift ================================================ // // RxNavigationControllerDelegateProxy.swift // RxCocoa // // Created by Diogo on 13/04/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift extension UINavigationController: HasDelegate { public typealias Delegate = UINavigationControllerDelegate } /// For more information take a look at `DelegateProxyType`. open class RxNavigationControllerDelegateProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var navigationController: UINavigationController? /// - parameter navigationController: Parent object for delegate proxy. public init(navigationController: ParentObject) { self.navigationController = navigationController super.init(parentObject: navigationController, delegateProxy: RxNavigationControllerDelegateProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxNavigationControllerDelegateProxy(navigationController: $0) } } } extension RxNavigationControllerDelegateProxy: UINavigationControllerDelegate {} #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxPickerViewDataSourceProxy.swift ================================================ // // RxPickerViewDataSourceProxy.swift // RxCocoa // // Created by Sergey Shulga on 05/07/2017. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // #if os(iOS) import UIKit import RxSwift extension UIPickerView: HasDataSource { public typealias DataSource = UIPickerViewDataSource } private let pickerViewDataSourceNotSet = PickerViewDataSourceNotSet() final private class PickerViewDataSourceNotSet: NSObject, UIPickerViewDataSource { func numberOfComponents(in pickerView: UIPickerView) -> Int { 0 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 0 } } /// For more information take a look at `DelegateProxyType`. public class RxPickerViewDataSourceProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var pickerView: UIPickerView? /// - parameter pickerView: Parent object for delegate proxy. public init(pickerView: ParentObject) { self.pickerView = pickerView super.init(parentObject: pickerView, delegateProxy: RxPickerViewDataSourceProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxPickerViewDataSourceProxy(pickerView: $0) } } private weak var _requiredMethodsDataSource: UIPickerViewDataSource? = pickerViewDataSourceNotSet /// For more information take a look at `DelegateProxyType`. public override func setForwardToDelegate(_ forwardToDelegate: UIPickerViewDataSource?, retainDelegate: Bool) { _requiredMethodsDataSource = forwardToDelegate ?? pickerViewDataSourceNotSet super.setForwardToDelegate(forwardToDelegate, retainDelegate: retainDelegate) } } // MARK: UIPickerViewDataSource extension RxPickerViewDataSourceProxy: UIPickerViewDataSource { /// Required delegate method implementation. public func numberOfComponents(in pickerView: UIPickerView) -> Int { (_requiredMethodsDataSource ?? pickerViewDataSourceNotSet).numberOfComponents(in: pickerView) } /// Required delegate method implementation. public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { (_requiredMethodsDataSource ?? pickerViewDataSourceNotSet).pickerView(pickerView, numberOfRowsInComponent: component) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxPickerViewDelegateProxy.swift ================================================ // // RxPickerViewDelegateProxy.swift // RxCocoa // // Created by Segii Shulga on 5/12/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if os(iOS) import RxSwift import UIKit extension UIPickerView: HasDelegate { public typealias Delegate = UIPickerViewDelegate } open class RxPickerViewDelegateProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var pickerView: UIPickerView? /// - parameter pickerView: Parent object for delegate proxy. public init(pickerView: ParentObject) { self.pickerView = pickerView super.init(parentObject: pickerView, delegateProxy: RxPickerViewDelegateProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxPickerViewDelegateProxy(pickerView: $0) } } } extension RxPickerViewDelegateProxy: UIPickerViewDelegate {} #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxScrollViewDelegateProxy.swift ================================================ // // RxScrollViewDelegateProxy.swift // RxCocoa // // Created by Krunoslav Zaher on 6/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import RxSwift import UIKit extension UIScrollView: HasDelegate { public typealias Delegate = UIScrollViewDelegate } /// For more information take a look at `DelegateProxyType`. open class RxScrollViewDelegateProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var scrollView: UIScrollView? /// - parameter scrollView: Parent object for delegate proxy. public init(scrollView: ParentObject) { self.scrollView = scrollView super.init(parentObject: scrollView, delegateProxy: RxScrollViewDelegateProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxScrollViewDelegateProxy(scrollView: $0) } self.register { RxTableViewDelegateProxy(tableView: $0) } self.register { RxCollectionViewDelegateProxy(collectionView: $0) } self.register { RxTextViewDelegateProxy(textView: $0) } } private var _contentOffsetBehaviorSubject: BehaviorSubject? private var _contentOffsetPublishSubject: PublishSubject<()>? /// Optimized version used for observing content offset changes. internal var contentOffsetBehaviorSubject: BehaviorSubject { if let subject = _contentOffsetBehaviorSubject { return subject } let subject = BehaviorSubject(value: self.scrollView?.contentOffset ?? CGPoint.zero) _contentOffsetBehaviorSubject = subject return subject } /// Optimized version used for observing content offset changes. internal var contentOffsetPublishSubject: PublishSubject<()> { if let subject = _contentOffsetPublishSubject { return subject } let subject = PublishSubject<()>() _contentOffsetPublishSubject = subject return subject } deinit { if let subject = _contentOffsetBehaviorSubject { subject.on(.completed) } if let subject = _contentOffsetPublishSubject { subject.on(.completed) } } } extension RxScrollViewDelegateProxy: UIScrollViewDelegate { /// For more information take a look at `DelegateProxyType`. public func scrollViewDidScroll(_ scrollView: UIScrollView) { if let subject = _contentOffsetBehaviorSubject { subject.on(.next(scrollView.contentOffset)) } if let subject = _contentOffsetPublishSubject { subject.on(.next(())) } self._forwardToDelegate?.scrollViewDidScroll?(scrollView) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxSearchBarDelegateProxy.swift ================================================ // // RxSearchBarDelegateProxy.swift // RxCocoa // // Created by Krunoslav Zaher on 7/4/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift extension UISearchBar: HasDelegate { public typealias Delegate = UISearchBarDelegate } /// For more information take a look at `DelegateProxyType`. open class RxSearchBarDelegateProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var searchBar: UISearchBar? /// - parameter searchBar: Parent object for delegate proxy. public init(searchBar: ParentObject) { self.searchBar = searchBar super.init(parentObject: searchBar, delegateProxy: RxSearchBarDelegateProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxSearchBarDelegateProxy(searchBar: $0) } } } extension RxSearchBarDelegateProxy: UISearchBarDelegate {} #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxSearchControllerDelegateProxy.swift ================================================ // // RxSearchControllerDelegateProxy.swift // RxCocoa // // Created by Segii Shulga on 3/17/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if os(iOS) import RxSwift import UIKit extension UISearchController: HasDelegate { public typealias Delegate = UISearchControllerDelegate } /// For more information take a look at `DelegateProxyType`. open class RxSearchControllerDelegateProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var searchController: UISearchController? /// - parameter searchController: Parent object for delegate proxy. public init(searchController: UISearchController) { self.searchController = searchController super.init(parentObject: searchController, delegateProxy: RxSearchControllerDelegateProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxSearchControllerDelegateProxy(searchController: $0) } } } extension RxSearchControllerDelegateProxy: UISearchControllerDelegate {} #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxTabBarControllerDelegateProxy.swift ================================================ // // RxTabBarControllerDelegateProxy.swift // RxCocoa // // Created by Yusuke Kita on 2016/12/07. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift extension UITabBarController: HasDelegate { public typealias Delegate = UITabBarControllerDelegate } /// For more information take a look at `DelegateProxyType`. open class RxTabBarControllerDelegateProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var tabBar: UITabBarController? /// - parameter tabBar: Parent object for delegate proxy. public init(tabBar: ParentObject) { self.tabBar = tabBar super.init(parentObject: tabBar, delegateProxy: RxTabBarControllerDelegateProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxTabBarControllerDelegateProxy(tabBar: $0) } } } extension RxTabBarControllerDelegateProxy: UITabBarControllerDelegate {} #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxTabBarDelegateProxy.swift ================================================ // // RxTabBarDelegateProxy.swift // RxCocoa // // Created by Jesse Farless on 5/14/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift extension UITabBar: HasDelegate { public typealias Delegate = UITabBarDelegate } /// For more information take a look at `DelegateProxyType`. open class RxTabBarDelegateProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var tabBar: UITabBar? /// - parameter tabBar: Parent object for delegate proxy. public init(tabBar: ParentObject) { self.tabBar = tabBar super.init(parentObject: tabBar, delegateProxy: RxTabBarDelegateProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxTabBarDelegateProxy(tabBar: $0) } } /// For more information take a look at `DelegateProxyType`. open class func currentDelegate(for object: ParentObject) -> UITabBarDelegate? { object.delegate } /// For more information take a look at `DelegateProxyType`. open class func setCurrentDelegate(_ delegate: UITabBarDelegate?, to object: ParentObject) { object.delegate = delegate } } extension RxTabBarDelegateProxy: UITabBarDelegate {} #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxTableViewDataSourcePrefetchingProxy.swift ================================================ // // RxTableViewDataSourcePrefetchingProxy.swift // RxCocoa // // Created by Rowan Livingstone on 2/15/18. // Copyright © 2018 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift @available(iOS 10.0, tvOS 10.0, *) extension UITableView: HasPrefetchDataSource { public typealias PrefetchDataSource = UITableViewDataSourcePrefetching } @available(iOS 10.0, tvOS 10.0, *) private let tableViewPrefetchDataSourceNotSet = TableViewPrefetchDataSourceNotSet() @available(iOS 10.0, tvOS 10.0, *) private final class TableViewPrefetchDataSourceNotSet : NSObject , UITableViewDataSourcePrefetching { func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {} } @available(iOS 10.0, tvOS 10.0, *) open class RxTableViewDataSourcePrefetchingProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var tableView: UITableView? /// - parameter tableView: Parent object for delegate proxy. public init(tableView: ParentObject) { self.tableView = tableView super.init(parentObject: tableView, delegateProxy: RxTableViewDataSourcePrefetchingProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxTableViewDataSourcePrefetchingProxy(tableView: $0) } } private var _prefetchRowsPublishSubject: PublishSubject<[IndexPath]>? /// Optimized version used for observing prefetch rows callbacks. internal var prefetchRowsPublishSubject: PublishSubject<[IndexPath]> { if let subject = _prefetchRowsPublishSubject { return subject } let subject = PublishSubject<[IndexPath]>() _prefetchRowsPublishSubject = subject return subject } private weak var _requiredMethodsPrefetchDataSource: UITableViewDataSourcePrefetching? = tableViewPrefetchDataSourceNotSet /// For more information take a look at `DelegateProxyType`. open override func setForwardToDelegate(_ forwardToDelegate: UITableViewDataSourcePrefetching?, retainDelegate: Bool) { _requiredMethodsPrefetchDataSource = forwardToDelegate ?? tableViewPrefetchDataSourceNotSet super.setForwardToDelegate(forwardToDelegate, retainDelegate: retainDelegate) } deinit { if let subject = _prefetchRowsPublishSubject { subject.on(.completed) } } } @available(iOS 10.0, tvOS 10.0, *) extension RxTableViewDataSourcePrefetchingProxy: UITableViewDataSourcePrefetching { /// Required delegate method implementation. public func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { if let subject = _prefetchRowsPublishSubject { subject.on(.next(indexPaths)) } (_requiredMethodsPrefetchDataSource ?? tableViewPrefetchDataSourceNotSet).tableView(tableView, prefetchRowsAt: indexPaths) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxTableViewDataSourceProxy.swift ================================================ // // RxTableViewDataSourceProxy.swift // RxCocoa // // Created by Krunoslav Zaher on 6/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift extension UITableView: HasDataSource { public typealias DataSource = UITableViewDataSource } private let tableViewDataSourceNotSet = TableViewDataSourceNotSet() private final class TableViewDataSourceNotSet : NSObject , UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { rxAbstractMethod(message: dataSourceNotSet) } } /// For more information take a look at `DelegateProxyType`. open class RxTableViewDataSourceProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var tableView: UITableView? /// - parameter tableView: Parent object for delegate proxy. public init(tableView: UITableView) { self.tableView = tableView super.init(parentObject: tableView, delegateProxy: RxTableViewDataSourceProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxTableViewDataSourceProxy(tableView: $0) } } private weak var _requiredMethodsDataSource: UITableViewDataSource? = tableViewDataSourceNotSet /// For more information take a look at `DelegateProxyType`. open override func setForwardToDelegate(_ forwardToDelegate: UITableViewDataSource?, retainDelegate: Bool) { _requiredMethodsDataSource = forwardToDelegate ?? tableViewDataSourceNotSet super.setForwardToDelegate(forwardToDelegate, retainDelegate: retainDelegate) } } extension RxTableViewDataSourceProxy: UITableViewDataSource { /// Required delegate method implementation. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { (_requiredMethodsDataSource ?? tableViewDataSourceNotSet).tableView(tableView, numberOfRowsInSection: section) } /// Required delegate method implementation. public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { (_requiredMethodsDataSource ?? tableViewDataSourceNotSet).tableView(tableView, cellForRowAt: indexPath) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxTableViewDelegateProxy.swift ================================================ // // RxTableViewDelegateProxy.swift // RxCocoa // // Created by Krunoslav Zaher on 6/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift /// For more information take a look at `DelegateProxyType`. open class RxTableViewDelegateProxy : RxScrollViewDelegateProxy { /// Typed parent object. public weak private(set) var tableView: UITableView? /// - parameter tableView: Parent object for delegate proxy. public init(tableView: UITableView) { self.tableView = tableView super.init(scrollView: tableView) } } extension RxTableViewDelegateProxy: UITableViewDelegate {} #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxTextStorageDelegateProxy.swift ================================================ // // RxTextStorageDelegateProxy.swift // RxCocoa // // Created by Segii Shulga on 12/30/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import RxSwift import UIKit extension NSTextStorage: HasDelegate { public typealias Delegate = NSTextStorageDelegate } open class RxTextStorageDelegateProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var textStorage: NSTextStorage? /// - parameter textStorage: Parent object for delegate proxy. public init(textStorage: NSTextStorage) { self.textStorage = textStorage super.init(parentObject: textStorage, delegateProxy: RxTextStorageDelegateProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxTextStorageDelegateProxy(textStorage: $0) } } } extension RxTextStorageDelegateProxy: NSTextStorageDelegate {} #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxTextViewDelegateProxy.swift ================================================ // // RxTextViewDelegateProxy.swift // RxCocoa // // Created by Yuta ToKoRo on 7/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift /// For more information take a look at `DelegateProxyType`. open class RxTextViewDelegateProxy : RxScrollViewDelegateProxy { /// Typed parent object. public weak private(set) var textView: UITextView? /// - parameter textview: Parent object for delegate proxy. public init(textView: UITextView) { self.textView = textView super.init(scrollView: textView) } } extension RxTextViewDelegateProxy: UITextViewDelegate { /// For more information take a look at `DelegateProxyType`. @objc open func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { /** We've had some issues with observing text changes. This is here just in case we need the same hack in future and that we wouldn't need to change the public interface. */ let forwardToDelegate = self.forwardToDelegate() as? UITextViewDelegate return forwardToDelegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text) ?? true } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/Proxies/RxWKNavigationDelegateProxy.swift ================================================ // // RxWKNavigationDelegateProxy.swift // RxCocoa // // Created by Giuseppe Lanza on 14/02/2020. // Copyright © 2020 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(macOS) import RxSwift import WebKit @available(iOS 8.0, OSX 10.10, OSXApplicationExtension 10.10, *) open class RxWKNavigationDelegateProxy : DelegateProxy , DelegateProxyType { /// Typed parent object. public weak private(set) var webView: WKWebView? /// - parameter webView: Parent object for delegate proxy. public init(webView: ParentObject) { self.webView = webView super.init(parentObject: webView, delegateProxy: RxWKNavigationDelegateProxy.self) } // Register known implementations public static func registerKnownImplementations() { self.register { RxWKNavigationDelegateProxy(webView: $0) } } public static func currentDelegate(for object: WKWebView) -> WKNavigationDelegate? { object.navigationDelegate } public static func setCurrentDelegate(_ delegate: WKNavigationDelegate?, to object: WKWebView) { object.navigationDelegate = delegate } } @available(iOS 8.0, OSX 10.10, OSXApplicationExtension 10.10, *) extension RxWKNavigationDelegateProxy: WKNavigationDelegate {} #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift ================================================ // // UIActivityIndicatorView+Rx.swift // RxCocoa // // Created by Ivan Persidskiy on 02/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift extension Reactive where Base: UIActivityIndicatorView { /// Bindable sink for `startAnimating()`, `stopAnimating()` methods. public var isAnimating: Binder { Binder(self.base) { activityIndicator, active in if active { activityIndicator.startAnimating() } else { activityIndicator.stopAnimating() } } } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIApplication+Rx.swift ================================================ // // UIApplication+Rx.swift // RxCocoa // // Created by Mads Bøgeskov on 18/01/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if os(iOS) import UIKit import RxSwift extension Reactive where Base: UIApplication { /// Bindable sink for `isNetworkActivityIndicatorVisible`. public var isNetworkActivityIndicatorVisible: Binder { return Binder(self.base) { application, active in application.isNetworkActivityIndicatorVisible = active } } /// Reactive wrapper for `UIApplication.didEnterBackgroundNotification` public static var didEnterBackground: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.didEnterBackgroundNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.willEnterForegroundNotification` public static var willEnterForeground: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.willEnterForegroundNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.didFinishLaunchingNotification` public static var didFinishLaunching: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.didFinishLaunchingNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.didBecomeActiveNotification` public static var didBecomeActive: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.didBecomeActiveNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.willResignActiveNotification` public static var willResignActive: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.willResignActiveNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.didReceiveMemoryWarningNotification` public static var didReceiveMemoryWarning: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.didReceiveMemoryWarningNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.willTerminateNotification` public static var willTerminate: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.willTerminateNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.significantTimeChangeNotification` public static var significantTimeChange: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.significantTimeChangeNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.backgroundRefreshStatusDidChangeNotification` public static var backgroundRefreshStatusDidChange: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.backgroundRefreshStatusDidChangeNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.protectedDataWillBecomeUnavailableNotification` public static var protectedDataWillBecomeUnavailable: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.protectedDataWillBecomeUnavailableNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.protectedDataDidBecomeAvailableNotification` public static var protectedDataDidBecomeAvailable: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.protectedDataDidBecomeAvailableNotification).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for `UIApplication.userDidTakeScreenshotNotification` public static var userDidTakeScreenshot: ControlEvent { let source = NotificationCenter.default.rx.notification(UIApplication.userDidTakeScreenshotNotification).map { _ in } return ControlEvent(events: source) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIBarButtonItem+Rx.swift ================================================ // // UIBarButtonItem+Rx.swift // RxCocoa // // Created by Daniel Tartaglia on 5/31/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift private var rx_tap_key: UInt8 = 0 extension Reactive where Base: UIBarButtonItem { /// Reactive wrapper for target action pattern on `self`. public var tap: ControlEvent<()> { let source = lazyInstanceObservable(&rx_tap_key) { () -> Observable<()> in Observable.create { [weak control = self.base] observer in guard let control = control else { observer.on(.completed) return Disposables.create() } let target = BarButtonItemTarget(barButtonItem: control) { observer.on(.next(())) } return target } .take(until: self.deallocated) .share() } return ControlEvent(events: source) } } @objc final class BarButtonItemTarget: RxTarget { typealias Callback = () -> Void weak var barButtonItem: UIBarButtonItem? var callback: Callback! init(barButtonItem: UIBarButtonItem, callback: @escaping () -> Void) { self.barButtonItem = barButtonItem self.callback = callback super.init() barButtonItem.target = self barButtonItem.action = #selector(BarButtonItemTarget.action(_:)) } override func dispose() { super.dispose() #if DEBUG MainScheduler.ensureRunningOnMainThread() #endif barButtonItem?.target = nil barButtonItem?.action = nil callback = nil } @objc func action(_ sender: AnyObject) { callback() } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIButton+Rx.swift ================================================ // // UIButton+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 3/28/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) import RxSwift import UIKit extension Reactive where Base: UIButton { /// Reactive wrapper for `TouchUpInside` control event. public var tap: ControlEvent { controlEvent(.touchUpInside) } } #endif #if os(tvOS) import RxSwift import UIKit extension Reactive where Base: UIButton { /// Reactive wrapper for `PrimaryActionTriggered` control event. public var primaryAction: ControlEvent { controlEvent(.primaryActionTriggered) } } #endif #if os(iOS) || os(tvOS) import RxSwift import UIKit extension Reactive where Base: UIButton { /// Reactive wrapper for `setTitle(_:for:)` public func title(for controlState: UIControl.State = []) -> Binder { Binder(self.base) { button, title in button.setTitle(title, for: controlState) } } /// Reactive wrapper for `setImage(_:for:)` public func image(for controlState: UIControl.State = []) -> Binder { Binder(self.base) { button, image in button.setImage(image, for: controlState) } } /// Reactive wrapper for `setBackgroundImage(_:for:)` public func backgroundImage(for controlState: UIControl.State = []) -> Binder { Binder(self.base) { button, image in button.setBackgroundImage(image, for: controlState) } } } #endif #if os(iOS) || os(tvOS) import RxSwift import UIKit extension Reactive where Base: UIButton { /// Reactive wrapper for `setAttributedTitle(_:controlState:)` public func attributedTitle(for controlState: UIControl.State = []) -> Binder { return Binder(self.base) { button, attributedTitle -> Void in button.setAttributedTitle(attributedTitle, for: controlState) } } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UICollectionView+Rx.swift ================================================ // // UICollectionView+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 4/2/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import RxSwift import UIKit // Items extension Reactive where Base: UICollectionView { /** Binds sequences of elements to collection view items. - parameter source: Observable sequence of items. - parameter cellFactory: Transform between sequence elements and view cells. - returns: Disposable object that can be used to unbind. Example let items = Observable.just([ 1, 2, 3 ]) items .bind(to: collectionView.rx.items) { (collectionView, row, element) in let indexPath = IndexPath(row: row, section: 0) let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! NumberCell cell.value?.text = "\(element) @ \(row)" return cell } .disposed(by: disposeBag) */ public func items (_ source: Source) -> (_ cellFactory: @escaping (UICollectionView, Int, Sequence.Element) -> UICollectionViewCell) -> Disposable where Source.Element == Sequence { return { cellFactory in let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper(cellFactory: cellFactory) return self.items(dataSource: dataSource)(source) } } /** Binds sequences of elements to collection view items. - parameter cellIdentifier: Identifier used to dequeue cells. - parameter source: Observable sequence of items. - parameter configureCell: Transform between sequence elements and view cells. - parameter cellType: Type of collection view cell. - returns: Disposable object that can be used to unbind. Example let items = Observable.just([ 1, 2, 3 ]) items .bind(to: collectionView.rx.items(cellIdentifier: "Cell", cellType: NumberCell.self)) { (row, element, cell) in cell.value?.text = "\(element) @ \(row)" } .disposed(by: disposeBag) */ public func items (cellIdentifier: String, cellType: Cell.Type = Cell.self) -> (_ source: Source) -> (_ configureCell: @escaping (Int, Sequence.Element, Cell) -> Void) -> Disposable where Source.Element == Sequence { return { source in return { configureCell in let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper { cv, i, item in let indexPath = IndexPath(item: i, section: 0) let cell = cv.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! Cell configureCell(i, item, cell) return cell } return self.items(dataSource: dataSource)(source) } } } /** Binds sequences of elements to collection view items using a custom reactive data used to perform the transformation. - parameter dataSource: Data source used to transform elements to view cells. - parameter source: Observable sequence of items. - returns: Disposable object that can be used to unbind. Example let dataSource = RxCollectionViewSectionedReloadDataSource>() let items = Observable.just([ SectionModel(model: "First section", items: [ 1.0, 2.0, 3.0 ]), SectionModel(model: "Second section", items: [ 1.0, 2.0, 3.0 ]), SectionModel(model: "Third section", items: [ 1.0, 2.0, 3.0 ]) ]) dataSource.configureCell = { (dataSource, cv, indexPath, element) in let cell = cv.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! NumberCell cell.value?.text = "\(element) @ row \(indexPath.row)" return cell } items .bind(to: collectionView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) */ public func items< DataSource: RxCollectionViewDataSourceType & UICollectionViewDataSource, Source: ObservableType> (dataSource: DataSource) -> (_ source: Source) -> Disposable where DataSource.Element == Source.Element { return { source in // This is called for side effects only, and to make sure delegate proxy is in place when // data source is being bound. // This is needed because theoretically the data source subscription itself might // call `self.rx.delegate`. If that happens, it might cause weird side effects since // setting data source will set delegate, and UICollectionView might get into a weird state. // Therefore it's better to set delegate proxy first, just to be sure. _ = self.delegate // Strong reference is needed because data source is in use until result subscription is disposed return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource, retainDataSource: true) { [weak collectionView = self.base] (_: RxCollectionViewDataSourceProxy, event) -> Void in guard let collectionView = collectionView else { return } dataSource.collectionView(collectionView, observedEvent: event) } } } } extension Reactive where Base: UICollectionView { public typealias DisplayCollectionViewCellEvent = (cell: UICollectionViewCell, at: IndexPath) public typealias DisplayCollectionViewSupplementaryViewEvent = (supplementaryView: UICollectionReusableView, elementKind: String, at: IndexPath) /// Reactive wrapper for `dataSource`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var dataSource: DelegateProxy { RxCollectionViewDataSourceProxy.proxy(for: base) } /// Installs data source as forwarding delegate on `rx.dataSource`. /// Data source won't be retained. /// /// It enables using normal delegate mechanism with reactive delegate mechanism. /// /// - parameter dataSource: Data source object. /// - returns: Disposable object that can be used to unbind the data source. public func setDataSource(_ dataSource: UICollectionViewDataSource) -> Disposable { RxCollectionViewDataSourceProxy.installForwardDelegate(dataSource, retainDelegate: false, onProxyForObject: self.base) } /// Reactive wrapper for `delegate` message `collectionView(_:didSelectItemAtIndexPath:)`. public var itemSelected: ControlEvent { let source = delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didSelectItemAt:))) .map { a in return try castOrThrow(IndexPath.self, a[1]) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `collectionView(_:didDeselectItemAtIndexPath:)`. public var itemDeselected: ControlEvent { let source = delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didDeselectItemAt:))) .map { a in return try castOrThrow(IndexPath.self, a[1]) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `collectionView(_:didHighlightItemAt:)`. public var itemHighlighted: ControlEvent { let source = delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didHighlightItemAt:))) .map { a in return try castOrThrow(IndexPath.self, a[1]) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `collectionView(_:didUnhighlightItemAt:)`. public var itemUnhighlighted: ControlEvent { let source = delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didUnhighlightItemAt:))) .map { a in return try castOrThrow(IndexPath.self, a[1]) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `collectionView:willDisplay:forItemAt:`. public var willDisplayCell: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:willDisplay:forItemAt:))) .map { a in return (try castOrThrow(UICollectionViewCell.self, a[1]), try castOrThrow(IndexPath.self, a[2])) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `collectionView(_:willDisplaySupplementaryView:forElementKind:at:)`. public var willDisplaySupplementaryView: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:willDisplaySupplementaryView:forElementKind:at:))) .map { a in return (try castOrThrow(UICollectionReusableView.self, a[1]), try castOrThrow(String.self, a[2]), try castOrThrow(IndexPath.self, a[3])) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `collectionView:didEndDisplaying:forItemAt:`. public var didEndDisplayingCell: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didEndDisplaying:forItemAt:))) .map { a in return (try castOrThrow(UICollectionViewCell.self, a[1]), try castOrThrow(IndexPath.self, a[2])) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `collectionView(_:didEndDisplayingSupplementaryView:forElementOfKind:at:)`. public var didEndDisplayingSupplementaryView: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didEndDisplayingSupplementaryView:forElementOfKind:at:))) .map { a in return (try castOrThrow(UICollectionReusableView.self, a[1]), try castOrThrow(String.self, a[2]), try castOrThrow(IndexPath.self, a[3])) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `collectionView(_:didSelectItemAtIndexPath:)`. /// /// It can be only used when one of the `rx.itemsWith*` methods is used to bind observable sequence, /// or any other data source conforming to `SectionedViewDataSourceType` protocol. /// /// ``` /// collectionView.rx.modelSelected(MyModel.self) /// .map { ... /// ``` public func modelSelected(_ modelType: T.Type) -> ControlEvent { let source: Observable = itemSelected.flatMap { [weak view = self.base as UICollectionView] indexPath -> Observable in guard let view = view else { return Observable.empty() } return Observable.just(try view.rx.model(at: indexPath)) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `collectionView(_:didSelectItemAtIndexPath:)`. /// /// It can be only used when one of the `rx.itemsWith*` methods is used to bind observable sequence, /// or any other data source conforming to `SectionedViewDataSourceType` protocol. /// /// ``` /// collectionView.rx.modelDeselected(MyModel.self) /// .map { ... /// ``` public func modelDeselected(_ modelType: T.Type) -> ControlEvent { let source: Observable = itemDeselected.flatMap { [weak view = self.base as UICollectionView] indexPath -> Observable in guard let view = view else { return Observable.empty() } return Observable.just(try view.rx.model(at: indexPath)) } return ControlEvent(events: source) } /// Synchronous helper method for retrieving a model at indexPath through a reactive data source public func model(at indexPath: IndexPath) throws -> T { let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.itemsWith*` methods was used.") let element = try dataSource.model(at: indexPath) return try castOrThrow(T.self, element) } } @available(iOS 10.0, tvOS 10.0, *) extension Reactive where Base: UICollectionView { /// Reactive wrapper for `prefetchDataSource`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var prefetchDataSource: DelegateProxy { RxCollectionViewDataSourcePrefetchingProxy.proxy(for: base) } /** Installs prefetch data source as forwarding delegate on `rx.prefetchDataSource`. Prefetch data source won't be retained. It enables using normal delegate mechanism with reactive delegate mechanism. - parameter prefetchDataSource: Prefetch data source object. - returns: Disposable object that can be used to unbind the data source. */ public func setPrefetchDataSource(_ prefetchDataSource: UICollectionViewDataSourcePrefetching) -> Disposable { return RxCollectionViewDataSourcePrefetchingProxy.installForwardDelegate(prefetchDataSource, retainDelegate: false, onProxyForObject: self.base) } /// Reactive wrapper for `prefetchDataSource` message `collectionView(_:prefetchItemsAt:)`. public var prefetchItems: ControlEvent<[IndexPath]> { let source = RxCollectionViewDataSourcePrefetchingProxy.proxy(for: base).prefetchItemsPublishSubject return ControlEvent(events: source) } /// Reactive wrapper for `prefetchDataSource` message `collectionView(_:cancelPrefetchingForItemsAt:)`. public var cancelPrefetchingForItems: ControlEvent<[IndexPath]> { let source = prefetchDataSource.methodInvoked(#selector(UICollectionViewDataSourcePrefetching.collectionView(_:cancelPrefetchingForItemsAt:))) .map { a in return try castOrThrow(Array.self, a[1]) } return ControlEvent(events: source) } } #endif #if os(tvOS) extension Reactive where Base: UICollectionView { /// Reactive wrapper for `delegate` message `collectionView(_:didUpdateFocusInContext:withAnimationCoordinator:)`. public var didUpdateFocusInContextWithAnimationCoordinator: ControlEvent<(context: UICollectionViewFocusUpdateContext, animationCoordinator: UIFocusAnimationCoordinator)> { let source = delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didUpdateFocusIn:with:))) .map { a -> (context: UICollectionViewFocusUpdateContext, animationCoordinator: UIFocusAnimationCoordinator) in let context = try castOrThrow(UICollectionViewFocusUpdateContext.self, a[1]) let animationCoordinator = try castOrThrow(UIFocusAnimationCoordinator.self, a[2]) return (context: context, animationCoordinator: animationCoordinator) } return ControlEvent(events: source) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIControl+Rx.swift ================================================ // // UIControl+Rx.swift // RxCocoa // // Created by Daniel Tartaglia on 5/23/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import RxSwift import UIKit extension Reactive where Base: UIControl { /// Reactive wrapper for target action pattern. /// /// - parameter controlEvents: Filter for observed event types. public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()> { let source: Observable = Observable.create { [weak control = self.base] observer in MainScheduler.ensureRunningOnMainThread() guard let control = control else { observer.on(.completed) return Disposables.create() } let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { _ in observer.on(.next(())) } return Disposables.create(with: controlTarget.dispose) } .take(until: deallocated) return ControlEvent(events: source) } /// Creates a `ControlProperty` that is triggered by target/action pattern value updates. /// /// - parameter controlEvents: Events that trigger value update sequence elements. /// - parameter getter: Property value getter. /// - parameter setter: Property value setter. public func controlProperty( editingEvents: UIControl.Event, getter: @escaping (Base) -> T, setter: @escaping (Base, T) -> Void ) -> ControlProperty { let source: Observable = Observable.create { [weak weakControl = base] observer in guard let control = weakControl else { observer.on(.completed) return Disposables.create() } observer.on(.next(getter(control))) let controlTarget = ControlTarget(control: control, controlEvents: editingEvents) { _ in if let control = weakControl { observer.on(.next(getter(control))) } } return Disposables.create(with: controlTarget.dispose) } .take(until: deallocated) let bindingObserver = Binder(base, binding: setter) return ControlProperty(values: source, valueSink: bindingObserver) } /// This is a separate method to better communicate to public consumers that /// an `editingEvent` needs to fire for control property to be updated. internal func controlPropertyWithDefaultEvents( editingEvents: UIControl.Event = [.allEditingEvents, .valueChanged], getter: @escaping (Base) -> T, setter: @escaping (Base, T) -> Void ) -> ControlProperty { return controlProperty( editingEvents: editingEvents, getter: getter, setter: setter ) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIDatePicker+Rx.swift ================================================ // // UIDatePicker+Rx.swift // RxCocoa // // Created by Daniel Tartaglia on 5/31/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) import RxSwift import UIKit extension Reactive where Base: UIDatePicker { /// Reactive wrapper for `date` property. public var date: ControlProperty { value } /// Reactive wrapper for `date` property. public var value: ControlProperty { return base.rx.controlPropertyWithDefaultEvents( getter: { datePicker in datePicker.date }, setter: { datePicker, value in datePicker.date = value } ) } /// Reactive wrapper for `countDownDuration` property. public var countDownDuration: ControlProperty { return base.rx.controlPropertyWithDefaultEvents( getter: { datePicker in datePicker.countDownDuration }, setter: { datePicker, value in datePicker.countDownDuration = value } ) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIGestureRecognizer+Rx.swift ================================================ // // UIGestureRecognizer+Rx.swift // RxCocoa // // Created by Carlos García on 10/6/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift // This should be only used from `MainScheduler` final class GestureTarget: RxTarget { typealias Callback = (Recognizer) -> Void let selector = #selector(ControlTarget.eventHandler(_:)) weak var gestureRecognizer: Recognizer? var callback: Callback? init(_ gestureRecognizer: Recognizer, callback: @escaping Callback) { self.gestureRecognizer = gestureRecognizer self.callback = callback super.init() gestureRecognizer.addTarget(self, action: selector) let method = self.method(for: selector) if method == nil { fatalError("Can't find method") } } @objc func eventHandler(_ sender: UIGestureRecognizer) { if let callback = self.callback, let gestureRecognizer = self.gestureRecognizer { callback(gestureRecognizer) } } override func dispose() { super.dispose() self.gestureRecognizer?.removeTarget(self, action: self.selector) self.callback = nil } } extension Reactive where Base: UIGestureRecognizer { /// Reactive wrapper for gesture recognizer events. public var event: ControlEvent { let source: Observable = Observable.create { [weak control = self.base] observer in MainScheduler.ensureRunningOnMainThread() guard let control = control else { observer.on(.completed) return Disposables.create() } let observer = GestureTarget(control) { control in observer.on(.next(control)) } return observer }.take(until: deallocated) return ControlEvent(events: source) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UINavigationController+Rx.swift ================================================ // // UINavigationController+Rx.swift // RxCocoa // // Created by Diogo on 13/04/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import RxSwift import UIKit extension Reactive where Base: UINavigationController { public typealias ShowEvent = (viewController: UIViewController, animated: Bool) /// Reactive wrapper for `delegate`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var delegate: DelegateProxy { RxNavigationControllerDelegateProxy.proxy(for: base) } /// Reactive wrapper for delegate method `navigationController(:willShow:animated:)`. public var willShow: ControlEvent { let source: Observable = delegate .methodInvoked(#selector(UINavigationControllerDelegate.navigationController(_:willShow:animated:))) .map { arg in let viewController = try castOrThrow(UIViewController.self, arg[1]) let animated = try castOrThrow(Bool.self, arg[2]) return (viewController, animated) } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `navigationController(:didShow:animated:)`. public var didShow: ControlEvent { let source: Observable = delegate .methodInvoked(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:))) .map { arg in let viewController = try castOrThrow(UIViewController.self, arg[1]) let animated = try castOrThrow(Bool.self, arg[2]) return (viewController, animated) } return ControlEvent(events: source) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIPickerView+Rx.swift ================================================ // // UIPickerView+Rx.swift // RxCocoa // // Created by Segii Shulga on 5/12/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if os(iOS) import RxSwift import UIKit extension Reactive where Base: UIPickerView { /// Reactive wrapper for `delegate`. /// For more information take a look at `DelegateProxyType` protocol documentation. public var delegate: DelegateProxy { return RxPickerViewDelegateProxy.proxy(for: base) } /// Installs delegate as forwarding delegate on `delegate`. /// Delegate won't be retained. /// /// It enables using normal delegate mechanism with reactive delegate mechanism. /// /// - parameter delegate: Delegate object. /// - returns: Disposable object that can be used to unbind the delegate. public func setDelegate(_ delegate: UIPickerViewDelegate) -> Disposable { return RxPickerViewDelegateProxy.installForwardDelegate(delegate, retainDelegate: false, onProxyForObject: self.base) } /** Reactive wrapper for `dataSource`. For more information take a look at `DelegateProxyType` protocol documentation. */ public var dataSource: DelegateProxy { return RxPickerViewDataSourceProxy.proxy(for: base) } /** Reactive wrapper for `delegate` message `pickerView:didSelectRow:inComponent:`. */ public var itemSelected: ControlEvent<(row: Int, component: Int)> { let source = delegate .methodInvoked(#selector(UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:))) .map { return (row: try castOrThrow(Int.self, $0[1]), component: try castOrThrow(Int.self, $0[2])) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `pickerView:didSelectRow:inComponent:`. It can be only used when one of the `rx.itemTitles, rx.itemAttributedTitles, items(_ source: O)` methods is used to bind observable sequence, or any other data source conforming to a `ViewDataSourceType` protocol. ``` pickerView.rx.modelSelected(MyModel.self) .map { ... ``` - parameter modelType: Type of a Model which bound to the dataSource */ public func modelSelected(_ modelType: T.Type) -> ControlEvent<[T]> { let source = itemSelected.flatMap { [weak view = self.base as UIPickerView] _, component -> Observable<[T]> in guard let view = view else { return Observable.empty() } let model: [T] = try (0 ..< view.numberOfComponents).map { component in let row = view.selectedRow(inComponent: component) return try view.rx.model(at: IndexPath(row: row, section: component)) } return Observable.just(model) } return ControlEvent(events: source) } /** Binds sequences of elements to picker view rows. - parameter source: Observable sequence of items. - parameter titleForRow: Transform between sequence elements and row titles. - returns: Disposable object that can be used to unbind. Example: let items = Observable.just([ "First Item", "Second Item", "Third Item" ]) items .bind(to: pickerView.rx.itemTitles) { (row, element) in return element.title } .disposed(by: disposeBag) */ public func itemTitles (_ source: Source) -> (_ titleForRow: @escaping (Int, Sequence.Element) -> String?) -> Disposable where Source.Element == Sequence { return { titleForRow in let adapter = RxStringPickerViewAdapter(titleForRow: titleForRow) return self.items(adapter: adapter)(source) } } /** Binds sequences of elements to picker view rows. - parameter source: Observable sequence of items. - parameter attributedTitleForRow: Transform between sequence elements and row attributed titles. - returns: Disposable object that can be used to unbind. Example: let items = Observable.just([ "First Item", "Second Item", "Third Item" ]) items .bind(to: pickerView.rx.itemAttributedTitles) { (row, element) in return NSAttributedString(string: element.title) } .disposed(by: disposeBag) */ public func itemAttributedTitles (_ source: Source) -> (_ attributedTitleForRow: @escaping (Int, Sequence.Element) -> NSAttributedString?) -> Disposable where Source.Element == Sequence { return { attributedTitleForRow in let adapter = RxAttributedStringPickerViewAdapter(attributedTitleForRow: attributedTitleForRow) return self.items(adapter: adapter)(source) } } /** Binds sequences of elements to picker view rows. - parameter source: Observable sequence of items. - parameter viewForRow: Transform between sequence elements and row views. - returns: Disposable object that can be used to unbind. Example: let items = Observable.just([ "First Item", "Second Item", "Third Item" ]) items .bind(to: pickerView.rx.items) { (row, element, view) in guard let myView = view as? MyView else { let view = MyView() view.configure(with: element) return view } myView.configure(with: element) return myView } .disposed(by: disposeBag) */ public func items (_ source: Source) -> (_ viewForRow: @escaping (Int, Sequence.Element, UIView?) -> UIView) -> Disposable where Source.Element == Sequence { return { viewForRow in let adapter = RxPickerViewAdapter(viewForRow: viewForRow) return self.items(adapter: adapter)(source) } } /** Binds sequences of elements to picker view rows using a custom reactive adapter used to perform the transformation. This method will retain the adapter for as long as the subscription isn't disposed (result `Disposable` being disposed). In case `source` observable sequence terminates successfully, the adapter will present latest element until the subscription isn't disposed. - parameter adapter: Adapter used to transform elements to picker components. - parameter source: Observable sequence of items. - returns: Disposable object that can be used to unbind. */ public func items(adapter: Adapter) -> (_ source: Source) -> Disposable where Source.Element == Adapter.Element { return { source in let delegateSubscription = self.setDelegate(adapter) let dataSourceSubscription = source.subscribeProxyDataSource(ofObject: self.base, dataSource: adapter, retainDataSource: true, binding: { [weak pickerView = self.base] (_: RxPickerViewDataSourceProxy, event) in guard let pickerView = pickerView else { return } adapter.pickerView(pickerView, observedEvent: event) }) return Disposables.create(delegateSubscription, dataSourceSubscription) } } /** Synchronous helper method for retrieving a model at indexPath through a reactive data source. */ public func model(at indexPath: IndexPath) throws -> T { let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.itemTitles, rx.itemAttributedTitles, items(_ source: O)` methods was used.") return castOrFatalError(try dataSource.model(at: indexPath)) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIRefreshControl+Rx.swift ================================================ // // UIRefreshControl+Rx.swift // RxCocoa // // Created by Yosuke Ishikawa on 1/31/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if os(iOS) import UIKit import RxSwift extension Reactive where Base: UIRefreshControl { /// Bindable sink for `beginRefreshing()`, `endRefreshing()` methods. public var isRefreshing: Binder { return Binder(self.base) { refreshControl, refresh in if refresh { refreshControl.beginRefreshing() } else { refreshControl.endRefreshing() } } } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIScrollView+Rx.swift ================================================ // // UIScrollView+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 4/3/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import RxSwift import UIKit extension Reactive where Base: UIScrollView { public typealias EndZoomEvent = (view: UIView?, scale: CGFloat) public typealias WillEndDraggingEvent = (velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) /// Reactive wrapper for `delegate`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var delegate: DelegateProxy { return RxScrollViewDelegateProxy.proxy(for: base) } /// Reactive wrapper for `contentOffset`. public var contentOffset: ControlProperty { let proxy = RxScrollViewDelegateProxy.proxy(for: base) let bindingObserver = Binder(self.base) { scrollView, contentOffset in scrollView.contentOffset = contentOffset } return ControlProperty(values: proxy.contentOffsetBehaviorSubject, valueSink: bindingObserver) } /// Reactive wrapper for delegate method `scrollViewDidScroll` public var didScroll: ControlEvent { let source = RxScrollViewDelegateProxy.proxy(for: base).contentOffsetPublishSubject return ControlEvent(events: source) } /// Reactive wrapper for delegate method `scrollViewWillBeginDecelerating` public var willBeginDecelerating: ControlEvent { let source = delegate.methodInvoked(#selector(UIScrollViewDelegate.scrollViewWillBeginDecelerating(_:))).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `scrollViewDidEndDecelerating` public var didEndDecelerating: ControlEvent { let source = delegate.methodInvoked(#selector(UIScrollViewDelegate.scrollViewDidEndDecelerating(_:))).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `scrollViewWillBeginDragging` public var willBeginDragging: ControlEvent { let source = delegate.methodInvoked(#selector(UIScrollViewDelegate.scrollViewWillBeginDragging(_:))).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)` public var willEndDragging: ControlEvent { let source = delegate.methodInvoked(#selector(UIScrollViewDelegate.scrollViewWillEndDragging(_:withVelocity:targetContentOffset:))) .map { value -> WillEndDraggingEvent in let velocity = try castOrThrow(CGPoint.self, value[1]) let targetContentOffsetValue = try castOrThrow(NSValue.self, value[2]) guard let rawPointer = targetContentOffsetValue.pointerValue else { throw RxCocoaError.unknown } let typedPointer = rawPointer.bindMemory(to: CGPoint.self, capacity: MemoryLayout.size) return (velocity, typedPointer) } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `scrollViewDidEndDragging(_:willDecelerate:)` public var didEndDragging: ControlEvent { let source = delegate.methodInvoked(#selector(UIScrollViewDelegate.scrollViewDidEndDragging(_:willDecelerate:))).map { value -> Bool in return try castOrThrow(Bool.self, value[1]) } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `scrollViewDidZoom` public var didZoom: ControlEvent { let source = delegate.methodInvoked(#selector(UIScrollViewDelegate.scrollViewDidZoom)).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `scrollViewDidScrollToTop` public var didScrollToTop: ControlEvent { let source = delegate.methodInvoked(#selector(UIScrollViewDelegate.scrollViewDidScrollToTop(_:))).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `scrollViewDidEndScrollingAnimation` public var didEndScrollingAnimation: ControlEvent { let source = delegate.methodInvoked(#selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation(_:))).map { _ in } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `scrollViewWillBeginZooming(_:with:)` public var willBeginZooming: ControlEvent { let source = delegate.methodInvoked(#selector(UIScrollViewDelegate.scrollViewWillBeginZooming(_:with:))).map { value -> UIView? in return try castOptionalOrThrow(UIView.self, value[1] as AnyObject) } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `scrollViewDidEndZooming(_:with:atScale:)` public var didEndZooming: ControlEvent { let source = delegate.methodInvoked(#selector(UIScrollViewDelegate.scrollViewDidEndZooming(_:with:atScale:))).map { value -> EndZoomEvent in return (try castOptionalOrThrow(UIView.self, value[1] as AnyObject), try castOrThrow(CGFloat.self, value[2])) } return ControlEvent(events: source) } /// Installs delegate as forwarding delegate on `delegate`. /// Delegate won't be retained. /// /// It enables using normal delegate mechanism with reactive delegate mechanism. /// /// - parameter delegate: Delegate object. /// - returns: Disposable object that can be used to unbind the delegate. public func setDelegate(_ delegate: UIScrollViewDelegate) -> Disposable { return RxScrollViewDelegateProxy.installForwardDelegate(delegate, retainDelegate: false, onProxyForObject: self.base) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UISearchBar+Rx.swift ================================================ // // UISearchBar+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 3/28/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import RxSwift import UIKit extension Reactive where Base: UISearchBar { /// Reactive wrapper for `delegate`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var delegate: DelegateProxy { RxSearchBarDelegateProxy.proxy(for: base) } /// Reactive wrapper for `text` property. public var text: ControlProperty { value } /// Reactive wrapper for `text` property. public var value: ControlProperty { let source: Observable = Observable.deferred { [weak searchBar = self.base as UISearchBar] () -> Observable in let text = searchBar?.text let textDidChange = (searchBar?.rx.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBar(_:textDidChange:))) ?? Observable.empty()) let didEndEditing = (searchBar?.rx.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBarTextDidEndEditing(_:))) ?? Observable.empty()) return Observable.merge(textDidChange, didEndEditing) .map { _ in searchBar?.text ?? "" } .startWith(text) } let bindingObserver = Binder(self.base) { (searchBar, text: String?) in searchBar.text = text } return ControlProperty(values: source, valueSink: bindingObserver) } /// Reactive wrapper for `selectedScopeButtonIndex` property. public var selectedScopeButtonIndex: ControlProperty { let source: Observable = Observable.deferred { [weak source = self.base as UISearchBar] () -> Observable in let index = source?.selectedScopeButtonIndex ?? 0 return (source?.rx.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBar(_:selectedScopeButtonIndexDidChange:))) ?? Observable.empty()) .map { a in return try castOrThrow(Int.self, a[1]) } .startWith(index) } let bindingObserver = Binder(self.base) { (searchBar, index: Int) in searchBar.selectedScopeButtonIndex = index } return ControlProperty(values: source, valueSink: bindingObserver) } #if os(iOS) /// Reactive wrapper for delegate method `searchBarCancelButtonClicked`. public var cancelButtonClicked: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBarCancelButtonClicked(_:))) .map { _ in return () } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `searchBarBookmarkButtonClicked`. public var bookmarkButtonClicked: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBarBookmarkButtonClicked(_:))) .map { _ in return () } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `searchBarResultsListButtonClicked`. public var resultsListButtonClicked: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBarResultsListButtonClicked(_:))) .map { _ in return () } return ControlEvent(events: source) } #endif /// Reactive wrapper for delegate method `searchBarSearchButtonClicked`. public var searchButtonClicked: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBarSearchButtonClicked(_:))) .map { _ in return () } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `searchBarTextDidBeginEditing`. public var textDidBeginEditing: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBarTextDidBeginEditing(_:))) .map { _ in return () } return ControlEvent(events: source) } /// Reactive wrapper for delegate method `searchBarTextDidEndEditing`. public var textDidEndEditing: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UISearchBarDelegate.searchBarTextDidEndEditing(_:))) .map { _ in return () } return ControlEvent(events: source) } /// Installs delegate as forwarding delegate on `delegate`. /// Delegate won't be retained. /// /// It enables using normal delegate mechanism with reactive delegate mechanism. /// /// - parameter delegate: Delegate object. /// - returns: Disposable object that can be used to unbind the delegate. public func setDelegate(_ delegate: UISearchBarDelegate) -> Disposable { RxSearchBarDelegateProxy.installForwardDelegate(delegate, retainDelegate: false, onProxyForObject: self.base) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UISearchController+Rx.swift ================================================ // // UISearchController+Rx.swift // RxCocoa // // Created by Segii Shulga on 3/17/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if os(iOS) import RxSwift import UIKit extension Reactive where Base: UISearchController { /// Reactive wrapper for `delegate`. /// For more information take a look at `DelegateProxyType` protocol documentation. public var delegate: DelegateProxy { return RxSearchControllerDelegateProxy.proxy(for: base) } /// Reactive wrapper for `delegate` message. public var didDismiss: Observable { return delegate .methodInvoked( #selector(UISearchControllerDelegate.didDismissSearchController(_:))) .map { _ in } } /// Reactive wrapper for `delegate` message. public var didPresent: Observable { return delegate .methodInvoked(#selector(UISearchControllerDelegate.didPresentSearchController(_:))) .map { _ in } } /// Reactive wrapper for `delegate` message. public var present: Observable { return delegate .methodInvoked( #selector(UISearchControllerDelegate.presentSearchController(_:))) .map { _ in } } /// Reactive wrapper for `delegate` message. public var willDismiss: Observable { return delegate .methodInvoked(#selector(UISearchControllerDelegate.willDismissSearchController(_:))) .map { _ in } } /// Reactive wrapper for `delegate` message. public var willPresent: Observable { return delegate .methodInvoked( #selector(UISearchControllerDelegate.willPresentSearchController(_:))) .map { _ in } } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UISegmentedControl+Rx.swift ================================================ // // UISegmentedControl+Rx.swift // RxCocoa // // Created by Carlos García on 8/7/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift extension Reactive where Base: UISegmentedControl { /// Reactive wrapper for `selectedSegmentIndex` property. public var selectedSegmentIndex: ControlProperty { value } /// Reactive wrapper for `selectedSegmentIndex` property. public var value: ControlProperty { return base.rx.controlPropertyWithDefaultEvents( getter: { segmentedControl in segmentedControl.selectedSegmentIndex }, setter: { segmentedControl, value in segmentedControl.selectedSegmentIndex = value } ) } /// Reactive wrapper for `setEnabled(_:forSegmentAt:)` public func enabledForSegment(at index: Int) -> Binder { return Binder(self.base) { segmentedControl, segmentEnabled -> Void in segmentedControl.setEnabled(segmentEnabled, forSegmentAt: index) } } /// Reactive wrapper for `setTitle(_:forSegmentAt:)` public func titleForSegment(at index: Int) -> Binder { return Binder(self.base) { segmentedControl, title -> Void in segmentedControl.setTitle(title, forSegmentAt: index) } } /// Reactive wrapper for `setImage(_:forSegmentAt:)` public func imageForSegment(at index: Int) -> Binder { return Binder(self.base) { segmentedControl, image -> Void in segmentedControl.setImage(image, forSegmentAt: index) } } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UISlider+Rx.swift ================================================ // // UISlider+Rx.swift // RxCocoa // // Created by Alexander van der Werff on 28/05/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) import RxSwift import UIKit extension Reactive where Base: UISlider { /// Reactive wrapper for `value` property. public var value: ControlProperty { return base.rx.controlPropertyWithDefaultEvents( getter: { slider in slider.value }, setter: { slider, value in slider.value = value } ) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UIStepper+Rx.swift ================================================ // // UIStepper+Rx.swift // RxCocoa // // Created by Yuta ToKoRo on 9/1/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) import UIKit import RxSwift extension Reactive where Base: UIStepper { /// Reactive wrapper for `value` property. public var value: ControlProperty { return base.rx.controlPropertyWithDefaultEvents( getter: { stepper in stepper.value }, setter: { stepper, value in stepper.value = value } ) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UISwitch+Rx.swift ================================================ // // UISwitch+Rx.swift // RxCocoa // // Created by Carlos García on 8/7/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) import UIKit import RxSwift extension Reactive where Base: UISwitch { /// Reactive wrapper for `isOn` property. public var isOn: ControlProperty { value } /// Reactive wrapper for `isOn` property. /// /// ⚠️ Versions prior to iOS 10.2 were leaking `UISwitch`'s, so on those versions /// underlying observable sequence won't complete when nothing holds a strong reference /// to `UISwitch`. public var value: ControlProperty { return base.rx.controlPropertyWithDefaultEvents( getter: { uiSwitch in uiSwitch.isOn }, setter: { uiSwitch, value in uiSwitch.isOn = value } ) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UITabBar+Rx.swift ================================================ // // UITabBar+Rx.swift // RxCocoa // // Created by Jesse Farless on 5/13/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift /** iOS only */ #if os(iOS) extension Reactive where Base: UITabBar { /// Reactive wrapper for `delegate` message `tabBar(_:willBeginCustomizing:)`. public var willBeginCustomizing: ControlEvent<[UITabBarItem]> { let source = delegate.methodInvoked(#selector(UITabBarDelegate.tabBar(_:willBeginCustomizing:))) .map { a in return try castOrThrow([UITabBarItem].self, a[1]) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `tabBar(_:didBeginCustomizing:)`. public var didBeginCustomizing: ControlEvent<[UITabBarItem]> { let source = delegate.methodInvoked(#selector(UITabBarDelegate.tabBar(_:didBeginCustomizing:))) .map { a in return try castOrThrow([UITabBarItem].self, a[1]) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `tabBar(_:willEndCustomizing:changed:)`. public var willEndCustomizing: ControlEvent<([UITabBarItem], Bool)> { let source = delegate.methodInvoked(#selector(UITabBarDelegate.tabBar(_:willEndCustomizing:changed:))) .map { (a: [Any]) -> (([UITabBarItem], Bool)) in let items = try castOrThrow([UITabBarItem].self, a[1]) let changed = try castOrThrow(Bool.self, a[2]) return (items, changed) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `tabBar(_:didEndCustomizing:changed:)`. public var didEndCustomizing: ControlEvent<([UITabBarItem], Bool)> { let source = delegate.methodInvoked(#selector(UITabBarDelegate.tabBar(_:didEndCustomizing:changed:))) .map { (a: [Any]) -> (([UITabBarItem], Bool)) in let items = try castOrThrow([UITabBarItem].self, a[1]) let changed = try castOrThrow(Bool.self, a[2]) return (items, changed) } return ControlEvent(events: source) } } #endif /** iOS and tvOS */ extension Reactive where Base: UITabBar { /// Reactive wrapper for `delegate`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var delegate: DelegateProxy { RxTabBarDelegateProxy.proxy(for: base) } /// Reactive wrapper for `delegate` message `tabBar(_:didSelect:)`. public var didSelectItem: ControlEvent { let source = delegate.methodInvoked(#selector(UITabBarDelegate.tabBar(_:didSelect:))) .map { a in return try castOrThrow(UITabBarItem.self, a[1]) } return ControlEvent(events: source) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UITabBarController+Rx.swift ================================================ // // UITabBarController+Rx.swift // RxCocoa // // Created by Yusuke Kita on 2016/12/07. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift /** iOS only */ #if os(iOS) extension Reactive where Base: UITabBarController { /// Reactive wrapper for `delegate` message `tabBarController:willBeginCustomizing:`. public var willBeginCustomizing: ControlEvent<[UIViewController]> { let source = delegate.methodInvoked(#selector(UITabBarControllerDelegate.tabBarController(_:willBeginCustomizing:))) .map { a in return try castOrThrow([UIViewController].self, a[1]) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `tabBarController:willEndCustomizing:changed:`. public var willEndCustomizing: ControlEvent<(viewControllers: [UIViewController], changed: Bool)> { let source = delegate.methodInvoked(#selector(UITabBarControllerDelegate.tabBarController(_:willEndCustomizing:changed:))) .map { (a: [Any]) -> (viewControllers: [UIViewController], changed: Bool) in let viewControllers = try castOrThrow([UIViewController].self, a[1]) let changed = try castOrThrow(Bool.self, a[2]) return (viewControllers, changed) } return ControlEvent(events: source) } /// Reactive wrapper for `delegate` message `tabBarController:didEndCustomizing:changed:`. public var didEndCustomizing: ControlEvent<(viewControllers: [UIViewController], changed: Bool)> { let source = delegate.methodInvoked(#selector(UITabBarControllerDelegate.tabBarController(_:didEndCustomizing:changed:))) .map { (a: [Any]) -> (viewControllers: [UIViewController], changed: Bool) in let viewControllers = try castOrThrow([UIViewController].self, a[1]) let changed = try castOrThrow(Bool.self, a[2]) return (viewControllers, changed) } return ControlEvent(events: source) } } #endif /** iOS and tvOS */ extension Reactive where Base: UITabBarController { /// Reactive wrapper for `delegate`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var delegate: DelegateProxy { RxTabBarControllerDelegateProxy.proxy(for: base) } /// Reactive wrapper for `delegate` message `tabBarController:didSelect:`. public var didSelect: ControlEvent { let source = delegate.methodInvoked(#selector(UITabBarControllerDelegate.tabBarController(_:didSelect:))) .map { a in return try castOrThrow(UIViewController.self, a[1]) } return ControlEvent(events: source) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UITableView+Rx.swift ================================================ // // UITableView+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 4/2/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import RxSwift import UIKit // Items extension Reactive where Base: UITableView { /** Binds sequences of elements to table view rows. - parameter source: Observable sequence of items. - parameter cellFactory: Transform between sequence elements and view cells. - returns: Disposable object that can be used to unbind. Example: let items = Observable.just([ "First Item", "Second Item", "Third Item" ]) items .bind(to: tableView.rx.items) { (tableView, row, element) in let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! cell.textLabel?.text = "\(element) @ row \(row)" return cell } .disposed(by: disposeBag) */ public func items (_ source: Source) -> (_ cellFactory: @escaping (UITableView, Int, Sequence.Element) -> UITableViewCell) -> Disposable where Source.Element == Sequence { return { cellFactory in let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper(cellFactory: cellFactory) return self.items(dataSource: dataSource)(source) } } /** Binds sequences of elements to table view rows. - parameter cellIdentifier: Identifier used to dequeue cells. - parameter source: Observable sequence of items. - parameter configureCell: Transform between sequence elements and view cells. - parameter cellType: Type of table view cell. - returns: Disposable object that can be used to unbind. Example: let items = Observable.just([ "First Item", "Second Item", "Third Item" ]) items .bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, element, cell) in cell.textLabel?.text = "\(element) @ row \(row)" } .disposed(by: disposeBag) */ public func items (cellIdentifier: String, cellType: Cell.Type = Cell.self) -> (_ source: Source) -> (_ configureCell: @escaping (Int, Sequence.Element, Cell) -> Void) -> Disposable where Source.Element == Sequence { return { source in return { configureCell in let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper { tv, i, item in let indexPath = IndexPath(item: i, section: 0) let cell = tv.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell configureCell(i, item, cell) return cell } return self.items(dataSource: dataSource)(source) } } } /** Binds sequences of elements to table view rows using a custom reactive data used to perform the transformation. This method will retain the data source for as long as the subscription isn't disposed (result `Disposable` being disposed). In case `source` observable sequence terminates successfully, the data source will present latest element until the subscription isn't disposed. - parameter dataSource: Data source used to transform elements to view cells. - parameter source: Observable sequence of items. - returns: Disposable object that can be used to unbind. */ public func items< DataSource: RxTableViewDataSourceType & UITableViewDataSource, Source: ObservableType> (dataSource: DataSource) -> (_ source: Source) -> Disposable where DataSource.Element == Source.Element { return { source in // This is called for side effects only, and to make sure delegate proxy is in place when // data source is being bound. // This is needed because theoretically the data source subscription itself might // call `self.rx.delegate`. If that happens, it might cause weird side effects since // setting data source will set delegate, and UITableView might get into a weird state. // Therefore it's better to set delegate proxy first, just to be sure. _ = self.delegate // Strong reference is needed because data source is in use until result subscription is disposed return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource as UITableViewDataSource, retainDataSource: true) { [weak tableView = self.base] (_: RxTableViewDataSourceProxy, event) -> Void in guard let tableView = tableView else { return } dataSource.tableView(tableView, observedEvent: event) } } } } extension Reactive where Base: UITableView { /** Reactive wrapper for `dataSource`. For more information take a look at `DelegateProxyType` protocol documentation. */ public var dataSource: DelegateProxy { RxTableViewDataSourceProxy.proxy(for: base) } /** Installs data source as forwarding delegate on `rx.dataSource`. Data source won't be retained. It enables using normal delegate mechanism with reactive delegate mechanism. - parameter dataSource: Data source object. - returns: Disposable object that can be used to unbind the data source. */ public func setDataSource(_ dataSource: UITableViewDataSource) -> Disposable { RxTableViewDataSourceProxy.installForwardDelegate(dataSource, retainDelegate: false, onProxyForObject: self.base) } // events /** Reactive wrapper for `delegate` message `tableView:didSelectRowAtIndexPath:`. */ public var itemSelected: ControlEvent { let source = self.delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didSelectRowAt:))) .map { a in return try castOrThrow(IndexPath.self, a[1]) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:didDeselectRowAtIndexPath:`. */ public var itemDeselected: ControlEvent { let source = self.delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didDeselectRowAt:))) .map { a in return try castOrThrow(IndexPath.self, a[1]) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:didHighlightRowAt:`. */ public var itemHighlighted: ControlEvent { let source = self.delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didHighlightRowAt:))) .map { a in return try castOrThrow(IndexPath.self, a[1]) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:didUnhighlightRowAt:`. */ public var itemUnhighlighted: ControlEvent { let source = self.delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didUnhighlightRowAt:))) .map { a in return try castOrThrow(IndexPath.self, a[1]) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:accessoryButtonTappedForRowWithIndexPath:`. */ public var itemAccessoryButtonTapped: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:accessoryButtonTappedForRowWith:))) .map { a in return try castOrThrow(IndexPath.self, a[1]) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`. */ public var itemInserted: ControlEvent { let source = self.dataSource.methodInvoked(#selector(UITableViewDataSource.tableView(_:commit:forRowAt:))) .filter { a in return UITableViewCell.EditingStyle(rawValue: (try castOrThrow(NSNumber.self, a[1])).intValue) == .insert } .map { a in return (try castOrThrow(IndexPath.self, a[2])) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`. */ public var itemDeleted: ControlEvent { let source = self.dataSource.methodInvoked(#selector(UITableViewDataSource.tableView(_:commit:forRowAt:))) .filter { a in return UITableViewCell.EditingStyle(rawValue: (try castOrThrow(NSNumber.self, a[1])).intValue) == .delete } .map { a in return try castOrThrow(IndexPath.self, a[2]) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:moveRowAtIndexPath:toIndexPath:`. */ public var itemMoved: ControlEvent { let source: Observable = self.dataSource.methodInvoked(#selector(UITableViewDataSource.tableView(_:moveRowAt:to:))) .map { a in return (try castOrThrow(IndexPath.self, a[1]), try castOrThrow(IndexPath.self, a[2])) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:willDisplayCell:forRowAtIndexPath:`. */ public var willDisplayCell: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:willDisplay:forRowAt:))) .map { a in return (try castOrThrow(UITableViewCell.self, a[1]), try castOrThrow(IndexPath.self, a[2])) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:didEndDisplayingCell:forRowAtIndexPath:`. */ public var didEndDisplayingCell: ControlEvent { let source: Observable = self.delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didEndDisplaying:forRowAt:))) .map { a in return (try castOrThrow(UITableViewCell.self, a[1]), try castOrThrow(IndexPath.self, a[2])) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:didSelectRowAtIndexPath:`. It can be only used when one of the `rx.itemsWith*` methods is used to bind observable sequence, or any other data source conforming to `SectionedViewDataSourceType` protocol. ``` tableView.rx.modelSelected(MyModel.self) .map { ... ``` */ public func modelSelected(_ modelType: T.Type) -> ControlEvent { let source: Observable = self.itemSelected.flatMap { [weak view = self.base as UITableView] indexPath -> Observable in guard let view = view else { return Observable.empty() } return Observable.just(try view.rx.model(at: indexPath)) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:didDeselectRowAtIndexPath:`. It can be only used when one of the `rx.itemsWith*` methods is used to bind observable sequence, or any other data source conforming to `SectionedViewDataSourceType` protocol. ``` tableView.rx.modelDeselected(MyModel.self) .map { ... ``` */ public func modelDeselected(_ modelType: T.Type) -> ControlEvent { let source: Observable = self.itemDeselected.flatMap { [weak view = self.base as UITableView] indexPath -> Observable in guard let view = view else { return Observable.empty() } return Observable.just(try view.rx.model(at: indexPath)) } return ControlEvent(events: source) } /** Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`. It can be only used when one of the `rx.itemsWith*` methods is used to bind observable sequence, or any other data source conforming to `SectionedViewDataSourceType` protocol. ``` tableView.rx.modelDeleted(MyModel.self) .map { ... ``` */ public func modelDeleted(_ modelType: T.Type) -> ControlEvent { let source: Observable = self.itemDeleted.flatMap { [weak view = self.base as UITableView] indexPath -> Observable in guard let view = view else { return Observable.empty() } return Observable.just(try view.rx.model(at: indexPath)) } return ControlEvent(events: source) } /** Synchronous helper method for retrieving a model at indexPath through a reactive data source. */ public func model(at indexPath: IndexPath) throws -> T { let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.items*` methods was used.") let element = try dataSource.model(at: indexPath) return castOrFatalError(element) } } @available(iOS 10.0, tvOS 10.0, *) extension Reactive where Base: UITableView { /// Reactive wrapper for `prefetchDataSource`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var prefetchDataSource: DelegateProxy { RxTableViewDataSourcePrefetchingProxy.proxy(for: base) } /** Installs prefetch data source as forwarding delegate on `rx.prefetchDataSource`. Prefetch data source won't be retained. It enables using normal delegate mechanism with reactive delegate mechanism. - parameter prefetchDataSource: Prefetch data source object. - returns: Disposable object that can be used to unbind the data source. */ public func setPrefetchDataSource(_ prefetchDataSource: UITableViewDataSourcePrefetching) -> Disposable { return RxTableViewDataSourcePrefetchingProxy.installForwardDelegate(prefetchDataSource, retainDelegate: false, onProxyForObject: self.base) } /// Reactive wrapper for `prefetchDataSource` message `tableView(_:prefetchRowsAt:)`. public var prefetchRows: ControlEvent<[IndexPath]> { let source = RxTableViewDataSourcePrefetchingProxy.proxy(for: base).prefetchRowsPublishSubject return ControlEvent(events: source) } /// Reactive wrapper for `prefetchDataSource` message `tableView(_:cancelPrefetchingForRowsAt:)`. public var cancelPrefetchingForRows: ControlEvent<[IndexPath]> { let source = prefetchDataSource.methodInvoked(#selector(UITableViewDataSourcePrefetching.tableView(_:cancelPrefetchingForRowsAt:))) .map { a in return try castOrThrow(Array.self, a[1]) } return ControlEvent(events: source) } } #endif #if os(tvOS) extension Reactive where Base: UITableView { /** Reactive wrapper for `delegate` message `tableView:didUpdateFocusInContext:withAnimationCoordinator:`. */ public var didUpdateFocusInContextWithAnimationCoordinator: ControlEvent<(context: UITableViewFocusUpdateContext, animationCoordinator: UIFocusAnimationCoordinator)> { let source = delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didUpdateFocusIn:with:))) .map { a -> (context: UITableViewFocusUpdateContext, animationCoordinator: UIFocusAnimationCoordinator) in let context = try castOrThrow(UITableViewFocusUpdateContext.self, a[1]) let animationCoordinator = try castOrThrow(UIFocusAnimationCoordinator.self, a[2]) return (context: context, animationCoordinator: animationCoordinator) } return ControlEvent(events: source) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UITextField+Rx.swift ================================================ // // UITextField+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 2/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import RxSwift import UIKit extension Reactive where Base: UITextField { /// Reactive wrapper for `text` property. public var text: ControlProperty { value } /// Reactive wrapper for `text` property. public var value: ControlProperty { return base.rx.controlPropertyWithDefaultEvents( getter: { textField in textField.text }, setter: { textField, value in // This check is important because setting text value always clears control state // including marked text selection which is important for proper input // when IME input method is used. if textField.text != value { textField.text = value } } ) } /// Bindable sink for `attributedText` property. public var attributedText: ControlProperty { return base.rx.controlPropertyWithDefaultEvents( getter: { textField in textField.attributedText }, setter: { textField, value in // This check is important because setting text value always clears control state // including marked text selection which is important for proper input // when IME input method is used. if textField.attributedText != value { textField.attributedText = value } } ) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/UITextView+Rx.swift ================================================ // // UITextView+Rx.swift // RxCocoa // // Created by Yuta ToKoRo on 7/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit import RxSwift extension Reactive where Base: UITextView { /// Reactive wrapper for `text` property public var text: ControlProperty { value } /// Reactive wrapper for `text` property. public var value: ControlProperty { let source: Observable = Observable.deferred { [weak textView = self.base] in let text = textView?.text let textChanged = textView?.textStorage // This project uses text storage notifications because // that's the only way to catch autocorrect changes // in all cases. Other suggestions are welcome. .rx.didProcessEditingRangeChangeInLength // This observe on is here because text storage // will emit event while process is not completely done, // so rebinding a value will cause an exception to be thrown. .observe(on:MainScheduler.asyncInstance) .map { _ in return textView?.textStorage.string } ?? Observable.empty() return textChanged .startWith(text) } let bindingObserver = Binder(self.base) { (textView, text: String?) in // This check is important because setting text value always clears control state // including marked text selection which is important for proper input // when IME input method is used. if textView.text != text { textView.text = text } } return ControlProperty(values: source, valueSink: bindingObserver) } /// Reactive wrapper for `attributedText` property. public var attributedText: ControlProperty { let source: Observable = Observable.deferred { [weak textView = self.base] in let attributedText = textView?.attributedText let textChanged: Observable = textView?.textStorage // This project uses text storage notifications because // that's the only way to catch autocorrect changes // in all cases. Other suggestions are welcome. .rx.didProcessEditingRangeChangeInLength // This observe on is here because attributedText storage // will emit event while process is not completely done, // so rebinding a value will cause an exception to be thrown. .observe(on:MainScheduler.asyncInstance) .map { _ in return textView?.attributedText } ?? Observable.empty() return textChanged .startWith(attributedText) } let bindingObserver = Binder(self.base) { (textView, attributedText: NSAttributedString?) in // This check is important because setting text value always clears control state // including marked text selection which is important for proper input // when IME input method is used. if textView.attributedText != attributedText { textView.attributedText = attributedText } } return ControlProperty(values: source, valueSink: bindingObserver) } /// Reactive wrapper for `delegate` message. public var didBeginEditing: ControlEvent<()> { return ControlEvent<()>(events: self.delegate.methodInvoked(#selector(UITextViewDelegate.textViewDidBeginEditing(_:))) .map { _ in return () }) } /// Reactive wrapper for `delegate` message. public var didEndEditing: ControlEvent<()> { return ControlEvent<()>(events: self.delegate.methodInvoked(#selector(UITextViewDelegate.textViewDidEndEditing(_:))) .map { _ in return () }) } /// Reactive wrapper for `delegate` message. public var didChange: ControlEvent<()> { return ControlEvent<()>(events: self.delegate.methodInvoked(#selector(UITextViewDelegate.textViewDidChange(_:))) .map { _ in return () }) } /// Reactive wrapper for `delegate` message. public var didChangeSelection: ControlEvent<()> { return ControlEvent<()>(events: self.delegate.methodInvoked(#selector(UITextViewDelegate.textViewDidChangeSelection(_:))) .map { _ in return () }) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/iOS/WKWebView+Rx.swift ================================================ // // WKWebView+Rx.swift // RxCocoa // // Created by Giuseppe Lanza on 14/02/2020. // Copyright © 2020 Krunoslav Zaher. All rights reserved. // #if os(iOS) || os(macOS) import RxSwift import WebKit @available(iOS 8.0, OSX 10.10, OSXApplicationExtension 10.10, *) extension Reactive where Base: WKWebView { /// Reactive wrapper for `navigationDelegate`. /// For more information take a look at `DelegateProxyType` protocol documentation. public var navigationDelegate: DelegateProxy { RxWKNavigationDelegateProxy.proxy(for: base) } /// Reactive wrapper for `navigationDelegate` message. public var didCommit: Observable { navigationDelegate .methodInvoked(#selector(WKNavigationDelegate.webView(_:didCommit:))) .map { a in try castOrThrow(WKNavigation.self, a[1]) } } /// Reactive wrapper for `navigationDelegate` message. public var didStartLoad: Observable { navigationDelegate .methodInvoked(#selector(WKNavigationDelegate.webView(_:didStartProvisionalNavigation:))) .map { a in try castOrThrow(WKNavigation.self, a[1]) } } /// Reactive wrapper for `navigationDelegate` message. public var didFinishLoad: Observable { navigationDelegate .methodInvoked(#selector(WKNavigationDelegate.webView(_:didFinish:))) .map { a in try castOrThrow(WKNavigation.self, a[1]) } } /// Reactive wrapper for `navigationDelegate` message. public var didFailLoad: Observable<(WKNavigation, Error)> { navigationDelegate .methodInvoked(#selector(WKNavigationDelegate.webView(_:didFail:withError:))) .map { a in ( try castOrThrow(WKNavigation.self, a[1]), try castOrThrow(Error.self, a[2]) ) } } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/macOS/NSButton+Rx.swift ================================================ // // NSButton+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 5/17/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(macOS) import RxSwift import Cocoa extension Reactive where Base: NSButton { /// Reactive wrapper for control event. public var tap: ControlEvent { self.controlEvent } /// Reactive wrapper for `state` property`. public var state: ControlProperty { return self.base.rx.controlProperty( getter: { control in return control.state }, setter: { (control: NSButton, state: NSControl.StateValue) in control.state = state } ) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/macOS/NSControl+Rx.swift ================================================ // // NSControl+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 5/31/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(macOS) import Cocoa import RxSwift private var rx_value_key: UInt8 = 0 private var rx_control_events_key: UInt8 = 0 extension Reactive where Base: NSControl { /// Reactive wrapper for control event. public var controlEvent: ControlEvent<()> { MainScheduler.ensureRunningOnMainThread() let source = self.lazyInstanceObservable(&rx_control_events_key) { () -> Observable in Observable.create { [weak control = self.base] observer in MainScheduler.ensureRunningOnMainThread() guard let control = control else { observer.on(.completed) return Disposables.create() } let observer = ControlTarget(control: control) { _ in observer.on(.next(())) } return observer } .take(until: self.deallocated) .share() } return ControlEvent(events: source) } /// Creates a `ControlProperty` that is triggered by target/action pattern value updates. /// /// - parameter getter: Property value getter. /// - parameter setter: Property value setter. public func controlProperty( getter: @escaping (Base) -> T, setter: @escaping (Base, T) -> Void ) -> ControlProperty { MainScheduler.ensureRunningOnMainThread() let source = self.base.rx.lazyInstanceObservable(&rx_value_key) { () -> Observable<()> in return Observable.create { [weak weakControl = self.base] (observer: AnyObserver<()>) in guard let control = weakControl else { observer.on(.completed) return Disposables.create() } observer.on(.next(())) let observer = ControlTarget(control: control) { _ in if weakControl != nil { observer.on(.next(())) } } return observer } .take(until: self.deallocated) .share(replay: 1, scope: .whileConnected) } .flatMap { [weak base] _ -> Observable in guard let control = base else { return Observable.empty() } return Observable.just(getter(control)) } let bindingObserver = Binder(self.base, binding: setter) return ControlProperty(values: source, valueSink: bindingObserver) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/macOS/NSSlider+Rx.swift ================================================ // // NSSlider+Rx.swift // RxCocoa // // Created by Junior B. on 24/05/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(macOS) import RxSwift import Cocoa extension Reactive where Base: NSSlider { /// Reactive wrapper for `value` property. public var value: ControlProperty { return self.base.rx.controlProperty( getter: { control -> Double in return control.doubleValue }, setter: { control, value in control.doubleValue = value } ) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/macOS/NSTextField+Rx.swift ================================================ // // NSTextField+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 5/17/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(macOS) import Cocoa import RxSwift /// Delegate proxy for `NSTextField`. /// /// For more information take a look at `DelegateProxyType`. open class RxTextFieldDelegateProxy : DelegateProxy , DelegateProxyType , NSTextFieldDelegate { /// Typed parent object. public weak private(set) var textField: NSTextField? /// Initializes `RxTextFieldDelegateProxy` /// /// - parameter textField: Parent object for delegate proxy. init(textField: NSTextField) { self.textField = textField super.init(parentObject: textField, delegateProxy: RxTextFieldDelegateProxy.self) } public static func registerKnownImplementations() { self.register { RxTextFieldDelegateProxy(textField: $0) } } fileprivate let textSubject = PublishSubject() // MARK: Delegate methods open func controlTextDidChange(_ notification: Notification) { let textField: NSTextField = castOrFatalError(notification.object) let nextValue = textField.stringValue self.textSubject.on(.next(nextValue)) _forwardToDelegate?.controlTextDidChange?(notification) } // MARK: Delegate proxy methods /// For more information take a look at `DelegateProxyType`. open class func currentDelegate(for object: ParentObject) -> NSTextFieldDelegate? { object.delegate } /// For more information take a look at `DelegateProxyType`. open class func setCurrentDelegate(_ delegate: NSTextFieldDelegate?, to object: ParentObject) { object.delegate = delegate } } extension Reactive where Base: NSTextField { /// Reactive wrapper for `delegate`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var delegate: DelegateProxy { RxTextFieldDelegateProxy.proxy(for: self.base) } /// Reactive wrapper for `text` property. public var text: ControlProperty { let delegate = RxTextFieldDelegateProxy.proxy(for: self.base) let source = Observable.deferred { [weak textField = self.base] in delegate.textSubject.startWith(textField?.stringValue) }.take(until: self.deallocated) let observer = Binder(self.base) { (control, value: String?) in control.stringValue = value ?? "" } return ControlProperty(values: source, valueSink: observer.asObserver()) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/macOS/NSTextView+Rx.swift ================================================ // // NSTextView+Rx.swift // RxCocoa // // Created by Cee on 8/5/18. // Copyright © 2018 Krunoslav Zaher. All rights reserved. // #if os(macOS) import Cocoa import RxSwift /// Delegate proxy for `NSTextView`. /// /// For more information take a look at `DelegateProxyType`. open class RxTextViewDelegateProxy: DelegateProxy, DelegateProxyType, NSTextViewDelegate { #if compiler(>=5.2) /// Typed parent object. /// /// - note: Since Swift 5.2 and Xcode 11.4, Apple have suddenly /// disallowed using `weak` for NSTextView. For more details /// see this GitHub Issue: https://git.io/JvSRn public private(set) var textView: NSTextView? #else /// Typed parent object. public weak private(set) var textView: NSTextView? #endif /// Initializes `RxTextViewDelegateProxy` /// /// - parameter textView: Parent object for delegate proxy. init(textView: NSTextView) { self.textView = textView super.init(parentObject: textView, delegateProxy: RxTextViewDelegateProxy.self) } public static func registerKnownImplementations() { self.register { RxTextViewDelegateProxy(textView: $0) } } fileprivate let textSubject = PublishSubject() // MARK: Delegate methods open func textDidChange(_ notification: Notification) { let textView: NSTextView = castOrFatalError(notification.object) let nextValue = textView.string self.textSubject.on(.next(nextValue)) self._forwardToDelegate?.textDidChange?(notification) } // MARK: Delegate proxy methods /// For more information take a look at `DelegateProxyType`. open class func currentDelegate(for object: ParentObject) -> NSTextViewDelegate? { object.delegate } /// For more information take a look at `DelegateProxyType`. open class func setCurrentDelegate(_ delegate: NSTextViewDelegate?, to object: ParentObject) { object.delegate = delegate } } extension Reactive where Base: NSTextView { /// Reactive wrapper for `delegate`. /// /// For more information take a look at `DelegateProxyType` protocol documentation. public var delegate: DelegateProxy { RxTextViewDelegateProxy.proxy(for: self.base) } /// Reactive wrapper for `string` property. public var string: ControlProperty { let delegate = RxTextViewDelegateProxy.proxy(for: self.base) let source = Observable.deferred { [weak textView = self.base] in delegate.textSubject.startWith(textView?.string ?? "") }.take(until: self.deallocated) let observer = Binder(self.base) { control, value in control.string = value } return ControlProperty(values: source, valueSink: observer.asObserver()) } } #endif ================================================ FILE: JetChat/Pods/RxCocoa/RxCocoa/macOS/NSView+Rx.swift ================================================ // // NSView+Rx.swift // RxCocoa // // Created by Krunoslav Zaher on 12/6/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(macOS) import Cocoa import RxSwift extension Reactive where Base: NSView { /// Bindable sink for `alphaValue` property. public var alpha: Binder { return Binder(self.base) { view, value in view.alphaValue = value } } } #endif ================================================ FILE: JetChat/Pods/RxCocoa.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 098A7E7AEC6AD385CF5A0FB8D79C27AF /* ObservableConvertibleType+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E21BC876BBD95A73D1EC5F17668884C /* ObservableConvertibleType+Driver.swift */; }; 0A9FE8B6140805209AF83AE31A3D3B6E /* UICollectionView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0C3C2A6E813EF1CA4D4B015C3C04D07 /* UICollectionView+Rx.swift */; }; 0D7383E630C64114395423715ACCA2E5 /* ControlProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 364B76EDCB5520C0CF711280AFE58D0D /* ControlProperty.swift */; }; 0E3AF17E3F196488AA3B62DA4B066364 /* RxTabBarControllerDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D080D0597579029EC0D51F19DF00 /* RxTabBarControllerDelegateProxy.swift */; }; 104C4A2E0EC23EDBB767A554F6D4BDCB /* UIActivityIndicatorView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C613FB8772A83708CBFE18F0662884D /* UIActivityIndicatorView+Rx.swift */; }; 11829AC72691F541D37C1A23E1EE8D1A /* PublishRelay+Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1696842F8126DAFFF7EE21C7238BDE /* PublishRelay+Signal.swift */; }; 12E43AD74AA0903AB456553B5D1DD056 /* NotificationCenter+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3B22F521A2DC59774E3F8EFCDEE0A1 /* NotificationCenter+Rx.swift */; }; 1A3A10A66FDBF09522B7C0F7276B3AC5 /* _RXKVOObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = DEDFA0E387E4A54E10D45D4D817F3963 /* _RXKVOObserver.m */; }; 1C10D4BE70FE89EC0324B23716FA45E5 /* KVORepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8FA3820805E40887076EA198C911FF /* KVORepresentable.swift */; }; 1EEA54B15BC5E6CD7F18AA8EF8924539 /* UIStepper+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531E900C9C6EDF756BB0587BD3E66D48 /* UIStepper+Rx.swift */; }; 1FAD000D7AED20DE6556288476BC1955 /* WKWebView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F1E559C3EB5AEE85C30CEE47E92F7 /* WKWebView+Rx.swift */; }; 1FE825E92010BE5DDCD0D97CED70A236 /* RxCollectionViewDataSourcePrefetchingProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F35E934D8630CDE840A2C882F51752F8 /* RxCollectionViewDataSourcePrefetchingProxy.swift */; }; 226FB079685A350A7522F6FDB772FC86 /* Observable+Bind.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABD8109B80E17857201D7A5F69440810 /* Observable+Bind.swift */; }; 237497A83B83D8551FD4DCCA2DBA2BCE /* TextInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E38136CEABB768390D4C30D6697F86E /* TextInput.swift */; }; 23AB7E0B2F1F9F0B4604A98829734FDB /* RxNavigationControllerDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C220743F207DB71B5DA783A5D37A0A1 /* RxNavigationControllerDelegateProxy.swift */; }; 27A7914471BBC1D85B251B95772859EC /* Signal+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519DECE1EAC0267F012AD1445738C0D6 /* Signal+Subscription.swift */; }; 28CC4ED3D89BB23506B287F8F5BCECD8 /* KVORepresentable+CoreGraphics.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF622DAFEBB22E624B4168E567EA2CAD /* KVORepresentable+CoreGraphics.swift */; }; 2B3CE62E5D8CCC3E9214740D10E20AAB /* UISwitch+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BB19EDA15250FCB1DA9BB0CAB0C3721 /* UISwitch+Rx.swift */; }; 2CD803DA7E83C82D290FE98FEEE687B8 /* RxCocoa.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF18C550B17C6FBACF466F5B7841071 /* RxCocoa.swift */; }; 3252EA71C9B932A7A6620770F5056F50 /* _RXObjCRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = 03DCE6BFF4FC3E84051394961B87F5F2 /* _RXObjCRuntime.m */; }; 33AE29CCD4F54BC43937521134A12BAC /* NSView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7F2F0FF7B85FF1F26D58EADD67F0ECD /* NSView+Rx.swift */; }; 342DEFDAF94AF1FC29BF33F4651A18E2 /* NSObject+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06E293B1F7E2E1E3CD617B5BF5C0A5F /* NSObject+Rx.swift */; }; 34B59D20C08209AA4BE18CA222723515 /* RxCocoaRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = 0DEDB5AB3CD0EB4A98D3B8B4C7E1A0FB /* RxCocoaRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; }; 381853F4D9C66B4FC877241753B77369 /* RxTableViewDataSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D844436D5AF4452273B8A6BA3FCDF06C /* RxTableViewDataSourceType.swift */; }; 3936A1BCFFCDB03BE54CAAE0D33A6659 /* RxTextStorageDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D6AFB2824027E4237941DA2B22ADBA4 /* RxTextStorageDelegateProxy.swift */; }; 3F2A44ED7330BA5E28225DA7D48529FD /* NSSlider+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5872C5E8D07CECF1A4BBF15CA45385 /* NSSlider+Rx.swift */; }; 41C1AD8E7712BB584CB989ABFBEF6BF2 /* RxTableViewDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 418F4B3851F74B10CEF9AC009087B6C9 /* RxTableViewDelegateProxy.swift */; }; 41CFFA76A5135FF4625D2E83CDA490D0 /* RxScrollViewDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664622FC7ADCA72F0188296E5537FD1A /* RxScrollViewDelegateProxy.swift */; }; 434B3976E3B3FC68F8A405A2ACCAEB3F /* RxCollectionViewDataSourceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F48E833FFD59608576FC073396E1D62A /* RxCollectionViewDataSourceProxy.swift */; }; 4A92C2186902E5B4A9BA5B268CEDC80A /* NSTextView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28093455FDEDA9AA6D5053A875A18CB9 /* NSTextView+Rx.swift */; }; 4F89F7E3A2286DB442688011E556D93B /* ControlEvent+Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002DFBB49F24FB32815F1779DC131C18 /* ControlEvent+Signal.swift */; }; 51B2FF394C94C6D6688CC055A546811D /* UIDatePicker+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC3176741A679B0CC60C74A031F7AE8 /* UIDatePicker+Rx.swift */; }; 5239D3EE3DDD3BC875FE100C4A83A1A5 /* RxCocoaObjCRuntimeError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D41DEB883AE5CAD6354F7DD5012C234 /* RxCocoaObjCRuntimeError+Extensions.swift */; }; 5274CF9FA22FC8DA1FC5AECFCE0847A5 /* RxWKNavigationDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D389EEC4E01BCB0880E351F5705832 /* RxWKNavigationDelegateProxy.swift */; }; 56962A5B70E43CC0D3219B7FBCD1DC88 /* ControlTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B516B9479F21A0B64D3D1C011E4F007 /* ControlTarget.swift */; }; 5778FB7C831F04053D46B6FEC03CE4D2 /* Platform.Darwin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C39B9734D96B948154C3D494FA0388A4 /* Platform.Darwin.swift */; }; 58383C193D1A398E965E3EF654586DB0 /* SectionedViewDataSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A189D909EA9E19138D228488FC2D42D /* SectionedViewDataSourceType.swift */; }; 58A0BA435F851165449E98CA422FA05E /* SharedSequence+Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F491FDB7F0E421975C083447433964 /* SharedSequence+Operators.swift */; }; 5A154D2ECAEFB42367BE0C0DDC87CF26 /* NSTextStorage+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F910B81C7E63912873F4A0CB790557C /* NSTextStorage+Rx.swift */; }; 5CE91A95754610E4A2BFE209EB19655A /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53333A480DFFD4501326625E9BDD166 /* Signal.swift */; }; 5DECCA3CB0C27DD1C88182731DA7FCD1 /* Driver+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B40407506F8044F716981DBD7B8E42 /* Driver+Subscription.swift */; }; 605192772D1262EAB3805CB53A090FF4 /* RxTextViewDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A5BBDABEB49C515059594CBEDC6E03 /* RxTextViewDelegateProxy.swift */; }; 607B3B2367464B8EA3A3C129F9BC9468 /* RxCocoa-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FAD8268E6FA199796F9CED85C6624B82 /* RxCocoa-dummy.m */; }; 60A953C8972283706C082AC42947B043 /* RxPickerViewDataSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 160361F87DBC37A623A47DCDFD551051 /* RxPickerViewDataSourceType.swift */; }; 60D9FA5934ECE047A78B009B4888CF2E /* RxTableViewDataSourcePrefetchingProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064483EEE715AA1F53F6516AB100D7F0 /* RxTableViewDataSourcePrefetchingProxy.swift */; }; 6234FE7ABB908DE1D812CA427861A9C8 /* _RX.h in Headers */ = {isa = PBXBuildFile; fileRef = D61A7E9765A1E785BBB9D6457FFD21BB /* _RX.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6264AC44995FE0034C5DFA936FF52CE6 /* NSObject+Rx+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5274DDB216BCF5961D86E4257DEC1D /* NSObject+Rx+RawRepresentable.swift */; }; 647650CB9D07A93E652BA2A36BC98ED3 /* _RXDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C1493C3A5D8FEDFDF3CCD4AB6E51D7 /* _RXDelegateProxy.m */; }; 65415D025EF91EAC5D577949B9BBF972 /* DelegateProxyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A03F4B96AFF29858BC629A8B090AF5F /* DelegateProxyType.swift */; }; 666CF8DCC50F87DC5EA2A002F3090744 /* UIButton+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D4561C808ADF530A19D9A9B103112FD /* UIButton+Rx.swift */; }; 6E0C2A2B9AD3209FB63014447F03605C /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ECEDC46A3E62964188CCF3C40F65F9E /* Queue.swift */; }; 6EE5697BB276A22553C7E15791D4E63E /* BehaviorRelay+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ADF1BD6894EF0BE74BE76DDC9ACBDA /* BehaviorRelay+Driver.swift */; }; 780A538787419596D0E84C16733D8F5B /* _RXObjCRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DB7F55D959F20D25DD4C487EFC07F72 /* _RXObjCRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7890261401D11E41D489E7482DBF27D3 /* RxCocoa-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4B95EB13B3EBEB41456A72C0BB2467 /* RxCocoa-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 798D7D484A939598FDF66594BFA7D093 /* RxCollectionViewReactiveArrayDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46BF695D0C14E8B8995BF9104A32852A /* RxCollectionViewReactiveArrayDataSource.swift */; }; 7B174FB45E0D3D1E40E0DF4690CB567A /* Infallible+Bind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39945FB894651B6D6CEE5B5CCAAB39B6 /* Infallible+Bind.swift */; }; 7B63B576043243A784D88F7B3F4980AA /* UIScrollView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED1CC66C39B05567BA11DEC2FE46A3C /* UIScrollView+Rx.swift */; }; 810634A222994C7706F54EB4E230AA77 /* UIRefreshControl+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB095AD6C8E6A1B417AF4068D21CE94D /* UIRefreshControl+Rx.swift */; }; 84C5FB7908E9B5BC31D1CDD7B0055805 /* UIPickerView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419D52CCFA192A27DB66AD5331BD53C1 /* UIPickerView+Rx.swift */; }; 881222EC54152E2C917B423BA4F05B4A /* UISearchBar+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 199341CFC396D4182CDEA0352FBA0001 /* UISearchBar+Rx.swift */; }; 8920F6CDD4B32D5FD864B1BD114E99C5 /* SharedSequence+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1602013F4297A124AB8D6C252987EE /* SharedSequence+Concurrency.swift */; }; 8A8CAA4924CBEB16B47B0C21BB10F623 /* UIApplication+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F54EDF8F559354284407AE8B231E4F /* UIApplication+Rx.swift */; }; 8B462AD8B0FF3AD90F432B4F3236E34C /* NSTextField+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8331A4F50B27E9830F697D6EB50A099C /* NSTextField+Rx.swift */; }; 8E51BF06D1533DD52DDC610CD7522C44 /* SharedSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BA09836FD1C6B89C5F8F649426AE81 /* SharedSequence.swift */; }; 900AD968BCEE5F2F560F1DECBEA60E77 /* NSControl+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E11BF10DFB0DAAC7E666F5D52735373 /* NSControl+Rx.swift */; }; 920D988553573899A3531BB5A924F059 /* DispatchQueue+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B6DB006DDCBCA6D9A9FD6BDD03ED16 /* DispatchQueue+Extensions.swift */; }; 9CBE76516E6A66842938813791692F8D /* SharedSequence+Operators+arity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCED2D97BAF3C4B699DD3A0C9287D92 /* SharedSequence+Operators+arity.swift */; }; A6C4D383F553AF4A6C535AA1E948E16C /* RxTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD3185464CAFF993AD6D374FC8C227C /* RxTarget.swift */; }; A7A6FAD8807FE23F758FE42188BE2CC4 /* _RX.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B76D72DD56052E44CEFE0B3AA89D935 /* _RX.m */; }; A85FB63BBE89ED14C5C79525503E19D2 /* ObservableConvertibleType+Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435AEB2A0B5B2F6DAAF62CBCB1AC1CA5 /* ObservableConvertibleType+Signal.swift */; }; AAEF72AB3F54C300BE8A0E38B3EDD165 /* RxCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 682547DE5125E9609B59C399B1E6B860 /* RxCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACFDAD9114D6CE2C55EBE8426596EE03 /* UITextField+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0725D3878B41FCB41B109CE751E1DBAE /* UITextField+Rx.swift */; }; AE968A1E6D48CBA335CA1A6E979F112A /* Platform.Linux.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA6D6085B836402880B91BCC16B290E1 /* Platform.Linux.swift */; }; B17237829E251D14EB3961E3D4111D26 /* PriorityQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 642329CE16BB5D97FF83D8A8DC7E7899 /* PriorityQueue.swift */; }; B2C23D0F3CE1CFA87F382088A313D8B7 /* UITableView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10FFF141041D1685F1C9404791CDA4C3 /* UITableView+Rx.swift */; }; B51FB88512FC3402BECBB139D70DA558 /* RxTabBarDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57C0432CC83B41777EB7DF59040003A /* RxTabBarDelegateProxy.swift */; }; B580A3541C6CA867B7139B900EF9F75B /* ItemEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4823EAF482A906EAEA94EF6579A8D682 /* ItemEvents.swift */; }; B8F4EC2BFB70D9447D321CEF6626CE30 /* UITabBarController+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC05DA808CF63858796C3557E883FBE2 /* UITabBarController+Rx.swift */; }; BABD8E634362EE8DF08ED2E16E85E122 /* InfiniteSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5710FCD7A158BC8E8C6A852C2974BE8 /* InfiniteSequence.swift */; }; BE891F57CB396F57AE5F8E5EC8B5DCEA /* UINavigationController+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9008D761B0B91DEE6FB43DCA293C6FE /* UINavigationController+Rx.swift */; }; BE9FDA452BDAD32EC3B6235C21DA652C /* NSButton+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = E969FEA40002C8156106DDEAD17D8FE0 /* NSButton+Rx.swift */; }; BF97E194A860C80412D21C0F058B9B8B /* RxPickerViewDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427C577BD2573F07758287D082600505 /* RxPickerViewDelegateProxy.swift */; }; C00A1498C51F71DB962DD96A8DAEADDF /* UITextView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B6C8E9CB55596433FE8E363E97628BD /* UITextView+Rx.swift */; }; C1A7217E02B3A958E081EAB51F824577 /* UIBarButtonItem+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C48F713F8C3FA312E772280EC6AAF9A /* UIBarButtonItem+Rx.swift */; }; C1AB3FB67E9B02A6D0E71212BF83E22E /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0A82E54E00A2DBB8FDE2F9525F0E0C7 /* Bag.swift */; }; CA7AD2946482A548E39CA56E04FDC743 /* UIGestureRecognizer+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21298B0D540B9CB92F0C3DEBD33B016B /* UIGestureRecognizer+Rx.swift */; }; CF833DDB131A18936EC27CFFF43D1102 /* _RXKVOObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 63736DB5341B4E027C44BAEFF63790B5 /* _RXKVOObserver.h */; settings = {ATTRIBUTES = (Public, ); }; }; CFE47AF2009EB38458461514FB0EA80D /* URLSession+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0160F85C822771F39C19B748C52FB70 /* URLSession+Rx.swift */; }; D3ECD252F47B0B568380545AB3315BFD /* UIControl+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C397DCC6A23E6AFBC349ABFEE20D8210 /* UIControl+Rx.swift */; }; D4C17E628FA568CB8C0AFF6C071E1A46 /* ObservableConvertibleType+SharedSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = A955B40E10C4A2CC03056F178EBF471F /* ObservableConvertibleType+SharedSequence.swift */; }; D53DFBA3A74839F6F6CA09D1E2A38AEF /* UISearchController+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6A164298AD5E5E27AC9AE27BC6CA30 /* UISearchController+Rx.swift */; }; D55339C30DC44456301E17FADC3BFEE4 /* NSObject+Rx+KVORepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A56E2AE05C83A12A3AA5767567AB81F /* NSObject+Rx+KVORepresentable.swift */; }; D573349235F40BF450B7EFFED85A339E /* RxPickerViewDataSourceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1379A7CB1CF86817672243D4D703F2 /* RxPickerViewDataSourceProxy.swift */; }; D78C66D3E918EDAA8FB5F78ECA2855EE /* RxCollectionViewDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE1A28ADD74DE82E6DB496EF57DB900 /* RxCollectionViewDelegateProxy.swift */; }; DB9E212AEFC7DACF8049A37761258191 /* RxCollectionViewDataSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98F3E7D5B2B2FF8F63550E9D4F191E4 /* RxCollectionViewDataSourceType.swift */; }; DE46057EDAAA20C49528797F2DDAD2D5 /* UITabBar+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD69E9E256BF488F303ADBBB67722394 /* UITabBar+Rx.swift */; }; DF0F55E4FD7E166EE7CF403791853F75 /* SchedulerType+SharedSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118A6499476B7A7590891826623777F7 /* SchedulerType+SharedSequence.swift */; }; E07AB03B47E4D40F97533A356F1675BE /* RecursiveLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E23ACB29FD0FE6D7D106E295629070D4 /* RecursiveLock.swift */; }; E14154C07AF7A39E264BC3B3EFD733CE /* KVORepresentable+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DBA2166BAA4EC8BB5AB8F711CC5444 /* KVORepresentable+Swift.swift */; }; E2E71C3F1C12D2802B7FEC8E2CBFD3F4 /* UISlider+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBDED56BB6BB2CD023E58D505E36020 /* UISlider+Rx.swift */; }; E4AF4B810C37B0A44DB48E8E472FA9B9 /* RxSearchControllerDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAEE11976EFF2FA8C6C9451A10C69626 /* RxSearchControllerDelegateProxy.swift */; }; E7EEF5D1E774B075FA2F6ECD002AB132 /* RxPickerViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029F591961A328578AD2F9BD8F332452 /* RxPickerViewAdapter.swift */; }; ED71233AE263ADAB4695DEEE5E5FD0B2 /* RxTableViewDataSourceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A9554911A17B58C5ECC56B07E92F260 /* RxTableViewDataSourceProxy.swift */; }; EEA18CC5BFD9765A32B4857C1463DD2A /* _RXDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 32925EFF911A8E65BCE578CD3299DA32 /* _RXDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; F1A4496340CA70A2E1F78812B253F059 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C7ED23069B43EDF96192D3FB8B679306 /* Foundation.framework */; }; F2E24C1A4ADFC8EB439A52429A813EC6 /* ControlEvent+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FABEC166FCD2446B0A67318C6514007 /* ControlEvent+Driver.swift */; }; F59650BAB5B15DCA84C5A18D012D2A96 /* RxTableViewReactiveArrayDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D25B52211A53D8D4EABDF123365709FD /* RxTableViewReactiveArrayDataSource.swift */; }; F64781CDBF7D029AB322BF643522ED09 /* Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CEA274E713D9A7EF90A08C93BBC999 /* Driver.swift */; }; F6850C0AC55C3AB12EF44EFF135E604F /* RxSearchBarDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E795BF02AF222B9D68D5E733EC29F028 /* RxSearchBarDelegateProxy.swift */; }; F765EC6C8B3CF42BE713A1AE101BBFF0 /* UISegmentedControl+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E9361181FD3DA8B4B15F01EF23DA8EB /* UISegmentedControl+Rx.swift */; }; FA8F9FC104EDDCD9A0C9C5CD129A37EF /* ControlProperty+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F10A6C30715483935DB493E75BFD2571 /* ControlProperty+Driver.swift */; }; FBEC656E28425F77C5BA0CC5B8DD25BE /* DelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E4CB5B55FDD57BEC362A034CA25D87 /* DelegateProxy.swift */; }; FFAAD7E296C63EF8E8CF0A6BEA125298 /* ControlEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE39D31B3302D7BE844917ACE4B2F39 /* ControlEvent.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 1A3FDE601F76A95F98A9A45A56E6C6BB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E190A0F56A6754915E4B6306E4B5826A /* RxSwift.xcodeproj */; proxyType = 1; remoteGlobalIDString = F0179EE061353B7A322F596E97844774; remoteInfo = RxSwift; }; 55C2E4D31EBDBBE38DCC9FD538A9C78A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5829B97DAB1D9A2C7BBEA71AC48E4228 /* RxRelay.xcodeproj */; proxyType = 1; remoteGlobalIDString = 564FA919E05BFD512DA9163BAB640EEE; remoteInfo = RxRelay; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 002DFBB49F24FB32815F1779DC131C18 /* ControlEvent+Signal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ControlEvent+Signal.swift"; path = "RxCocoa/Traits/Signal/ControlEvent+Signal.swift"; sourceTree = ""; }; 029F591961A328578AD2F9BD8F332452 /* RxPickerViewAdapter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxPickerViewAdapter.swift; path = RxCocoa/iOS/DataSources/RxPickerViewAdapter.swift; sourceTree = ""; }; 03DCE6BFF4FC3E84051394961B87F5F2 /* _RXObjCRuntime.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = _RXObjCRuntime.m; path = RxCocoa/Runtime/_RXObjCRuntime.m; sourceTree = ""; }; 064483EEE715AA1F53F6516AB100D7F0 /* RxTableViewDataSourcePrefetchingProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxTableViewDataSourcePrefetchingProxy.swift; path = RxCocoa/iOS/Proxies/RxTableViewDataSourcePrefetchingProxy.swift; sourceTree = ""; }; 0725D3878B41FCB41B109CE751E1DBAE /* UITextField+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UITextField+Rx.swift"; path = "RxCocoa/iOS/UITextField+Rx.swift"; sourceTree = ""; }; 0DEDB5AB3CD0EB4A98D3B8B4C7E1A0FB /* RxCocoaRuntime.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RxCocoaRuntime.h; path = RxCocoa/Runtime/include/RxCocoaRuntime.h; sourceTree = ""; }; 0E21BC876BBD95A73D1EC5F17668884C /* ObservableConvertibleType+Driver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ObservableConvertibleType+Driver.swift"; path = "RxCocoa/Traits/Driver/ObservableConvertibleType+Driver.swift"; sourceTree = ""; }; 0EBDED56BB6BB2CD023E58D505E36020 /* UISlider+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UISlider+Rx.swift"; path = "RxCocoa/iOS/UISlider+Rx.swift"; sourceTree = ""; }; 10FFF141041D1685F1C9404791CDA4C3 /* UITableView+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UITableView+Rx.swift"; path = "RxCocoa/iOS/UITableView+Rx.swift"; sourceTree = ""; }; 118A6499476B7A7590891826623777F7 /* SchedulerType+SharedSequence.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SchedulerType+SharedSequence.swift"; path = "RxCocoa/Traits/SharedSequence/SchedulerType+SharedSequence.swift"; sourceTree = ""; }; 160361F87DBC37A623A47DCDFD551051 /* RxPickerViewDataSourceType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxPickerViewDataSourceType.swift; path = RxCocoa/iOS/Protocols/RxPickerViewDataSourceType.swift; sourceTree = ""; }; 199341CFC396D4182CDEA0352FBA0001 /* UISearchBar+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UISearchBar+Rx.swift"; path = "RxCocoa/iOS/UISearchBar+Rx.swift"; sourceTree = ""; }; 1AE39D31B3302D7BE844917ACE4B2F39 /* ControlEvent.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ControlEvent.swift; path = RxCocoa/Traits/ControlEvent.swift; sourceTree = ""; }; 1CF18C550B17C6FBACF466F5B7841071 /* RxCocoa.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxCocoa.swift; path = RxCocoa/RxCocoa.swift; sourceTree = ""; }; 1D41DEB883AE5CAD6354F7DD5012C234 /* RxCocoaObjCRuntimeError+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "RxCocoaObjCRuntimeError+Extensions.swift"; path = "RxCocoa/Common/RxCocoaObjCRuntimeError+Extensions.swift"; sourceTree = ""; }; 1FABEC166FCD2446B0A67318C6514007 /* ControlEvent+Driver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ControlEvent+Driver.swift"; path = "RxCocoa/Traits/Driver/ControlEvent+Driver.swift"; sourceTree = ""; }; 21298B0D540B9CB92F0C3DEBD33B016B /* UIGestureRecognizer+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIGestureRecognizer+Rx.swift"; path = "RxCocoa/iOS/UIGestureRecognizer+Rx.swift"; sourceTree = ""; }; 28093455FDEDA9AA6D5053A875A18CB9 /* NSTextView+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSTextView+Rx.swift"; path = "RxCocoa/macOS/NSTextView+Rx.swift"; sourceTree = ""; }; 29DBA2166BAA4EC8BB5AB8F711CC5444 /* KVORepresentable+Swift.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "KVORepresentable+Swift.swift"; path = "RxCocoa/Foundation/KVORepresentable+Swift.swift"; sourceTree = ""; }; 2ECEDC46A3E62964188CCF3C40F65F9E /* Queue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Queue.swift; path = Platform/DataStructures/Queue.swift; sourceTree = ""; }; 31D389EEC4E01BCB0880E351F5705832 /* RxWKNavigationDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxWKNavigationDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxWKNavigationDelegateProxy.swift; sourceTree = ""; }; 32925EFF911A8E65BCE578CD3299DA32 /* _RXDelegateProxy.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = _RXDelegateProxy.h; path = RxCocoa/Runtime/include/_RXDelegateProxy.h; sourceTree = ""; }; 364B76EDCB5520C0CF711280AFE58D0D /* ControlProperty.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ControlProperty.swift; path = RxCocoa/Traits/ControlProperty.swift; sourceTree = ""; }; 39945FB894651B6D6CEE5B5CCAAB39B6 /* Infallible+Bind.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Infallible+Bind.swift"; path = "RxCocoa/Common/Infallible+Bind.swift"; sourceTree = ""; }; 3A1602013F4297A124AB8D6C252987EE /* SharedSequence+Concurrency.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SharedSequence+Concurrency.swift"; path = "RxCocoa/Traits/SharedSequence/SharedSequence+Concurrency.swift"; sourceTree = ""; }; 3A56E2AE05C83A12A3AA5767567AB81F /* NSObject+Rx+KVORepresentable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSObject+Rx+KVORepresentable.swift"; path = "RxCocoa/Foundation/NSObject+Rx+KVORepresentable.swift"; sourceTree = ""; }; 3B6C8E9CB55596433FE8E363E97628BD /* UITextView+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UITextView+Rx.swift"; path = "RxCocoa/iOS/UITextView+Rx.swift"; sourceTree = ""; }; 3BB19EDA15250FCB1DA9BB0CAB0C3721 /* UISwitch+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UISwitch+Rx.swift"; path = "RxCocoa/iOS/UISwitch+Rx.swift"; sourceTree = ""; }; 3F8FA3820805E40887076EA198C911FF /* KVORepresentable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KVORepresentable.swift; path = RxCocoa/Foundation/KVORepresentable.swift; sourceTree = ""; }; 418F4B3851F74B10CEF9AC009087B6C9 /* RxTableViewDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxTableViewDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxTableViewDelegateProxy.swift; sourceTree = ""; }; 419D52CCFA192A27DB66AD5331BD53C1 /* UIPickerView+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIPickerView+Rx.swift"; path = "RxCocoa/iOS/UIPickerView+Rx.swift"; sourceTree = ""; }; 427C577BD2573F07758287D082600505 /* RxPickerViewDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxPickerViewDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxPickerViewDelegateProxy.swift; sourceTree = ""; }; 435AEB2A0B5B2F6DAAF62CBCB1AC1CA5 /* ObservableConvertibleType+Signal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ObservableConvertibleType+Signal.swift"; path = "RxCocoa/Traits/Signal/ObservableConvertibleType+Signal.swift"; sourceTree = ""; }; 45C1493C3A5D8FEDFDF3CCD4AB6E51D7 /* _RXDelegateProxy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = _RXDelegateProxy.m; path = RxCocoa/Runtime/_RXDelegateProxy.m; sourceTree = ""; }; 46BF695D0C14E8B8995BF9104A32852A /* RxCollectionViewReactiveArrayDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxCollectionViewReactiveArrayDataSource.swift; path = RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift; sourceTree = ""; }; 4823EAF482A906EAEA94EF6579A8D682 /* ItemEvents.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ItemEvents.swift; path = RxCocoa/iOS/Events/ItemEvents.swift; sourceTree = ""; }; 49F491FDB7F0E421975C083447433964 /* SharedSequence+Operators.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SharedSequence+Operators.swift"; path = "RxCocoa/Traits/SharedSequence/SharedSequence+Operators.swift"; sourceTree = ""; }; 4C220743F207DB71B5DA783A5D37A0A1 /* RxNavigationControllerDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxNavigationControllerDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxNavigationControllerDelegateProxy.swift; sourceTree = ""; }; 4D4561C808ADF530A19D9A9B103112FD /* UIButton+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIButton+Rx.swift"; path = "RxCocoa/iOS/UIButton+Rx.swift"; sourceTree = ""; }; 4F6A164298AD5E5E27AC9AE27BC6CA30 /* UISearchController+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UISearchController+Rx.swift"; path = "RxCocoa/iOS/UISearchController+Rx.swift"; sourceTree = ""; }; 519DECE1EAC0267F012AD1445738C0D6 /* Signal+Subscription.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Signal+Subscription.swift"; path = "RxCocoa/Traits/Signal/Signal+Subscription.swift"; sourceTree = ""; }; 531E900C9C6EDF756BB0587BD3E66D48 /* UIStepper+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIStepper+Rx.swift"; path = "RxCocoa/iOS/UIStepper+Rx.swift"; sourceTree = ""; }; 546FC66ECA46E6D8B26DE8D7B529667C /* RxCocoa.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = RxCocoa.debug.xcconfig; sourceTree = ""; }; 54B6DB006DDCBCA6D9A9FD6BDD03ED16 /* DispatchQueue+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DispatchQueue+Extensions.swift"; path = "Platform/DispatchQueue+Extensions.swift"; sourceTree = ""; }; 5829B97DAB1D9A2C7BBEA71AC48E4228 /* RxRelay */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RxRelay; path = RxRelay.xcodeproj; sourceTree = ""; }; 5B76D72DD56052E44CEFE0B3AA89D935 /* _RX.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = _RX.m; path = RxCocoa/Runtime/_RX.m; sourceTree = ""; }; 5C79F9EACD23B1AF09974E693F7CD6EA /* RxCocoa.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = RxCocoa.modulemap; sourceTree = ""; }; 5E9361181FD3DA8B4B15F01EF23DA8EB /* UISegmentedControl+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UISegmentedControl+Rx.swift"; path = "RxCocoa/iOS/UISegmentedControl+Rx.swift"; sourceTree = ""; }; 5ED1CC66C39B05567BA11DEC2FE46A3C /* UIScrollView+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIScrollView+Rx.swift"; path = "RxCocoa/iOS/UIScrollView+Rx.swift"; sourceTree = ""; }; 5F5872C5E8D07CECF1A4BBF15CA45385 /* NSSlider+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSSlider+Rx.swift"; path = "RxCocoa/macOS/NSSlider+Rx.swift"; sourceTree = ""; }; 5F910B81C7E63912873F4A0CB790557C /* NSTextStorage+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSTextStorage+Rx.swift"; path = "RxCocoa/iOS/NSTextStorage+Rx.swift"; sourceTree = ""; }; 63736DB5341B4E027C44BAEFF63790B5 /* _RXKVOObserver.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = _RXKVOObserver.h; path = RxCocoa/Runtime/include/_RXKVOObserver.h; sourceTree = ""; }; 642329CE16BB5D97FF83D8A8DC7E7899 /* PriorityQueue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PriorityQueue.swift; path = Platform/DataStructures/PriorityQueue.swift; sourceTree = ""; }; 664622FC7ADCA72F0188296E5537FD1A /* RxScrollViewDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxScrollViewDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxScrollViewDelegateProxy.swift; sourceTree = ""; }; 682547DE5125E9609B59C399B1E6B860 /* RxCocoa.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RxCocoa.h; path = RxCocoa/RxCocoa.h; sourceTree = ""; }; 6A03F4B96AFF29858BC629A8B090AF5F /* DelegateProxyType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DelegateProxyType.swift; path = RxCocoa/Common/DelegateProxyType.swift; sourceTree = ""; }; 6A189D909EA9E19138D228488FC2D42D /* SectionedViewDataSourceType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SectionedViewDataSourceType.swift; path = RxCocoa/Common/SectionedViewDataSourceType.swift; sourceTree = ""; }; 6A9554911A17B58C5ECC56B07E92F260 /* RxTableViewDataSourceProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxTableViewDataSourceProxy.swift; path = RxCocoa/iOS/Proxies/RxTableViewDataSourceProxy.swift; sourceTree = ""; }; 7AE1A28ADD74DE82E6DB496EF57DB900 /* RxCollectionViewDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxCollectionViewDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxCollectionViewDelegateProxy.swift; sourceTree = ""; }; 7B516B9479F21A0B64D3D1C011E4F007 /* ControlTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ControlTarget.swift; path = RxCocoa/Common/ControlTarget.swift; sourceTree = ""; }; 7DB7F55D959F20D25DD4C487EFC07F72 /* _RXObjCRuntime.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = _RXObjCRuntime.h; path = RxCocoa/Runtime/include/_RXObjCRuntime.h; sourceTree = ""; }; 8331A4F50B27E9830F697D6EB50A099C /* NSTextField+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSTextField+Rx.swift"; path = "RxCocoa/macOS/NSTextField+Rx.swift"; sourceTree = ""; }; 848F1E559C3EB5AEE85C30CEE47E92F7 /* WKWebView+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "WKWebView+Rx.swift"; path = "RxCocoa/iOS/WKWebView+Rx.swift"; sourceTree = ""; }; 85A5BBDABEB49C515059594CBEDC6E03 /* RxTextViewDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxTextViewDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxTextViewDelegateProxy.swift; sourceTree = ""; }; 8C48F713F8C3FA312E772280EC6AAF9A /* UIBarButtonItem+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIBarButtonItem+Rx.swift"; path = "RxCocoa/iOS/UIBarButtonItem+Rx.swift"; sourceTree = ""; }; 8D6AFB2824027E4237941DA2B22ADBA4 /* RxTextStorageDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxTextStorageDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxTextStorageDelegateProxy.swift; sourceTree = ""; }; 8E38136CEABB768390D4C30D6697F86E /* TextInput.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TextInput.swift; path = RxCocoa/Common/TextInput.swift; sourceTree = ""; }; 9C613FB8772A83708CBFE18F0662884D /* UIActivityIndicatorView+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIActivityIndicatorView+Rx.swift"; path = "RxCocoa/iOS/UIActivityIndicatorView+Rx.swift"; sourceTree = ""; }; 9E11BF10DFB0DAAC7E666F5D52735373 /* NSControl+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSControl+Rx.swift"; path = "RxCocoa/macOS/NSControl+Rx.swift"; sourceTree = ""; }; 9F1696842F8126DAFFF7EE21C7238BDE /* PublishRelay+Signal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "PublishRelay+Signal.swift"; path = "RxCocoa/Traits/Signal/PublishRelay+Signal.swift"; sourceTree = ""; }; A0160F85C822771F39C19B748C52FB70 /* URLSession+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLSession+Rx.swift"; path = "RxCocoa/Foundation/URLSession+Rx.swift"; sourceTree = ""; }; A1B40407506F8044F716981DBD7B8E42 /* Driver+Subscription.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Driver+Subscription.swift"; path = "RxCocoa/Traits/Driver/Driver+Subscription.swift"; sourceTree = ""; }; A5BA09836FD1C6B89C5F8F649426AE81 /* SharedSequence.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SharedSequence.swift; path = RxCocoa/Traits/SharedSequence/SharedSequence.swift; sourceTree = ""; }; A9008D761B0B91DEE6FB43DCA293C6FE /* UINavigationController+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UINavigationController+Rx.swift"; path = "RxCocoa/iOS/UINavigationController+Rx.swift"; sourceTree = ""; }; A955B40E10C4A2CC03056F178EBF471F /* ObservableConvertibleType+SharedSequence.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ObservableConvertibleType+SharedSequence.swift"; path = "RxCocoa/Traits/SharedSequence/ObservableConvertibleType+SharedSequence.swift"; sourceTree = ""; }; AAD3185464CAFF993AD6D374FC8C227C /* RxTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxTarget.swift; path = RxCocoa/Common/RxTarget.swift; sourceTree = ""; }; ABD8109B80E17857201D7A5F69440810 /* Observable+Bind.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Observable+Bind.swift"; path = "RxCocoa/Common/Observable+Bind.swift"; sourceTree = ""; }; AC05DA808CF63858796C3557E883FBE2 /* UITabBarController+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UITabBarController+Rx.swift"; path = "RxCocoa/iOS/UITabBarController+Rx.swift"; sourceTree = ""; }; AC85B53F47EEFA85FBD54111AD218FFE /* RxCocoa.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = RxCocoa.release.xcconfig; sourceTree = ""; }; B5710FCD7A158BC8E8C6A852C2974BE8 /* InfiniteSequence.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = InfiniteSequence.swift; path = Platform/DataStructures/InfiniteSequence.swift; sourceTree = ""; }; B8CEA274E713D9A7EF90A08C93BBC999 /* Driver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Driver.swift; path = RxCocoa/Traits/Driver/Driver.swift; sourceTree = ""; }; BAEE11976EFF2FA8C6C9451A10C69626 /* RxSearchControllerDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxSearchControllerDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxSearchControllerDelegateProxy.swift; sourceTree = ""; }; BB095AD6C8E6A1B417AF4068D21CE94D /* UIRefreshControl+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIRefreshControl+Rx.swift"; path = "RxCocoa/iOS/UIRefreshControl+Rx.swift"; sourceTree = ""; }; BC3B22F521A2DC59774E3F8EFCDEE0A1 /* NotificationCenter+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NotificationCenter+Rx.swift"; path = "RxCocoa/Foundation/NotificationCenter+Rx.swift"; sourceTree = ""; }; C00ED7A94C67870E506FB3DEB1E0BC81 /* RxCocoa-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "RxCocoa-Info.plist"; sourceTree = ""; }; C397DCC6A23E6AFBC349ABFEE20D8210 /* UIControl+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIControl+Rx.swift"; path = "RxCocoa/iOS/UIControl+Rx.swift"; sourceTree = ""; }; C39B9734D96B948154C3D494FA0388A4 /* Platform.Darwin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Platform.Darwin.swift; path = Platform/Platform.Darwin.swift; sourceTree = ""; }; C425D080D0597579029EC0D51F19DF00 /* RxTabBarControllerDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxTabBarControllerDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxTabBarControllerDelegateProxy.swift; sourceTree = ""; }; C7ED23069B43EDF96192D3FB8B679306 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; C98F3E7D5B2B2FF8F63550E9D4F191E4 /* RxCollectionViewDataSourceType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxCollectionViewDataSourceType.swift; path = RxCocoa/iOS/Protocols/RxCollectionViewDataSourceType.swift; sourceTree = ""; }; CA6D6085B836402880B91BCC16B290E1 /* Platform.Linux.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Platform.Linux.swift; path = Platform/Platform.Linux.swift; sourceTree = ""; }; CC4B95EB13B3EBEB41456A72C0BB2467 /* RxCocoa-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "RxCocoa-umbrella.h"; sourceTree = ""; }; CE5274DDB216BCF5961D86E4257DEC1D /* NSObject+Rx+RawRepresentable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSObject+Rx+RawRepresentable.swift"; path = "RxCocoa/Foundation/NSObject+Rx+RawRepresentable.swift"; sourceTree = ""; }; D25B52211A53D8D4EABDF123365709FD /* RxTableViewReactiveArrayDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxTableViewReactiveArrayDataSource.swift; path = RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift; sourceTree = ""; }; D2ADF1BD6894EF0BE74BE76DDC9ACBDA /* BehaviorRelay+Driver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "BehaviorRelay+Driver.swift"; path = "RxCocoa/Traits/Driver/BehaviorRelay+Driver.swift"; sourceTree = ""; }; D53333A480DFFD4501326625E9BDD166 /* Signal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Signal.swift; path = RxCocoa/Traits/Signal/Signal.swift; sourceTree = ""; }; D57C0432CC83B41777EB7DF59040003A /* RxTabBarDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxTabBarDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxTabBarDelegateProxy.swift; sourceTree = ""; }; D614C716A0210E767055886DB071EC83 /* RxCocoa-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "RxCocoa-prefix.pch"; sourceTree = ""; }; D61A7E9765A1E785BBB9D6457FFD21BB /* _RX.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = _RX.h; path = RxCocoa/Runtime/include/_RX.h; sourceTree = ""; }; D844436D5AF4452273B8A6BA3FCDF06C /* RxTableViewDataSourceType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxTableViewDataSourceType.swift; path = RxCocoa/iOS/Protocols/RxTableViewDataSourceType.swift; sourceTree = ""; }; DEDFA0E387E4A54E10D45D4D817F3963 /* _RXKVOObserver.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = _RXKVOObserver.m; path = RxCocoa/Runtime/_RXKVOObserver.m; sourceTree = ""; }; E06E293B1F7E2E1E3CD617B5BF5C0A5F /* NSObject+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSObject+Rx.swift"; path = "RxCocoa/Foundation/NSObject+Rx.swift"; sourceTree = ""; }; E0A82E54E00A2DBB8FDE2F9525F0E0C7 /* Bag.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Bag.swift; path = Platform/DataStructures/Bag.swift; sourceTree = ""; }; E0C3C2A6E813EF1CA4D4B015C3C04D07 /* UICollectionView+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UICollectionView+Rx.swift"; path = "RxCocoa/iOS/UICollectionView+Rx.swift"; sourceTree = ""; }; E190A0F56A6754915E4B6306E4B5826A /* RxSwift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RxSwift; path = RxSwift.xcodeproj; sourceTree = ""; }; E23ACB29FD0FE6D7D106E295629070D4 /* RecursiveLock.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RecursiveLock.swift; path = Platform/RecursiveLock.swift; sourceTree = ""; }; E795BF02AF222B9D68D5E733EC29F028 /* RxSearchBarDelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxSearchBarDelegateProxy.swift; path = RxCocoa/iOS/Proxies/RxSearchBarDelegateProxy.swift; sourceTree = ""; }; E7F2F0FF7B85FF1F26D58EADD67F0ECD /* NSView+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSView+Rx.swift"; path = "RxCocoa/macOS/NSView+Rx.swift"; sourceTree = ""; }; E969FEA40002C8156106DDEAD17D8FE0 /* NSButton+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSButton+Rx.swift"; path = "RxCocoa/macOS/NSButton+Rx.swift"; sourceTree = ""; }; EB1379A7CB1CF86817672243D4D703F2 /* RxPickerViewDataSourceProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxPickerViewDataSourceProxy.swift; path = RxCocoa/iOS/Proxies/RxPickerViewDataSourceProxy.swift; sourceTree = ""; }; EF622DAFEBB22E624B4168E567EA2CAD /* KVORepresentable+CoreGraphics.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "KVORepresentable+CoreGraphics.swift"; path = "RxCocoa/Foundation/KVORepresentable+CoreGraphics.swift"; sourceTree = ""; }; F10A6C30715483935DB493E75BFD2571 /* ControlProperty+Driver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ControlProperty+Driver.swift"; path = "RxCocoa/Traits/Driver/ControlProperty+Driver.swift"; sourceTree = ""; }; F1F54EDF8F559354284407AE8B231E4F /* UIApplication+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIApplication+Rx.swift"; path = "RxCocoa/iOS/UIApplication+Rx.swift"; sourceTree = ""; }; F2E4CB5B55FDD57BEC362A034CA25D87 /* DelegateProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DelegateProxy.swift; path = RxCocoa/Common/DelegateProxy.swift; sourceTree = ""; }; F35E934D8630CDE840A2C882F51752F8 /* RxCollectionViewDataSourcePrefetchingProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxCollectionViewDataSourcePrefetchingProxy.swift; path = RxCocoa/iOS/Proxies/RxCollectionViewDataSourcePrefetchingProxy.swift; sourceTree = ""; }; F48E833FFD59608576FC073396E1D62A /* RxCollectionViewDataSourceProxy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RxCollectionViewDataSourceProxy.swift; path = RxCocoa/iOS/Proxies/RxCollectionViewDataSourceProxy.swift; sourceTree = ""; }; F49FD34447716E9C149BEC0B4BF25379 /* RxCocoa */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = RxCocoa; path = RxCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FAD8268E6FA199796F9CED85C6624B82 /* RxCocoa-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "RxCocoa-dummy.m"; sourceTree = ""; }; FD69E9E256BF488F303ADBBB67722394 /* UITabBar+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UITabBar+Rx.swift"; path = "RxCocoa/iOS/UITabBar+Rx.swift"; sourceTree = ""; }; FDC3176741A679B0CC60C74A031F7AE8 /* UIDatePicker+Rx.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIDatePicker+Rx.swift"; path = "RxCocoa/iOS/UIDatePicker+Rx.swift"; sourceTree = ""; }; FFCED2D97BAF3C4B699DD3A0C9287D92 /* SharedSequence+Operators+arity.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SharedSequence+Operators+arity.swift"; path = "RxCocoa/Traits/SharedSequence/SharedSequence+Operators+arity.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F98EE57E25923546DDDF354BC054BB3D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F1A4496340CA70A2E1F78812B253F059 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 069D72AED8E2051A22B1EA4BBCA2C236 /* Dependencies */ = { isa = PBXGroup; children = ( 5829B97DAB1D9A2C7BBEA71AC48E4228 /* RxRelay */, E190A0F56A6754915E4B6306E4B5826A /* RxSwift */, ); name = Dependencies; sourceTree = ""; }; 351129349E28D54DF7A90F3CFC798AE9 /* iOS */ = { isa = PBXGroup; children = ( C7ED23069B43EDF96192D3FB8B679306 /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; 58962E70A647EBABB00A8A8B1EA4530C /* RxCocoa */ = { isa = PBXGroup; children = ( D61A7E9765A1E785BBB9D6457FFD21BB /* _RX.h */, 5B76D72DD56052E44CEFE0B3AA89D935 /* _RX.m */, 32925EFF911A8E65BCE578CD3299DA32 /* _RXDelegateProxy.h */, 45C1493C3A5D8FEDFDF3CCD4AB6E51D7 /* _RXDelegateProxy.m */, 63736DB5341B4E027C44BAEFF63790B5 /* _RXKVOObserver.h */, DEDFA0E387E4A54E10D45D4D817F3963 /* _RXKVOObserver.m */, 7DB7F55D959F20D25DD4C487EFC07F72 /* _RXObjCRuntime.h */, 03DCE6BFF4FC3E84051394961B87F5F2 /* _RXObjCRuntime.m */, E0A82E54E00A2DBB8FDE2F9525F0E0C7 /* Bag.swift */, D2ADF1BD6894EF0BE74BE76DDC9ACBDA /* BehaviorRelay+Driver.swift */, 1AE39D31B3302D7BE844917ACE4B2F39 /* ControlEvent.swift */, 1FABEC166FCD2446B0A67318C6514007 /* ControlEvent+Driver.swift */, 002DFBB49F24FB32815F1779DC131C18 /* ControlEvent+Signal.swift */, 364B76EDCB5520C0CF711280AFE58D0D /* ControlProperty.swift */, F10A6C30715483935DB493E75BFD2571 /* ControlProperty+Driver.swift */, 7B516B9479F21A0B64D3D1C011E4F007 /* ControlTarget.swift */, F2E4CB5B55FDD57BEC362A034CA25D87 /* DelegateProxy.swift */, 6A03F4B96AFF29858BC629A8B090AF5F /* DelegateProxyType.swift */, 54B6DB006DDCBCA6D9A9FD6BDD03ED16 /* DispatchQueue+Extensions.swift */, B8CEA274E713D9A7EF90A08C93BBC999 /* Driver.swift */, A1B40407506F8044F716981DBD7B8E42 /* Driver+Subscription.swift */, 39945FB894651B6D6CEE5B5CCAAB39B6 /* Infallible+Bind.swift */, B5710FCD7A158BC8E8C6A852C2974BE8 /* InfiniteSequence.swift */, 4823EAF482A906EAEA94EF6579A8D682 /* ItemEvents.swift */, 3F8FA3820805E40887076EA198C911FF /* KVORepresentable.swift */, EF622DAFEBB22E624B4168E567EA2CAD /* KVORepresentable+CoreGraphics.swift */, 29DBA2166BAA4EC8BB5AB8F711CC5444 /* KVORepresentable+Swift.swift */, BC3B22F521A2DC59774E3F8EFCDEE0A1 /* NotificationCenter+Rx.swift */, E969FEA40002C8156106DDEAD17D8FE0 /* NSButton+Rx.swift */, 9E11BF10DFB0DAAC7E666F5D52735373 /* NSControl+Rx.swift */, E06E293B1F7E2E1E3CD617B5BF5C0A5F /* NSObject+Rx.swift */, 3A56E2AE05C83A12A3AA5767567AB81F /* NSObject+Rx+KVORepresentable.swift */, CE5274DDB216BCF5961D86E4257DEC1D /* NSObject+Rx+RawRepresentable.swift */, 5F5872C5E8D07CECF1A4BBF15CA45385 /* NSSlider+Rx.swift */, 8331A4F50B27E9830F697D6EB50A099C /* NSTextField+Rx.swift */, 5F910B81C7E63912873F4A0CB790557C /* NSTextStorage+Rx.swift */, 28093455FDEDA9AA6D5053A875A18CB9 /* NSTextView+Rx.swift */, E7F2F0FF7B85FF1F26D58EADD67F0ECD /* NSView+Rx.swift */, ABD8109B80E17857201D7A5F69440810 /* Observable+Bind.swift */, 0E21BC876BBD95A73D1EC5F17668884C /* ObservableConvertibleType+Driver.swift */, A955B40E10C4A2CC03056F178EBF471F /* ObservableConvertibleType+SharedSequence.swift */, 435AEB2A0B5B2F6DAAF62CBCB1AC1CA5 /* ObservableConvertibleType+Signal.swift */, C39B9734D96B948154C3D494FA0388A4 /* Platform.Darwin.swift */, CA6D6085B836402880B91BCC16B290E1 /* Platform.Linux.swift */, 642329CE16BB5D97FF83D8A8DC7E7899 /* PriorityQueue.swift */, 9F1696842F8126DAFFF7EE21C7238BDE /* PublishRelay+Signal.swift */, 2ECEDC46A3E62964188CCF3C40F65F9E /* Queue.swift */, E23ACB29FD0FE6D7D106E295629070D4 /* RecursiveLock.swift */, 682547DE5125E9609B59C399B1E6B860 /* RxCocoa.h */, 1CF18C550B17C6FBACF466F5B7841071 /* RxCocoa.swift */, 1D41DEB883AE5CAD6354F7DD5012C234 /* RxCocoaObjCRuntimeError+Extensions.swift */, 0DEDB5AB3CD0EB4A98D3B8B4C7E1A0FB /* RxCocoaRuntime.h */, F35E934D8630CDE840A2C882F51752F8 /* RxCollectionViewDataSourcePrefetchingProxy.swift */, F48E833FFD59608576FC073396E1D62A /* RxCollectionViewDataSourceProxy.swift */, C98F3E7D5B2B2FF8F63550E9D4F191E4 /* RxCollectionViewDataSourceType.swift */, 7AE1A28ADD74DE82E6DB496EF57DB900 /* RxCollectionViewDelegateProxy.swift */, 46BF695D0C14E8B8995BF9104A32852A /* RxCollectionViewReactiveArrayDataSource.swift */, 4C220743F207DB71B5DA783A5D37A0A1 /* RxNavigationControllerDelegateProxy.swift */, 029F591961A328578AD2F9BD8F332452 /* RxPickerViewAdapter.swift */, EB1379A7CB1CF86817672243D4D703F2 /* RxPickerViewDataSourceProxy.swift */, 160361F87DBC37A623A47DCDFD551051 /* RxPickerViewDataSourceType.swift */, 427C577BD2573F07758287D082600505 /* RxPickerViewDelegateProxy.swift */, 664622FC7ADCA72F0188296E5537FD1A /* RxScrollViewDelegateProxy.swift */, E795BF02AF222B9D68D5E733EC29F028 /* RxSearchBarDelegateProxy.swift */, BAEE11976EFF2FA8C6C9451A10C69626 /* RxSearchControllerDelegateProxy.swift */, C425D080D0597579029EC0D51F19DF00 /* RxTabBarControllerDelegateProxy.swift */, D57C0432CC83B41777EB7DF59040003A /* RxTabBarDelegateProxy.swift */, 064483EEE715AA1F53F6516AB100D7F0 /* RxTableViewDataSourcePrefetchingProxy.swift */, 6A9554911A17B58C5ECC56B07E92F260 /* RxTableViewDataSourceProxy.swift */, D844436D5AF4452273B8A6BA3FCDF06C /* RxTableViewDataSourceType.swift */, 418F4B3851F74B10CEF9AC009087B6C9 /* RxTableViewDelegateProxy.swift */, D25B52211A53D8D4EABDF123365709FD /* RxTableViewReactiveArrayDataSource.swift */, AAD3185464CAFF993AD6D374FC8C227C /* RxTarget.swift */, 8D6AFB2824027E4237941DA2B22ADBA4 /* RxTextStorageDelegateProxy.swift */, 85A5BBDABEB49C515059594CBEDC6E03 /* RxTextViewDelegateProxy.swift */, 31D389EEC4E01BCB0880E351F5705832 /* RxWKNavigationDelegateProxy.swift */, 118A6499476B7A7590891826623777F7 /* SchedulerType+SharedSequence.swift */, 6A189D909EA9E19138D228488FC2D42D /* SectionedViewDataSourceType.swift */, A5BA09836FD1C6B89C5F8F649426AE81 /* SharedSequence.swift */, 3A1602013F4297A124AB8D6C252987EE /* SharedSequence+Concurrency.swift */, 49F491FDB7F0E421975C083447433964 /* SharedSequence+Operators.swift */, FFCED2D97BAF3C4B699DD3A0C9287D92 /* SharedSequence+Operators+arity.swift */, D53333A480DFFD4501326625E9BDD166 /* Signal.swift */, 519DECE1EAC0267F012AD1445738C0D6 /* Signal+Subscription.swift */, 8E38136CEABB768390D4C30D6697F86E /* TextInput.swift */, 9C613FB8772A83708CBFE18F0662884D /* UIActivityIndicatorView+Rx.swift */, F1F54EDF8F559354284407AE8B231E4F /* UIApplication+Rx.swift */, 8C48F713F8C3FA312E772280EC6AAF9A /* UIBarButtonItem+Rx.swift */, 4D4561C808ADF530A19D9A9B103112FD /* UIButton+Rx.swift */, E0C3C2A6E813EF1CA4D4B015C3C04D07 /* UICollectionView+Rx.swift */, C397DCC6A23E6AFBC349ABFEE20D8210 /* UIControl+Rx.swift */, FDC3176741A679B0CC60C74A031F7AE8 /* UIDatePicker+Rx.swift */, 21298B0D540B9CB92F0C3DEBD33B016B /* UIGestureRecognizer+Rx.swift */, A9008D761B0B91DEE6FB43DCA293C6FE /* UINavigationController+Rx.swift */, 419D52CCFA192A27DB66AD5331BD53C1 /* UIPickerView+Rx.swift */, BB095AD6C8E6A1B417AF4068D21CE94D /* UIRefreshControl+Rx.swift */, 5ED1CC66C39B05567BA11DEC2FE46A3C /* UIScrollView+Rx.swift */, 199341CFC396D4182CDEA0352FBA0001 /* UISearchBar+Rx.swift */, 4F6A164298AD5E5E27AC9AE27BC6CA30 /* UISearchController+Rx.swift */, 5E9361181FD3DA8B4B15F01EF23DA8EB /* UISegmentedControl+Rx.swift */, 0EBDED56BB6BB2CD023E58D505E36020 /* UISlider+Rx.swift */, 531E900C9C6EDF756BB0587BD3E66D48 /* UIStepper+Rx.swift */, 3BB19EDA15250FCB1DA9BB0CAB0C3721 /* UISwitch+Rx.swift */, FD69E9E256BF488F303ADBBB67722394 /* UITabBar+Rx.swift */, AC05DA808CF63858796C3557E883FBE2 /* UITabBarController+Rx.swift */, 10FFF141041D1685F1C9404791CDA4C3 /* UITableView+Rx.swift */, 0725D3878B41FCB41B109CE751E1DBAE /* UITextField+Rx.swift */, 3B6C8E9CB55596433FE8E363E97628BD /* UITextView+Rx.swift */, A0160F85C822771F39C19B748C52FB70 /* URLSession+Rx.swift */, 848F1E559C3EB5AEE85C30CEE47E92F7 /* WKWebView+Rx.swift */, AD2044CD3328CC34408C6610B149D731 /* Support Files */, ); name = RxCocoa; path = RxCocoa; sourceTree = ""; }; 72AE6DBF7BF6FDD000607B8900842CF0 /* Frameworks */ = { isa = PBXGroup; children = ( 351129349E28D54DF7A90F3CFC798AE9 /* iOS */, ); name = Frameworks; sourceTree = ""; }; 95A220A7205294D013A843080C3AA9A1 = { isa = PBXGroup; children = ( 069D72AED8E2051A22B1EA4BBCA2C236 /* Dependencies */, 72AE6DBF7BF6FDD000607B8900842CF0 /* Frameworks */, D68E3A95B23F66ABCAA48719832C977E /* Products */, 58962E70A647EBABB00A8A8B1EA4530C /* RxCocoa */, ); sourceTree = ""; }; AD2044CD3328CC34408C6610B149D731 /* Support Files */ = { isa = PBXGroup; children = ( 5C79F9EACD23B1AF09974E693F7CD6EA /* RxCocoa.modulemap */, FAD8268E6FA199796F9CED85C6624B82 /* RxCocoa-dummy.m */, C00ED7A94C67870E506FB3DEB1E0BC81 /* RxCocoa-Info.plist */, D614C716A0210E767055886DB071EC83 /* RxCocoa-prefix.pch */, CC4B95EB13B3EBEB41456A72C0BB2467 /* RxCocoa-umbrella.h */, 546FC66ECA46E6D8B26DE8D7B529667C /* RxCocoa.debug.xcconfig */, AC85B53F47EEFA85FBD54111AD218FFE /* RxCocoa.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/RxCocoa"; sourceTree = ""; }; D68E3A95B23F66ABCAA48719832C977E /* Products */ = { isa = PBXGroup; children = ( F49FD34447716E9C149BEC0B4BF25379 /* RxCocoa */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 1796ACC221DBBB273A84DF2D3DF74BA6 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 6234FE7ABB908DE1D812CA427861A9C8 /* _RX.h in Headers */, EEA18CC5BFD9765A32B4857C1463DD2A /* _RXDelegateProxy.h in Headers */, CF833DDB131A18936EC27CFFF43D1102 /* _RXKVOObserver.h in Headers */, 780A538787419596D0E84C16733D8F5B /* _RXObjCRuntime.h in Headers */, AAEF72AB3F54C300BE8A0E38B3EDD165 /* RxCocoa.h in Headers */, 7890261401D11E41D489E7482DBF27D3 /* RxCocoa-umbrella.h in Headers */, 34B59D20C08209AA4BE18CA222723515 /* RxCocoaRuntime.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ BC5183FBB16A06C1D86620B00CFE6269 /* RxCocoa */ = { isa = PBXNativeTarget; buildConfigurationList = 49BB90FE41B8F40F74D59310A4D9CDDF /* Build configuration list for PBXNativeTarget "RxCocoa" */; buildPhases = ( 1796ACC221DBBB273A84DF2D3DF74BA6 /* Headers */, 920E8009C2EF8FD47499EA88D46EC369 /* Sources */, F98EE57E25923546DDDF354BC054BB3D /* Frameworks */, 688453866F7C40908D34EAE91AA8C99E /* Resources */, ); buildRules = ( ); dependencies = ( 64577F91726E1F8952B73156517BF76C /* PBXTargetDependency */, D3BB07EC7DCBED892310633CA5E6A7EC /* PBXTargetDependency */, ); name = RxCocoa; productName = RxCocoa; productReference = F49FD34447716E9C149BEC0B4BF25379 /* RxCocoa */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ BC13E29019356DA02577B1A44DC8F73B /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 81CC78A99DBC0B0E1EA5A5CE2AA3515E /* Build configuration list for PBXProject "RxCocoa" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 95A220A7205294D013A843080C3AA9A1; productRefGroup = D68E3A95B23F66ABCAA48719832C977E /* Products */; projectDirPath = ""; projectReferences = ( { ProjectRef = E190A0F56A6754915E4B6306E4B5826A /* RxSwift */; }, { ProjectRef = 5829B97DAB1D9A2C7BBEA71AC48E4228 /* RxRelay */; }, ); projectRoot = ""; targets = ( BC5183FBB16A06C1D86620B00CFE6269 /* RxCocoa */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 688453866F7C40908D34EAE91AA8C99E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 920E8009C2EF8FD47499EA88D46EC369 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A7A6FAD8807FE23F758FE42188BE2CC4 /* _RX.m in Sources */, 647650CB9D07A93E652BA2A36BC98ED3 /* _RXDelegateProxy.m in Sources */, 1A3A10A66FDBF09522B7C0F7276B3AC5 /* _RXKVOObserver.m in Sources */, 3252EA71C9B932A7A6620770F5056F50 /* _RXObjCRuntime.m in Sources */, C1AB3FB67E9B02A6D0E71212BF83E22E /* Bag.swift in Sources */, 6EE5697BB276A22553C7E15791D4E63E /* BehaviorRelay+Driver.swift in Sources */, FFAAD7E296C63EF8E8CF0A6BEA125298 /* ControlEvent.swift in Sources */, F2E24C1A4ADFC8EB439A52429A813EC6 /* ControlEvent+Driver.swift in Sources */, 4F89F7E3A2286DB442688011E556D93B /* ControlEvent+Signal.swift in Sources */, 0D7383E630C64114395423715ACCA2E5 /* ControlProperty.swift in Sources */, FA8F9FC104EDDCD9A0C9C5CD129A37EF /* ControlProperty+Driver.swift in Sources */, 56962A5B70E43CC0D3219B7FBCD1DC88 /* ControlTarget.swift in Sources */, FBEC656E28425F77C5BA0CC5B8DD25BE /* DelegateProxy.swift in Sources */, 65415D025EF91EAC5D577949B9BBF972 /* DelegateProxyType.swift in Sources */, 920D988553573899A3531BB5A924F059 /* DispatchQueue+Extensions.swift in Sources */, F64781CDBF7D029AB322BF643522ED09 /* Driver.swift in Sources */, 5DECCA3CB0C27DD1C88182731DA7FCD1 /* Driver+Subscription.swift in Sources */, 7B174FB45E0D3D1E40E0DF4690CB567A /* Infallible+Bind.swift in Sources */, BABD8E634362EE8DF08ED2E16E85E122 /* InfiniteSequence.swift in Sources */, B580A3541C6CA867B7139B900EF9F75B /* ItemEvents.swift in Sources */, 1C10D4BE70FE89EC0324B23716FA45E5 /* KVORepresentable.swift in Sources */, 28CC4ED3D89BB23506B287F8F5BCECD8 /* KVORepresentable+CoreGraphics.swift in Sources */, E14154C07AF7A39E264BC3B3EFD733CE /* KVORepresentable+Swift.swift in Sources */, 12E43AD74AA0903AB456553B5D1DD056 /* NotificationCenter+Rx.swift in Sources */, BE9FDA452BDAD32EC3B6235C21DA652C /* NSButton+Rx.swift in Sources */, 900AD968BCEE5F2F560F1DECBEA60E77 /* NSControl+Rx.swift in Sources */, 342DEFDAF94AF1FC29BF33F4651A18E2 /* NSObject+Rx.swift in Sources */, D55339C30DC44456301E17FADC3BFEE4 /* NSObject+Rx+KVORepresentable.swift in Sources */, 6264AC44995FE0034C5DFA936FF52CE6 /* NSObject+Rx+RawRepresentable.swift in Sources */, 3F2A44ED7330BA5E28225DA7D48529FD /* NSSlider+Rx.swift in Sources */, 8B462AD8B0FF3AD90F432B4F3236E34C /* NSTextField+Rx.swift in Sources */, 5A154D2ECAEFB42367BE0C0DDC87CF26 /* NSTextStorage+Rx.swift in Sources */, 4A92C2186902E5B4A9BA5B268CEDC80A /* NSTextView+Rx.swift in Sources */, 33AE29CCD4F54BC43937521134A12BAC /* NSView+Rx.swift in Sources */, 226FB079685A350A7522F6FDB772FC86 /* Observable+Bind.swift in Sources */, 098A7E7AEC6AD385CF5A0FB8D79C27AF /* ObservableConvertibleType+Driver.swift in Sources */, D4C17E628FA568CB8C0AFF6C071E1A46 /* ObservableConvertibleType+SharedSequence.swift in Sources */, A85FB63BBE89ED14C5C79525503E19D2 /* ObservableConvertibleType+Signal.swift in Sources */, 5778FB7C831F04053D46B6FEC03CE4D2 /* Platform.Darwin.swift in Sources */, AE968A1E6D48CBA335CA1A6E979F112A /* Platform.Linux.swift in Sources */, B17237829E251D14EB3961E3D4111D26 /* PriorityQueue.swift in Sources */, 11829AC72691F541D37C1A23E1EE8D1A /* PublishRelay+Signal.swift in Sources */, 6E0C2A2B9AD3209FB63014447F03605C /* Queue.swift in Sources */, E07AB03B47E4D40F97533A356F1675BE /* RecursiveLock.swift in Sources */, 2CD803DA7E83C82D290FE98FEEE687B8 /* RxCocoa.swift in Sources */, 607B3B2367464B8EA3A3C129F9BC9468 /* RxCocoa-dummy.m in Sources */, 5239D3EE3DDD3BC875FE100C4A83A1A5 /* RxCocoaObjCRuntimeError+Extensions.swift in Sources */, 1FE825E92010BE5DDCD0D97CED70A236 /* RxCollectionViewDataSourcePrefetchingProxy.swift in Sources */, 434B3976E3B3FC68F8A405A2ACCAEB3F /* RxCollectionViewDataSourceProxy.swift in Sources */, DB9E212AEFC7DACF8049A37761258191 /* RxCollectionViewDataSourceType.swift in Sources */, D78C66D3E918EDAA8FB5F78ECA2855EE /* RxCollectionViewDelegateProxy.swift in Sources */, 798D7D484A939598FDF66594BFA7D093 /* RxCollectionViewReactiveArrayDataSource.swift in Sources */, 23AB7E0B2F1F9F0B4604A98829734FDB /* RxNavigationControllerDelegateProxy.swift in Sources */, E7EEF5D1E774B075FA2F6ECD002AB132 /* RxPickerViewAdapter.swift in Sources */, D573349235F40BF450B7EFFED85A339E /* RxPickerViewDataSourceProxy.swift in Sources */, 60A953C8972283706C082AC42947B043 /* RxPickerViewDataSourceType.swift in Sources */, BF97E194A860C80412D21C0F058B9B8B /* RxPickerViewDelegateProxy.swift in Sources */, 41CFFA76A5135FF4625D2E83CDA490D0 /* RxScrollViewDelegateProxy.swift in Sources */, F6850C0AC55C3AB12EF44EFF135E604F /* RxSearchBarDelegateProxy.swift in Sources */, E4AF4B810C37B0A44DB48E8E472FA9B9 /* RxSearchControllerDelegateProxy.swift in Sources */, 0E3AF17E3F196488AA3B62DA4B066364 /* RxTabBarControllerDelegateProxy.swift in Sources */, B51FB88512FC3402BECBB139D70DA558 /* RxTabBarDelegateProxy.swift in Sources */, 60D9FA5934ECE047A78B009B4888CF2E /* RxTableViewDataSourcePrefetchingProxy.swift in Sources */, ED71233AE263ADAB4695DEEE5E5FD0B2 /* RxTableViewDataSourceProxy.swift in Sources */, 381853F4D9C66B4FC877241753B77369 /* RxTableViewDataSourceType.swift in Sources */, 41C1AD8E7712BB584CB989ABFBEF6BF2 /* RxTableViewDelegateProxy.swift in Sources */, F59650BAB5B15DCA84C5A18D012D2A96 /* RxTableViewReactiveArrayDataSource.swift in Sources */, A6C4D383F553AF4A6C535AA1E948E16C /* RxTarget.swift in Sources */, 3936A1BCFFCDB03BE54CAAE0D33A6659 /* RxTextStorageDelegateProxy.swift in Sources */, 605192772D1262EAB3805CB53A090FF4 /* RxTextViewDelegateProxy.swift in Sources */, 5274CF9FA22FC8DA1FC5AECFCE0847A5 /* RxWKNavigationDelegateProxy.swift in Sources */, DF0F55E4FD7E166EE7CF403791853F75 /* SchedulerType+SharedSequence.swift in Sources */, 58383C193D1A398E965E3EF654586DB0 /* SectionedViewDataSourceType.swift in Sources */, 8E51BF06D1533DD52DDC610CD7522C44 /* SharedSequence.swift in Sources */, 8920F6CDD4B32D5FD864B1BD114E99C5 /* SharedSequence+Concurrency.swift in Sources */, 58A0BA435F851165449E98CA422FA05E /* SharedSequence+Operators.swift in Sources */, 9CBE76516E6A66842938813791692F8D /* SharedSequence+Operators+arity.swift in Sources */, 5CE91A95754610E4A2BFE209EB19655A /* Signal.swift in Sources */, 27A7914471BBC1D85B251B95772859EC /* Signal+Subscription.swift in Sources */, 237497A83B83D8551FD4DCCA2DBA2BCE /* TextInput.swift in Sources */, 104C4A2E0EC23EDBB767A554F6D4BDCB /* UIActivityIndicatorView+Rx.swift in Sources */, 8A8CAA4924CBEB16B47B0C21BB10F623 /* UIApplication+Rx.swift in Sources */, C1A7217E02B3A958E081EAB51F824577 /* UIBarButtonItem+Rx.swift in Sources */, 666CF8DCC50F87DC5EA2A002F3090744 /* UIButton+Rx.swift in Sources */, 0A9FE8B6140805209AF83AE31A3D3B6E /* UICollectionView+Rx.swift in Sources */, D3ECD252F47B0B568380545AB3315BFD /* UIControl+Rx.swift in Sources */, 51B2FF394C94C6D6688CC055A546811D /* UIDatePicker+Rx.swift in Sources */, CA7AD2946482A548E39CA56E04FDC743 /* UIGestureRecognizer+Rx.swift in Sources */, BE891F57CB396F57AE5F8E5EC8B5DCEA /* UINavigationController+Rx.swift in Sources */, 84C5FB7908E9B5BC31D1CDD7B0055805 /* UIPickerView+Rx.swift in Sources */, 810634A222994C7706F54EB4E230AA77 /* UIRefreshControl+Rx.swift in Sources */, 7B63B576043243A784D88F7B3F4980AA /* UIScrollView+Rx.swift in Sources */, 881222EC54152E2C917B423BA4F05B4A /* UISearchBar+Rx.swift in Sources */, D53DFBA3A74839F6F6CA09D1E2A38AEF /* UISearchController+Rx.swift in Sources */, F765EC6C8B3CF42BE713A1AE101BBFF0 /* UISegmentedControl+Rx.swift in Sources */, E2E71C3F1C12D2802B7FEC8E2CBFD3F4 /* UISlider+Rx.swift in Sources */, 1EEA54B15BC5E6CD7F18AA8EF8924539 /* UIStepper+Rx.swift in Sources */, 2B3CE62E5D8CCC3E9214740D10E20AAB /* UISwitch+Rx.swift in Sources */, DE46057EDAAA20C49528797F2DDAD2D5 /* UITabBar+Rx.swift in Sources */, B8F4EC2BFB70D9447D321CEF6626CE30 /* UITabBarController+Rx.swift in Sources */, B2C23D0F3CE1CFA87F382088A313D8B7 /* UITableView+Rx.swift in Sources */, ACFDAD9114D6CE2C55EBE8426596EE03 /* UITextField+Rx.swift in Sources */, C00A1498C51F71DB962DD96A8DAEADDF /* UITextView+Rx.swift in Sources */, CFE47AF2009EB38458461514FB0EA80D /* URLSession+Rx.swift in Sources */, 1FAD000D7AED20DE6556288476BC1955 /* WKWebView+Rx.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 64577F91726E1F8952B73156517BF76C /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RxRelay; targetProxy = 55C2E4D31EBDBBE38DCC9FD538A9C78A /* PBXContainerItemProxy */; }; D3BB07EC7DCBED892310633CA5E6A7EC /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RxSwift; targetProxy = 1A3FDE601F76A95F98A9A45A56E6C6BB /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 7A03F8A886B2A34B3B1F4B2D5468D564 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 844180D45F2EF00EA04FD68917672A3D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = AC85B53F47EEFA85FBD54111AD218FFE /* RxCocoa.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/RxCocoa/RxCocoa-prefix.pch"; INFOPLIST_FILE = "Target Support Files/RxCocoa/RxCocoa-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/RxCocoa/RxCocoa.modulemap"; PRODUCT_MODULE_NAME = RxCocoa; PRODUCT_NAME = RxCocoa; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.1; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; AE18049A515F339995CB5C61E7A58AD2 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 546FC66ECA46E6D8B26DE8D7B529667C /* RxCocoa.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/RxCocoa/RxCocoa-prefix.pch"; INFOPLIST_FILE = "Target Support Files/RxCocoa/RxCocoa-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/RxCocoa/RxCocoa.modulemap"; PRODUCT_MODULE_NAME = RxCocoa; PRODUCT_NAME = RxCocoa; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.1; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; E6A1D73D45653B3B146897E8D9978E54 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 49BB90FE41B8F40F74D59310A4D9CDDF /* Build configuration list for PBXNativeTarget "RxCocoa" */ = { isa = XCConfigurationList; buildConfigurations = ( AE18049A515F339995CB5C61E7A58AD2 /* Debug */, 844180D45F2EF00EA04FD68917672A3D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 81CC78A99DBC0B0E1EA5A5CE2AA3515E /* Build configuration list for PBXProject "RxCocoa" */ = { isa = XCConfigurationList; buildConfigurations = ( 7A03F8A886B2A34B3B1F4B2D5468D564 /* Debug */, E6A1D73D45653B3B146897E8D9978E54 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = BC13E29019356DA02577B1A44DC8F73B /* Project object */; } ================================================ FILE: JetChat/Pods/RxRelay/LICENSE.md ================================================ **The MIT License** **Copyright © 2015 Krunoslav Zaher, Shai Mishali** **All rights reserved.** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/RxRelay/README.md ================================================

RxSwift Logo
Build Status Supported Platforms: iOS, macOS, tvOS, watchOS & Linux

Rx is a [generic abstraction of computation](https://youtu.be/looJcaeboBY) expressed through `Observable` interface, which lets you broadcast and subscribe to values and other events from an `Observable` stream. RxSwift is the Swift-specific implementation of the [Reactive Extensions](http://reactivex.io) standard.

RxSwift Observable Example of a price constantly changing and updating the app's UI

While this version aims to stay true to the original spirit and naming conventions of Rx, this projects also aims to provide a true Swift-first API for Rx APIs. Cross platform documentation can be found on [ReactiveX.io](http://reactivex.io/). Like other Rx implementation, RxSwift's intention is to enable easy composition of asynchronous operations and streams of data in the form of `Observable` objects and a suite of methods to transform and compose these pieces of asynchronous work. KVO observation, async operations, UI Events and other streams of data are all unified under [abstraction of sequence](Documentation/GettingStarted.md#observables-aka-sequences). This is the reason why Rx is so simple, elegant and powerful. ## I came here because I want to ... ###### ... understand * [why use rx?](Documentation/Why.md) * [the basics, getting started with RxSwift](Documentation/GettingStarted.md) * [traits](Documentation/Traits.md) - what are `Single`, `Completable`, `Maybe`, `Driver`, and `ControlProperty` ... and why do they exist? * [testing](Documentation/UnitTests.md) * [tips and common errors](Documentation/Tips.md) * [debugging](Documentation/GettingStarted.md#debugging) * [the math behind Rx](Documentation/MathBehindRx.md) * [what are hot and cold observable sequences?](Documentation/HotAndColdObservables.md) ###### ... install * Integrate RxSwift/RxCocoa with my app. [Installation Guide](#installation) ###### ... hack around * with the example app. [Running Example App](Documentation/ExampleApp.md) * with operators in playgrounds. [Playgrounds](Documentation/Playgrounds.md) ###### ... interact * All of this is great, but it would be nice to talk with other people using RxSwift and exchange experiences.
[Join Slack Channel](http://slack.rxswift.org) * Report a problem using the library. [Open an Issue With Bug Template](.github/ISSUE_TEMPLATE.md) * Request a new feature. [Open an Issue With Feature Request Template](Documentation/NewFeatureRequestTemplate.md) * Help out [Check out contribution guide](CONTRIBUTING.md) ###### ... compare * [with Combine and ReactiveSwift](Documentation/ComparisonWithOtherLibraries.md). ###### ... understand the structure RxSwift is as compositional as the asynchronous work it drives. The core unit is RxSwift itself, while other dependencies can be added for UI Work, testing, and more. It comprises five separate components depending on each other in the following way: ```none ┌──────────────┐ ┌──────────────┐ │ RxCocoa ├────▶ RxRelay │ └───────┬──────┘ └──────┬───────┘ │ │ ┌───────▼──────────────────▼───────┐ │ RxSwift │ └───────▲──────────────────▲───────┘ │ │ ┌───────┴──────┐ ┌──────┴───────┐ │ RxTest │ │ RxBlocking │ └──────────────┘ └──────────────┘ ``` * **RxSwift**: The core of RxSwift, providing the Rx standard as (mostly) defined by [ReactiveX](https://reactivex.io). It has no other dependencies. * **RxCocoa**: Provides Cocoa-specific capabilities for general iOS/macOS/watchOS & tvOS app development, such as Shared Sequences, Traits, and much more. It depends on both `RxSwift` and `RxRelay`. * **RxRelay**: Provides `PublishRelay`, `BehaviorRelay` and `ReplayRelay`, three [simple wrappers around Subjects](https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Subjects.md#relays). It depends on `RxSwift`. * **RxTest** and **RxBlocking**: Provides testing capabilities for Rx-based systems. It depends on `RxSwift`. ## Usage
Here's an example In Action
Define search for GitHub repositories ...
let searchResults = searchBar.rx.text.orEmpty
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest { query -> Observable<[Repository]> in
        if query.isEmpty {
            return .just([])
        }
        return searchGitHub(query)
            .catchAndReturn([])
    }
    .observe(on: MainScheduler.instance)
... then bind the results to your tableview
searchResults
    .bind(to: tableView.rx.items(cellIdentifier: "Cell")) {
        (index, repository: Repository, cell) in
        cell.textLabel?.text = repository.name
        cell.detailTextLabel?.text = repository.url
    }
    .disposed(by: disposeBag)
## Requirements * Xcode 12.x * Swift 5.x For Xcode 11 and below, [use RxSwift 5.x](https://github.com/ReactiveX/RxSwift/releases/tag/5.1.1). ## Installation RxSwift doesn't contain any external dependencies. These are currently the supported installation options: ### Manual Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run the sample app ### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html) ```ruby # Podfile use_frameworks! target 'YOUR_TARGET_NAME' do pod 'RxSwift', '6.5.0' pod 'RxCocoa', '6.5.0' end # RxTest and RxBlocking make the most sense in the context of unit/integration tests target 'YOUR_TESTING_TARGET' do pod 'RxBlocking', '6.5.0' pod 'RxTest', '6.5.0' end ``` Replace `YOUR_TARGET_NAME` and then, in the `Podfile` directory, type: ```bash $ pod install ``` ### XCFrameworks Each release starting with RxSwift 6 includes `*.xcframework` framework binaries. Simply drag the needed framework binaries to your **Frameworks, Libraries, and Embedded Content** section under your target's **General** tab. > **Note**: If you're using `RxCocoa`, be sure to also drag **RxCocoaRuntime.xcframework** before importing `RxCocoa`. XCFrameworks instructions ### [Carthage](https://github.com/Carthage/Carthage) Add this to `Cartfile` ``` github "ReactiveX/RxSwift" "6.5.0" ``` ```bash $ carthage update ``` #### Carthage as a Static Library Carthage defaults to building RxSwift as a Dynamic Library. If you wish to build RxSwift as a Static Library using Carthage you may use the script below to manually modify the framework type before building with Carthage: ```bash carthage update RxSwift --platform iOS --no-build sed -i -e 's/MACH_O_TYPE = mh_dylib/MACH_O_TYPE = staticlib/g' Carthage/Checkouts/RxSwift/Rx.xcodeproj/project.pbxproj carthage build RxSwift --platform iOS ``` ### [Swift Package Manager](https://github.com/apple/swift-package-manager) > **Note**: There is a critical cross-dependency bug affecting many projects including RxSwift in Swift Package Manager. We've [filed a bug (SR-12303)](https://bugs.swift.org/browse/SR-12303) in early 2020 but have no answer yet. Your mileage may vary. A partial workaround can be found [here](https://github.com/ReactiveX/RxSwift/issues/2127#issuecomment-717830502). Create a `Package.swift` file. ```swift // swift-tools-version:5.0 import PackageDescription let package = Package( name: "RxTestProject", dependencies: [ .package(url: "https://github.com/ReactiveX/RxSwift.git", .exact("6.5.0")) ], targets: [ .target(name: "RxTestProject", dependencies: ["RxSwift", "RxCocoa"]) ] ) ``` ```bash $ swift build ``` To build or test a module with RxTest dependency, set `TEST=1`. ```bash $ TEST=1 swift test ``` ### Manually using git submodules * Add RxSwift as a submodule ```bash $ git submodule add git@github.com:ReactiveX/RxSwift.git ``` * Drag `Rx.xcodeproj` into Project Navigator * Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxSwift`, `RxCocoa` and `RxRelay` targets ## References * [http://reactivex.io/](http://reactivex.io/) * [Reactive Extensions GitHub (GitHub)](https://github.com/Reactive-Extensions) * [RxSwift RayWenderlich.com Book](https://store.raywenderlich.com/products/rxswift-reactive-programming-with-swift) * [RxSwift: Debunking the myth of hard (YouTube)](https://www.youtube.com/watch?v=GdvLP0ZAhhc) * [Boxue.io RxSwift Online Course](https://boxueio.com/series/rxswift-101) (Chinese 🇨🇳) * [Expert to Expert: Brian Beckman and Erik Meijer - Inside the .NET Reactive Framework (Rx) (video)](https://youtu.be/looJcaeboBY) * [Reactive Programming Overview (Jafar Husain from Netflix)](https://youtu.be/-8Y1-lE6NSA) * [Subject/Observer is Dual to Iterator (paper)](http://csl.stanford.edu/~christos/pldi2010.fit/meijer.duality.pdf) * [Rx standard sequence operators visualized (visualization tool)](http://rxmarbles.com/) * [Haskell](https://www.haskell.org/) ================================================ FILE: JetChat/Pods/RxRelay/RxRelay/BehaviorRelay.swift ================================================ // // BehaviorRelay.swift // RxRelay // // Created by Krunoslav Zaher on 10/7/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // import RxSwift /// BehaviorRelay is a wrapper for `BehaviorSubject`. /// /// Unlike `BehaviorSubject` it can't terminate with error or completed. public final class BehaviorRelay: ObservableType { private let subject: BehaviorSubject /// Accepts `event` and emits it to subscribers public func accept(_ event: Element) { self.subject.onNext(event) } /// Current value of behavior subject public var value: Element { // this try! is ok because subject can't error out or be disposed return try! self.subject.value() } /// Initializes behavior relay with initial value. public init(value: Element) { self.subject = BehaviorSubject(value: value) } /// Subscribes observer public func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { self.subject.subscribe(observer) } /// - returns: Canonical interface for push style sequence public func asObservable() -> Observable { self.subject.asObservable() } } ================================================ FILE: JetChat/Pods/RxRelay/RxRelay/Observable+Bind.swift ================================================ // // Observable+Bind.swift // RxRelay // // Created by Shai Mishali on 09/04/2019. // Copyright © 2019 Krunoslav Zaher. All rights reserved. // import RxSwift extension ObservableType { /** Creates new subscription and sends elements to publish relay(s). In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter relays: Target publish relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer. */ public func bind(to relays: PublishRelay...) -> Disposable { bind(to: relays) } /** Creates new subscription and sends elements to publish relay(s). In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter relays: Target publish relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer. */ public func bind(to relays: PublishRelay...) -> Disposable { self.map { $0 as Element? }.bind(to: relays) } /** Creates new subscription and sends elements to publish relay(s). In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter relays: Target publish relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer. */ private func bind(to relays: [PublishRelay]) -> Disposable { subscribe { e in switch e { case let .next(element): relays.forEach { $0.accept(element) } case let .error(error): rxFatalErrorInDebug("Binding error to publish relay: \(error)") case .completed: break } } } /** Creates new subscription and sends elements to behavior relay(s). In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter relays: Target behavior relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer. */ public func bind(to relays: BehaviorRelay...) -> Disposable { self.bind(to: relays) } /** Creates new subscription and sends elements to behavior relay(s). In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter relays: Target behavior relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer. */ public func bind(to relays: BehaviorRelay...) -> Disposable { self.map { $0 as Element? }.bind(to: relays) } /** Creates new subscription and sends elements to behavior relay(s). In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter relays: Target behavior relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer. */ private func bind(to relays: [BehaviorRelay]) -> Disposable { subscribe { e in switch e { case let .next(element): relays.forEach { $0.accept(element) } case let .error(error): rxFatalErrorInDebug("Binding error to behavior relay: \(error)") case .completed: break } } } /** Creates new subscription and sends elements to replay relay(s). In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter relays: Target replay relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer. */ public func bind(to relays: ReplayRelay...) -> Disposable { self.bind(to: relays) } /** Creates new subscription and sends elements to replay relay(s). In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter relays: Target replay relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer. */ public func bind(to relays: ReplayRelay...) -> Disposable { self.map { $0 as Element? }.bind(to: relays) } /** Creates new subscription and sends elements to replay relay(s). In case error occurs in debug mode, `fatalError` will be raised. In case error occurs in release mode, `error` will be logged. - parameter relays: Target replay relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer. */ private func bind(to relays: [ReplayRelay]) -> Disposable { subscribe { e in switch e { case let .next(element): relays.forEach { $0.accept(element) } case let .error(error): rxFatalErrorInDebug("Binding error to behavior relay: \(error)") case .completed: break } } } } ================================================ FILE: JetChat/Pods/RxRelay/RxRelay/PublishRelay.swift ================================================ // // PublishRelay.swift // RxRelay // // Created by Krunoslav Zaher on 3/28/15. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // import RxSwift /// PublishRelay is a wrapper for `PublishSubject`. /// /// Unlike `PublishSubject` it can't terminate with error or completed. public final class PublishRelay: ObservableType { private let subject: PublishSubject // Accepts `event` and emits it to subscribers public func accept(_ event: Element) { self.subject.onNext(event) } /// Initializes with internal empty subject. public init() { self.subject = PublishSubject() } /// Subscribes observer public func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { self.subject.subscribe(observer) } /// - returns: Canonical interface for push style sequence public func asObservable() -> Observable { self.subject.asObservable() } } ================================================ FILE: JetChat/Pods/RxRelay/RxRelay/ReplayRelay.swift ================================================ // // ReplayRelay.swift // RxRelay // // Created by Zsolt Kovacs on 12/22/19. // Copyright © 2019 Krunoslav Zaher. All rights reserved. // import RxSwift /// ReplayRelay is a wrapper for `ReplaySubject`. /// /// Unlike `ReplaySubject` it can't terminate with an error or complete. public final class ReplayRelay: ObservableType { private let subject: ReplaySubject // Accepts `event` and emits it to subscribers public func accept(_ event: Element) { self.subject.onNext(event) } private init(subject: ReplaySubject) { self.subject = subject } /// Creates new instance of `ReplayRelay` that replays at most `bufferSize` last elements sent to it. /// /// - parameter bufferSize: Maximal number of elements to replay to observers after subscription. /// - returns: New instance of replay relay. public static func create(bufferSize: Int) -> ReplayRelay { ReplayRelay(subject: ReplaySubject.create(bufferSize: bufferSize)) } /// Creates a new instance of `ReplayRelay` that buffers all the sent to it. /// To avoid filling up memory, developer needs to make sure that the use case will only ever store a 'reasonable' /// number of elements. public static func createUnbound() -> ReplayRelay { ReplayRelay(subject: ReplaySubject.createUnbounded()) } /// Subscribes observer public func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { self.subject.subscribe(observer) } /// - returns: Canonical interface for push style sequence public func asObservable() -> Observable { self.subject.asObserver() } } ================================================ FILE: JetChat/Pods/RxRelay/RxRelay/Utils.swift ================================================ // // Utils.swift // RxRelay // // Created by Shai Mishali on 09/04/2019. // Copyright © 2019 Krunoslav Zaher. All rights reserved. // import Foundation func rxFatalErrorInDebug(_ lastMessage: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) { #if DEBUG fatalError(lastMessage(), file: file, line: line) #else print("\(file):\(line): \(lastMessage())") #endif } ================================================ FILE: JetChat/Pods/RxRelay.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 056A93B10A15D20355DEB8F3697616BF /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E84CD13BBFD583AA9136D1B7E46C5B4 /* Utils.swift */; }; 1B240D27253BEEF148EE5164A6F40682 /* RxRelay-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 66C06BF32A9C9BF9542C07BACDD98E12 /* RxRelay-dummy.m */; }; 3DC67EC304BFA615A55DA6EEF602F814 /* BehaviorRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662251FB3592F2E7FC84D05CB4B5A1F4 /* BehaviorRelay.swift */; }; 43F4BF92F27667275BBD17C15A6135D8 /* ReplayRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1EB673CFD1E3F0678C69C26AE74188 /* ReplayRelay.swift */; }; 5CE6FCBF1770EE758D88202BEDBED54E /* RxRelay-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = E85F5C980FF8AB5BDDB8A079AB8D6DCF /* RxRelay-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; A5869B0D2382E0FB54B8C52DEF9FC9F8 /* PublishRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61680D5052F6BB28F54E1CF70B2E07E9 /* PublishRelay.swift */; }; A63C03DE2E632FC4A2AA2C43791D681C /* Observable+Bind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87446FFDA976E3069C74F26929DA29B8 /* Observable+Bind.swift */; }; B8113D4F5119EE9FB2BAE829F70C638E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11FDCA3EBE361C9E7904D776C9E5C900 /* Foundation.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 430BBCAD0F0EA4745FE9E3BA856575A7 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 869F01310E427726268AB4C7B3FF922D /* RxSwift.xcodeproj */; proxyType = 1; remoteGlobalIDString = F0179EE061353B7A322F596E97844774; remoteInfo = RxSwift; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 11FDCA3EBE361C9E7904D776C9E5C900 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 1F1EB673CFD1E3F0678C69C26AE74188 /* ReplayRelay.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ReplayRelay.swift; path = RxRelay/ReplayRelay.swift; sourceTree = ""; }; 3E84CD13BBFD583AA9136D1B7E46C5B4 /* Utils.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = RxRelay/Utils.swift; sourceTree = ""; }; 4D37CF8715A815DC60F75EB7F730B314 /* RxRelay.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = RxRelay.release.xcconfig; sourceTree = ""; }; 61680D5052F6BB28F54E1CF70B2E07E9 /* PublishRelay.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PublishRelay.swift; path = RxRelay/PublishRelay.swift; sourceTree = ""; }; 662251FB3592F2E7FC84D05CB4B5A1F4 /* BehaviorRelay.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BehaviorRelay.swift; path = RxRelay/BehaviorRelay.swift; sourceTree = ""; }; 66C06BF32A9C9BF9542C07BACDD98E12 /* RxRelay-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "RxRelay-dummy.m"; sourceTree = ""; }; 6C045753EE2C4C1CA434A974162F6FC6 /* RxRelay-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "RxRelay-Info.plist"; sourceTree = ""; }; 869F01310E427726268AB4C7B3FF922D /* RxSwift */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RxSwift; path = RxSwift.xcodeproj; sourceTree = ""; }; 87446FFDA976E3069C74F26929DA29B8 /* Observable+Bind.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Observable+Bind.swift"; path = "RxRelay/Observable+Bind.swift"; sourceTree = ""; }; 89ABB905FAE747B9F5105F54157BFECA /* RxRelay-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "RxRelay-prefix.pch"; sourceTree = ""; }; B546861C56544DEBBDF2AD71F1BDF8F2 /* RxRelay */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = RxRelay; path = RxRelay.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C449609859F9F00F9994D3CFFA87347E /* RxRelay.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = RxRelay.debug.xcconfig; sourceTree = ""; }; E85F5C980FF8AB5BDDB8A079AB8D6DCF /* RxRelay-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "RxRelay-umbrella.h"; sourceTree = ""; }; FCA95D24B192AD464328515165460764 /* RxRelay.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = RxRelay.modulemap; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 9EA96926B60F6965AF872CC7D7D3A232 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B8113D4F5119EE9FB2BAE829F70C638E /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 15A7FF4C1504433D353269213557AA3B /* Products */ = { isa = PBXGroup; children = ( B546861C56544DEBBDF2AD71F1BDF8F2 /* RxRelay */, ); name = Products; sourceTree = ""; }; 3C95B9B7E356A90CDFC189D5E6FD6A2E = { isa = PBXGroup; children = ( AA8CAAE9561BB37539B14170AB4A7D24 /* Dependencies */, F60856D28D548F8DE584AE0A7D942C4E /* Frameworks */, 15A7FF4C1504433D353269213557AA3B /* Products */, E5755840E79CEA46DAE6981304421B69 /* RxRelay */, ); sourceTree = ""; }; 48085DE583C0316E3DFD71FF7D17FE32 /* iOS */ = { isa = PBXGroup; children = ( 11FDCA3EBE361C9E7904D776C9E5C900 /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; AA8CAAE9561BB37539B14170AB4A7D24 /* Dependencies */ = { isa = PBXGroup; children = ( 869F01310E427726268AB4C7B3FF922D /* RxSwift */, ); name = Dependencies; sourceTree = ""; }; DE38E1D20A8AED0345DEBBA0FCA2584E /* Support Files */ = { isa = PBXGroup; children = ( FCA95D24B192AD464328515165460764 /* RxRelay.modulemap */, 66C06BF32A9C9BF9542C07BACDD98E12 /* RxRelay-dummy.m */, 6C045753EE2C4C1CA434A974162F6FC6 /* RxRelay-Info.plist */, 89ABB905FAE747B9F5105F54157BFECA /* RxRelay-prefix.pch */, E85F5C980FF8AB5BDDB8A079AB8D6DCF /* RxRelay-umbrella.h */, C449609859F9F00F9994D3CFFA87347E /* RxRelay.debug.xcconfig */, 4D37CF8715A815DC60F75EB7F730B314 /* RxRelay.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/RxRelay"; sourceTree = ""; }; E5755840E79CEA46DAE6981304421B69 /* RxRelay */ = { isa = PBXGroup; children = ( 662251FB3592F2E7FC84D05CB4B5A1F4 /* BehaviorRelay.swift */, 87446FFDA976E3069C74F26929DA29B8 /* Observable+Bind.swift */, 61680D5052F6BB28F54E1CF70B2E07E9 /* PublishRelay.swift */, 1F1EB673CFD1E3F0678C69C26AE74188 /* ReplayRelay.swift */, 3E84CD13BBFD583AA9136D1B7E46C5B4 /* Utils.swift */, DE38E1D20A8AED0345DEBBA0FCA2584E /* Support Files */, ); name = RxRelay; path = RxRelay; sourceTree = ""; }; F60856D28D548F8DE584AE0A7D942C4E /* Frameworks */ = { isa = PBXGroup; children = ( 48085DE583C0316E3DFD71FF7D17FE32 /* iOS */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 92F15476B199E5203689CE2C82910E73 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 5CE6FCBF1770EE758D88202BEDBED54E /* RxRelay-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 564FA919E05BFD512DA9163BAB640EEE /* RxRelay */ = { isa = PBXNativeTarget; buildConfigurationList = 140C2DB6CCF6ACA018A47C30C9C0C236 /* Build configuration list for PBXNativeTarget "RxRelay" */; buildPhases = ( 92F15476B199E5203689CE2C82910E73 /* Headers */, BF771E8C8BD97B7D99FA77D7BBD436A4 /* Sources */, 9EA96926B60F6965AF872CC7D7D3A232 /* Frameworks */, B9A8E7262A4C7EF4FBADC699646D22FB /* Resources */, ); buildRules = ( ); dependencies = ( C572E404D447CDD5847C8F97E6C2B3B1 /* PBXTargetDependency */, ); name = RxRelay; productName = RxRelay; productReference = B546861C56544DEBBDF2AD71F1BDF8F2 /* RxRelay */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ ACF0932BA2A502AF6DF97FEB57C23FB9 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 9234695EF8CA2CCC67C337DE7A7F64E2 /* Build configuration list for PBXProject "RxRelay" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 3C95B9B7E356A90CDFC189D5E6FD6A2E; productRefGroup = 15A7FF4C1504433D353269213557AA3B /* Products */; projectDirPath = ""; projectReferences = ( { ProjectRef = 869F01310E427726268AB4C7B3FF922D /* RxSwift */; }, ); projectRoot = ""; targets = ( 564FA919E05BFD512DA9163BAB640EEE /* RxRelay */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ B9A8E7262A4C7EF4FBADC699646D22FB /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ BF771E8C8BD97B7D99FA77D7BBD436A4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3DC67EC304BFA615A55DA6EEF602F814 /* BehaviorRelay.swift in Sources */, A63C03DE2E632FC4A2AA2C43791D681C /* Observable+Bind.swift in Sources */, A5869B0D2382E0FB54B8C52DEF9FC9F8 /* PublishRelay.swift in Sources */, 43F4BF92F27667275BBD17C15A6135D8 /* ReplayRelay.swift in Sources */, 1B240D27253BEEF148EE5164A6F40682 /* RxRelay-dummy.m in Sources */, 056A93B10A15D20355DEB8F3697616BF /* Utils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ C572E404D447CDD5847C8F97E6C2B3B1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RxSwift; targetProxy = 430BBCAD0F0EA4745FE9E3BA856575A7 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 4C9DF352DF8D6A9E4127CDE07D109ADC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; CC39B756EA8ACFAC4BDCB81FF0163A52 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4D37CF8715A815DC60F75EB7F730B314 /* RxRelay.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/RxRelay/RxRelay-prefix.pch"; INFOPLIST_FILE = "Target Support Files/RxRelay/RxRelay-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/RxRelay/RxRelay.modulemap"; PRODUCT_MODULE_NAME = RxRelay; PRODUCT_NAME = RxRelay; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.1; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; D009D5FC3AF2B20FADF3FFFEABFC568C /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = C449609859F9F00F9994D3CFFA87347E /* RxRelay.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/RxRelay/RxRelay-prefix.pch"; INFOPLIST_FILE = "Target Support Files/RxRelay/RxRelay-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/RxRelay/RxRelay.modulemap"; PRODUCT_MODULE_NAME = RxRelay; PRODUCT_NAME = RxRelay; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.1; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; DDC4F0402179834578B9E993293F1B70 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 140C2DB6CCF6ACA018A47C30C9C0C236 /* Build configuration list for PBXNativeTarget "RxRelay" */ = { isa = XCConfigurationList; buildConfigurations = ( D009D5FC3AF2B20FADF3FFFEABFC568C /* Debug */, CC39B756EA8ACFAC4BDCB81FF0163A52 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 9234695EF8CA2CCC67C337DE7A7F64E2 /* Build configuration list for PBXProject "RxRelay" */ = { isa = XCConfigurationList; buildConfigurations = ( 4C9DF352DF8D6A9E4127CDE07D109ADC /* Debug */, DDC4F0402179834578B9E993293F1B70 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = ACF0932BA2A502AF6DF97FEB57C23FB9 /* Project object */; } ================================================ FILE: JetChat/Pods/RxSwift/LICENSE.md ================================================ **The MIT License** **Copyright © 2015 Krunoslav Zaher, Shai Mishali** **All rights reserved.** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/RxSwift/Platform/AtomicInt.swift ================================================ // // AtomicInt.swift // Platform // // Created by Krunoslav Zaher on 10/28/18. // Copyright © 2018 Krunoslav Zaher. All rights reserved. // import Foundation final class AtomicInt: NSLock { fileprivate var value: Int32 public init(_ value: Int32 = 0) { self.value = value } } @discardableResult @inline(__always) func add(_ this: AtomicInt, _ value: Int32) -> Int32 { this.lock() let oldValue = this.value this.value += value this.unlock() return oldValue } @discardableResult @inline(__always) func sub(_ this: AtomicInt, _ value: Int32) -> Int32 { this.lock() let oldValue = this.value this.value -= value this.unlock() return oldValue } @discardableResult @inline(__always) func fetchOr(_ this: AtomicInt, _ mask: Int32) -> Int32 { this.lock() let oldValue = this.value this.value |= mask this.unlock() return oldValue } @inline(__always) func load(_ this: AtomicInt) -> Int32 { this.lock() let oldValue = this.value this.unlock() return oldValue } @discardableResult @inline(__always) func increment(_ this: AtomicInt) -> Int32 { add(this, 1) } @discardableResult @inline(__always) func decrement(_ this: AtomicInt) -> Int32 { sub(this, 1) } @inline(__always) func isFlagSet(_ this: AtomicInt, _ mask: Int32) -> Bool { (load(this) & mask) != 0 } ================================================ FILE: JetChat/Pods/RxSwift/Platform/DataStructures/Bag.swift ================================================ // // Bag.swift // Platform // // Created by Krunoslav Zaher on 2/28/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Swift let arrayDictionaryMaxSize = 30 struct BagKey { /** Unique identifier for object added to `Bag`. It's underlying type is UInt64. If we assume there in an idealized CPU that works at 4GHz, it would take ~150 years of continuous running time for it to overflow. */ fileprivate let rawValue: UInt64 } /** Data structure that represents a bag of elements typed `T`. Single element can be stored multiple times. Time and space complexity of insertion and deletion is O(n). It is suitable for storing small number of elements. */ struct Bag : CustomDebugStringConvertible { /// Type of identifier for inserted elements. typealias KeyType = BagKey typealias Entry = (key: BagKey, value: T) private var _nextKey: BagKey = BagKey(rawValue: 0) // data // first fill inline variables var _key0: BagKey? var _value0: T? // then fill "array dictionary" var _pairs = ContiguousArray() // last is sparse dictionary var _dictionary: [BagKey: T]? var _onlyFastPath = true /// Creates new empty `Bag`. init() { } /** Inserts `value` into bag. - parameter element: Element to insert. - returns: Key that can be used to remove element from bag. */ mutating func insert(_ element: T) -> BagKey { let key = _nextKey _nextKey = BagKey(rawValue: _nextKey.rawValue &+ 1) if _key0 == nil { _key0 = key _value0 = element return key } _onlyFastPath = false if _dictionary != nil { _dictionary![key] = element return key } if _pairs.count < arrayDictionaryMaxSize { _pairs.append((key: key, value: element)) return key } _dictionary = [key: element] return key } /// - returns: Number of elements in bag. var count: Int { let dictionaryCount: Int = _dictionary?.count ?? 0 return (_value0 != nil ? 1 : 0) + _pairs.count + dictionaryCount } /// Removes all elements from bag and clears capacity. mutating func removeAll() { _key0 = nil _value0 = nil _pairs.removeAll(keepingCapacity: false) _dictionary?.removeAll(keepingCapacity: false) } /** Removes element with a specific `key` from bag. - parameter key: Key that identifies element to remove from bag. - returns: Element that bag contained, or nil in case element was already removed. */ mutating func removeKey(_ key: BagKey) -> T? { if _key0 == key { _key0 = nil let value = _value0! _value0 = nil return value } if let existingObject = _dictionary?.removeValue(forKey: key) { return existingObject } for i in 0 ..< _pairs.count where _pairs[i].key == key { let value = _pairs[i].value _pairs.remove(at: i) return value } return nil } } extension Bag { /// A textual representation of `self`, suitable for debugging. var debugDescription : String { "\(self.count) elements in Bag" } } extension Bag { /// Enumerates elements inside the bag. /// /// - parameter action: Enumeration closure. func forEach(_ action: (T) -> Void) { if _onlyFastPath { if let value0 = _value0 { action(value0) } return } let value0 = _value0 let dictionary = _dictionary if let value0 = value0 { action(value0) } for i in 0 ..< _pairs.count { action(_pairs[i].value) } if dictionary?.count ?? 0 > 0 { for element in dictionary!.values { action(element) } } } } extension BagKey: Hashable { func hash(into hasher: inout Hasher) { hasher.combine(rawValue) } } func ==(lhs: BagKey, rhs: BagKey) -> Bool { lhs.rawValue == rhs.rawValue } ================================================ FILE: JetChat/Pods/RxSwift/Platform/DataStructures/InfiniteSequence.swift ================================================ // // InfiniteSequence.swift // Platform // // Created by Krunoslav Zaher on 6/13/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Sequence that repeats `repeatedValue` infinite number of times. struct InfiniteSequence : Sequence { typealias Iterator = AnyIterator private let repeatedValue: Element init(repeatedValue: Element) { self.repeatedValue = repeatedValue } func makeIterator() -> Iterator { let repeatedValue = self.repeatedValue return AnyIterator { repeatedValue } } } ================================================ FILE: JetChat/Pods/RxSwift/Platform/DataStructures/PriorityQueue.swift ================================================ // // PriorityQueue.swift // Platform // // Created by Krunoslav Zaher on 12/27/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // struct PriorityQueue { private let hasHigherPriority: (Element, Element) -> Bool private let isEqual: (Element, Element) -> Bool private var elements = [Element]() init(hasHigherPriority: @escaping (Element, Element) -> Bool, isEqual: @escaping (Element, Element) -> Bool) { self.hasHigherPriority = hasHigherPriority self.isEqual = isEqual } mutating func enqueue(_ element: Element) { elements.append(element) bubbleToHigherPriority(elements.count - 1) } func peek() -> Element? { elements.first } var isEmpty: Bool { elements.count == 0 } mutating func dequeue() -> Element? { guard let front = peek() else { return nil } removeAt(0) return front } mutating func remove(_ element: Element) { for i in 0 ..< elements.count { if self.isEqual(elements[i], element) { removeAt(i) return } } } private mutating func removeAt(_ index: Int) { let removingLast = index == elements.count - 1 if !removingLast { elements.swapAt(index, elements.count - 1) } _ = elements.popLast() if !removingLast { bubbleToHigherPriority(index) bubbleToLowerPriority(index) } } private mutating func bubbleToHigherPriority(_ initialUnbalancedIndex: Int) { precondition(initialUnbalancedIndex >= 0) precondition(initialUnbalancedIndex < elements.count) var unbalancedIndex = initialUnbalancedIndex while unbalancedIndex > 0 { let parentIndex = (unbalancedIndex - 1) / 2 guard self.hasHigherPriority(elements[unbalancedIndex], elements[parentIndex]) else { break } elements.swapAt(unbalancedIndex, parentIndex) unbalancedIndex = parentIndex } } private mutating func bubbleToLowerPriority(_ initialUnbalancedIndex: Int) { precondition(initialUnbalancedIndex >= 0) precondition(initialUnbalancedIndex < elements.count) var unbalancedIndex = initialUnbalancedIndex while true { let leftChildIndex = unbalancedIndex * 2 + 1 let rightChildIndex = unbalancedIndex * 2 + 2 var highestPriorityIndex = unbalancedIndex if leftChildIndex < elements.count && self.hasHigherPriority(elements[leftChildIndex], elements[highestPriorityIndex]) { highestPriorityIndex = leftChildIndex } if rightChildIndex < elements.count && self.hasHigherPriority(elements[rightChildIndex], elements[highestPriorityIndex]) { highestPriorityIndex = rightChildIndex } guard highestPriorityIndex != unbalancedIndex else { break } elements.swapAt(highestPriorityIndex, unbalancedIndex) unbalancedIndex = highestPriorityIndex } } } extension PriorityQueue : CustomDebugStringConvertible { var debugDescription: String { elements.debugDescription } } ================================================ FILE: JetChat/Pods/RxSwift/Platform/DataStructures/Queue.swift ================================================ // // Queue.swift // Platform // // Created by Krunoslav Zaher on 3/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /** Data structure that represents queue. Complexity of `enqueue`, `dequeue` is O(1) when number of operations is averaged over N operations. Complexity of `peek` is O(1). */ struct Queue: Sequence { /// Type of generator. typealias Generator = AnyIterator private let resizeFactor = 2 private var storage: ContiguousArray private var innerCount = 0 private var pushNextIndex = 0 private let initialCapacity: Int /** Creates new queue. - parameter capacity: Capacity of newly created queue. */ init(capacity: Int) { initialCapacity = capacity storage = ContiguousArray(repeating: nil, count: capacity) } private var dequeueIndex: Int { let index = pushNextIndex - count return index < 0 ? index + storage.count : index } /// - returns: Is queue empty. var isEmpty: Bool { count == 0 } /// - returns: Number of elements inside queue. var count: Int { innerCount } /// - returns: Element in front of a list of elements to `dequeue`. func peek() -> T { precondition(count > 0) return storage[dequeueIndex]! } mutating private func resizeTo(_ size: Int) { var newStorage = ContiguousArray(repeating: nil, count: size) let count = self.count let dequeueIndex = self.dequeueIndex let spaceToEndOfQueue = storage.count - dequeueIndex // first batch is from dequeue index to end of array let countElementsInFirstBatch = Swift.min(count, spaceToEndOfQueue) // second batch is wrapped from start of array to end of queue let numberOfElementsInSecondBatch = count - countElementsInFirstBatch newStorage[0 ..< countElementsInFirstBatch] = storage[dequeueIndex ..< (dequeueIndex + countElementsInFirstBatch)] newStorage[countElementsInFirstBatch ..< (countElementsInFirstBatch + numberOfElementsInSecondBatch)] = storage[0 ..< numberOfElementsInSecondBatch] self.innerCount = count pushNextIndex = count storage = newStorage } /// Enqueues `element`. /// /// - parameter element: Element to enqueue. mutating func enqueue(_ element: T) { if count == storage.count { resizeTo(Swift.max(storage.count, 1) * resizeFactor) } storage[pushNextIndex] = element pushNextIndex += 1 innerCount += 1 if pushNextIndex >= storage.count { pushNextIndex -= storage.count } } private mutating func dequeueElementOnly() -> T { precondition(count > 0) let index = dequeueIndex defer { storage[index] = nil innerCount -= 1 } return storage[index]! } /// Dequeues element or throws an exception in case queue is empty. /// /// - returns: Dequeued element. mutating func dequeue() -> T? { if self.count == 0 { return nil } defer { let downsizeLimit = storage.count / (resizeFactor * resizeFactor) if count < downsizeLimit && downsizeLimit >= initialCapacity { resizeTo(storage.count / resizeFactor) } } return dequeueElementOnly() } /// - returns: Generator of contained elements. func makeIterator() -> AnyIterator { var i = dequeueIndex var innerCount = count return AnyIterator { if innerCount == 0 { return nil } defer { innerCount -= 1 i += 1 } if i >= self.storage.count { i -= self.storage.count } return self.storage[i] } } } ================================================ FILE: JetChat/Pods/RxSwift/Platform/DispatchQueue+Extensions.swift ================================================ // // DispatchQueue+Extensions.swift // Platform // // Created by Krunoslav Zaher on 10/22/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // import Dispatch extension DispatchQueue { private static var token: DispatchSpecificKey<()> = { let key = DispatchSpecificKey<()>() DispatchQueue.main.setSpecific(key: key, value: ()) return key }() static var isMain: Bool { DispatchQueue.getSpecific(key: token) != nil } } ================================================ FILE: JetChat/Pods/RxSwift/Platform/Platform.Darwin.swift ================================================ // // Platform.Darwin.swift // Platform // // Created by Krunoslav Zaher on 12/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) import Darwin import Foundation extension Thread { static func setThreadLocalStorageValue(_ value: T?, forKey key: NSCopying) { let currentThread = Thread.current let threadDictionary = currentThread.threadDictionary if let newValue = value { threadDictionary[key] = newValue } else { threadDictionary[key] = nil } } static func getThreadLocalStorageValueForKey(_ key: NSCopying) -> T? { let currentThread = Thread.current let threadDictionary = currentThread.threadDictionary return threadDictionary[key] as? T } } #endif ================================================ FILE: JetChat/Pods/RxSwift/Platform/Platform.Linux.swift ================================================ // // Platform.Linux.swift // Platform // // Created by Krunoslav Zaher on 12/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(Linux) import Foundation extension Thread { static func setThreadLocalStorageValue(_ value: T?, forKey key: String) { if let newValue = value { Thread.current.threadDictionary[key] = newValue } else { Thread.current.threadDictionary[key] = nil } } static func getThreadLocalStorageValueForKey(_ key: String) -> T? { let currentThread = Thread.current let threadDictionary = currentThread.threadDictionary return threadDictionary[key] as? T } } #endif ================================================ FILE: JetChat/Pods/RxSwift/Platform/RecursiveLock.swift ================================================ // // RecursiveLock.swift // Platform // // Created by Krunoslav Zaher on 12/18/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // import Foundation #if TRACE_RESOURCES class RecursiveLock: NSRecursiveLock { override init() { _ = Resources.incrementTotal() super.init() } override func lock() { super.lock() _ = Resources.incrementTotal() } override func unlock() { super.unlock() _ = Resources.decrementTotal() } deinit { _ = Resources.decrementTotal() } } #else typealias RecursiveLock = NSRecursiveLock #endif ================================================ FILE: JetChat/Pods/RxSwift/README.md ================================================

RxSwift Logo
Build Status Supported Platforms: iOS, macOS, tvOS, watchOS & Linux

Rx is a [generic abstraction of computation](https://youtu.be/looJcaeboBY) expressed through `Observable` interface, which lets you broadcast and subscribe to values and other events from an `Observable` stream. RxSwift is the Swift-specific implementation of the [Reactive Extensions](http://reactivex.io) standard.

RxSwift Observable Example of a price constantly changing and updating the app's UI

While this version aims to stay true to the original spirit and naming conventions of Rx, this projects also aims to provide a true Swift-first API for Rx APIs. Cross platform documentation can be found on [ReactiveX.io](http://reactivex.io/). Like other Rx implementation, RxSwift's intention is to enable easy composition of asynchronous operations and streams of data in the form of `Observable` objects and a suite of methods to transform and compose these pieces of asynchronous work. KVO observation, async operations, UI Events and other streams of data are all unified under [abstraction of sequence](Documentation/GettingStarted.md#observables-aka-sequences). This is the reason why Rx is so simple, elegant and powerful. ## I came here because I want to ... ###### ... understand * [why use rx?](Documentation/Why.md) * [the basics, getting started with RxSwift](Documentation/GettingStarted.md) * [traits](Documentation/Traits.md) - what are `Single`, `Completable`, `Maybe`, `Driver`, and `ControlProperty` ... and why do they exist? * [testing](Documentation/UnitTests.md) * [tips and common errors](Documentation/Tips.md) * [debugging](Documentation/GettingStarted.md#debugging) * [the math behind Rx](Documentation/MathBehindRx.md) * [what are hot and cold observable sequences?](Documentation/HotAndColdObservables.md) ###### ... install * Integrate RxSwift/RxCocoa with my app. [Installation Guide](#installation) ###### ... hack around * with the example app. [Running Example App](Documentation/ExampleApp.md) * with operators in playgrounds. [Playgrounds](Documentation/Playgrounds.md) ###### ... interact * All of this is great, but it would be nice to talk with other people using RxSwift and exchange experiences.
[Join Slack Channel](http://slack.rxswift.org) * Report a problem using the library. [Open an Issue With Bug Template](.github/ISSUE_TEMPLATE.md) * Request a new feature. [Open an Issue With Feature Request Template](Documentation/NewFeatureRequestTemplate.md) * Help out [Check out contribution guide](CONTRIBUTING.md) ###### ... compare * [with Combine and ReactiveSwift](Documentation/ComparisonWithOtherLibraries.md). ###### ... understand the structure RxSwift is as compositional as the asynchronous work it drives. The core unit is RxSwift itself, while other dependencies can be added for UI Work, testing, and more. It comprises five separate components depending on each other in the following way: ```none ┌──────────────┐ ┌──────────────┐ │ RxCocoa ├────▶ RxRelay │ └───────┬──────┘ └──────┬───────┘ │ │ ┌───────▼──────────────────▼───────┐ │ RxSwift │ └───────▲──────────────────▲───────┘ │ │ ┌───────┴──────┐ ┌──────┴───────┐ │ RxTest │ │ RxBlocking │ └──────────────┘ └──────────────┘ ``` * **RxSwift**: The core of RxSwift, providing the Rx standard as (mostly) defined by [ReactiveX](https://reactivex.io). It has no other dependencies. * **RxCocoa**: Provides Cocoa-specific capabilities for general iOS/macOS/watchOS & tvOS app development, such as Shared Sequences, Traits, and much more. It depends on both `RxSwift` and `RxRelay`. * **RxRelay**: Provides `PublishRelay`, `BehaviorRelay` and `ReplayRelay`, three [simple wrappers around Subjects](https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Subjects.md#relays). It depends on `RxSwift`. * **RxTest** and **RxBlocking**: Provides testing capabilities for Rx-based systems. It depends on `RxSwift`. ## Usage
Here's an example In Action
Define search for GitHub repositories ...
let searchResults = searchBar.rx.text.orEmpty
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest { query -> Observable<[Repository]> in
        if query.isEmpty {
            return .just([])
        }
        return searchGitHub(query)
            .catchAndReturn([])
    }
    .observe(on: MainScheduler.instance)
... then bind the results to your tableview
searchResults
    .bind(to: tableView.rx.items(cellIdentifier: "Cell")) {
        (index, repository: Repository, cell) in
        cell.textLabel?.text = repository.name
        cell.detailTextLabel?.text = repository.url
    }
    .disposed(by: disposeBag)
## Requirements * Xcode 12.x * Swift 5.x For Xcode 11 and below, [use RxSwift 5.x](https://github.com/ReactiveX/RxSwift/releases/tag/5.1.1). ## Installation RxSwift doesn't contain any external dependencies. These are currently the supported installation options: ### Manual Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run the sample app ### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html) ```ruby # Podfile use_frameworks! target 'YOUR_TARGET_NAME' do pod 'RxSwift', '6.5.0' pod 'RxCocoa', '6.5.0' end # RxTest and RxBlocking make the most sense in the context of unit/integration tests target 'YOUR_TESTING_TARGET' do pod 'RxBlocking', '6.5.0' pod 'RxTest', '6.5.0' end ``` Replace `YOUR_TARGET_NAME` and then, in the `Podfile` directory, type: ```bash $ pod install ``` ### XCFrameworks Each release starting with RxSwift 6 includes `*.xcframework` framework binaries. Simply drag the needed framework binaries to your **Frameworks, Libraries, and Embedded Content** section under your target's **General** tab. > **Note**: If you're using `RxCocoa`, be sure to also drag **RxCocoaRuntime.xcframework** before importing `RxCocoa`. XCFrameworks instructions ### [Carthage](https://github.com/Carthage/Carthage) Add this to `Cartfile` ``` github "ReactiveX/RxSwift" "6.5.0" ``` ```bash $ carthage update ``` #### Carthage as a Static Library Carthage defaults to building RxSwift as a Dynamic Library. If you wish to build RxSwift as a Static Library using Carthage you may use the script below to manually modify the framework type before building with Carthage: ```bash carthage update RxSwift --platform iOS --no-build sed -i -e 's/MACH_O_TYPE = mh_dylib/MACH_O_TYPE = staticlib/g' Carthage/Checkouts/RxSwift/Rx.xcodeproj/project.pbxproj carthage build RxSwift --platform iOS ``` ### [Swift Package Manager](https://github.com/apple/swift-package-manager) > **Note**: There is a critical cross-dependency bug affecting many projects including RxSwift in Swift Package Manager. We've [filed a bug (SR-12303)](https://bugs.swift.org/browse/SR-12303) in early 2020 but have no answer yet. Your mileage may vary. A partial workaround can be found [here](https://github.com/ReactiveX/RxSwift/issues/2127#issuecomment-717830502). Create a `Package.swift` file. ```swift // swift-tools-version:5.0 import PackageDescription let package = Package( name: "RxTestProject", dependencies: [ .package(url: "https://github.com/ReactiveX/RxSwift.git", .exact("6.5.0")) ], targets: [ .target(name: "RxTestProject", dependencies: ["RxSwift", "RxCocoa"]) ] ) ``` ```bash $ swift build ``` To build or test a module with RxTest dependency, set `TEST=1`. ```bash $ TEST=1 swift test ``` ### Manually using git submodules * Add RxSwift as a submodule ```bash $ git submodule add git@github.com:ReactiveX/RxSwift.git ``` * Drag `Rx.xcodeproj` into Project Navigator * Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxSwift`, `RxCocoa` and `RxRelay` targets ## References * [http://reactivex.io/](http://reactivex.io/) * [Reactive Extensions GitHub (GitHub)](https://github.com/Reactive-Extensions) * [RxSwift RayWenderlich.com Book](https://store.raywenderlich.com/products/rxswift-reactive-programming-with-swift) * [RxSwift: Debunking the myth of hard (YouTube)](https://www.youtube.com/watch?v=GdvLP0ZAhhc) * [Boxue.io RxSwift Online Course](https://boxueio.com/series/rxswift-101) (Chinese 🇨🇳) * [Expert to Expert: Brian Beckman and Erik Meijer - Inside the .NET Reactive Framework (Rx) (video)](https://youtu.be/looJcaeboBY) * [Reactive Programming Overview (Jafar Husain from Netflix)](https://youtu.be/-8Y1-lE6NSA) * [Subject/Observer is Dual to Iterator (paper)](http://csl.stanford.edu/~christos/pldi2010.fit/meijer.duality.pdf) * [Rx standard sequence operators visualized (visualization tool)](http://rxmarbles.com/) * [Haskell](https://www.haskell.org/) ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/AnyObserver.swift ================================================ // // AnyObserver.swift // RxSwift // // Created by Krunoslav Zaher on 2/28/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// A type-erased `ObserverType`. /// /// Forwards operations to an arbitrary underlying observer with the same `Element` type, hiding the specifics of the underlying observer type. public struct AnyObserver : ObserverType { /// Anonymous event handler type. public typealias EventHandler = (Event) -> Void private let observer: EventHandler /// Construct an instance whose `on(event)` calls `eventHandler(event)` /// /// - parameter eventHandler: Event handler that observes sequences events. public init(eventHandler: @escaping EventHandler) { self.observer = eventHandler } /// Construct an instance whose `on(event)` calls `observer.on(event)` /// /// - parameter observer: Observer that receives sequence events. public init(_ observer: Observer) where Observer.Element == Element { self.observer = observer.on } /// Send `event` to this observer. /// /// - parameter event: Event instance. public func on(_ event: Event) { self.observer(event) } /// Erases type of observer and returns canonical observer. /// /// - returns: type erased observer. public func asObserver() -> AnyObserver { self } } extension AnyObserver { /// Collection of `AnyObserver`s typealias s = Bag<(Event) -> Void> } extension ObserverType { /// Erases type of observer and returns canonical observer. /// /// - returns: type erased observer. public func asObserver() -> AnyObserver { AnyObserver(self) } /// Transforms observer of type R to type E using custom transform method. /// Each event sent to result observer is transformed and sent to `self`. /// /// - returns: observer that transforms events. public func mapObserver(_ transform: @escaping (Result) throws -> Element) -> AnyObserver { AnyObserver { e in self.on(e.map(transform)) } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Binder.swift ================================================ // // Binder.swift // RxSwift // // Created by Krunoslav Zaher on 9/17/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // /** Observer that enforces interface binding rules: * can't bind errors (in debug builds binding of errors causes `fatalError` in release builds errors are being logged) * ensures binding is performed on a specific scheduler `Binder` doesn't retain target and in case target is released, element isn't bound. By default it binds elements on main scheduler. */ public struct Binder: ObserverType { public typealias Element = Value private let binding: (Event) -> Void /// Initializes `Binder` /// /// - parameter target: Target object. /// - parameter scheduler: Scheduler used to bind the events. /// - parameter binding: Binding logic. public init(_ target: Target, scheduler: ImmediateSchedulerType = MainScheduler(), binding: @escaping (Target, Value) -> Void) { weak var weakTarget = target self.binding = { event in switch event { case .next(let element): _ = scheduler.schedule(element) { element in if let target = weakTarget { binding(target, element) } return Disposables.create() } case .error(let error): rxFatalErrorInDebug("Binding error: \(error)") case .completed: break } } } /// Binds next element to owner view as described in `binding`. public func on(_ event: Event) { self.binding(event) } /// Erases type of observer. /// /// - returns: type erased observer. public func asObserver() -> AnyObserver { AnyObserver(eventHandler: self.on) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Cancelable.swift ================================================ // // Cancelable.swift // RxSwift // // Created by Krunoslav Zaher on 3/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents disposable resource with state tracking. public protocol Cancelable : Disposable { /// Was resource disposed. var isDisposed: Bool { get } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Concurrency/AsyncLock.swift ================================================ // // AsyncLock.swift // RxSwift // // Created by Krunoslav Zaher on 3/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /** In case nobody holds this lock, the work will be queued and executed immediately on thread that is requesting lock. In case there is somebody currently holding that lock, action will be enqueued. When owned of the lock finishes with it's processing, it will also execute and pending work. That means that enqueued work could possibly be executed later on a different thread. */ final class AsyncLock : Disposable , Lock , SynchronizedDisposeType { typealias Action = () -> Void private var _lock = SpinLock() private var queue: Queue = Queue(capacity: 0) private var isExecuting: Bool = false private var hasFaulted: Bool = false // lock { func lock() { self._lock.lock() } func unlock() { self._lock.unlock() } // } private func enqueue(_ action: I) -> I? { self.lock(); defer { self.unlock() } if self.hasFaulted { return nil } if self.isExecuting { self.queue.enqueue(action) return nil } self.isExecuting = true return action } private func dequeue() -> I? { self.lock(); defer { self.unlock() } if !self.queue.isEmpty { return self.queue.dequeue() } else { self.isExecuting = false return nil } } func invoke(_ action: I) { let firstEnqueuedAction = self.enqueue(action) if let firstEnqueuedAction = firstEnqueuedAction { firstEnqueuedAction.invoke() } else { // action is enqueued, it's somebody else's concern now return } while true { let nextAction = self.dequeue() if let nextAction = nextAction { nextAction.invoke() } else { return } } } func dispose() { self.synchronizedDispose() } func synchronized_dispose() { self.queue = Queue(capacity: 0) self.hasFaulted = true } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Concurrency/Lock.swift ================================================ // // Lock.swift // RxSwift // // Created by Krunoslav Zaher on 3/31/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // protocol Lock { func lock() func unlock() } // https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000321.html typealias SpinLock = RecursiveLock extension RecursiveLock : Lock { @inline(__always) final func performLocked(_ action: () -> T) -> T { self.lock(); defer { self.unlock() } return action() } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Concurrency/LockOwnerType.swift ================================================ // // LockOwnerType.swift // RxSwift // // Created by Krunoslav Zaher on 10/25/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // protocol LockOwnerType: AnyObject, Lock { var lock: RecursiveLock { get } } extension LockOwnerType { func lock() { self.lock.lock() } func unlock() { self.lock.unlock() } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Concurrency/SynchronizedDisposeType.swift ================================================ // // SynchronizedDisposeType.swift // RxSwift // // Created by Krunoslav Zaher on 10/25/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // protocol SynchronizedDisposeType: AnyObject, Disposable, Lock { func synchronized_dispose() } extension SynchronizedDisposeType { func synchronizedDispose() { self.lock(); defer { self.unlock() } self.synchronized_dispose() } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Concurrency/SynchronizedOnType.swift ================================================ // // SynchronizedOnType.swift // RxSwift // // Created by Krunoslav Zaher on 10/25/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // protocol SynchronizedOnType: AnyObject, ObserverType, Lock { func synchronized_on(_ event: Event) } extension SynchronizedOnType { func synchronizedOn(_ event: Event) { self.lock(); defer { self.unlock() } self.synchronized_on(event) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Concurrency/SynchronizedUnsubscribeType.swift ================================================ // // SynchronizedUnsubscribeType.swift // RxSwift // // Created by Krunoslav Zaher on 10/25/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // protocol SynchronizedUnsubscribeType: AnyObject { associatedtype DisposeKey func synchronizedUnsubscribe(_ disposeKey: DisposeKey) } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/ConnectableObservableType.swift ================================================ // // ConnectableObservableType.swift // RxSwift // // Created by Krunoslav Zaher on 3/1/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /** Represents an observable sequence wrapper that can be connected and disconnected from its underlying observable sequence. */ public protocol ConnectableObservableType : ObservableType { /** Connects the observable wrapper to its source. All subscribed observers will receive values from the underlying observable sequence as long as the connection is established. - returns: Disposable used to disconnect the observable wrapper from its source, causing subscribed observer to stop receiving values from the underlying observable sequence. */ func connect() -> Disposable } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Date+Dispatch.swift ================================================ // // Date+Dispatch.swift // RxSwift // // Created by Krunoslav Zaher on 4/14/19. // Copyright © 2019 Krunoslav Zaher. All rights reserved. // import Dispatch import Foundation extension DispatchTimeInterval { var convertToSecondsFactor: Double { switch self { case .nanoseconds: return 1_000_000_000.0 case .microseconds: return 1_000_000.0 case .milliseconds: return 1_000.0 case .seconds: return 1.0 case .never: fatalError() @unknown default: fatalError() } } func map(_ transform: (Int, Double) -> Int) -> DispatchTimeInterval { switch self { case .nanoseconds(let value): return .nanoseconds(transform(value, 1_000_000_000.0)) case .microseconds(let value): return .microseconds(transform(value, 1_000_000.0)) case .milliseconds(let value): return .milliseconds(transform(value, 1_000.0)) case .seconds(let value): return .seconds(transform(value, 1.0)) case .never: return .never @unknown default: fatalError() } } var isNow: Bool { switch self { case .nanoseconds(let value), .microseconds(let value), .milliseconds(let value), .seconds(let value): return value == 0 case .never: return false @unknown default: fatalError() } } internal func reduceWithSpanBetween(earlierDate: Date, laterDate: Date) -> DispatchTimeInterval { return self.map { value, factor in let interval = laterDate.timeIntervalSince(earlierDate) let remainder = Double(value) - interval * factor guard remainder > 0 else { return 0 } return Int(remainder.rounded(.toNearestOrAwayFromZero)) } } } extension Date { internal func addingDispatchInterval(_ dispatchInterval: DispatchTimeInterval) -> Date { switch dispatchInterval { case .nanoseconds(let value), .microseconds(let value), .milliseconds(let value), .seconds(let value): return self.addingTimeInterval(TimeInterval(value) / dispatchInterval.convertToSecondsFactor) case .never: return Date.distantFuture @unknown default: fatalError() } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposable.swift ================================================ // // Disposable.swift // RxSwift // // Created by Krunoslav Zaher on 2/8/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents a disposable resource. public protocol Disposable { /// Dispose resource. func dispose() } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/AnonymousDisposable.swift ================================================ // // AnonymousDisposable.swift // RxSwift // // Created by Krunoslav Zaher on 2/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents an Action-based disposable. /// /// When dispose method is called, disposal action will be dereferenced. private final class AnonymousDisposable : DisposeBase, Cancelable { public typealias DisposeAction = () -> Void private let disposed = AtomicInt(0) private var disposeAction: DisposeAction? /// - returns: Was resource disposed. public var isDisposed: Bool { isFlagSet(self.disposed, 1) } /// Constructs a new disposable with the given action used for disposal. /// /// - parameter disposeAction: Disposal action which will be run upon calling `dispose`. private init(_ disposeAction: @escaping DisposeAction) { self.disposeAction = disposeAction super.init() } // Non-deprecated version of the constructor, used by `Disposables.create(with:)` fileprivate init(disposeAction: @escaping DisposeAction) { self.disposeAction = disposeAction super.init() } /// Calls the disposal action if and only if the current instance hasn't been disposed yet. /// /// After invoking disposal action, disposal action will be dereferenced. fileprivate func dispose() { if fetchOr(self.disposed, 1) == 0 { if let action = self.disposeAction { self.disposeAction = nil action() } } } } extension Disposables { /// Constructs a new disposable with the given action used for disposal. /// /// - parameter dispose: Disposal action which will be run upon calling `dispose`. public static func create(with dispose: @escaping () -> Void) -> Cancelable { AnonymousDisposable(disposeAction: dispose) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/BinaryDisposable.swift ================================================ // // BinaryDisposable.swift // RxSwift // // Created by Krunoslav Zaher on 6/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents two disposable resources that are disposed together. private final class BinaryDisposable : DisposeBase, Cancelable { private let disposed = AtomicInt(0) // state private var disposable1: Disposable? private var disposable2: Disposable? /// - returns: Was resource disposed. var isDisposed: Bool { isFlagSet(self.disposed, 1) } /// Constructs new binary disposable from two disposables. /// /// - parameter disposable1: First disposable /// - parameter disposable2: Second disposable init(_ disposable1: Disposable, _ disposable2: Disposable) { self.disposable1 = disposable1 self.disposable2 = disposable2 super.init() } /// Calls the disposal action if and only if the current instance hasn't been disposed yet. /// /// After invoking disposal action, disposal action will be dereferenced. func dispose() { if fetchOr(self.disposed, 1) == 0 { self.disposable1?.dispose() self.disposable2?.dispose() self.disposable1 = nil self.disposable2 = nil } } } extension Disposables { /// Creates a disposable with the given disposables. public static func create(_ disposable1: Disposable, _ disposable2: Disposable) -> Cancelable { BinaryDisposable(disposable1, disposable2) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/BooleanDisposable.swift ================================================ // // BooleanDisposable.swift // RxSwift // // Created by Junior B. on 10/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents a disposable resource that can be checked for disposal status. public final class BooleanDisposable : Cancelable { internal static let BooleanDisposableTrue = BooleanDisposable(isDisposed: true) private var disposed = false /// Initializes a new instance of the `BooleanDisposable` class public init() { } /// Initializes a new instance of the `BooleanDisposable` class with given value public init(isDisposed: Bool) { self.disposed = isDisposed } /// - returns: Was resource disposed. public var isDisposed: Bool { self.disposed } /// Sets the status to disposed, which can be observer through the `isDisposed` property. public func dispose() { self.disposed = true } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/CompositeDisposable.swift ================================================ // // CompositeDisposable.swift // RxSwift // // Created by Krunoslav Zaher on 2/20/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents a group of disposable resources that are disposed together. public final class CompositeDisposable : DisposeBase, Cancelable { /// Key used to remove disposable from composite disposable public struct DisposeKey { fileprivate let key: BagKey fileprivate init(key: BagKey) { self.key = key } } private var lock = SpinLock() // state private var disposables: Bag? = Bag() public var isDisposed: Bool { self.lock.performLocked { self.disposables == nil } } public override init() { } /// Initializes a new instance of composite disposable with the specified number of disposables. public init(_ disposable1: Disposable, _ disposable2: Disposable) { // This overload is here to make sure we are using optimized version up to 4 arguments. _ = self.disposables!.insert(disposable1) _ = self.disposables!.insert(disposable2) } /// Initializes a new instance of composite disposable with the specified number of disposables. public init(_ disposable1: Disposable, _ disposable2: Disposable, _ disposable3: Disposable) { // This overload is here to make sure we are using optimized version up to 4 arguments. _ = self.disposables!.insert(disposable1) _ = self.disposables!.insert(disposable2) _ = self.disposables!.insert(disposable3) } /// Initializes a new instance of composite disposable with the specified number of disposables. public init(_ disposable1: Disposable, _ disposable2: Disposable, _ disposable3: Disposable, _ disposable4: Disposable, _ disposables: Disposable...) { // This overload is here to make sure we are using optimized version up to 4 arguments. _ = self.disposables!.insert(disposable1) _ = self.disposables!.insert(disposable2) _ = self.disposables!.insert(disposable3) _ = self.disposables!.insert(disposable4) for disposable in disposables { _ = self.disposables!.insert(disposable) } } /// Initializes a new instance of composite disposable with the specified number of disposables. public init(disposables: [Disposable]) { for disposable in disposables { _ = self.disposables!.insert(disposable) } } /** Adds a disposable to the CompositeDisposable or disposes the disposable if the CompositeDisposable is disposed. - parameter disposable: Disposable to add. - returns: Key that can be used to remove disposable from composite disposable. In case dispose bag was already disposed `nil` will be returned. */ public func insert(_ disposable: Disposable) -> DisposeKey? { let key = self._insert(disposable) if key == nil { disposable.dispose() } return key } private func _insert(_ disposable: Disposable) -> DisposeKey? { self.lock.performLocked { let bagKey = self.disposables?.insert(disposable) return bagKey.map(DisposeKey.init) } } /// - returns: Gets the number of disposables contained in the `CompositeDisposable`. public var count: Int { self.lock.performLocked { self.disposables?.count ?? 0 } } /// Removes and disposes the disposable identified by `disposeKey` from the CompositeDisposable. /// /// - parameter disposeKey: Key used to identify disposable to be removed. public func remove(for disposeKey: DisposeKey) { self._remove(for: disposeKey)?.dispose() } private func _remove(for disposeKey: DisposeKey) -> Disposable? { self.lock.performLocked { self.disposables?.removeKey(disposeKey.key) } } /// Disposes all disposables in the group and removes them from the group. public func dispose() { if let disposables = self._dispose() { disposeAll(in: disposables) } } private func _dispose() -> Bag? { self.lock.performLocked { let current = self.disposables self.disposables = nil return current } } } extension Disposables { /// Creates a disposable with the given disposables. public static func create(_ disposable1: Disposable, _ disposable2: Disposable, _ disposable3: Disposable) -> Cancelable { CompositeDisposable(disposable1, disposable2, disposable3) } /// Creates a disposable with the given disposables. public static func create(_ disposable1: Disposable, _ disposable2: Disposable, _ disposable3: Disposable, _ disposables: Disposable ...) -> Cancelable { var disposables = disposables disposables.append(disposable1) disposables.append(disposable2) disposables.append(disposable3) return CompositeDisposable(disposables: disposables) } /// Creates a disposable with the given disposables. public static func create(_ disposables: [Disposable]) -> Cancelable { switch disposables.count { case 2: return Disposables.create(disposables[0], disposables[1]) default: return CompositeDisposable(disposables: disposables) } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/Disposables.swift ================================================ // // Disposables.swift // RxSwift // // Created by Mohsen Ramezanpoor on 01/08/2016. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // /// A collection of utility methods for common disposable operations. public struct Disposables { private init() {} } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/DisposeBag.swift ================================================ // // DisposeBag.swift // RxSwift // // Created by Krunoslav Zaher on 3/25/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension Disposable { /// Adds `self` to `bag` /// /// - parameter bag: `DisposeBag` to add `self` to. public func disposed(by bag: DisposeBag) { bag.insert(self) } } /** Thread safe bag that disposes added disposables on `deinit`. This returns ARC (RAII) like resource management to `RxSwift`. In case contained disposables need to be disposed, just put a different dispose bag or create a new one in its place. self.existingDisposeBag = DisposeBag() In case explicit disposal is necessary, there is also `CompositeDisposable`. */ public final class DisposeBag: DisposeBase { private var lock = SpinLock() // state private var disposables = [Disposable]() private var isDisposed = false /// Constructs new empty dispose bag. public override init() { super.init() } /// Adds `disposable` to be disposed when dispose bag is being deinited. /// /// - parameter disposable: Disposable to add. public func insert(_ disposable: Disposable) { self._insert(disposable)?.dispose() } private func _insert(_ disposable: Disposable) -> Disposable? { self.lock.performLocked { if self.isDisposed { return disposable } self.disposables.append(disposable) return nil } } /// This is internal on purpose, take a look at `CompositeDisposable` instead. private func dispose() { let oldDisposables = self._dispose() for disposable in oldDisposables { disposable.dispose() } } private func _dispose() -> [Disposable] { self.lock.performLocked { let disposables = self.disposables self.disposables.removeAll(keepingCapacity: false) self.isDisposed = true return disposables } } deinit { self.dispose() } } extension DisposeBag { /// Convenience init allows a list of disposables to be gathered for disposal. public convenience init(disposing disposables: Disposable...) { self.init() self.disposables += disposables } /// Convenience init which utilizes a function builder to let you pass in a list of /// disposables to make a DisposeBag of. public convenience init(@DisposableBuilder builder: () -> [Disposable]) { self.init(disposing: builder()) } /// Convenience init allows an array of disposables to be gathered for disposal. public convenience init(disposing disposables: [Disposable]) { self.init() self.disposables += disposables } /// Convenience function allows a list of disposables to be gathered for disposal. public func insert(_ disposables: Disposable...) { self.insert(disposables) } /// Convenience function allows a list of disposables to be gathered for disposal. public func insert(@DisposableBuilder builder: () -> [Disposable]) { self.insert(builder()) } /// Convenience function allows an array of disposables to be gathered for disposal. public func insert(_ disposables: [Disposable]) { self.lock.performLocked { if self.isDisposed { disposables.forEach { $0.dispose() } } else { self.disposables += disposables } } } /// A function builder accepting a list of Disposables and returning them as an array. #if swift(>=5.4) @resultBuilder public struct DisposableBuilder { public static func buildBlock(_ disposables: Disposable...) -> [Disposable] { return disposables } } #else @_functionBuilder public struct DisposableBuilder { public static func buildBlock(_ disposables: Disposable...) -> [Disposable] { return disposables } } #endif } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/DisposeBase.swift ================================================ // // DisposeBase.swift // RxSwift // // Created by Krunoslav Zaher on 4/4/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Base class for all disposables. public class DisposeBase { init() { #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif } deinit { #if TRACE_RESOURCES _ = Resources.decrementTotal() #endif } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/NopDisposable.swift ================================================ // // NopDisposable.swift // RxSwift // // Created by Krunoslav Zaher on 2/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents a disposable that does nothing on disposal. /// /// Nop = No Operation private struct NopDisposable : Disposable { fileprivate static let noOp: Disposable = NopDisposable() private init() { } /// Does nothing. public func dispose() { } } extension Disposables { /** Creates a disposable that does nothing on disposal. */ static public func create() -> Disposable { NopDisposable.noOp } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/RefCountDisposable.swift ================================================ // // RefCountDisposable.swift // RxSwift // // Created by Junior B. on 10/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents a disposable resource that only disposes its underlying disposable resource when all dependent disposable objects have been disposed. public final class RefCountDisposable : DisposeBase, Cancelable { private var lock = SpinLock() private var disposable = nil as Disposable? private var primaryDisposed = false private var count = 0 /// - returns: Was resource disposed. public var isDisposed: Bool { self.lock.performLocked { self.disposable == nil } } /// Initializes a new instance of the `RefCountDisposable`. public init(disposable: Disposable) { self.disposable = disposable super.init() } /** Holds a dependent disposable that when disposed decreases the refcount on the underlying disposable. When getter is called, a dependent disposable contributing to the reference count that manages the underlying disposable's lifetime is returned. */ public func retain() -> Disposable { self.lock.performLocked { if self.disposable != nil { do { _ = try incrementChecked(&self.count) } catch { rxFatalError("RefCountDisposable increment failed") } return RefCountInnerDisposable(self) } else { return Disposables.create() } } } /// Disposes the underlying disposable only when all dependent disposables have been disposed. public func dispose() { let oldDisposable: Disposable? = self.lock.performLocked { if let oldDisposable = self.disposable, !self.primaryDisposed { self.primaryDisposed = true if self.count == 0 { self.disposable = nil return oldDisposable } } return nil } if let disposable = oldDisposable { disposable.dispose() } } fileprivate func release() { let oldDisposable: Disposable? = self.lock.performLocked { if let oldDisposable = self.disposable { do { _ = try decrementChecked(&self.count) } catch { rxFatalError("RefCountDisposable decrement on release failed") } guard self.count >= 0 else { rxFatalError("RefCountDisposable counter is lower than 0") } if self.primaryDisposed && self.count == 0 { self.disposable = nil return oldDisposable } } return nil } if let disposable = oldDisposable { disposable.dispose() } } } internal final class RefCountInnerDisposable: DisposeBase, Disposable { private let parent: RefCountDisposable private let isDisposed = AtomicInt(0) init(_ parent: RefCountDisposable) { self.parent = parent super.init() } internal func dispose() { if fetchOr(self.isDisposed, 1) == 0 { self.parent.release() } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/ScheduledDisposable.swift ================================================ // // ScheduledDisposable.swift // RxSwift // // Created by Krunoslav Zaher on 6/13/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // private let disposeScheduledDisposable: (ScheduledDisposable) -> Disposable = { sd in sd.disposeInner() return Disposables.create() } /// Represents a disposable resource whose disposal invocation will be scheduled on the specified scheduler. public final class ScheduledDisposable : Cancelable { public let scheduler: ImmediateSchedulerType private let disposed = AtomicInt(0) // state private var disposable: Disposable? /// - returns: Was resource disposed. public var isDisposed: Bool { isFlagSet(self.disposed, 1) } /** Initializes a new instance of the `ScheduledDisposable` that uses a `scheduler` on which to dispose the `disposable`. - parameter scheduler: Scheduler where the disposable resource will be disposed on. - parameter disposable: Disposable resource to dispose on the given scheduler. */ public init(scheduler: ImmediateSchedulerType, disposable: Disposable) { self.scheduler = scheduler self.disposable = disposable } /// Disposes the wrapped disposable on the provided scheduler. public func dispose() { _ = self.scheduler.schedule(self, action: disposeScheduledDisposable) } func disposeInner() { if fetchOr(self.disposed, 1) == 0 { self.disposable!.dispose() self.disposable = nil } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/SerialDisposable.swift ================================================ // // SerialDisposable.swift // RxSwift // // Created by Krunoslav Zaher on 3/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource, causing automatic disposal of the previous underlying disposable resource. public final class SerialDisposable : DisposeBase, Cancelable { private var lock = SpinLock() // state private var current = nil as Disposable? private var disposed = false /// - returns: Was resource disposed. public var isDisposed: Bool { self.disposed } /// Initializes a new instance of the `SerialDisposable`. override public init() { super.init() } /** Gets or sets the underlying disposable. Assigning this property disposes the previous disposable object. If the `SerialDisposable` has already been disposed, assignment to this property causes immediate disposal of the given disposable object. */ public var disposable: Disposable { get { self.lock.performLocked { self.current ?? Disposables.create() } } set (newDisposable) { let disposable: Disposable? = self.lock.performLocked { if self.isDisposed { return newDisposable } else { let toDispose = self.current self.current = newDisposable return toDispose } } if let disposable = disposable { disposable.dispose() } } } /// Disposes the underlying disposable as well as all future replacements. public func dispose() { self._dispose()?.dispose() } private func _dispose() -> Disposable? { self.lock.performLocked { guard !self.isDisposed else { return nil } self.disposed = true let current = self.current self.current = nil return current } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/SingleAssignmentDisposable.swift ================================================ // // SingleAssignmentDisposable.swift // RxSwift // // Created by Krunoslav Zaher on 2/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /** Represents a disposable resource which only allows a single assignment of its underlying disposable resource. If an underlying disposable resource has already been set, future attempts to set the underlying disposable resource will throw an exception. */ public final class SingleAssignmentDisposable : DisposeBase, Cancelable { private struct DisposeState: OptionSet { let rawValue: Int32 static let disposed = DisposeState(rawValue: 1 << 0) static let disposableSet = DisposeState(rawValue: 1 << 1) } // state private let state = AtomicInt(0) private var disposable = nil as Disposable? /// - returns: A value that indicates whether the object is disposed. public var isDisposed: Bool { isFlagSet(self.state, DisposeState.disposed.rawValue) } /// Initializes a new instance of the `SingleAssignmentDisposable`. public override init() { super.init() } /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined. /// /// **Throws exception if the `SingleAssignmentDisposable` has already been assigned to.** public func setDisposable(_ disposable: Disposable) { self.disposable = disposable let previousState = fetchOr(self.state, DisposeState.disposableSet.rawValue) if (previousState & DisposeState.disposableSet.rawValue) != 0 { rxFatalError("oldState.disposable != nil") } if (previousState & DisposeState.disposed.rawValue) != 0 { disposable.dispose() self.disposable = nil } } /// Disposes the underlying disposable. public func dispose() { let previousState = fetchOr(self.state, DisposeState.disposed.rawValue) if (previousState & DisposeState.disposed.rawValue) != 0 { return } if (previousState & DisposeState.disposableSet.rawValue) != 0 { guard let disposable = self.disposable else { rxFatalError("Disposable not set") } disposable.dispose() self.disposable = nil } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Disposables/SubscriptionDisposable.swift ================================================ // // SubscriptionDisposable.swift // RxSwift // // Created by Krunoslav Zaher on 10/25/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // struct SubscriptionDisposable : Disposable { private let key: T.DisposeKey private weak var owner: T? init(owner: T, key: T.DisposeKey) { self.owner = owner self.key = key } func dispose() { self.owner?.synchronizedUnsubscribe(self.key) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Errors.swift ================================================ // // Errors.swift // RxSwift // // Created by Krunoslav Zaher on 3/28/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // let RxErrorDomain = "RxErrorDomain" let RxCompositeFailures = "RxCompositeFailures" /// Generic Rx error codes. public enum RxError : Swift.Error , CustomDebugStringConvertible { /// Unknown error occurred. case unknown /// Performing an action on disposed object. case disposed(object: AnyObject) /// Arithmetic overflow error. case overflow /// Argument out of range error. case argumentOutOfRange /// Sequence doesn't contain any elements. case noElements /// Sequence contains more than one element. case moreThanOneElement /// Timeout error. case timeout } extension RxError { /// A textual representation of `self`, suitable for debugging. public var debugDescription: String { switch self { case .unknown: return "Unknown error occurred." case .disposed(let object): return "Object `\(object)` was already disposed." case .overflow: return "Arithmetic overflow occurred." case .argumentOutOfRange: return "Argument out of range." case .noElements: return "Sequence doesn't contain any elements." case .moreThanOneElement: return "Sequence contains more than one element." case .timeout: return "Sequence timeout." } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Event.swift ================================================ // // Event.swift // RxSwift // // Created by Krunoslav Zaher on 2/8/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents a sequence event. /// /// Sequence grammar: /// **next\* (error | completed)** @frozen public enum Event { /// Next element is produced. case next(Element) /// Sequence terminated with an error. case error(Swift.Error) /// Sequence completed successfully. case completed } extension Event: CustomDebugStringConvertible { /// Description of event. public var debugDescription: String { switch self { case .next(let value): return "next(\(value))" case .error(let error): return "error(\(error))" case .completed: return "completed" } } } extension Event { /// Is `completed` or `error` event. public var isStopEvent: Bool { switch self { case .next: return false case .error, .completed: return true } } /// If `next` event, returns element value. public var element: Element? { if case .next(let value) = self { return value } return nil } /// If `error` event, returns error. public var error: Swift.Error? { if case .error(let error) = self { return error } return nil } /// If `completed` event, returns `true`. public var isCompleted: Bool { if case .completed = self { return true } return false } } extension Event { /// Maps sequence elements using transform. If error happens during the transform, `.error` /// will be returned as value. public func map(_ transform: (Element) throws -> Result) -> Event { do { switch self { case let .next(element): return .next(try transform(element)) case let .error(error): return .error(error) case .completed: return .completed } } catch let e { return .error(e) } } } /// A type that can be converted to `Event`. public protocol EventConvertible { /// Type of element in event associatedtype Element /// Event representation of this instance var event: Event { get } } extension Event: EventConvertible { /// Event representation of this instance public var event: Event { self } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Extensions/Bag+Rx.swift ================================================ // // Bag+Rx.swift // RxSwift // // Created by Krunoslav Zaher on 10/19/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // // MARK: forEach @inline(__always) func dispatch(_ bag: Bag<(Event) -> Void>, _ event: Event) { bag._value0?(event) if bag._onlyFastPath { return } let pairs = bag._pairs for i in 0 ..< pairs.count { pairs[i].value(event) } if let dictionary = bag._dictionary { for element in dictionary.values { element(event) } } } /// Dispatches `dispose` to all disposables contained inside bag. func disposeAll(in bag: Bag) { bag._value0?.dispose() if bag._onlyFastPath { return } let pairs = bag._pairs for i in 0 ..< pairs.count { pairs[i].value.dispose() } if let dictionary = bag._dictionary { for element in dictionary.values { element.dispose() } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/GroupedObservable.swift ================================================ // // GroupedObservable.swift // RxSwift // // Created by Tomi Koskinen on 01/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents an observable sequence of elements that have a common key. public struct GroupedObservable : ObservableType { /// Gets the common key. public let key: Key private let source: Observable /// Initializes grouped observable sequence with key and source observable sequence. /// /// - parameter key: Grouped observable sequence key /// - parameter source: Observable sequence that represents sequence of elements for the key /// - returns: Grouped observable sequence of elements for the specific key public init(key: Key, source: Observable) { self.key = key self.source = source } /// Subscribes `observer` to receive events for this sequence. public func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { self.source.subscribe(observer) } /// Converts `self` to `Observable` sequence. public func asObservable() -> Observable { self.source } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/ImmediateSchedulerType.swift ================================================ // // ImmediateSchedulerType.swift // RxSwift // // Created by Krunoslav Zaher on 5/31/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents an object that immediately schedules units of work. public protocol ImmediateSchedulerType { /** Schedules an action to be executed immediately. - parameter state: State passed to the action to be executed. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ func schedule(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable } extension ImmediateSchedulerType { /** Schedules an action to be executed recursively. - parameter state: State passed to the action to be executed. - parameter action: Action to execute recursively. The last parameter passed to the action is used to trigger recursive scheduling of the action, passing in recursive invocation state. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func scheduleRecursive(_ state: State, action: @escaping (_ state: State, _ recurse: (State) -> Void) -> Void) -> Disposable { let recursiveScheduler = RecursiveImmediateScheduler(action: action, scheduler: self) recursiveScheduler.schedule(state) return Disposables.create(with: recursiveScheduler.dispose) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observable+Concurrency.swift ================================================ // // Observable+Concurrency.swift // RxSwift // // Created by Shai Mishali on 22/09/2021. // Copyright © 2021 Krunoslav Zaher. All rights reserved. // #if swift(>=5.5.2) && canImport(_Concurrency) import Foundation @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public extension ObservableConvertibleType { /// Allows iterating over the values of an Observable /// asynchronously via Swift's concurrency features (`async/await`) /// /// A sample usage would look like so: /// /// ```swift /// do { /// for try await value in observable.values { /// // Handle emitted values /// } /// } catch { /// // Handle error /// } /// ``` var values: AsyncThrowingStream { AsyncThrowingStream { continuation in let disposable = asObservable().subscribe( onNext: { value in continuation.yield(value) }, onError: { error in continuation.finish(throwing: error) }, onCompleted: { continuation.finish() }, onDisposed: { continuation.onTermination?(.cancelled) } ) continuation.onTermination = { @Sendable _ in disposable.dispose() } } } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public extension AsyncSequence { /// Convert an `AsyncSequence` to an `Observable` emitting /// values of the asynchronous sequence's type /// /// - returns: An `Observable` of the async sequence's type func asObservable() -> Observable { Observable.create { observer in let task = Task { do { for try await value in self { observer.onNext(value) } observer.onCompleted() } catch { observer.onError(error) } } return Disposables.create { task.cancel() } } } } #endif ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observable.swift ================================================ // // Observable.swift // RxSwift // // Created by Krunoslav Zaher on 2/8/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// A type-erased `ObservableType`. /// /// It represents a push style sequence. public class Observable : ObservableType { init() { #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif } public func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { rxAbstractMethod() } public func asObservable() -> Observable { self } deinit { #if TRACE_RESOURCES _ = Resources.decrementTotal() #endif } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/ObservableConvertibleType.swift ================================================ // // ObservableConvertibleType.swift // RxSwift // // Created by Krunoslav Zaher on 9/17/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Type that can be converted to observable sequence (`Observable`). public protocol ObservableConvertibleType { /// Type of elements in sequence. associatedtype Element /// Converts `self` to `Observable` sequence. /// /// - returns: Observable sequence that represents `self`. func asObservable() -> Observable } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/ObservableType+Extensions.swift ================================================ // // ObservableType+Extensions.swift // RxSwift // // Created by Krunoslav Zaher on 2/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if DEBUG import Foundation #endif extension ObservableType { /** Subscribes an event handler to an observable sequence. - parameter on: Action to invoke for each event in the observable sequence. - returns: Subscription object used to unsubscribe from the observable sequence. */ public func subscribe(_ on: @escaping (Event) -> Void) -> Disposable { let observer = AnonymousObserver { e in on(e) } return self.asObservable().subscribe(observer) } /** Subscribes an element handler, an error handler, a completion handler and disposed handler to an observable sequence. Also, take in an object and provide an unretained, safe to use (i.e. not implicitly unwrapped), reference to it along with the events emitted by the sequence. - Note: If `object` can't be retained, none of the other closures will be invoked. - parameter object: The object to provide an unretained reference on. - parameter onNext: Action to invoke for each element in the observable sequence. - parameter onError: Action to invoke upon errored termination of the observable sequence. - parameter onCompleted: Action to invoke upon graceful termination of the observable sequence. - parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has gracefully completed, errored, or if the generation is canceled by disposing subscription). - returns: Subscription object used to unsubscribe from the observable sequence. */ public func subscribe( with object: Object, onNext: ((Object, Element) -> Void)? = nil, onError: ((Object, Swift.Error) -> Void)? = nil, onCompleted: ((Object) -> Void)? = nil, onDisposed: ((Object) -> Void)? = nil ) -> Disposable { subscribe( onNext: { [weak object] in guard let object = object else { return } onNext?(object, $0) }, onError: { [weak object] in guard let object = object else { return } onError?(object, $0) }, onCompleted: { [weak object] in guard let object = object else { return } onCompleted?(object) }, onDisposed: { [weak object] in guard let object = object else { return } onDisposed?(object) } ) } /** Subscribes an element handler, an error handler, a completion handler and disposed handler to an observable sequence. - parameter onNext: Action to invoke for each element in the observable sequence. - parameter onError: Action to invoke upon errored termination of the observable sequence. - parameter onCompleted: Action to invoke upon graceful termination of the observable sequence. - parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has gracefully completed, errored, or if the generation is canceled by disposing subscription). - returns: Subscription object used to unsubscribe from the observable sequence. */ public func subscribe( onNext: ((Element) -> Void)? = nil, onError: ((Swift.Error) -> Void)? = nil, onCompleted: (() -> Void)? = nil, onDisposed: (() -> Void)? = nil ) -> Disposable { let disposable: Disposable if let disposed = onDisposed { disposable = Disposables.create(with: disposed) } else { disposable = Disposables.create() } #if DEBUG let synchronizationTracker = SynchronizationTracker() #endif let callStack = Hooks.recordCallStackOnError ? Hooks.customCaptureSubscriptionCallstack() : [] let observer = AnonymousObserver { event in #if DEBUG synchronizationTracker.register(synchronizationErrorMessage: .default) defer { synchronizationTracker.unregister() } #endif switch event { case .next(let value): onNext?(value) case .error(let error): if let onError = onError { onError(error) } else { Hooks.defaultErrorHandler(callStack, error) } disposable.dispose() case .completed: onCompleted?() disposable.dispose() } } return Disposables.create( self.asObservable().subscribe(observer), disposable ) } } import Foundation extension Hooks { public typealias DefaultErrorHandler = (_ subscriptionCallStack: [String], _ error: Error) -> Void public typealias CustomCaptureSubscriptionCallstack = () -> [String] private static let lock = RecursiveLock() private static var _defaultErrorHandler: DefaultErrorHandler = { subscriptionCallStack, error in #if DEBUG let serializedCallStack = subscriptionCallStack.joined(separator: "\n") print("Unhandled error happened: \(error)") if !serializedCallStack.isEmpty { print("subscription called from:\n\(serializedCallStack)") } #endif } private static var _customCaptureSubscriptionCallstack: CustomCaptureSubscriptionCallstack = { #if DEBUG return Thread.callStackSymbols #else return [] #endif } /// Error handler called in case onError handler wasn't provided. public static var defaultErrorHandler: DefaultErrorHandler { get { lock.performLocked { _defaultErrorHandler } } set { lock.performLocked { _defaultErrorHandler = newValue } } } /// Subscription callstack block to fetch custom callstack information. public static var customCaptureSubscriptionCallstack: CustomCaptureSubscriptionCallstack { get { lock.performLocked { _customCaptureSubscriptionCallstack } } set { lock.performLocked { _customCaptureSubscriptionCallstack = newValue } } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/ObservableType.swift ================================================ // // ObservableType.swift // RxSwift // // Created by Krunoslav Zaher on 8/8/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Represents a push style sequence. public protocol ObservableType: ObservableConvertibleType { /** Subscribes `observer` to receive events for this sequence. ### Grammar **Next\* (Error | Completed)?** * sequences can produce zero or more elements so zero or more `Next` events can be sent to `observer` * once an `Error` or `Completed` event is sent, the sequence terminates and can't produce any other elements It is possible that events are sent from different threads, but no two events can be sent concurrently to `observer`. ### Resource Management When sequence sends `Complete` or `Error` event all internal resources that compute sequence elements will be freed. To cancel production of sequence elements and free resources immediately, call `dispose` on returned subscription. - returns: Subscription for `observer` that can be used to cancel production of sequence elements and free resources. */ func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element } extension ObservableType { /// Default implementation of converting `ObservableType` to `Observable`. public func asObservable() -> Observable { // temporary workaround //return Observable.create(subscribe: self.subscribe) Observable.create { o in self.subscribe(o) } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/AddRef.swift ================================================ // // AddRef.swift // RxSwift // // Created by Junior B. on 30/10/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // final class AddRefSink : Sink, ObserverType { typealias Element = Observer.Element override init(observer: Observer, cancel: Cancelable) { super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next: self.forwardOn(event) case .completed, .error: self.forwardOn(event) self.dispose() } } } final class AddRef : Producer { private let source: Observable private let refCount: RefCountDisposable init(source: Observable, refCount: RefCountDisposable) { self.source = source self.refCount = refCount } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let releaseDisposable = self.refCount.retain() let sink = AddRefSink(observer: observer, cancel: cancel) let subscription = Disposables.create(releaseDisposable, self.source.subscribe(sink)) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Amb.swift ================================================ // // Amb.swift // RxSwift // // Created by Krunoslav Zaher on 6/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Propagates the observable sequence that reacts first. - seealso: [amb operator on reactivex.io](http://reactivex.io/documentation/operators/amb.html) - returns: An observable sequence that surfaces any of the given sequences, whichever reacted first. */ public static func amb(_ sequence: Sequence) -> Observable where Sequence.Element == Observable { sequence.reduce(Observable.never()) { a, o in a.amb(o.asObservable()) } } } extension ObservableType { /** Propagates the observable sequence that reacts first. - seealso: [amb operator on reactivex.io](http://reactivex.io/documentation/operators/amb.html) - parameter right: Second observable sequence. - returns: An observable sequence that surfaces either of the given sequences, whichever reacted first. */ public func amb (_ right: O2) -> Observable where O2.Element == Element { Amb(left: self.asObservable(), right: right.asObservable()) } } private enum AmbState { case neither case left case right } final private class AmbObserver: ObserverType { typealias Element = Observer.Element typealias Parent = AmbSink typealias This = AmbObserver typealias Sink = (This, Event) -> Void private let parent: Parent fileprivate var sink: Sink fileprivate var cancel: Disposable init(parent: Parent, cancel: Disposable, sink: @escaping Sink) { #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif self.parent = parent self.sink = sink self.cancel = cancel } func on(_ event: Event) { self.sink(self, event) if event.isStopEvent { self.cancel.dispose() } } deinit { #if TRACE_RESOURCES _ = Resources.decrementTotal() #endif } } final private class AmbSink: Sink { typealias Element = Observer.Element typealias Parent = Amb typealias AmbObserverType = AmbObserver private let parent: Parent private let lock = RecursiveLock() // state private var choice = AmbState.neither init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let disposeAll = Disposables.create(subscription1, subscription2) let forwardEvent = { (o: AmbObserverType, event: Event) -> Void in self.forwardOn(event) if event.isStopEvent { self.dispose() } } let decide = { (o: AmbObserverType, event: Event, me: AmbState, otherSubscription: Disposable) in self.lock.performLocked { if self.choice == .neither { self.choice = me o.sink = forwardEvent o.cancel = disposeAll otherSubscription.dispose() } if self.choice == me { self.forwardOn(event) if event.isStopEvent { self.dispose() } } } } let sink1 = AmbObserver(parent: self, cancel: subscription1) { o, e in decide(o, e, .left, subscription2) } let sink2 = AmbObserver(parent: self, cancel: subscription1) { o, e in decide(o, e, .right, subscription1) } subscription1.setDisposable(self.parent.left.subscribe(sink1)) subscription2.setDisposable(self.parent.right.subscribe(sink2)) return disposeAll } } final private class Amb: Producer { fileprivate let left: Observable fileprivate let right: Observable init(left: Observable, right: Observable) { self.left = left self.right = right } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = AmbSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/AsMaybe.swift ================================================ // // AsMaybe.swift // RxSwift // // Created by Krunoslav Zaher on 3/12/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // private final class AsMaybeSink : Sink, ObserverType { typealias Element = Observer.Element private var element: Event? func on(_ event: Event) { switch event { case .next: if self.element != nil { self.forwardOn(.error(RxError.moreThanOneElement)) self.dispose() } self.element = event case .error: self.forwardOn(event) self.dispose() case .completed: if let element = self.element { self.forwardOn(element) } self.forwardOn(.completed) self.dispose() } } } final class AsMaybe: Producer { private let source: Observable init(source: Observable) { self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = AsMaybeSink(observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/AsSingle.swift ================================================ // // AsSingle.swift // RxSwift // // Created by Krunoslav Zaher on 3/12/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // private final class AsSingleSink : Sink, ObserverType { typealias Element = Observer.Element private var element: Event? func on(_ event: Event) { switch event { case .next: if self.element != nil { self.forwardOn(.error(RxError.moreThanOneElement)) self.dispose() } self.element = event case .error: self.forwardOn(event) self.dispose() case .completed: if let element = self.element { self.forwardOn(element) self.forwardOn(.completed) } else { self.forwardOn(.error(RxError.noElements)) } self.dispose() } } } final class AsSingle: Producer { private let source: Observable init(source: Observable) { self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = AsSingleSink(observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Buffer.swift ================================================ // // Buffer.swift // RxSwift // // Created by Krunoslav Zaher on 9/13/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Projects each element of an observable sequence into a buffer that's sent out when either it's full or a given amount of time has elapsed, using the specified scheduler to run timers. A useful real-world analogy of this overload is the behavior of a ferry leaving the dock when all seats are taken, or at the scheduled time of departure, whichever event occurs first. - seealso: [buffer operator on reactivex.io](http://reactivex.io/documentation/operators/buffer.html) - parameter timeSpan: Maximum time length of a buffer. - parameter count: Maximum element count of a buffer. - parameter scheduler: Scheduler to run buffering timers on. - returns: An observable sequence of buffers. */ public func buffer(timeSpan: RxTimeInterval, count: Int, scheduler: SchedulerType) -> Observable<[Element]> { BufferTimeCount(source: self.asObservable(), timeSpan: timeSpan, count: count, scheduler: scheduler) } } final private class BufferTimeCount: Producer<[Element]> { fileprivate let timeSpan: RxTimeInterval fileprivate let count: Int fileprivate let scheduler: SchedulerType fileprivate let source: Observable init(source: Observable, timeSpan: RxTimeInterval, count: Int, scheduler: SchedulerType) { self.source = source self.timeSpan = timeSpan self.count = count self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == [Element] { let sink = BufferTimeCountSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } final private class BufferTimeCountSink : Sink , LockOwnerType , ObserverType , SynchronizedOnType where Observer.Element == [Element] { typealias Parent = BufferTimeCount private let parent: Parent let lock = RecursiveLock() // state private let timerD = SerialDisposable() private var buffer = [Element]() private var windowID = 0 init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { self.createTimer(self.windowID) return Disposables.create(timerD, parent.source.subscribe(self)) } func startNewWindowAndSendCurrentOne() { self.windowID = self.windowID &+ 1 let windowID = self.windowID let buffer = self.buffer self.buffer = [] self.forwardOn(.next(buffer)) self.createTimer(windowID) } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next(let element): self.buffer.append(element) if self.buffer.count == self.parent.count { self.startNewWindowAndSendCurrentOne() } case .error(let error): self.buffer = [] self.forwardOn(.error(error)) self.dispose() case .completed: self.forwardOn(.next(self.buffer)) self.forwardOn(.completed) self.dispose() } } func createTimer(_ windowID: Int) { if self.timerD.isDisposed { return } if self.windowID != windowID { return } let nextTimer = SingleAssignmentDisposable() self.timerD.disposable = nextTimer let disposable = self.parent.scheduler.scheduleRelative(windowID, dueTime: self.parent.timeSpan) { previousWindowID in self.lock.performLocked { if previousWindowID != self.windowID { return } self.startNewWindowAndSendCurrentOne() } return Disposables.create() } nextTimer.setDisposable(disposable) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Catch.swift ================================================ // // Catch.swift // RxSwift // // Created by Krunoslav Zaher on 4/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Continues an observable sequence that is terminated by an error with the observable sequence produced by the handler. - seealso: [catch operator on reactivex.io](http://reactivex.io/documentation/operators/catch.html) - parameter handler: Error handler function, producing another observable sequence. - returns: An observable sequence containing the source sequence's elements, followed by the elements produced by the handler's resulting observable sequence in case an error occurred. */ public func `catch`(_ handler: @escaping (Swift.Error) throws -> Observable) -> Observable { Catch(source: self.asObservable(), handler: handler) } /** Continues an observable sequence that is terminated by an error with the observable sequence produced by the handler. - seealso: [catch operator on reactivex.io](http://reactivex.io/documentation/operators/catch.html) - parameter handler: Error handler function, producing another observable sequence. - returns: An observable sequence containing the source sequence's elements, followed by the elements produced by the handler's resulting observable sequence in case an error occurred. */ @available(*, deprecated, renamed: "catch(_:)") public func catchError(_ handler: @escaping (Swift.Error) throws -> Observable) -> Observable { `catch`(handler) } /** Continues an observable sequence that is terminated by an error with a single element. - seealso: [catch operator on reactivex.io](http://reactivex.io/documentation/operators/catch.html) - parameter element: Last element in an observable sequence in case error occurs. - returns: An observable sequence containing the source sequence's elements, followed by the `element` in case an error occurred. */ public func catchAndReturn(_ element: Element) -> Observable { Catch(source: self.asObservable(), handler: { _ in Observable.just(element) }) } /** Continues an observable sequence that is terminated by an error with a single element. - seealso: [catch operator on reactivex.io](http://reactivex.io/documentation/operators/catch.html) - parameter element: Last element in an observable sequence in case error occurs. - returns: An observable sequence containing the source sequence's elements, followed by the `element` in case an error occurred. */ @available(*, deprecated, renamed: "catchAndReturn(_:)") public func catchErrorJustReturn(_ element: Element) -> Observable { catchAndReturn(element) } } extension ObservableType { /** Continues an observable sequence that is terminated by an error with the next observable sequence. - seealso: [catch operator on reactivex.io](http://reactivex.io/documentation/operators/catch.html) - returns: An observable sequence containing elements from consecutive source sequences until a source sequence terminates successfully. */ @available(*, deprecated, renamed: "catch(onSuccess:onFailure:onDisposed:)") public static func catchError(_ sequence: Sequence) -> Observable where Sequence.Element == Observable { `catch`(sequence: sequence) } /** Continues an observable sequence that is terminated by an error with the next observable sequence. - seealso: [catch operator on reactivex.io](http://reactivex.io/documentation/operators/catch.html) - returns: An observable sequence containing elements from consecutive source sequences until a source sequence terminates successfully. */ public static func `catch`(sequence: Sequence) -> Observable where Sequence.Element == Observable { CatchSequence(sources: sequence) } } extension ObservableType { /** Repeats the source observable sequence until it successfully terminates. **This could potentially create an infinite sequence.** - seealso: [retry operator on reactivex.io](http://reactivex.io/documentation/operators/retry.html) - returns: Observable sequence to repeat until it successfully terminates. */ public func retry() -> Observable { CatchSequence(sources: InfiniteSequence(repeatedValue: self.asObservable())) } /** Repeats the source observable sequence the specified number of times in case of an error or until it successfully terminates. If you encounter an error and want it to retry once, then you must use `retry(2)` - seealso: [retry operator on reactivex.io](http://reactivex.io/documentation/operators/retry.html) - parameter maxAttemptCount: Maximum number of times to repeat the sequence. - returns: An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully. */ public func retry(_ maxAttemptCount: Int) -> Observable { CatchSequence(sources: Swift.repeatElement(self.asObservable(), count: maxAttemptCount)) } } // catch with callback final private class CatchSinkProxy: ObserverType { typealias Element = Observer.Element typealias Parent = CatchSink private let parent: Parent init(parent: Parent) { self.parent = parent } func on(_ event: Event) { self.parent.forwardOn(event) switch event { case .next: break case .error, .completed: self.parent.dispose() } } } final private class CatchSink: Sink, ObserverType { typealias Element = Observer.Element typealias Parent = Catch private let parent: Parent private let subscription = SerialDisposable() init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { let d1 = SingleAssignmentDisposable() self.subscription.disposable = d1 d1.setDisposable(self.parent.source.subscribe(self)) return self.subscription } func on(_ event: Event) { switch event { case .next: self.forwardOn(event) case .completed: self.forwardOn(event) self.dispose() case .error(let error): do { let catchSequence = try self.parent.handler(error) let observer = CatchSinkProxy(parent: self) self.subscription.disposable = catchSequence.subscribe(observer) } catch let e { self.forwardOn(.error(e)) self.dispose() } } } } final private class Catch: Producer { typealias Handler = (Swift.Error) throws -> Observable fileprivate let source: Observable fileprivate let handler: Handler init(source: Observable, handler: @escaping Handler) { self.source = source self.handler = handler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = CatchSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // catch enumerable final private class CatchSequenceSink : TailRecursiveSink , ObserverType where Sequence.Element: ObservableConvertibleType, Sequence.Element.Element == Observer.Element { typealias Element = Observer.Element typealias Parent = CatchSequence private var lastError: Swift.Error? override init(observer: Observer, cancel: Cancelable) { super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next: self.forwardOn(event) case .error(let error): self.lastError = error self.schedule(.moveNext) case .completed: self.forwardOn(event) self.dispose() } } override func subscribeToNext(_ source: Observable) -> Disposable { source.subscribe(self) } override func done() { if let lastError = self.lastError { self.forwardOn(.error(lastError)) } else { self.forwardOn(.completed) } self.dispose() } override func extract(_ observable: Observable) -> SequenceGenerator? { if let onError = observable as? CatchSequence { return (onError.sources.makeIterator(), nil) } else { return nil } } } final private class CatchSequence: Producer where Sequence.Element: ObservableConvertibleType { typealias Element = Sequence.Element.Element let sources: Sequence init(sources: Sequence) { self.sources = sources } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = CatchSequenceSink(observer: observer, cancel: cancel) let subscription = sink.run((self.sources.makeIterator(), nil)) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/CombineLatest+Collection.swift ================================================ // // CombineLatest+Collection.swift // RxSwift // // Created by Krunoslav Zaher on 8/29/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - seealso: [combinelatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest(_ collection: Collection, resultSelector: @escaping ([Collection.Element.Element]) throws -> Element) -> Observable where Collection.Element: ObservableType { CombineLatestCollectionType(sources: collection, resultSelector: resultSelector) } /** Merges the specified observable sequences into one observable sequence whenever any of the observable sequences produces an element. - seealso: [combinelatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest(_ collection: Collection) -> Observable<[Element]> where Collection.Element: ObservableType, Collection.Element.Element == Element { CombineLatestCollectionType(sources: collection, resultSelector: { $0 }) } } final private class CombineLatestCollectionTypeSink : Sink where Collection.Element: ObservableConvertibleType { typealias Result = Observer.Element typealias Parent = CombineLatestCollectionType typealias SourceElement = Collection.Element.Element let parent: Parent let lock = RecursiveLock() // state var numberOfValues = 0 var values: [SourceElement?] var isDone: [Bool] var numberOfDone = 0 var subscriptions: [SingleAssignmentDisposable] init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.values = [SourceElement?](repeating: nil, count: parent.count) self.isDone = [Bool](repeating: false, count: parent.count) self.subscriptions = [SingleAssignmentDisposable]() self.subscriptions.reserveCapacity(parent.count) for _ in 0 ..< parent.count { self.subscriptions.append(SingleAssignmentDisposable()) } super.init(observer: observer, cancel: cancel) } func on(_ event: Event, atIndex: Int) { self.lock.lock(); defer { self.lock.unlock() } switch event { case .next(let element): if self.values[atIndex] == nil { self.numberOfValues += 1 } self.values[atIndex] = element if self.numberOfValues < self.parent.count { let numberOfOthersThatAreDone = self.numberOfDone - (self.isDone[atIndex] ? 1 : 0) if numberOfOthersThatAreDone == self.parent.count - 1 { self.forwardOn(.completed) self.dispose() } return } do { let result = try self.parent.resultSelector(self.values.map { $0! }) self.forwardOn(.next(result)) } catch let error { self.forwardOn(.error(error)) self.dispose() } case .error(let error): self.forwardOn(.error(error)) self.dispose() case .completed: if self.isDone[atIndex] { return } self.isDone[atIndex] = true self.numberOfDone += 1 if self.numberOfDone == self.parent.count { self.forwardOn(.completed) self.dispose() } else { self.subscriptions[atIndex].dispose() } } } func run() -> Disposable { var j = 0 for i in self.parent.sources { let index = j let source = i.asObservable() let disposable = source.subscribe(AnyObserver { event in self.on(event, atIndex: index) }) self.subscriptions[j].setDisposable(disposable) j += 1 } if self.parent.sources.isEmpty { do { let result = try self.parent.resultSelector([]) self.forwardOn(.next(result)) self.forwardOn(.completed) self.dispose() } catch let error { self.forwardOn(.error(error)) self.dispose() } } return Disposables.create(subscriptions) } } final private class CombineLatestCollectionType: Producer where Collection.Element: ObservableConvertibleType { typealias ResultSelector = ([Collection.Element.Element]) throws -> Result let sources: Collection let resultSelector: ResultSelector let count: Int init(sources: Collection, resultSelector: @escaping ResultSelector) { self.sources = sources self.resultSelector = resultSelector self.count = self.sources.count } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = CombineLatestCollectionTypeSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/CombineLatest+arity.swift ================================================ // This file is autogenerated. Take a look at `Preprocessor` target in RxSwift project // // CombineLatest+arity.swift // RxSwift // // Created by Krunoslav Zaher on 4/22/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // // 2 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, resultSelector: @escaping (O1.Element, O2.Element) throws -> Element) -> Observable { return CombineLatest2( source1: source1.asObservable(), source2: source2.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2) -> Observable<(O1.Element, O2.Element)> { return CombineLatest2( source1: source1.asObservable(), source2: source2.asObservable(), resultSelector: { ($0, $1) } ) } } final class CombineLatestSink2_ : CombineLatestSink { typealias Result = Observer.Element typealias Parent = CombineLatest2 let parent: Parent var latestElement1: E1! = nil var latestElement2: E2! = nil init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 2, observer: observer, cancel: cancel) } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let observer1 = CombineLatestObserver(lock: self.lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self.latestElement1 = e }, this: subscription1) let observer2 = CombineLatestObserver(lock: self.lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self.latestElement2 = e }, this: subscription2) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) return Disposables.create([ subscription1, subscription2 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.latestElement1, self.latestElement2) } } final class CombineLatest2 : Producer { typealias ResultSelector = (E1, E2) throws -> Result let source1: Observable let source2: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = CombineLatestSink2_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 3 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, resultSelector: @escaping (O1.Element, O2.Element, O3.Element) throws -> Element) -> Observable { return CombineLatest3( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3) -> Observable<(O1.Element, O2.Element, O3.Element)> { return CombineLatest3( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), resultSelector: { ($0, $1, $2) } ) } } final class CombineLatestSink3_ : CombineLatestSink { typealias Result = Observer.Element typealias Parent = CombineLatest3 let parent: Parent var latestElement1: E1! = nil var latestElement2: E2! = nil var latestElement3: E3! = nil init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 3, observer: observer, cancel: cancel) } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let observer1 = CombineLatestObserver(lock: self.lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self.latestElement1 = e }, this: subscription1) let observer2 = CombineLatestObserver(lock: self.lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self.latestElement2 = e }, this: subscription2) let observer3 = CombineLatestObserver(lock: self.lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self.latestElement3 = e }, this: subscription3) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) return Disposables.create([ subscription1, subscription2, subscription3 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.latestElement1, self.latestElement2, self.latestElement3) } } final class CombineLatest3 : Producer { typealias ResultSelector = (E1, E2, E3) throws -> Result let source1: Observable let source2: Observable let source3: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = CombineLatestSink3_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 4 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element) throws -> Element) -> Observable { return CombineLatest4( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4) -> Observable<(O1.Element, O2.Element, O3.Element, O4.Element)> { return CombineLatest4( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), resultSelector: { ($0, $1, $2, $3) } ) } } final class CombineLatestSink4_ : CombineLatestSink { typealias Result = Observer.Element typealias Parent = CombineLatest4 let parent: Parent var latestElement1: E1! = nil var latestElement2: E2! = nil var latestElement3: E3! = nil var latestElement4: E4! = nil init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 4, observer: observer, cancel: cancel) } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let subscription4 = SingleAssignmentDisposable() let observer1 = CombineLatestObserver(lock: self.lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self.latestElement1 = e }, this: subscription1) let observer2 = CombineLatestObserver(lock: self.lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self.latestElement2 = e }, this: subscription2) let observer3 = CombineLatestObserver(lock: self.lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self.latestElement3 = e }, this: subscription3) let observer4 = CombineLatestObserver(lock: self.lock, parent: self, index: 3, setLatestValue: { (e: E4) -> Void in self.latestElement4 = e }, this: subscription4) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) subscription4.setDisposable(self.parent.source4.subscribe(observer4)) return Disposables.create([ subscription1, subscription2, subscription3, subscription4 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.latestElement1, self.latestElement2, self.latestElement3, self.latestElement4) } } final class CombineLatest4 : Producer { typealias ResultSelector = (E1, E2, E3, E4) throws -> Result let source1: Observable let source2: Observable let source3: Observable let source4: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, source4: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.source4 = source4 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = CombineLatestSink4_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 5 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element) throws -> Element) -> Observable { return CombineLatest5( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5) -> Observable<(O1.Element, O2.Element, O3.Element, O4.Element, O5.Element)> { return CombineLatest5( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), resultSelector: { ($0, $1, $2, $3, $4) } ) } } final class CombineLatestSink5_ : CombineLatestSink { typealias Result = Observer.Element typealias Parent = CombineLatest5 let parent: Parent var latestElement1: E1! = nil var latestElement2: E2! = nil var latestElement3: E3! = nil var latestElement4: E4! = nil var latestElement5: E5! = nil init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 5, observer: observer, cancel: cancel) } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let subscription4 = SingleAssignmentDisposable() let subscription5 = SingleAssignmentDisposable() let observer1 = CombineLatestObserver(lock: self.lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self.latestElement1 = e }, this: subscription1) let observer2 = CombineLatestObserver(lock: self.lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self.latestElement2 = e }, this: subscription2) let observer3 = CombineLatestObserver(lock: self.lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self.latestElement3 = e }, this: subscription3) let observer4 = CombineLatestObserver(lock: self.lock, parent: self, index: 3, setLatestValue: { (e: E4) -> Void in self.latestElement4 = e }, this: subscription4) let observer5 = CombineLatestObserver(lock: self.lock, parent: self, index: 4, setLatestValue: { (e: E5) -> Void in self.latestElement5 = e }, this: subscription5) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) subscription4.setDisposable(self.parent.source4.subscribe(observer4)) subscription5.setDisposable(self.parent.source5.subscribe(observer5)) return Disposables.create([ subscription1, subscription2, subscription3, subscription4, subscription5 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.latestElement1, self.latestElement2, self.latestElement3, self.latestElement4, self.latestElement5) } } final class CombineLatest5 : Producer { typealias ResultSelector = (E1, E2, E3, E4, E5) throws -> Result let source1: Observable let source2: Observable let source3: Observable let source4: Observable let source5: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, source4: Observable, source5: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.source4 = source4 self.source5 = source5 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = CombineLatestSink5_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 6 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element) throws -> Element) -> Observable { return CombineLatest6( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6) -> Observable<(O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element)> { return CombineLatest6( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), resultSelector: { ($0, $1, $2, $3, $4, $5) } ) } } final class CombineLatestSink6_ : CombineLatestSink { typealias Result = Observer.Element typealias Parent = CombineLatest6 let parent: Parent var latestElement1: E1! = nil var latestElement2: E2! = nil var latestElement3: E3! = nil var latestElement4: E4! = nil var latestElement5: E5! = nil var latestElement6: E6! = nil init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 6, observer: observer, cancel: cancel) } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let subscription4 = SingleAssignmentDisposable() let subscription5 = SingleAssignmentDisposable() let subscription6 = SingleAssignmentDisposable() let observer1 = CombineLatestObserver(lock: self.lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self.latestElement1 = e }, this: subscription1) let observer2 = CombineLatestObserver(lock: self.lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self.latestElement2 = e }, this: subscription2) let observer3 = CombineLatestObserver(lock: self.lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self.latestElement3 = e }, this: subscription3) let observer4 = CombineLatestObserver(lock: self.lock, parent: self, index: 3, setLatestValue: { (e: E4) -> Void in self.latestElement4 = e }, this: subscription4) let observer5 = CombineLatestObserver(lock: self.lock, parent: self, index: 4, setLatestValue: { (e: E5) -> Void in self.latestElement5 = e }, this: subscription5) let observer6 = CombineLatestObserver(lock: self.lock, parent: self, index: 5, setLatestValue: { (e: E6) -> Void in self.latestElement6 = e }, this: subscription6) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) subscription4.setDisposable(self.parent.source4.subscribe(observer4)) subscription5.setDisposable(self.parent.source5.subscribe(observer5)) subscription6.setDisposable(self.parent.source6.subscribe(observer6)) return Disposables.create([ subscription1, subscription2, subscription3, subscription4, subscription5, subscription6 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.latestElement1, self.latestElement2, self.latestElement3, self.latestElement4, self.latestElement5, self.latestElement6) } } final class CombineLatest6 : Producer { typealias ResultSelector = (E1, E2, E3, E4, E5, E6) throws -> Result let source1: Observable let source2: Observable let source3: Observable let source4: Observable let source5: Observable let source6: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, source4: Observable, source5: Observable, source6: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.source4 = source4 self.source5 = source5 self.source6 = source6 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = CombineLatestSink6_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 7 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element) throws -> Element) -> Observable { return CombineLatest7( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), source7: source7.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7) -> Observable<(O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element)> { return CombineLatest7( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), source7: source7.asObservable(), resultSelector: { ($0, $1, $2, $3, $4, $5, $6) } ) } } final class CombineLatestSink7_ : CombineLatestSink { typealias Result = Observer.Element typealias Parent = CombineLatest7 let parent: Parent var latestElement1: E1! = nil var latestElement2: E2! = nil var latestElement3: E3! = nil var latestElement4: E4! = nil var latestElement5: E5! = nil var latestElement6: E6! = nil var latestElement7: E7! = nil init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 7, observer: observer, cancel: cancel) } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let subscription4 = SingleAssignmentDisposable() let subscription5 = SingleAssignmentDisposable() let subscription6 = SingleAssignmentDisposable() let subscription7 = SingleAssignmentDisposable() let observer1 = CombineLatestObserver(lock: self.lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self.latestElement1 = e }, this: subscription1) let observer2 = CombineLatestObserver(lock: self.lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self.latestElement2 = e }, this: subscription2) let observer3 = CombineLatestObserver(lock: self.lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self.latestElement3 = e }, this: subscription3) let observer4 = CombineLatestObserver(lock: self.lock, parent: self, index: 3, setLatestValue: { (e: E4) -> Void in self.latestElement4 = e }, this: subscription4) let observer5 = CombineLatestObserver(lock: self.lock, parent: self, index: 4, setLatestValue: { (e: E5) -> Void in self.latestElement5 = e }, this: subscription5) let observer6 = CombineLatestObserver(lock: self.lock, parent: self, index: 5, setLatestValue: { (e: E6) -> Void in self.latestElement6 = e }, this: subscription6) let observer7 = CombineLatestObserver(lock: self.lock, parent: self, index: 6, setLatestValue: { (e: E7) -> Void in self.latestElement7 = e }, this: subscription7) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) subscription4.setDisposable(self.parent.source4.subscribe(observer4)) subscription5.setDisposable(self.parent.source5.subscribe(observer5)) subscription6.setDisposable(self.parent.source6.subscribe(observer6)) subscription7.setDisposable(self.parent.source7.subscribe(observer7)) return Disposables.create([ subscription1, subscription2, subscription3, subscription4, subscription5, subscription6, subscription7 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.latestElement1, self.latestElement2, self.latestElement3, self.latestElement4, self.latestElement5, self.latestElement6, self.latestElement7) } } final class CombineLatest7 : Producer { typealias ResultSelector = (E1, E2, E3, E4, E5, E6, E7) throws -> Result let source1: Observable let source2: Observable let source3: Observable let source4: Observable let source5: Observable let source6: Observable let source7: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, source4: Observable, source5: Observable, source6: Observable, source7: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.source4 = source4 self.source5 = source5 self.source6 = source6 self.source7 = source7 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = CombineLatestSink7_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 8 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - parameter resultSelector: Function to invoke whenever any of the sources produces an element. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element, O8.Element) throws -> Element) -> Observable { return CombineLatest8( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), source7: source7.asObservable(), source8: source8.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever any of the observable sequences produces an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func combineLatest (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8) -> Observable<(O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element, O8.Element)> { return CombineLatest8( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), source7: source7.asObservable(), source8: source8.asObservable(), resultSelector: { ($0, $1, $2, $3, $4, $5, $6, $7) } ) } } final class CombineLatestSink8_ : CombineLatestSink { typealias Result = Observer.Element typealias Parent = CombineLatest8 let parent: Parent var latestElement1: E1! = nil var latestElement2: E2! = nil var latestElement3: E3! = nil var latestElement4: E4! = nil var latestElement5: E5! = nil var latestElement6: E6! = nil var latestElement7: E7! = nil var latestElement8: E8! = nil init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 8, observer: observer, cancel: cancel) } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let subscription4 = SingleAssignmentDisposable() let subscription5 = SingleAssignmentDisposable() let subscription6 = SingleAssignmentDisposable() let subscription7 = SingleAssignmentDisposable() let subscription8 = SingleAssignmentDisposable() let observer1 = CombineLatestObserver(lock: self.lock, parent: self, index: 0, setLatestValue: { (e: E1) -> Void in self.latestElement1 = e }, this: subscription1) let observer2 = CombineLatestObserver(lock: self.lock, parent: self, index: 1, setLatestValue: { (e: E2) -> Void in self.latestElement2 = e }, this: subscription2) let observer3 = CombineLatestObserver(lock: self.lock, parent: self, index: 2, setLatestValue: { (e: E3) -> Void in self.latestElement3 = e }, this: subscription3) let observer4 = CombineLatestObserver(lock: self.lock, parent: self, index: 3, setLatestValue: { (e: E4) -> Void in self.latestElement4 = e }, this: subscription4) let observer5 = CombineLatestObserver(lock: self.lock, parent: self, index: 4, setLatestValue: { (e: E5) -> Void in self.latestElement5 = e }, this: subscription5) let observer6 = CombineLatestObserver(lock: self.lock, parent: self, index: 5, setLatestValue: { (e: E6) -> Void in self.latestElement6 = e }, this: subscription6) let observer7 = CombineLatestObserver(lock: self.lock, parent: self, index: 6, setLatestValue: { (e: E7) -> Void in self.latestElement7 = e }, this: subscription7) let observer8 = CombineLatestObserver(lock: self.lock, parent: self, index: 7, setLatestValue: { (e: E8) -> Void in self.latestElement8 = e }, this: subscription8) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) subscription4.setDisposable(self.parent.source4.subscribe(observer4)) subscription5.setDisposable(self.parent.source5.subscribe(observer5)) subscription6.setDisposable(self.parent.source6.subscribe(observer6)) subscription7.setDisposable(self.parent.source7.subscribe(observer7)) subscription8.setDisposable(self.parent.source8.subscribe(observer8)) return Disposables.create([ subscription1, subscription2, subscription3, subscription4, subscription5, subscription6, subscription7, subscription8 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.latestElement1, self.latestElement2, self.latestElement3, self.latestElement4, self.latestElement5, self.latestElement6, self.latestElement7, self.latestElement8) } } final class CombineLatest8 : Producer { typealias ResultSelector = (E1, E2, E3, E4, E5, E6, E7, E8) throws -> Result let source1: Observable let source2: Observable let source3: Observable let source4: Observable let source5: Observable let source6: Observable let source7: Observable let source8: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, source4: Observable, source5: Observable, source6: Observable, source7: Observable, source8: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.source4 = source4 self.source5 = source5 self.source6 = source6 self.source7 = source7 self.source8 = source8 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = CombineLatestSink8_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/CombineLatest.swift ================================================ // // CombineLatest.swift // RxSwift // // Created by Krunoslav Zaher on 3/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // protocol CombineLatestProtocol: AnyObject { func next(_ index: Int) func fail(_ error: Swift.Error) func done(_ index: Int) } class CombineLatestSink : Sink , CombineLatestProtocol { typealias Element = Observer.Element let lock = RecursiveLock() private let arity: Int private var numberOfValues = 0 private var numberOfDone = 0 private var hasValue: [Bool] private var isDone: [Bool] init(arity: Int, observer: Observer, cancel: Cancelable) { self.arity = arity self.hasValue = [Bool](repeating: false, count: arity) self.isDone = [Bool](repeating: false, count: arity) super.init(observer: observer, cancel: cancel) } func getResult() throws -> Element { rxAbstractMethod() } func next(_ index: Int) { if !self.hasValue[index] { self.hasValue[index] = true self.numberOfValues += 1 } if self.numberOfValues == self.arity { do { let result = try self.getResult() self.forwardOn(.next(result)) } catch let e { self.forwardOn(.error(e)) self.dispose() } } else { var allOthersDone = true for i in 0 ..< self.arity { if i != index && !self.isDone[i] { allOthersDone = false break } } if allOthersDone { self.forwardOn(.completed) self.dispose() } } } func fail(_ error: Swift.Error) { self.forwardOn(.error(error)) self.dispose() } func done(_ index: Int) { if self.isDone[index] { return } self.isDone[index] = true self.numberOfDone += 1 if self.numberOfDone == self.arity { self.forwardOn(.completed) self.dispose() } } } final class CombineLatestObserver : ObserverType , LockOwnerType , SynchronizedOnType { typealias ValueSetter = (Element) -> Void private let parent: CombineLatestProtocol let lock: RecursiveLock private let index: Int private let this: Disposable private let setLatestValue: ValueSetter init(lock: RecursiveLock, parent: CombineLatestProtocol, index: Int, setLatestValue: @escaping ValueSetter, this: Disposable) { self.lock = lock self.parent = parent self.index = index self.this = this self.setLatestValue = setLatestValue } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next(let value): self.setLatestValue(value) self.parent.next(self.index) case .error(let error): self.this.dispose() self.parent.fail(error) case .completed: self.this.dispose() self.parent.done(self.index) } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/CompactMap.swift ================================================ // // CompactMap.swift // RxSwift // // Created by Michael Long on 04/09/2019. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Projects each element of an observable sequence into an optional form and filters all optional results. - parameter transform: A transform function to apply to each source element and which returns an element or nil. - returns: An observable sequence whose elements are the result of filtering the transform function for each element of the source. */ public func compactMap(_ transform: @escaping (Element) throws -> Result?) -> Observable { CompactMap(source: self.asObservable(), transform: transform) } } final private class CompactMapSink: Sink, ObserverType { typealias Transform = (SourceType) throws -> ResultType? typealias ResultType = Observer.Element typealias Element = SourceType private let transform: Transform init(transform: @escaping Transform, observer: Observer, cancel: Cancelable) { self.transform = transform super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let element): do { if let mappedElement = try self.transform(element) { self.forwardOn(.next(mappedElement)) } } catch let e { self.forwardOn(.error(e)) self.dispose() } case .error(let error): self.forwardOn(.error(error)) self.dispose() case .completed: self.forwardOn(.completed) self.dispose() } } } final private class CompactMap: Producer { typealias Transform = (SourceType) throws -> ResultType? private let source: Observable private let transform: Transform init(source: Observable, transform: @escaping Transform) { self.source = source self.transform = transform } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == ResultType { let sink = CompactMapSink(transform: self.transform, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Concat.swift ================================================ // // Concat.swift // RxSwift // // Created by Krunoslav Zaher on 3/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Concatenates the second observable sequence to `self` upon successful termination of `self`. - seealso: [concat operator on reactivex.io](http://reactivex.io/documentation/operators/concat.html) - parameter second: Second observable sequence. - returns: An observable sequence that contains the elements of `self`, followed by those of the second sequence. */ public func concat(_ second: Source) -> Observable where Source.Element == Element { Observable.concat([self.asObservable(), second.asObservable()]) } } extension ObservableType { /** Concatenates all observable sequences in the given sequence, as long as the previous observable sequence terminated successfully. This operator has tail recursive optimizations that will prevent stack overflow. Optimizations will be performed in cases equivalent to following: [1, [2, [3, .....].concat()].concat].concat() - seealso: [concat operator on reactivex.io](http://reactivex.io/documentation/operators/concat.html) - returns: An observable sequence that contains the elements of each given sequence, in sequential order. */ public static func concat(_ sequence: Sequence) -> Observable where Sequence.Element == Observable { return Concat(sources: sequence, count: nil) } /** Concatenates all observable sequences in the given collection, as long as the previous observable sequence terminated successfully. This operator has tail recursive optimizations that will prevent stack overflow. Optimizations will be performed in cases equivalent to following: [1, [2, [3, .....].concat()].concat].concat() - seealso: [concat operator on reactivex.io](http://reactivex.io/documentation/operators/concat.html) - returns: An observable sequence that contains the elements of each given sequence, in sequential order. */ public static func concat(_ collection: Collection) -> Observable where Collection.Element == Observable { return Concat(sources: collection, count: Int64(collection.count)) } /** Concatenates all observable sequences in the given collection, as long as the previous observable sequence terminated successfully. This operator has tail recursive optimizations that will prevent stack overflow. Optimizations will be performed in cases equivalent to following: [1, [2, [3, .....].concat()].concat].concat() - seealso: [concat operator on reactivex.io](http://reactivex.io/documentation/operators/concat.html) - returns: An observable sequence that contains the elements of each given sequence, in sequential order. */ public static func concat(_ sources: Observable ...) -> Observable { Concat(sources: sources, count: Int64(sources.count)) } } final private class ConcatSink : TailRecursiveSink , ObserverType where Sequence.Element: ObservableConvertibleType, Sequence.Element.Element == Observer.Element { typealias Element = Observer.Element override init(observer: Observer, cancel: Cancelable) { super.init(observer: observer, cancel: cancel) } func on(_ event: Event){ switch event { case .next: self.forwardOn(event) case .error: self.forwardOn(event) self.dispose() case .completed: self.schedule(.moveNext) } } override func subscribeToNext(_ source: Observable) -> Disposable { source.subscribe(self) } override func extract(_ observable: Observable) -> SequenceGenerator? { if let source = observable as? Concat { return (source.sources.makeIterator(), source.count) } else { return nil } } } final private class Concat: Producer where Sequence.Element: ObservableConvertibleType { typealias Element = Sequence.Element.Element fileprivate let sources: Sequence fileprivate let count: IntMax? init(sources: Sequence, count: IntMax?) { self.sources = sources self.count = count } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = ConcatSink(observer: observer, cancel: cancel) let subscription = sink.run((self.sources.makeIterator(), self.count)) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Create.swift ================================================ // // Create.swift // RxSwift // // Created by Krunoslav Zaher on 2/8/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { // MARK: create /** Creates an observable sequence from a specified subscribe method implementation. - seealso: [create operator on reactivex.io](http://reactivex.io/documentation/operators/create.html) - parameter subscribe: Implementation of the resulting observable sequence's `subscribe` method. - returns: The observable sequence with the specified implementation for the `subscribe` method. */ public static func create(_ subscribe: @escaping (AnyObserver) -> Disposable) -> Observable { AnonymousObservable(subscribe) } } final private class AnonymousObservableSink: Sink, ObserverType { typealias Element = Observer.Element typealias Parent = AnonymousObservable // state private let isStopped = AtomicInt(0) #if DEBUG private let synchronizationTracker = SynchronizationTracker() #endif override init(observer: Observer, cancel: Cancelable) { super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { #if DEBUG self.synchronizationTracker.register(synchronizationErrorMessage: .default) defer { self.synchronizationTracker.unregister() } #endif switch event { case .next: if load(self.isStopped) == 1 { return } self.forwardOn(event) case .error, .completed: if fetchOr(self.isStopped, 1) == 0 { self.forwardOn(event) self.dispose() } } } func run(_ parent: Parent) -> Disposable { parent.subscribeHandler(AnyObserver(self)) } } final private class AnonymousObservable: Producer { typealias SubscribeHandler = (AnyObserver) -> Disposable let subscribeHandler: SubscribeHandler init(_ subscribeHandler: @escaping SubscribeHandler) { self.subscribeHandler = subscribeHandler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = AnonymousObservableSink(observer: observer, cancel: cancel) let subscription = sink.run(self) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Debounce.swift ================================================ // // Debounce.swift // RxSwift // // Created by Krunoslav Zaher on 9/11/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Ignores elements from an observable sequence which are followed by another element within a specified relative time duration, using the specified scheduler to run throttling timers. - seealso: [debounce operator on reactivex.io](http://reactivex.io/documentation/operators/debounce.html) - parameter dueTime: Throttling duration for each element. - parameter scheduler: Scheduler to run the throttle timers on. - returns: The throttled sequence. */ public func debounce(_ dueTime: RxTimeInterval, scheduler: SchedulerType) -> Observable { return Debounce(source: self.asObservable(), dueTime: dueTime, scheduler: scheduler) } } final private class DebounceSink : Sink , ObserverType , LockOwnerType , SynchronizedOnType { typealias Element = Observer.Element typealias ParentType = Debounce private let parent: ParentType let lock = RecursiveLock() // state private var id = 0 as UInt64 private var value: Element? let cancellable = SerialDisposable() init(parent: ParentType, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { let subscription = self.parent.source.subscribe(self) return Disposables.create(subscription, cancellable) } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next(let element): self.id = self.id &+ 1 let currentId = self.id self.value = element let scheduler = self.parent.scheduler let dueTime = self.parent.dueTime let d = SingleAssignmentDisposable() self.cancellable.disposable = d d.setDisposable(scheduler.scheduleRelative(currentId, dueTime: dueTime, action: self.propagate)) case .error: self.value = nil self.forwardOn(event) self.dispose() case .completed: if let value = self.value { self.value = nil self.forwardOn(.next(value)) } self.forwardOn(.completed) self.dispose() } } func propagate(_ currentId: UInt64) -> Disposable { self.lock.performLocked { let originalValue = self.value if let value = originalValue, self.id == currentId { self.value = nil self.forwardOn(.next(value)) } return Disposables.create() } } } final private class Debounce: Producer { fileprivate let source: Observable fileprivate let dueTime: RxTimeInterval fileprivate let scheduler: SchedulerType init(source: Observable, dueTime: RxTimeInterval, scheduler: SchedulerType) { self.source = source self.dueTime = dueTime self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = DebounceSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Debug.swift ================================================ // // Debug.swift // RxSwift // // Created by Krunoslav Zaher on 5/2/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Foundation extension ObservableType { /** Prints received events for all observers on standard output. - seealso: [do operator on reactivex.io](http://reactivex.io/documentation/operators/do.html) - parameter identifier: Identifier that is printed together with event description to standard output. - parameter trimOutput: Should output be trimmed to max 40 characters. - returns: An observable sequence whose events are printed to standard output. */ public func debug(_ identifier: String? = nil, trimOutput: Bool = false, file: String = #file, line: UInt = #line, function: String = #function) -> Observable { return Debug(source: self, identifier: identifier, trimOutput: trimOutput, file: file, line: line, function: function) } } private let dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" private func logEvent(_ identifier: String, dateFormat: DateFormatter, content: String) { print("\(dateFormat.string(from: Date())): \(identifier) -> \(content)") } final private class DebugSink: Sink, ObserverType where Observer.Element == Source.Element { typealias Element = Observer.Element typealias Parent = Debug private let parent: Parent private let timestampFormatter = DateFormatter() init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.timestampFormatter.dateFormat = dateFormat logEvent(self.parent.identifier, dateFormat: self.timestampFormatter, content: "subscribed") super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { let maxEventTextLength = 40 let eventText = "\(event)" let eventNormalized = (eventText.count > maxEventTextLength) && self.parent.trimOutput ? String(eventText.prefix(maxEventTextLength / 2)) + "..." + String(eventText.suffix(maxEventTextLength / 2)) : eventText logEvent(self.parent.identifier, dateFormat: self.timestampFormatter, content: "Event \(eventNormalized)") self.forwardOn(event) if event.isStopEvent { self.dispose() } } override func dispose() { if !self.isDisposed { logEvent(self.parent.identifier, dateFormat: self.timestampFormatter, content: "isDisposed") } super.dispose() } } final private class Debug: Producer { fileprivate let identifier: String fileprivate let trimOutput: Bool private let source: Source init(source: Source, identifier: String?, trimOutput: Bool, file: String, line: UInt, function: String) { self.trimOutput = trimOutput if let identifier = identifier { self.identifier = identifier } else { let trimmedFile: String if let lastIndex = file.lastIndex(of: "/") { trimmedFile = String(file[file.index(after: lastIndex) ..< file.endIndex]) } else { trimmedFile = file } self.identifier = "\(trimmedFile):\(line) (\(function))" } self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Source.Element { let sink = DebugSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Decode.swift ================================================ // // Decode.swift // RxSwift // // Created by Shai Mishali on 24/07/2020. // Copyright © 2020 Krunoslav Zaher. All rights reserved. // import Foundation public extension ObservableType where Element == Data { /// Attempt to decode the emitted `Data` using a provided decoder. /// /// - parameter type: A `Decodable`-conforming type to attempt to decode to /// - parameter decoder: A capable decoder, e.g. `JSONDecoder` or `PropertyListDecoder` /// /// - note: If using a custom decoder, it must conform to the `DataDecoder` protocol. /// /// - returns: An `Observable` of the decoded type func decode(type: Item.Type, decoder: Decoder) -> Observable { map { try decoder.decode(type, from: $0) } } } /// Represents an entity capable of decoding raw `Data` /// into a concrete `Decodable` type public protocol DataDecoder { func decode(_ type: Item.Type, from data: Data) throws -> Item } extension JSONDecoder: DataDecoder {} extension PropertyListDecoder: DataDecoder {} ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/DefaultIfEmpty.swift ================================================ // // DefaultIfEmpty.swift // RxSwift // // Created by sergdort on 23/12/2016. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Emits elements from the source observable sequence, or a default element if the source observable sequence is empty. - seealso: [DefaultIfEmpty operator on reactivex.io](http://reactivex.io/documentation/operators/defaultifempty.html) - parameter default: Default element to be sent if the source does not emit any elements - returns: An observable sequence which emits default element end completes in case the original sequence is empty */ public func ifEmpty(default: Element) -> Observable { DefaultIfEmpty(source: self.asObservable(), default: `default`) } } final private class DefaultIfEmptySink: Sink, ObserverType { typealias Element = Observer.Element private let `default`: Element private var isEmpty = true init(default: Element, observer: Observer, cancel: Cancelable) { self.default = `default` super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next: self.isEmpty = false self.forwardOn(event) case .error: self.forwardOn(event) self.dispose() case .completed: if self.isEmpty { self.forwardOn(.next(self.default)) } self.forwardOn(.completed) self.dispose() } } } final private class DefaultIfEmpty: Producer { private let source: Observable private let `default`: SourceType init(source: Observable, `default`: SourceType) { self.source = source self.default = `default` } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == SourceType { let sink = DefaultIfEmptySink(default: self.default, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Deferred.swift ================================================ // // Deferred.swift // RxSwift // // Created by Krunoslav Zaher on 4/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes. - seealso: [defer operator on reactivex.io](http://reactivex.io/documentation/operators/defer.html) - parameter observableFactory: Observable factory function to invoke for each observer that subscribes to the resulting sequence. - returns: An observable sequence whose observers trigger an invocation of the given observable factory function. */ public static func deferred(_ observableFactory: @escaping () throws -> Observable) -> Observable { Deferred(observableFactory: observableFactory) } } final private class DeferredSink: Sink, ObserverType where Source.Element == Observer.Element { typealias Element = Observer.Element private let observableFactory: () throws -> Source init(observableFactory: @escaping () throws -> Source, observer: Observer, cancel: Cancelable) { self.observableFactory = observableFactory super.init(observer: observer, cancel: cancel) } func run() -> Disposable { do { let result = try self.observableFactory() return result.subscribe(self) } catch let e { self.forwardOn(.error(e)) self.dispose() return Disposables.create() } } func on(_ event: Event) { self.forwardOn(event) switch event { case .next: break case .error: self.dispose() case .completed: self.dispose() } } } final private class Deferred: Producer { typealias Factory = () throws -> Source private let observableFactory : Factory init(observableFactory: @escaping Factory) { self.observableFactory = observableFactory } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Source.Element { let sink = DeferredSink(observableFactory: self.observableFactory, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Delay.swift ================================================ // // Delay.swift // RxSwift // // Created by tarunon on 2016/02/09. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // import Foundation extension ObservableType { /** Returns an observable sequence by the source observable sequence shifted forward in time by a specified delay. Error events from the source observable sequence are not delayed. - seealso: [delay operator on reactivex.io](http://reactivex.io/documentation/operators/delay.html) - parameter dueTime: Relative time shift of the source by. - parameter scheduler: Scheduler to run the subscription delay timer on. - returns: the source Observable shifted in time by the specified delay. */ public func delay(_ dueTime: RxTimeInterval, scheduler: SchedulerType) -> Observable { return Delay(source: self.asObservable(), dueTime: dueTime, scheduler: scheduler) } } final private class DelaySink : Sink , ObserverType { typealias Element = Observer.Element typealias Source = Observable typealias DisposeKey = Bag.KeyType private let lock = RecursiveLock() private let dueTime: RxTimeInterval private let scheduler: SchedulerType private let sourceSubscription = SingleAssignmentDisposable() private let cancelable = SerialDisposable() // is scheduled some action private var active = false // is "run loop" on different scheduler running private var running = false private var errorEvent: Event? // state private var queue = Queue<(eventTime: RxTime, event: Event)>(capacity: 0) init(observer: Observer, dueTime: RxTimeInterval, scheduler: SchedulerType, cancel: Cancelable) { self.dueTime = dueTime self.scheduler = scheduler super.init(observer: observer, cancel: cancel) } // All of these complications in this method are caused by the fact that // error should be propagated immediately. Error can be potentially received on different // scheduler so this process needs to be synchronized somehow. // // Another complication is that scheduler is potentially concurrent so internal queue is used. func drainQueue(state: (), scheduler: AnyRecursiveScheduler<()>) { self.lock.lock() let hasFailed = self.errorEvent != nil if !hasFailed { self.running = true } self.lock.unlock() if hasFailed { return } var ranAtLeastOnce = false while true { self.lock.lock() let errorEvent = self.errorEvent let eventToForwardImmediately = ranAtLeastOnce ? nil : self.queue.dequeue()?.event let nextEventToScheduleOriginalTime: Date? = ranAtLeastOnce && !self.queue.isEmpty ? self.queue.peek().eventTime : nil if errorEvent == nil { if eventToForwardImmediately != nil { } else if nextEventToScheduleOriginalTime != nil { self.running = false } else { self.running = false self.active = false } } self.lock.unlock() if let errorEvent = errorEvent { self.forwardOn(errorEvent) self.dispose() return } else { if let eventToForwardImmediately = eventToForwardImmediately { ranAtLeastOnce = true self.forwardOn(eventToForwardImmediately) if case .completed = eventToForwardImmediately { self.dispose() return } } else if let nextEventToScheduleOriginalTime = nextEventToScheduleOriginalTime { scheduler.schedule((), dueTime: self.dueTime.reduceWithSpanBetween(earlierDate: nextEventToScheduleOriginalTime, laterDate: self.scheduler.now)) return } else { return } } } } func on(_ event: Event) { if event.isStopEvent { self.sourceSubscription.dispose() } switch event { case .error: self.lock.lock() let shouldSendImmediately = !self.running self.queue = Queue(capacity: 0) self.errorEvent = event self.lock.unlock() if shouldSendImmediately { self.forwardOn(event) self.dispose() } default: self.lock.lock() let shouldSchedule = !self.active self.active = true self.queue.enqueue((self.scheduler.now, event)) self.lock.unlock() if shouldSchedule { self.cancelable.disposable = self.scheduler.scheduleRecursive((), dueTime: self.dueTime, action: self.drainQueue) } } } func run(source: Observable) -> Disposable { self.sourceSubscription.setDisposable(source.subscribe(self)) return Disposables.create(sourceSubscription, cancelable) } } final private class Delay: Producer { private let source: Observable private let dueTime: RxTimeInterval private let scheduler: SchedulerType init(source: Observable, dueTime: RxTimeInterval, scheduler: SchedulerType) { self.source = source self.dueTime = dueTime self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = DelaySink(observer: observer, dueTime: self.dueTime, scheduler: self.scheduler, cancel: cancel) let subscription = sink.run(source: self.source) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/DelaySubscription.swift ================================================ // // DelaySubscription.swift // RxSwift // // Created by Krunoslav Zaher on 6/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Time shifts the observable sequence by delaying the subscription with the specified relative time duration, using the specified scheduler to run timers. - seealso: [delay operator on reactivex.io](http://reactivex.io/documentation/operators/delay.html) - parameter dueTime: Relative time shift of the subscription. - parameter scheduler: Scheduler to run the subscription delay timer on. - returns: Time-shifted sequence. */ public func delaySubscription(_ dueTime: RxTimeInterval, scheduler: SchedulerType) -> Observable { DelaySubscription(source: self.asObservable(), dueTime: dueTime, scheduler: scheduler) } } final private class DelaySubscriptionSink : Sink, ObserverType { typealias Element = Observer.Element func on(_ event: Event) { self.forwardOn(event) if event.isStopEvent { self.dispose() } } } final private class DelaySubscription: Producer { private let source: Observable private let dueTime: RxTimeInterval private let scheduler: SchedulerType init(source: Observable, dueTime: RxTimeInterval, scheduler: SchedulerType) { self.source = source self.dueTime = dueTime self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = DelaySubscriptionSink(observer: observer, cancel: cancel) let subscription = self.scheduler.scheduleRelative((), dueTime: self.dueTime) { _ in return self.source.subscribe(sink) } return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Dematerialize.swift ================================================ // // Dematerialize.swift // RxSwift // // Created by Jamie Pinkham on 3/13/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // extension ObservableType where Element: EventConvertible { /** Convert any previously materialized Observable into it's original form. - seealso: [materialize operator on reactivex.io](http://reactivex.io/documentation/operators/materialize-dematerialize.html) - returns: The dematerialized observable sequence. */ public func dematerialize() -> Observable { Dematerialize(source: self.asObservable()) } } private final class DematerializeSink: Sink, ObserverType where Observer.Element == T.Element { fileprivate func on(_ event: Event) { switch event { case .next(let element): self.forwardOn(element.event) if element.event.isStopEvent { self.dispose() } case .completed: self.forwardOn(.completed) self.dispose() case .error(let error): self.forwardOn(.error(error)) self.dispose() } } } final private class Dematerialize: Producer { private let source: Observable init(source: Observable) { self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == T.Element { let sink = DematerializeSink(observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/DistinctUntilChanged.swift ================================================ // // DistinctUntilChanged.swift // RxSwift // // Created by Krunoslav Zaher on 3/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType where Element: Equatable { /** Returns an observable sequence that contains only distinct contiguous elements according to equality operator. - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) - returns: An observable sequence only containing the distinct contiguous elements, based on equality operator, from the source sequence. */ public func distinctUntilChanged() -> Observable { self.distinctUntilChanged({ $0 }, comparer: { ($0 == $1) }) } } extension ObservableType { /** Returns an observable sequence that contains only distinct contiguous elements according to the `keySelector`. - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) - parameter keySelector: A function to compute the comparison key for each element. - returns: An observable sequence only containing the distinct contiguous elements, based on a computed key value, from the source sequence. */ public func distinctUntilChanged(_ keySelector: @escaping (Element) throws -> Key) -> Observable { self.distinctUntilChanged(keySelector, comparer: { $0 == $1 }) } /** Returns an observable sequence that contains only distinct contiguous elements according to the `comparer`. - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) - parameter comparer: Equality comparer for computed key values. - returns: An observable sequence only containing the distinct contiguous elements, based on `comparer`, from the source sequence. */ public func distinctUntilChanged(_ comparer: @escaping (Element, Element) throws -> Bool) -> Observable { self.distinctUntilChanged({ $0 }, comparer: comparer) } /** Returns an observable sequence that contains only distinct contiguous elements according to the keySelector and the comparer. - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) - parameter keySelector: A function to compute the comparison key for each element. - parameter comparer: Equality comparer for computed key values. - returns: An observable sequence only containing the distinct contiguous elements, based on a computed key value and the comparer, from the source sequence. */ public func distinctUntilChanged(_ keySelector: @escaping (Element) throws -> K, comparer: @escaping (K, K) throws -> Bool) -> Observable { return DistinctUntilChanged(source: self.asObservable(), selector: keySelector, comparer: comparer) } /** Returns an observable sequence that contains only contiguous elements with distinct values in the provided key path on each object. - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) - returns: An observable sequence only containing the distinct contiguous elements, based on equality operator on the provided key path */ public func distinctUntilChanged(at keyPath: KeyPath) -> Observable { self.distinctUntilChanged { $0[keyPath: keyPath] == $1[keyPath: keyPath] } } } final private class DistinctUntilChangedSink: Sink, ObserverType { typealias Element = Observer.Element private let parent: DistinctUntilChanged private var currentKey: Key? init(parent: DistinctUntilChanged, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): do { let key = try self.parent.selector(value) var areEqual = false if let currentKey = self.currentKey { areEqual = try self.parent.comparer(currentKey, key) } if areEqual { return } self.currentKey = key self.forwardOn(event) } catch let error { self.forwardOn(.error(error)) self.dispose() } case .error, .completed: self.forwardOn(event) self.dispose() } } } final private class DistinctUntilChanged: Producer { typealias KeySelector = (Element) throws -> Key typealias EqualityComparer = (Key, Key) throws -> Bool private let source: Observable fileprivate let selector: KeySelector fileprivate let comparer: EqualityComparer init(source: Observable, selector: @escaping KeySelector, comparer: @escaping EqualityComparer) { self.source = source self.selector = selector self.comparer = comparer } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = DistinctUntilChangedSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Do.swift ================================================ // // Do.swift // RxSwift // // Created by Krunoslav Zaher on 2/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Invokes an action for each event in the observable sequence, and propagates all observer messages through the result sequence. - seealso: [do operator on reactivex.io](http://reactivex.io/documentation/operators/do.html) - parameter onNext: Action to invoke for each element in the observable sequence. - parameter afterNext: Action to invoke for each element after the observable has passed an onNext event along to its downstream. - parameter onError: Action to invoke upon errored termination of the observable sequence. - parameter afterError: Action to invoke after errored termination of the observable sequence. - parameter onCompleted: Action to invoke upon graceful termination of the observable sequence. - parameter afterCompleted: Action to invoke after graceful termination of the observable sequence. - parameter onSubscribe: Action to invoke before subscribing to source observable sequence. - parameter onSubscribed: Action to invoke after subscribing to source observable sequence. - parameter onDispose: Action to invoke after subscription to source observable has been disposed for any reason. It can be either because sequence terminates for some reason or observer subscription being disposed. - returns: The source sequence with the side-effecting behavior applied. */ public func `do`(onNext: ((Element) throws -> Void)? = nil, afterNext: ((Element) throws -> Void)? = nil, onError: ((Swift.Error) throws -> Void)? = nil, afterError: ((Swift.Error) throws -> Void)? = nil, onCompleted: (() throws -> Void)? = nil, afterCompleted: (() throws -> Void)? = nil, onSubscribe: (() -> Void)? = nil, onSubscribed: (() -> Void)? = nil, onDispose: (() -> Void)? = nil) -> Observable { return Do(source: self.asObservable(), eventHandler: { e in switch e { case .next(let element): try onNext?(element) case .error(let e): try onError?(e) case .completed: try onCompleted?() } }, afterEventHandler: { e in switch e { case .next(let element): try afterNext?(element) case .error(let e): try afterError?(e) case .completed: try afterCompleted?() } }, onSubscribe: onSubscribe, onSubscribed: onSubscribed, onDispose: onDispose) } } final private class DoSink: Sink, ObserverType { typealias Element = Observer.Element typealias EventHandler = (Event) throws -> Void typealias AfterEventHandler = (Event) throws -> Void private let eventHandler: EventHandler private let afterEventHandler: AfterEventHandler init(eventHandler: @escaping EventHandler, afterEventHandler: @escaping AfterEventHandler, observer: Observer, cancel: Cancelable) { self.eventHandler = eventHandler self.afterEventHandler = afterEventHandler super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { do { try self.eventHandler(event) self.forwardOn(event) try self.afterEventHandler(event) if event.isStopEvent { self.dispose() } } catch let error { self.forwardOn(.error(error)) self.dispose() } } } final private class Do: Producer { typealias EventHandler = (Event) throws -> Void typealias AfterEventHandler = (Event) throws -> Void private let source: Observable private let eventHandler: EventHandler private let afterEventHandler: AfterEventHandler private let onSubscribe: (() -> Void)? private let onSubscribed: (() -> Void)? private let onDispose: (() -> Void)? init(source: Observable, eventHandler: @escaping EventHandler, afterEventHandler: @escaping AfterEventHandler, onSubscribe: (() -> Void)?, onSubscribed: (() -> Void)?, onDispose: (() -> Void)?) { self.source = source self.eventHandler = eventHandler self.afterEventHandler = afterEventHandler self.onSubscribe = onSubscribe self.onSubscribed = onSubscribed self.onDispose = onDispose } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { self.onSubscribe?() let sink = DoSink(eventHandler: self.eventHandler, afterEventHandler: self.afterEventHandler, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) self.onSubscribed?() let onDispose = self.onDispose let allSubscriptions = Disposables.create { subscription.dispose() onDispose?() } return (sink: sink, subscription: allSubscriptions) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/ElementAt.swift ================================================ // // ElementAt.swift // RxSwift // // Created by Junior B. on 21/10/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns a sequence emitting only element _n_ emitted by an Observable - seealso: [elementAt operator on reactivex.io](http://reactivex.io/documentation/operators/elementat.html) - parameter index: The index of the required element (starting from 0). - returns: An observable sequence that emits the desired element as its own sole emission. */ @available(*, deprecated, renamed: "element(at:)") public func elementAt(_ index: Int) -> Observable { element(at: index) } /** Returns a sequence emitting only element _n_ emitted by an Observable - seealso: [elementAt operator on reactivex.io](http://reactivex.io/documentation/operators/elementat.html) - parameter index: The index of the required element (starting from 0). - returns: An observable sequence that emits the desired element as its own sole emission. */ public func element(at index: Int) -> Observable { ElementAt(source: self.asObservable(), index: index, throwOnEmpty: true) } } final private class ElementAtSink: Sink, ObserverType { typealias SourceType = Observer.Element typealias Parent = ElementAt let parent: Parent var i: Int init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.i = parent.index super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next: if self.i == 0 { self.forwardOn(event) self.forwardOn(.completed) self.dispose() } do { _ = try decrementChecked(&self.i) } catch let e { self.forwardOn(.error(e)) self.dispose() return } case .error(let e): self.forwardOn(.error(e)) self.dispose() case .completed: if self.parent.throwOnEmpty { self.forwardOn(.error(RxError.argumentOutOfRange)) } else { self.forwardOn(.completed) } self.dispose() } } } final private class ElementAt: Producer { let source: Observable let throwOnEmpty: Bool let index: Int init(source: Observable, index: Int, throwOnEmpty: Bool) { if index < 0 { rxFatalError("index can't be negative") } self.source = source self.index = index self.throwOnEmpty = throwOnEmpty } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == SourceType { let sink = ElementAtSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Empty.swift ================================================ // // Empty.swift // RxSwift // // Created by Krunoslav Zaher on 8/30/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns an empty observable sequence, using the specified scheduler to send out the single `Completed` message. - seealso: [empty operator on reactivex.io](http://reactivex.io/documentation/operators/empty-never-throw.html) - returns: An observable sequence with no elements. */ public static func empty() -> Observable { EmptyProducer() } } final private class EmptyProducer: Producer { override func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { observer.on(.completed) return Disposables.create() } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Enumerated.swift ================================================ // // Enumerated.swift // RxSwift // // Created by Krunoslav Zaher on 8/6/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Enumerates the elements of an observable sequence. - seealso: [map operator on reactivex.io](http://reactivex.io/documentation/operators/map.html) - returns: An observable sequence that contains tuples of source sequence elements and their indexes. */ public func enumerated() -> Observable<(index: Int, element: Element)> { Enumerated(source: self.asObservable()) } } final private class EnumeratedSink: Sink, ObserverType where Observer.Element == (index: Int, element: Element) { var index = 0 func on(_ event: Event) { switch event { case .next(let value): do { let nextIndex = try incrementChecked(&self.index) let next = (index: nextIndex, element: value) self.forwardOn(.next(next)) } catch let e { self.forwardOn(.error(e)) self.dispose() } case .completed: self.forwardOn(.completed) self.dispose() case .error(let error): self.forwardOn(.error(error)) self.dispose() } } } final private class Enumerated: Producer<(index: Int, element: Element)> { private let source: Observable init(source: Observable) { self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == (index: Int, element: Element) { let sink = EnumeratedSink(observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Error.swift ================================================ // // Error.swift // RxSwift // // Created by Krunoslav Zaher on 8/30/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns an observable sequence that terminates with an `error`. - seealso: [throw operator on reactivex.io](http://reactivex.io/documentation/operators/empty-never-throw.html) - returns: The observable sequence that terminates with specified error. */ public static func error(_ error: Swift.Error) -> Observable { ErrorProducer(error: error) } } final private class ErrorProducer: Producer { private let error: Swift.Error init(error: Swift.Error) { self.error = error } override func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { observer.on(.error(self.error)) return Disposables.create() } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Filter.swift ================================================ // // Filter.swift // RxSwift // // Created by Krunoslav Zaher on 2/17/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Filters the elements of an observable sequence based on a predicate. - seealso: [filter operator on reactivex.io](http://reactivex.io/documentation/operators/filter.html) - parameter predicate: A function to test each source element for a condition. - returns: An observable sequence that contains elements from the input sequence that satisfy the condition. */ public func filter(_ predicate: @escaping (Element) throws -> Bool) -> Observable { Filter(source: self.asObservable(), predicate: predicate) } } extension ObservableType { /** Skips elements and completes (or errors) when the observable sequence completes (or errors). Equivalent to filter that always returns false. - seealso: [ignoreElements operator on reactivex.io](http://reactivex.io/documentation/operators/ignoreelements.html) - returns: An observable sequence that skips all elements of the source sequence. */ public func ignoreElements() -> Observable { self.flatMap { _ in Observable.empty() } } } final private class FilterSink: Sink, ObserverType { typealias Predicate = (Element) throws -> Bool typealias Element = Observer.Element private let predicate: Predicate init(predicate: @escaping Predicate, observer: Observer, cancel: Cancelable) { self.predicate = predicate super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): do { let satisfies = try self.predicate(value) if satisfies { self.forwardOn(.next(value)) } } catch let e { self.forwardOn(.error(e)) self.dispose() } case .completed, .error: self.forwardOn(event) self.dispose() } } } final private class Filter: Producer { typealias Predicate = (Element) throws -> Bool private let source: Observable private let predicate: Predicate init(source: Observable, predicate: @escaping Predicate) { self.source = source self.predicate = predicate } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = FilterSink(predicate: self.predicate, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/First.swift ================================================ // // First.swift // RxSwift // // Created by Krunoslav Zaher on 7/31/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // private final class FirstSink : Sink, ObserverType where Observer.Element == Element? { typealias Parent = First func on(_ event: Event) { switch event { case .next(let value): self.forwardOn(.next(value)) self.forwardOn(.completed) self.dispose() case .error(let error): self.forwardOn(.error(error)) self.dispose() case .completed: self.forwardOn(.next(nil)) self.forwardOn(.completed) self.dispose() } } } final class First: Producer { private let source: Observable init(source: Observable) { self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element? { let sink = FirstSink(observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Generate.swift ================================================ // // Generate.swift // RxSwift // // Created by Krunoslav Zaher on 9/2/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Generates an observable sequence by running a state-driven loop producing the sequence's elements, using the specified scheduler to run the loop send out observer messages. - seealso: [create operator on reactivex.io](http://reactivex.io/documentation/operators/create.html) - parameter initialState: Initial state. - parameter condition: Condition to terminate generation (upon returning `false`). - parameter iterate: Iteration step function. - parameter scheduler: Scheduler on which to run the generator loop. - returns: The generated sequence. */ public static func generate(initialState: Element, condition: @escaping (Element) throws -> Bool, scheduler: ImmediateSchedulerType = CurrentThreadScheduler.instance, iterate: @escaping (Element) throws -> Element) -> Observable { Generate(initialState: initialState, condition: condition, iterate: iterate, resultSelector: { $0 }, scheduler: scheduler) } } final private class GenerateSink: Sink { typealias Parent = Generate private let parent: Parent private var state: Sequence init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.state = parent.initialState super.init(observer: observer, cancel: cancel) } func run() -> Disposable { return self.parent.scheduler.scheduleRecursive(true) { isFirst, recurse -> Void in do { if !isFirst { self.state = try self.parent.iterate(self.state) } if try self.parent.condition(self.state) { let result = try self.parent.resultSelector(self.state) self.forwardOn(.next(result)) recurse(false) } else { self.forwardOn(.completed) self.dispose() } } catch let error { self.forwardOn(.error(error)) self.dispose() } } } } final private class Generate: Producer { fileprivate let initialState: Sequence fileprivate let condition: (Sequence) throws -> Bool fileprivate let iterate: (Sequence) throws -> Sequence fileprivate let resultSelector: (Sequence) throws -> Element fileprivate let scheduler: ImmediateSchedulerType init(initialState: Sequence, condition: @escaping (Sequence) throws -> Bool, iterate: @escaping (Sequence) throws -> Sequence, resultSelector: @escaping (Sequence) throws -> Element, scheduler: ImmediateSchedulerType) { self.initialState = initialState self.condition = condition self.iterate = iterate self.resultSelector = resultSelector self.scheduler = scheduler super.init() } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = GenerateSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/GroupBy.swift ================================================ // // GroupBy.swift // RxSwift // // Created by Tomi Koskinen on 01/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /* Groups the elements of an observable sequence according to a specified key selector function. - seealso: [groupBy operator on reactivex.io](http://reactivex.io/documentation/operators/groupby.html) - parameter keySelector: A function to extract the key for each element. - returns: A sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. */ public func groupBy(keySelector: @escaping (Element) throws -> Key) -> Observable> { GroupBy(source: self.asObservable(), selector: keySelector) } } final private class GroupedObservableImpl: Observable { private var subject: PublishSubject private var refCount: RefCountDisposable init(subject: PublishSubject, refCount: RefCountDisposable) { self.subject = subject self.refCount = refCount } override public func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { let release = self.refCount.retain() let subscription = self.subject.subscribe(observer) return Disposables.create(release, subscription) } } final private class GroupBySink : Sink , ObserverType where Observer.Element == GroupedObservable { typealias ResultType = Observer.Element typealias Parent = GroupBy private let parent: Parent private let subscription = SingleAssignmentDisposable() private var refCountDisposable: RefCountDisposable! private var groupedSubjectTable: [Key: PublishSubject] init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.groupedSubjectTable = [Key: PublishSubject]() super.init(observer: observer, cancel: cancel) } func run() -> Disposable { self.refCountDisposable = RefCountDisposable(disposable: self.subscription) self.subscription.setDisposable(self.parent.source.subscribe(self)) return self.refCountDisposable } private func onGroupEvent(key: Key, value: Element) { if let writer = self.groupedSubjectTable[key] { writer.on(.next(value)) } else { let writer = PublishSubject() self.groupedSubjectTable[key] = writer let group = GroupedObservable( key: key, source: GroupedObservableImpl(subject: writer, refCount: refCountDisposable) ) self.forwardOn(.next(group)) writer.on(.next(value)) } } final func on(_ event: Event) { switch event { case let .next(value): do { let groupKey = try self.parent.selector(value) self.onGroupEvent(key: groupKey, value: value) } catch let e { self.error(e) return } case let .error(e): self.error(e) case .completed: self.forwardOnGroups(event: .completed) self.forwardOn(.completed) self.subscription.dispose() self.dispose() } } final func error(_ error: Swift.Error) { self.forwardOnGroups(event: .error(error)) self.forwardOn(.error(error)) self.subscription.dispose() self.dispose() } final func forwardOnGroups(event: Event) { for writer in self.groupedSubjectTable.values { writer.on(event) } } } final private class GroupBy: Producer> { typealias KeySelector = (Element) throws -> Key fileprivate let source: Observable fileprivate let selector: KeySelector init(source: Observable, selector: @escaping KeySelector) { self.source = source self.selector = selector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == GroupedObservable { let sink = GroupBySink(parent: self, observer: observer, cancel: cancel) return (sink: sink, subscription: sink.run()) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Just.swift ================================================ // // Just.swift // RxSwift // // Created by Krunoslav Zaher on 8/30/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns an observable sequence that contains a single element. - seealso: [just operator on reactivex.io](http://reactivex.io/documentation/operators/just.html) - parameter element: Single element in the resulting observable sequence. - returns: An observable sequence containing the single specified element. */ public static func just(_ element: Element) -> Observable { Just(element: element) } /** Returns an observable sequence that contains a single element. - seealso: [just operator on reactivex.io](http://reactivex.io/documentation/operators/just.html) - parameter element: Single element in the resulting observable sequence. - parameter scheduler: Scheduler to send the single element on. - returns: An observable sequence containing the single specified element. */ public static func just(_ element: Element, scheduler: ImmediateSchedulerType) -> Observable { JustScheduled(element: element, scheduler: scheduler) } } final private class JustScheduledSink: Sink { typealias Parent = JustScheduled private let parent: Parent init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { let scheduler = self.parent.scheduler return scheduler.schedule(self.parent.element) { element in self.forwardOn(.next(element)) return scheduler.schedule(()) { _ in self.forwardOn(.completed) self.dispose() return Disposables.create() } } } } final private class JustScheduled: Producer { fileprivate let scheduler: ImmediateSchedulerType fileprivate let element: Element init(element: Element, scheduler: ImmediateSchedulerType) { self.scheduler = scheduler self.element = element } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = JustScheduledSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } final private class Just: Producer { private let element: Element init(element: Element) { self.element = element } override func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { observer.on(.next(self.element)) observer.on(.completed) return Disposables.create() } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Map.swift ================================================ // // Map.swift // RxSwift // // Created by Krunoslav Zaher on 3/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Projects each element of an observable sequence into a new form. - seealso: [map operator on reactivex.io](http://reactivex.io/documentation/operators/map.html) - parameter transform: A transform function to apply to each source element. - returns: An observable sequence whose elements are the result of invoking the transform function on each element of source. */ public func map(_ transform: @escaping (Element) throws -> Result) -> Observable { Map(source: self.asObservable(), transform: transform) } } final private class MapSink: Sink, ObserverType { typealias Transform = (SourceType) throws -> ResultType typealias ResultType = Observer.Element typealias Element = SourceType private let transform: Transform init(transform: @escaping Transform, observer: Observer, cancel: Cancelable) { self.transform = transform super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let element): do { let mappedElement = try self.transform(element) self.forwardOn(.next(mappedElement)) } catch let e { self.forwardOn(.error(e)) self.dispose() } case .error(let error): self.forwardOn(.error(error)) self.dispose() case .completed: self.forwardOn(.completed) self.dispose() } } } final private class Map: Producer { typealias Transform = (SourceType) throws -> ResultType private let source: Observable private let transform: Transform init(source: Observable, transform: @escaping Transform) { self.source = source self.transform = transform } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == ResultType { let sink = MapSink(transform: self.transform, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Materialize.swift ================================================ // // Materialize.swift // RxSwift // // Created by sergdort on 08/03/2017. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Convert any Observable into an Observable of its events. - seealso: [materialize operator on reactivex.io](http://reactivex.io/documentation/operators/materialize-dematerialize.html) - returns: An observable sequence that wraps events in an Event. The returned Observable never errors, but it does complete after observing all of the events of the underlying Observable. */ public func materialize() -> Observable> { Materialize(source: self.asObservable()) } } private final class MaterializeSink: Sink, ObserverType where Observer.Element == Event { func on(_ event: Event) { self.forwardOn(.next(event)) if event.isStopEvent { self.forwardOn(.completed) self.dispose() } } } final private class Materialize: Producer> { private let source: Observable init(source: Observable) { self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = MaterializeSink(observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Merge.swift ================================================ // // Merge.swift // RxSwift // // Created by Krunoslav Zaher on 3/28/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. - seealso: [flatMap operator on reactivex.io](http://reactivex.io/documentation/operators/flatmap.html) - parameter selector: A transform function to apply to each element. - returns: An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. */ public func flatMap(_ selector: @escaping (Element) throws -> Source) -> Observable { return FlatMap(source: self.asObservable(), selector: selector) } } extension ObservableType { /** Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. If element is received while there is some projected observable sequence being merged it will simply be ignored. - seealso: [flatMapFirst operator on reactivex.io](http://reactivex.io/documentation/operators/flatmap.html) - parameter selector: A transform function to apply to element that was observed while no observable is executing in parallel. - returns: An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence that was received while no other sequence was being calculated. */ public func flatMapFirst(_ selector: @escaping (Element) throws -> Source) -> Observable { return FlatMapFirst(source: self.asObservable(), selector: selector) } } extension ObservableType where Element: ObservableConvertibleType { /** Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence. - seealso: [merge operator on reactivex.io](http://reactivex.io/documentation/operators/merge.html) - returns: The observable sequence that merges the elements of the observable sequences. */ public func merge() -> Observable { Merge(source: self.asObservable()) } /** Merges elements from all inner observable sequences into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences. - seealso: [merge operator on reactivex.io](http://reactivex.io/documentation/operators/merge.html) - parameter maxConcurrent: Maximum number of inner observable sequences being subscribed to concurrently. - returns: The observable sequence that merges the elements of the inner sequences. */ public func merge(maxConcurrent: Int) -> Observable { MergeLimited(source: self.asObservable(), maxConcurrent: maxConcurrent) } } extension ObservableType where Element: ObservableConvertibleType { /** Concatenates all inner observable sequences, as long as the previous observable sequence terminated successfully. - seealso: [concat operator on reactivex.io](http://reactivex.io/documentation/operators/concat.html) - returns: An observable sequence that contains the elements of each observed inner sequence, in sequential order. */ public func concat() -> Observable { self.merge(maxConcurrent: 1) } } extension ObservableType { /** Merges elements from all observable sequences from collection into a single observable sequence. - seealso: [merge operator on reactivex.io](http://reactivex.io/documentation/operators/merge.html) - parameter sources: Collection of observable sequences to merge. - returns: The observable sequence that merges the elements of the observable sequences. */ public static func merge(_ sources: Collection) -> Observable where Collection.Element == Observable { MergeArray(sources: Array(sources)) } /** Merges elements from all observable sequences from array into a single observable sequence. - seealso: [merge operator on reactivex.io](http://reactivex.io/documentation/operators/merge.html) - parameter sources: Array of observable sequences to merge. - returns: The observable sequence that merges the elements of the observable sequences. */ public static func merge(_ sources: [Observable]) -> Observable { MergeArray(sources: sources) } /** Merges elements from all observable sequences into a single observable sequence. - seealso: [merge operator on reactivex.io](http://reactivex.io/documentation/operators/merge.html) - parameter sources: Collection of observable sequences to merge. - returns: The observable sequence that merges the elements of the observable sequences. */ public static func merge(_ sources: Observable...) -> Observable { MergeArray(sources: sources) } } // MARK: concatMap extension ObservableType { /** Projects each element of an observable sequence to an observable sequence and concatenates the resulting observable sequences into one observable sequence. - seealso: [concat operator on reactivex.io](http://reactivex.io/documentation/operators/concat.html) - returns: An observable sequence that contains the elements of each observed inner sequence, in sequential order. */ public func concatMap(_ selector: @escaping (Element) throws -> Source) -> Observable { return ConcatMap(source: self.asObservable(), selector: selector) } } private final class MergeLimitedSinkIter : ObserverType , LockOwnerType , SynchronizedOnType where SourceSequence.Element == Observer.Element { typealias Element = Observer.Element typealias DisposeKey = CompositeDisposable.DisposeKey typealias Parent = MergeLimitedSink private let parent: Parent private let disposeKey: DisposeKey var lock: RecursiveLock { self.parent.lock } init(parent: Parent, disposeKey: DisposeKey) { self.parent = parent self.disposeKey = disposeKey } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next: self.parent.forwardOn(event) case .error: self.parent.forwardOn(event) self.parent.dispose() case .completed: self.parent.group.remove(for: self.disposeKey) if let next = self.parent.queue.dequeue() { self.parent.subscribe(next, group: self.parent.group) } else { self.parent.activeCount -= 1 if self.parent.stopped && self.parent.activeCount == 0 { self.parent.forwardOn(.completed) self.parent.dispose() } } } } } private final class ConcatMapSink: MergeLimitedSink where Observer.Element == SourceSequence.Element { typealias Selector = (SourceElement) throws -> SourceSequence private let selector: Selector init(selector: @escaping Selector, observer: Observer, cancel: Cancelable) { self.selector = selector super.init(maxConcurrent: 1, observer: observer, cancel: cancel) } override func performMap(_ element: SourceElement) throws -> SourceSequence { try self.selector(element) } } private final class MergeLimitedBasicSink: MergeLimitedSink where Observer.Element == SourceSequence.Element { override func performMap(_ element: SourceSequence) throws -> SourceSequence { element } } private class MergeLimitedSink : Sink , ObserverType where Observer.Element == SourceSequence.Element { typealias QueueType = Queue let maxConcurrent: Int let lock = RecursiveLock() // state var stopped = false var activeCount = 0 var queue = QueueType(capacity: 2) let sourceSubscription = SingleAssignmentDisposable() let group = CompositeDisposable() init(maxConcurrent: Int, observer: Observer, cancel: Cancelable) { self.maxConcurrent = maxConcurrent super.init(observer: observer, cancel: cancel) } func run(_ source: Observable) -> Disposable { _ = self.group.insert(self.sourceSubscription) let disposable = source.subscribe(self) self.sourceSubscription.setDisposable(disposable) return self.group } func subscribe(_ innerSource: SourceSequence, group: CompositeDisposable) { let subscription = SingleAssignmentDisposable() let key = group.insert(subscription) if let key = key { let observer = MergeLimitedSinkIter(parent: self, disposeKey: key) let disposable = innerSource.asObservable().subscribe(observer) subscription.setDisposable(disposable) } } func performMap(_ element: SourceElement) throws -> SourceSequence { rxAbstractMethod() } @inline(__always) final private func nextElementArrived(element: SourceElement) -> SourceSequence? { self.lock.performLocked { let subscribe: Bool if self.activeCount < self.maxConcurrent { self.activeCount += 1 subscribe = true } else { do { let value = try self.performMap(element) self.queue.enqueue(value) } catch { self.forwardOn(.error(error)) self.dispose() } subscribe = false } if subscribe { do { return try self.performMap(element) } catch { self.forwardOn(.error(error)) self.dispose() } } return nil } } func on(_ event: Event) { switch event { case .next(let element): if let sequence = self.nextElementArrived(element: element) { self.subscribe(sequence, group: self.group) } case .error(let error): self.lock.performLocked { self.forwardOn(.error(error)) self.dispose() } case .completed: self.lock.performLocked { if self.activeCount == 0 { self.forwardOn(.completed) self.dispose() } else { self.sourceSubscription.dispose() } self.stopped = true } } } } final private class MergeLimited: Producer { private let source: Observable private let maxConcurrent: Int init(source: Observable, maxConcurrent: Int) { self.source = source self.maxConcurrent = maxConcurrent } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == SourceSequence.Element { let sink = MergeLimitedBasicSink(maxConcurrent: self.maxConcurrent, observer: observer, cancel: cancel) let subscription = sink.run(self.source) return (sink: sink, subscription: subscription) } } // MARK: Merge private final class MergeBasicSink : MergeSink where Observer.Element == Source.Element { override func performMap(_ element: Source) throws -> Source { element } } // MARK: flatMap private final class FlatMapSink : MergeSink where Observer.Element == SourceSequence.Element { typealias Selector = (SourceElement) throws -> SourceSequence private let selector: Selector init(selector: @escaping Selector, observer: Observer, cancel: Cancelable) { self.selector = selector super.init(observer: observer, cancel: cancel) } override func performMap(_ element: SourceElement) throws -> SourceSequence { try self.selector(element) } } // MARK: FlatMapFirst private final class FlatMapFirstSink : MergeSink where Observer.Element == SourceSequence.Element { typealias Selector = (SourceElement) throws -> SourceSequence private let selector: Selector override var subscribeNext: Bool { self.activeCount == 0 } init(selector: @escaping Selector, observer: Observer, cancel: Cancelable) { self.selector = selector super.init(observer: observer, cancel: cancel) } override func performMap(_ element: SourceElement) throws -> SourceSequence { try self.selector(element) } } private final class MergeSinkIter : ObserverType where Observer.Element == SourceSequence.Element { typealias Parent = MergeSink typealias DisposeKey = CompositeDisposable.DisposeKey typealias Element = Observer.Element private let parent: Parent private let disposeKey: DisposeKey init(parent: Parent, disposeKey: DisposeKey) { self.parent = parent self.disposeKey = disposeKey } func on(_ event: Event) { self.parent.lock.performLocked { switch event { case .next(let value): self.parent.forwardOn(.next(value)) case .error(let error): self.parent.forwardOn(.error(error)) self.parent.dispose() case .completed: self.parent.group.remove(for: self.disposeKey) self.parent.activeCount -= 1 self.parent.checkCompleted() } } } } private class MergeSink : Sink , ObserverType where Observer.Element == SourceSequence.Element { typealias ResultType = Observer.Element typealias Element = SourceElement let lock = RecursiveLock() var subscribeNext: Bool { true } // state let group = CompositeDisposable() let sourceSubscription = SingleAssignmentDisposable() var activeCount = 0 var stopped = false override init(observer: Observer, cancel: Cancelable) { super.init(observer: observer, cancel: cancel) } func performMap(_ element: SourceElement) throws -> SourceSequence { rxAbstractMethod() } @inline(__always) final private func nextElementArrived(element: SourceElement) -> SourceSequence? { self.lock.performLocked { if !self.subscribeNext { return nil } do { let value = try self.performMap(element) self.activeCount += 1 return value } catch let e { self.forwardOn(.error(e)) self.dispose() return nil } } } func on(_ event: Event) { switch event { case .next(let element): if let value = self.nextElementArrived(element: element) { self.subscribeInner(value.asObservable()) } case .error(let error): self.lock.performLocked { self.forwardOn(.error(error)) self.dispose() } case .completed: self.lock.performLocked { self.stopped = true self.sourceSubscription.dispose() self.checkCompleted() } } } func subscribeInner(_ source: Observable) { let iterDisposable = SingleAssignmentDisposable() if let disposeKey = self.group.insert(iterDisposable) { let iter = MergeSinkIter(parent: self, disposeKey: disposeKey) let subscription = source.subscribe(iter) iterDisposable.setDisposable(subscription) } } func run(_ sources: [Observable]) -> Disposable { self.activeCount += sources.count for source in sources { self.subscribeInner(source) } self.stopped = true self.checkCompleted() return self.group } @inline(__always) func checkCompleted() { if self.stopped && self.activeCount == 0 { self.forwardOn(.completed) self.dispose() } } func run(_ source: Observable) -> Disposable { _ = self.group.insert(self.sourceSubscription) let subscription = source.subscribe(self) self.sourceSubscription.setDisposable(subscription) return self.group } } // MARK: Producers final private class FlatMap: Producer { typealias Selector = (SourceElement) throws -> SourceSequence private let source: Observable private let selector: Selector init(source: Observable, selector: @escaping Selector) { self.source = source self.selector = selector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == SourceSequence.Element { let sink = FlatMapSink(selector: self.selector, observer: observer, cancel: cancel) let subscription = sink.run(self.source) return (sink: sink, subscription: subscription) } } final private class FlatMapFirst: Producer { typealias Selector = (SourceElement) throws -> SourceSequence private let source: Observable private let selector: Selector init(source: Observable, selector: @escaping Selector) { self.source = source self.selector = selector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == SourceSequence.Element { let sink = FlatMapFirstSink(selector: self.selector, observer: observer, cancel: cancel) let subscription = sink.run(self.source) return (sink: sink, subscription: subscription) } } final class ConcatMap: Producer { typealias Selector = (SourceElement) throws -> SourceSequence private let source: Observable private let selector: Selector init(source: Observable, selector: @escaping Selector) { self.source = source self.selector = selector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == SourceSequence.Element { let sink = ConcatMapSink(selector: self.selector, observer: observer, cancel: cancel) let subscription = sink.run(self.source) return (sink: sink, subscription: subscription) } } final class Merge : Producer { private let source: Observable init(source: Observable) { self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == SourceSequence.Element { let sink = MergeBasicSink(observer: observer, cancel: cancel) let subscription = sink.run(self.source) return (sink: sink, subscription: subscription) } } final private class MergeArray: Producer { private let sources: [Observable] init(sources: [Observable]) { self.sources = sources } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = MergeBasicSink, Observer>(observer: observer, cancel: cancel) let subscription = sink.run(self.sources) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Multicast.swift ================================================ // // Multicast.swift // RxSwift // // Created by Krunoslav Zaher on 2/27/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /** Represents an observable wrapper that can be connected and disconnected from its underlying observable sequence. */ public class ConnectableObservable : Observable , ConnectableObservableType { /** Connects the observable wrapper to its source. All subscribed observers will receive values from the underlying observable sequence as long as the connection is established. - returns: Disposable used to disconnect the observable wrapper from its source, causing subscribed observer to stop receiving values from the underlying observable sequence. */ public func connect() -> Disposable { rxAbstractMethod() } } extension ObservableType { /** Multicasts the source sequence notifications through an instantiated subject into all uses of the sequence within a selector function. Each subscription to the resulting sequence causes a separate multicast invocation, exposing the sequence resulting from the selector function's invocation. For specializations with fixed subject types, see `publish` and `replay`. - seealso: [multicast operator on reactivex.io](http://reactivex.io/documentation/operators/publish.html) - parameter subjectSelector: Factory function to create an intermediate subject through which the source sequence's elements will be multicast to the selector function. - parameter selector: Selector function which can use the multicasted source sequence subject to the policies enforced by the created subject. - returns: An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a selector function. */ public func multicast(_ subjectSelector: @escaping () throws -> Subject, selector: @escaping (Observable) throws -> Observable) -> Observable where Subject.Observer.Element == Element { return Multicast( source: self.asObservable(), subjectSelector: subjectSelector, selector: selector ) } } extension ObservableType { /** Returns a connectable observable sequence that shares a single subscription to the underlying sequence. This operator is a specialization of `multicast` using a `PublishSubject`. - seealso: [publish operator on reactivex.io](http://reactivex.io/documentation/operators/publish.html) - returns: A connectable observable sequence that shares a single subscription to the underlying sequence. */ public func publish() -> ConnectableObservable { self.multicast { PublishSubject() } } } extension ObservableType { /** Returns a connectable observable sequence that shares a single subscription to the underlying sequence replaying bufferSize elements. This operator is a specialization of `multicast` using a `ReplaySubject`. - seealso: [replay operator on reactivex.io](http://reactivex.io/documentation/operators/replay.html) - parameter bufferSize: Maximum element count of the replay buffer. - returns: A connectable observable sequence that shares a single subscription to the underlying sequence. */ public func replay(_ bufferSize: Int) -> ConnectableObservable { self.multicast { ReplaySubject.create(bufferSize: bufferSize) } } /** Returns a connectable observable sequence that shares a single subscription to the underlying sequence replaying all elements. This operator is a specialization of `multicast` using a `ReplaySubject`. - seealso: [replay operator on reactivex.io](http://reactivex.io/documentation/operators/replay.html) - returns: A connectable observable sequence that shares a single subscription to the underlying sequence. */ public func replayAll() -> ConnectableObservable { self.multicast { ReplaySubject.createUnbounded() } } } extension ConnectableObservableType { /** Returns an observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. - seealso: [refCount operator on reactivex.io](http://reactivex.io/documentation/operators/refcount.html) - returns: An observable sequence that stays connected to the source as long as there is at least one subscription to the observable sequence. */ public func refCount() -> Observable { RefCount(source: self) } } extension ObservableType { /** Multicasts the source sequence notifications through the specified subject to the resulting connectable observable. Upon connection of the connectable observable, the subject is subscribed to the source exactly one, and messages are forwarded to the observers registered with the connectable observable. For specializations with fixed subject types, see `publish` and `replay`. - seealso: [multicast operator on reactivex.io](http://reactivex.io/documentation/operators/publish.html) - parameter subject: Subject to push source elements into. - returns: A connectable observable sequence that upon connection causes the source sequence to push results into the specified subject. */ public func multicast(_ subject: Subject) -> ConnectableObservable where Subject.Observer.Element == Element { ConnectableObservableAdapter(source: self.asObservable(), makeSubject: { subject }) } /** Multicasts the source sequence notifications through an instantiated subject to the resulting connectable observable. Upon connection of the connectable observable, the subject is subscribed to the source exactly one, and messages are forwarded to the observers registered with the connectable observable. Subject is cleared on connection disposal or in case source sequence produces terminal event. - seealso: [multicast operator on reactivex.io](http://reactivex.io/documentation/operators/publish.html) - parameter makeSubject: Factory function used to instantiate a subject for each connection. - returns: A connectable observable sequence that upon connection causes the source sequence to push results into the specified subject. */ public func multicast(makeSubject: @escaping () -> Subject) -> ConnectableObservable where Subject.Observer.Element == Element { ConnectableObservableAdapter(source: self.asObservable(), makeSubject: makeSubject) } } final private class Connection: ObserverType, Disposable { typealias Element = Subject.Observer.Element private var lock: RecursiveLock // state private var parent: ConnectableObservableAdapter? private var subscription : Disposable? private var subjectObserver: Subject.Observer private let disposed = AtomicInt(0) init(parent: ConnectableObservableAdapter, subjectObserver: Subject.Observer, lock: RecursiveLock, subscription: Disposable) { self.parent = parent self.subscription = subscription self.lock = lock self.subjectObserver = subjectObserver } func on(_ event: Event) { if isFlagSet(self.disposed, 1) { return } if event.isStopEvent { self.dispose() } self.subjectObserver.on(event) } func dispose() { lock.lock(); defer { lock.unlock() } fetchOr(self.disposed, 1) guard let parent = self.parent else { return } if parent.connection === self { parent.connection = nil parent.subject = nil } self.parent = nil self.subscription?.dispose() self.subscription = nil } } final private class ConnectableObservableAdapter : ConnectableObservable { typealias ConnectionType = Connection private let source: Observable private let makeSubject: () -> Subject fileprivate let lock = RecursiveLock() fileprivate var subject: Subject? // state fileprivate var connection: ConnectionType? init(source: Observable, makeSubject: @escaping () -> Subject) { self.source = source self.makeSubject = makeSubject self.subject = nil self.connection = nil } override func connect() -> Disposable { return self.lock.performLocked { if let connection = self.connection { return connection } let singleAssignmentDisposable = SingleAssignmentDisposable() let connection = Connection(parent: self, subjectObserver: self.lazySubject.asObserver(), lock: self.lock, subscription: singleAssignmentDisposable) self.connection = connection let subscription = self.source.subscribe(connection) singleAssignmentDisposable.setDisposable(subscription) return connection } } private var lazySubject: Subject { if let subject = self.subject { return subject } let subject = self.makeSubject() self.subject = subject return subject } override func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Subject.Element { self.lazySubject.subscribe(observer) } } final private class RefCountSink : Sink , ObserverType where ConnectableSource.Element == Observer.Element { typealias Element = Observer.Element typealias Parent = RefCount private let parent: Parent private var connectionIdSnapshot: Int64 = -1 init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { let subscription = self.parent.source.subscribe(self) self.parent.lock.lock(); defer { self.parent.lock.unlock() } self.connectionIdSnapshot = self.parent.connectionId if self.isDisposed { return Disposables.create() } if self.parent.count == 0 { self.parent.count = 1 self.parent.connectableSubscription = self.parent.source.connect() } else { self.parent.count += 1 } return Disposables.create { subscription.dispose() self.parent.lock.lock(); defer { self.parent.lock.unlock() } if self.parent.connectionId != self.connectionIdSnapshot { return } if self.parent.count == 1 { self.parent.count = 0 guard let connectableSubscription = self.parent.connectableSubscription else { return } connectableSubscription.dispose() self.parent.connectableSubscription = nil } else if self.parent.count > 1 { self.parent.count -= 1 } else { rxFatalError("Something went wrong with RefCount disposing mechanism") } } } func on(_ event: Event) { switch event { case .next: self.forwardOn(event) case .error, .completed: self.parent.lock.lock() if self.parent.connectionId == self.connectionIdSnapshot { let connection = self.parent.connectableSubscription defer { connection?.dispose() } self.parent.count = 0 self.parent.connectionId = self.parent.connectionId &+ 1 self.parent.connectableSubscription = nil } self.parent.lock.unlock() self.forwardOn(event) self.dispose() } } } final private class RefCount: Producer { fileprivate let lock = RecursiveLock() // state fileprivate var count = 0 fileprivate var connectionId: Int64 = 0 fileprivate var connectableSubscription = nil as Disposable? fileprivate let source: ConnectableSource init(source: ConnectableSource) { self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == ConnectableSource.Element { let sink = RefCountSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } final private class MulticastSink: Sink, ObserverType { typealias Element = Observer.Element typealias ResultType = Element typealias MutlicastType = Multicast private let parent: MutlicastType init(parent: MutlicastType, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { do { let subject = try self.parent.subjectSelector() let connectable = ConnectableObservableAdapter(source: self.parent.source, makeSubject: { subject }) let observable = try self.parent.selector(connectable) let subscription = observable.subscribe(self) let connection = connectable.connect() return Disposables.create(subscription, connection) } catch let e { self.forwardOn(.error(e)) self.dispose() return Disposables.create() } } func on(_ event: Event) { self.forwardOn(event) switch event { case .next: break case .error, .completed: self.dispose() } } } final private class Multicast: Producer { typealias SubjectSelectorType = () throws -> Subject typealias SelectorType = (Observable) throws -> Observable fileprivate let source: Observable fileprivate let subjectSelector: SubjectSelectorType fileprivate let selector: SelectorType init(source: Observable, subjectSelector: @escaping SubjectSelectorType, selector: @escaping SelectorType) { self.source = source self.subjectSelector = subjectSelector self.selector = selector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = MulticastSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Never.swift ================================================ // // Never.swift // RxSwift // // Created by Krunoslav Zaher on 8/30/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns a non-terminating observable sequence, which can be used to denote an infinite duration. - seealso: [never operator on reactivex.io](http://reactivex.io/documentation/operators/empty-never-throw.html) - returns: An observable sequence whose observers will never get called. */ public static func never() -> Observable { NeverProducer() } } final private class NeverProducer: Producer { override func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { Disposables.create() } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/ObserveOn.swift ================================================ // // ObserveOn.swift // RxSwift // // Created by Krunoslav Zaher on 7/25/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Wraps the source sequence in order to run its observer callbacks on the specified scheduler. This only invokes observer callbacks on a `scheduler`. In case the subscription and/or unsubscription actions have side-effects that require to be run on a scheduler, use `subscribeOn`. - seealso: [observeOn operator on reactivex.io](http://reactivex.io/documentation/operators/observeon.html) - parameter scheduler: Scheduler to notify observers on. - returns: The source sequence whose observations happen on the specified scheduler. */ public func observe(on scheduler: ImmediateSchedulerType) -> Observable { guard let serialScheduler = scheduler as? SerialDispatchQueueScheduler else { return ObserveOn(source: self.asObservable(), scheduler: scheduler) } return ObserveOnSerialDispatchQueue(source: self.asObservable(), scheduler: serialScheduler) } /** Wraps the source sequence in order to run its observer callbacks on the specified scheduler. This only invokes observer callbacks on a `scheduler`. In case the subscription and/or unsubscription actions have side-effects that require to be run on a scheduler, use `subscribeOn`. - seealso: [observeOn operator on reactivex.io](http://reactivex.io/documentation/operators/observeon.html) - parameter scheduler: Scheduler to notify observers on. - returns: The source sequence whose observations happen on the specified scheduler. */ @available(*, deprecated, renamed: "observe(on:)") public func observeOn(_ scheduler: ImmediateSchedulerType) -> Observable { observe(on: scheduler) } } final private class ObserveOn: Producer { let scheduler: ImmediateSchedulerType let source: Observable init(source: Observable, scheduler: ImmediateSchedulerType) { self.scheduler = scheduler self.source = source #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = ObserveOnSink(scheduler: self.scheduler, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } #if TRACE_RESOURCES deinit { _ = Resources.decrementTotal() } #endif } enum ObserveOnState : Int32 { // pump is not running case stopped = 0 // pump is running case running = 1 } final private class ObserveOnSink: ObserverBase { typealias Element = Observer.Element let scheduler: ImmediateSchedulerType var lock = SpinLock() let observer: Observer // state var state = ObserveOnState.stopped var queue = Queue>(capacity: 10) let scheduleDisposable = SerialDisposable() let cancel: Cancelable init(scheduler: ImmediateSchedulerType, observer: Observer, cancel: Cancelable) { self.scheduler = scheduler self.observer = observer self.cancel = cancel } override func onCore(_ event: Event) { let shouldStart = self.lock.performLocked { () -> Bool in self.queue.enqueue(event) switch self.state { case .stopped: self.state = .running return true case .running: return false } } if shouldStart { self.scheduleDisposable.disposable = self.scheduler.scheduleRecursive((), action: self.run) } } func run(_ state: (), _ recurse: (()) -> Void) { let (nextEvent, observer) = self.lock.performLocked { () -> (Event?, Observer) in if !self.queue.isEmpty { return (self.queue.dequeue(), self.observer) } else { self.state = .stopped return (nil, self.observer) } } if let nextEvent = nextEvent, !self.cancel.isDisposed { observer.on(nextEvent) if nextEvent.isStopEvent { self.dispose() } } else { return } let shouldContinue = self.shouldContinue_synchronized() if shouldContinue { recurse(()) } } func shouldContinue_synchronized() -> Bool { self.lock.performLocked { let isEmpty = self.queue.isEmpty if isEmpty { self.state = .stopped } return !isEmpty } } override func dispose() { super.dispose() self.cancel.dispose() self.scheduleDisposable.dispose() } } #if TRACE_RESOURCES private let numberOfSerialDispatchObservables = AtomicInt(0) extension Resources { /** Counts number of `SerialDispatchQueueObservables`. Purposed for unit tests. */ public static var numberOfSerialDispatchQueueObservables: Int32 { return load(numberOfSerialDispatchObservables) } } #endif final private class ObserveOnSerialDispatchQueueSink: ObserverBase { let scheduler: SerialDispatchQueueScheduler let observer: Observer let cancel: Cancelable var cachedScheduleLambda: (((sink: ObserveOnSerialDispatchQueueSink, event: Event)) -> Disposable)! init(scheduler: SerialDispatchQueueScheduler, observer: Observer, cancel: Cancelable) { self.scheduler = scheduler self.observer = observer self.cancel = cancel super.init() self.cachedScheduleLambda = { pair in guard !cancel.isDisposed else { return Disposables.create() } pair.sink.observer.on(pair.event) if pair.event.isStopEvent { pair.sink.dispose() } return Disposables.create() } } override func onCore(_ event: Event) { _ = self.scheduler.schedule((self, event), action: self.cachedScheduleLambda!) } override func dispose() { super.dispose() self.cancel.dispose() } } final private class ObserveOnSerialDispatchQueue: Producer { let scheduler: SerialDispatchQueueScheduler let source: Observable init(source: Observable, scheduler: SerialDispatchQueueScheduler) { self.scheduler = scheduler self.source = source #if TRACE_RESOURCES _ = Resources.incrementTotal() _ = increment(numberOfSerialDispatchObservables) #endif } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = ObserveOnSerialDispatchQueueSink(scheduler: self.scheduler, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } #if TRACE_RESOURCES deinit { _ = Resources.decrementTotal() _ = decrement(numberOfSerialDispatchObservables) } #endif } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Optional.swift ================================================ // // Optional.swift // RxSwift // // Created by tarunon on 2016/12/13. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Converts a optional to an observable sequence. - seealso: [from operator on reactivex.io](http://reactivex.io/documentation/operators/from.html) - parameter optional: Optional element in the resulting observable sequence. - returns: An observable sequence containing the wrapped value or not from given optional. */ public static func from(optional: Element?) -> Observable { ObservableOptional(optional: optional) } /** Converts a optional to an observable sequence. - seealso: [from operator on reactivex.io](http://reactivex.io/documentation/operators/from.html) - parameter optional: Optional element in the resulting observable sequence. - parameter scheduler: Scheduler to send the optional element on. - returns: An observable sequence containing the wrapped value or not from given optional. */ public static func from(optional: Element?, scheduler: ImmediateSchedulerType) -> Observable { ObservableOptionalScheduled(optional: optional, scheduler: scheduler) } } final private class ObservableOptionalScheduledSink: Sink { typealias Element = Observer.Element typealias Parent = ObservableOptionalScheduled private let parent: Parent init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { return self.parent.scheduler.schedule(self.parent.optional) { (optional: Element?) -> Disposable in if let next = optional { self.forwardOn(.next(next)) return self.parent.scheduler.schedule(()) { _ in self.forwardOn(.completed) self.dispose() return Disposables.create() } } else { self.forwardOn(.completed) self.dispose() return Disposables.create() } } } } final private class ObservableOptionalScheduled: Producer { fileprivate let optional: Element? fileprivate let scheduler: ImmediateSchedulerType init(optional: Element?, scheduler: ImmediateSchedulerType) { self.optional = optional self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = ObservableOptionalScheduledSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } final private class ObservableOptional: Producer { private let optional: Element? init(optional: Element?) { self.optional = optional } override func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { if let element = self.optional { observer.on(.next(element)) } observer.on(.completed) return Disposables.create() } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Producer.swift ================================================ // // Producer.swift // RxSwift // // Created by Krunoslav Zaher on 2/20/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // class Producer: Observable { override init() { super.init() } override func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { if !CurrentThreadScheduler.isScheduleRequired { // The returned disposable needs to release all references once it was disposed. let disposer = SinkDisposer() let sinkAndSubscription = self.run(observer, cancel: disposer) disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription) return disposer } else { return CurrentThreadScheduler.instance.schedule(()) { _ in let disposer = SinkDisposer() let sinkAndSubscription = self.run(observer, cancel: disposer) disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription) return disposer } } } func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { rxAbstractMethod() } } private final class SinkDisposer: Cancelable { private enum DisposeState: Int32 { case disposed = 1 case sinkAndSubscriptionSet = 2 } private let state = AtomicInt(0) private var sink: Disposable? private var subscription: Disposable? var isDisposed: Bool { isFlagSet(self.state, DisposeState.disposed.rawValue) } func setSinkAndSubscription(sink: Disposable, subscription: Disposable) { self.sink = sink self.subscription = subscription let previousState = fetchOr(self.state, DisposeState.sinkAndSubscriptionSet.rawValue) if (previousState & DisposeState.sinkAndSubscriptionSet.rawValue) != 0 { rxFatalError("Sink and subscription were already set") } if (previousState & DisposeState.disposed.rawValue) != 0 { sink.dispose() subscription.dispose() self.sink = nil self.subscription = nil } } func dispose() { let previousState = fetchOr(self.state, DisposeState.disposed.rawValue) if (previousState & DisposeState.disposed.rawValue) != 0 { return } if (previousState & DisposeState.sinkAndSubscriptionSet.rawValue) != 0 { guard let sink = self.sink else { rxFatalError("Sink not set") } guard let subscription = self.subscription else { rxFatalError("Subscription not set") } sink.dispose() subscription.dispose() self.sink = nil self.subscription = nil } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Range.swift ================================================ // // Range.swift // RxSwift // // Created by Krunoslav Zaher on 9/13/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType where Element: RxAbstractInteger { /** Generates an observable sequence of integral numbers within a specified range, using the specified scheduler to generate and send out observer messages. - seealso: [range operator on reactivex.io](http://reactivex.io/documentation/operators/range.html) - parameter start: The value of the first integer in the sequence. - parameter count: The number of sequential integers to generate. - parameter scheduler: Scheduler to run the generator loop on. - returns: An observable sequence that contains a range of sequential integral numbers. */ public static func range(start: Element, count: Element, scheduler: ImmediateSchedulerType = CurrentThreadScheduler.instance) -> Observable { RangeProducer(start: start, count: count, scheduler: scheduler) } } final private class RangeProducer: Producer { fileprivate let start: Element fileprivate let count: Element fileprivate let scheduler: ImmediateSchedulerType init(start: Element, count: Element, scheduler: ImmediateSchedulerType) { guard count >= 0 else { rxFatalError("count can't be negative") } guard start &+ (count - 1) >= start || count == 0 else { rxFatalError("overflow of count") } self.start = start self.count = count self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = RangeSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } final private class RangeSink: Sink where Observer.Element: RxAbstractInteger { typealias Parent = RangeProducer private let parent: Parent init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { return self.parent.scheduler.scheduleRecursive(0 as Observer.Element) { i, recurse in if i < self.parent.count { self.forwardOn(.next(self.parent.start + i)) recurse(i + 1) } else { self.forwardOn(.completed) self.dispose() } } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Reduce.swift ================================================ // // Reduce.swift // RxSwift // // Created by Krunoslav Zaher on 4/1/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Applies an `accumulator` function over an observable sequence, returning the result of the aggregation as a single element in the result sequence. The specified `seed` value is used as the initial accumulator value. For aggregation behavior with incremental intermediate results, see `scan`. - seealso: [reduce operator on reactivex.io](http://reactivex.io/documentation/operators/reduce.html) - parameter seed: The initial accumulator value. - parameter accumulator: A accumulator function to be invoked on each element. - parameter mapResult: A function to transform the final accumulator value into the result value. - returns: An observable sequence containing a single element with the final accumulator value. */ public func reduce(_ seed: A, accumulator: @escaping (A, Element) throws -> A, mapResult: @escaping (A) throws -> Result) -> Observable { Reduce(source: self.asObservable(), seed: seed, accumulator: accumulator, mapResult: mapResult) } /** Applies an `accumulator` function over an observable sequence, returning the result of the aggregation as a single element in the result sequence. The specified `seed` value is used as the initial accumulator value. For aggregation behavior with incremental intermediate results, see `scan`. - seealso: [reduce operator on reactivex.io](http://reactivex.io/documentation/operators/reduce.html) - parameter seed: The initial accumulator value. - parameter accumulator: A accumulator function to be invoked on each element. - returns: An observable sequence containing a single element with the final accumulator value. */ public func reduce(_ seed: A, accumulator: @escaping (A, Element) throws -> A) -> Observable { Reduce(source: self.asObservable(), seed: seed, accumulator: accumulator, mapResult: { $0 }) } } final private class ReduceSink: Sink, ObserverType { typealias ResultType = Observer.Element typealias Parent = Reduce private let parent: Parent private var accumulation: AccumulateType init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.accumulation = parent.seed super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): do { self.accumulation = try self.parent.accumulator(self.accumulation, value) } catch let e { self.forwardOn(.error(e)) self.dispose() } case .error(let e): self.forwardOn(.error(e)) self.dispose() case .completed: do { let result = try self.parent.mapResult(self.accumulation) self.forwardOn(.next(result)) self.forwardOn(.completed) self.dispose() } catch let e { self.forwardOn(.error(e)) self.dispose() } } } } final private class Reduce: Producer { typealias AccumulatorType = (AccumulateType, SourceType) throws -> AccumulateType typealias ResultSelectorType = (AccumulateType) throws -> ResultType private let source: Observable fileprivate let seed: AccumulateType fileprivate let accumulator: AccumulatorType fileprivate let mapResult: ResultSelectorType init(source: Observable, seed: AccumulateType, accumulator: @escaping AccumulatorType, mapResult: @escaping ResultSelectorType) { self.source = source self.seed = seed self.accumulator = accumulator self.mapResult = mapResult } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == ResultType { let sink = ReduceSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Repeat.swift ================================================ // // Repeat.swift // RxSwift // // Created by Krunoslav Zaher on 9/13/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Generates an observable sequence that repeats the given element infinitely, using the specified scheduler to send out observer messages. - seealso: [repeat operator on reactivex.io](http://reactivex.io/documentation/operators/repeat.html) - parameter element: Element to repeat. - parameter scheduler: Scheduler to run the producer loop on. - returns: An observable sequence that repeats the given element infinitely. */ public static func repeatElement(_ element: Element, scheduler: ImmediateSchedulerType = CurrentThreadScheduler.instance) -> Observable { RepeatElement(element: element, scheduler: scheduler) } } final private class RepeatElement: Producer { fileprivate let element: Element fileprivate let scheduler: ImmediateSchedulerType init(element: Element, scheduler: ImmediateSchedulerType) { self.element = element self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = RepeatElementSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } final private class RepeatElementSink: Sink { typealias Parent = RepeatElement private let parent: Parent init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { return self.parent.scheduler.scheduleRecursive(self.parent.element) { e, recurse in self.forwardOn(.next(e)) recurse(e) } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/RetryWhen.swift ================================================ // // RetryWhen.swift // RxSwift // // Created by Junior B. on 06/10/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Repeats the source observable sequence on error when the notifier emits a next value. If the source observable errors and the notifier completes, it will complete the source sequence. - seealso: [retry operator on reactivex.io](http://reactivex.io/documentation/operators/retry.html) - parameter notificationHandler: A handler that is passed an observable sequence of errors raised by the source observable and returns and observable that either continues, completes or errors. This behavior is then applied to the source observable. - returns: An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully or is notified to error or complete. */ public func retry(when notificationHandler: @escaping (Observable) -> TriggerObservable) -> Observable { RetryWhenSequence(sources: InfiniteSequence(repeatedValue: self.asObservable()), notificationHandler: notificationHandler) } /** Repeats the source observable sequence on error when the notifier emits a next value. If the source observable errors and the notifier completes, it will complete the source sequence. - seealso: [retry operator on reactivex.io](http://reactivex.io/documentation/operators/retry.html) - parameter notificationHandler: A handler that is passed an observable sequence of errors raised by the source observable and returns and observable that either continues, completes or errors. This behavior is then applied to the source observable. - returns: An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully or is notified to error or complete. */ @available(*, deprecated, renamed: "retry(when:)") public func retryWhen(_ notificationHandler: @escaping (Observable) -> TriggerObservable) -> Observable { retry(when: notificationHandler) } /** Repeats the source observable sequence on error when the notifier emits a next value. If the source observable errors and the notifier completes, it will complete the source sequence. - seealso: [retry operator on reactivex.io](http://reactivex.io/documentation/operators/retry.html) - parameter notificationHandler: A handler that is passed an observable sequence of errors raised by the source observable and returns and observable that either continues, completes or errors. This behavior is then applied to the source observable. - returns: An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully or is notified to error or complete. */ public func retry(when notificationHandler: @escaping (Observable) -> TriggerObservable) -> Observable { RetryWhenSequence(sources: InfiniteSequence(repeatedValue: self.asObservable()), notificationHandler: notificationHandler) } /** Repeats the source observable sequence on error when the notifier emits a next value. If the source observable errors and the notifier completes, it will complete the source sequence. - seealso: [retry operator on reactivex.io](http://reactivex.io/documentation/operators/retry.html) - parameter notificationHandler: A handler that is passed an observable sequence of errors raised by the source observable and returns and observable that either continues, completes or errors. This behavior is then applied to the source observable. - returns: An observable sequence producing the elements of the given sequence repeatedly until it terminates successfully or is notified to error or complete. */ @available(*, deprecated, renamed: "retry(when:)") public func retryWhen(_ notificationHandler: @escaping (Observable) -> TriggerObservable) -> Observable { RetryWhenSequence(sources: InfiniteSequence(repeatedValue: self.asObservable()), notificationHandler: notificationHandler) } } final private class RetryTriggerSink : ObserverType where Sequence.Element: ObservableType, Sequence.Element.Element == Observer.Element { typealias Element = TriggerObservable.Element typealias Parent = RetryWhenSequenceSinkIter private let parent: Parent init(parent: Parent) { self.parent = parent } func on(_ event: Event) { switch event { case .next: self.parent.parent.lastError = nil self.parent.parent.schedule(.moveNext) case .error(let e): self.parent.parent.forwardOn(.error(e)) self.parent.parent.dispose() case .completed: self.parent.parent.forwardOn(.completed) self.parent.parent.dispose() } } } final private class RetryWhenSequenceSinkIter : ObserverType , Disposable where Sequence.Element: ObservableType, Sequence.Element.Element == Observer.Element { typealias Element = Observer.Element typealias Parent = RetryWhenSequenceSink fileprivate let parent: Parent private let errorHandlerSubscription = SingleAssignmentDisposable() private let subscription: Disposable init(parent: Parent, subscription: Disposable) { self.parent = parent self.subscription = subscription } func on(_ event: Event) { switch event { case .next: self.parent.forwardOn(event) case .error(let error): self.parent.lastError = error if let failedWith = error as? Error { // dispose current subscription self.subscription.dispose() let errorHandlerSubscription = self.parent.notifier.subscribe(RetryTriggerSink(parent: self)) self.errorHandlerSubscription.setDisposable(errorHandlerSubscription) self.parent.errorSubject.on(.next(failedWith)) } else { self.parent.forwardOn(.error(error)) self.parent.dispose() } case .completed: self.parent.forwardOn(event) self.parent.dispose() } } final func dispose() { self.subscription.dispose() self.errorHandlerSubscription.dispose() } } final private class RetryWhenSequenceSink : TailRecursiveSink where Sequence.Element: ObservableType, Sequence.Element.Element == Observer.Element { typealias Element = Observer.Element typealias Parent = RetryWhenSequence let lock = RecursiveLock() private let parent: Parent fileprivate var lastError: Swift.Error? fileprivate let errorSubject = PublishSubject() private let handler: Observable fileprivate let notifier = PublishSubject() init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.handler = parent.notificationHandler(self.errorSubject).asObservable() super.init(observer: observer, cancel: cancel) } override func done() { if let lastError = self.lastError { self.forwardOn(.error(lastError)) self.lastError = nil } else { self.forwardOn(.completed) } self.dispose() } override func extract(_ observable: Observable) -> SequenceGenerator? { // It is important to always return `nil` here because there are side effects in the `run` method // that are dependant on particular `retryWhen` operator so single operator stack can't be reused in this // case. return nil } override func subscribeToNext(_ source: Observable) -> Disposable { let subscription = SingleAssignmentDisposable() let iter = RetryWhenSequenceSinkIter(parent: self, subscription: subscription) subscription.setDisposable(source.subscribe(iter)) return iter } override func run(_ sources: SequenceGenerator) -> Disposable { let triggerSubscription = self.handler.subscribe(self.notifier.asObserver()) let superSubscription = super.run(sources) return Disposables.create(superSubscription, triggerSubscription) } } final private class RetryWhenSequence: Producer where Sequence.Element: ObservableType { typealias Element = Sequence.Element.Element private let sources: Sequence fileprivate let notificationHandler: (Observable) -> TriggerObservable init(sources: Sequence, notificationHandler: @escaping (Observable) -> TriggerObservable) { self.sources = sources self.notificationHandler = notificationHandler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = RetryWhenSequenceSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run((self.sources.makeIterator(), nil)) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Sample.swift ================================================ // // Sample.swift // RxSwift // // Created by Krunoslav Zaher on 5/1/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Samples the source observable sequence using a sampler observable sequence producing sampling ticks. Upon each sampling tick, the latest element (if any) in the source sequence during the last sampling interval is sent to the resulting sequence. **In case there were no new elements between sampler ticks, you may provide a default value to be emitted, instead to the resulting sequence otherwise no element is sent.** - seealso: [sample operator on reactivex.io](http://reactivex.io/documentation/operators/sample.html) - parameter sampler: Sampling tick sequence. - parameter defaultValue: a value to return if there are no new elements between sampler ticks - returns: Sampled observable sequence. */ public func sample(_ sampler: Source, defaultValue: Element? = nil) -> Observable { return Sample(source: self.asObservable(), sampler: sampler.asObservable(), defaultValue: defaultValue) } } final private class SamplerSink : ObserverType , LockOwnerType , SynchronizedOnType { typealias Element = SampleType typealias Parent = SampleSequenceSink private let parent: Parent var lock: RecursiveLock { self.parent.lock } init(parent: Parent) { self.parent = parent } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next, .completed: if let element = parent.element ?? self.parent.defaultValue { self.parent.element = nil self.parent.forwardOn(.next(element)) } if self.parent.atEnd { self.parent.forwardOn(.completed) self.parent.dispose() } case .error(let e): self.parent.forwardOn(.error(e)) self.parent.dispose() } } } final private class SampleSequenceSink : Sink , ObserverType , LockOwnerType , SynchronizedOnType { typealias Element = Observer.Element typealias Parent = Sample fileprivate let parent: Parent fileprivate let defaultValue: Element? let lock = RecursiveLock() // state fileprivate var element = nil as Element? fileprivate var atEnd = false private let sourceSubscription = SingleAssignmentDisposable() init(parent: Parent, observer: Observer, cancel: Cancelable, defaultValue: Element? = nil) { self.parent = parent self.defaultValue = defaultValue super.init(observer: observer, cancel: cancel) } func run() -> Disposable { self.sourceSubscription.setDisposable(self.parent.source.subscribe(self)) let samplerSubscription = self.parent.sampler.subscribe(SamplerSink(parent: self)) return Disposables.create(sourceSubscription, samplerSubscription) } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next(let element): self.element = element case .error: self.forwardOn(event) self.dispose() case .completed: self.atEnd = true self.sourceSubscription.dispose() } } } final private class Sample: Producer { fileprivate let source: Observable fileprivate let sampler: Observable fileprivate let defaultValue: Element? init(source: Observable, sampler: Observable, defaultValue: Element? = nil) { self.source = source self.sampler = sampler self.defaultValue = defaultValue } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = SampleSequenceSink(parent: self, observer: observer, cancel: cancel, defaultValue: self.defaultValue) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Scan.swift ================================================ // // Scan.swift // RxSwift // // Created by Krunoslav Zaher on 6/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Applies an accumulator function over an observable sequence and returns each intermediate result. The specified seed value is used as the initial accumulator value. For aggregation behavior with no intermediate results, see `reduce`. - seealso: [scan operator on reactivex.io](http://reactivex.io/documentation/operators/scan.html) - parameter seed: The initial accumulator value. - parameter accumulator: An accumulator function to be invoked on each element. - returns: An observable sequence containing the accumulated values. */ public func scan(into seed: A, accumulator: @escaping (inout A, Element) throws -> Void) -> Observable { Scan(source: self.asObservable(), seed: seed, accumulator: accumulator) } /** Applies an accumulator function over an observable sequence and returns each intermediate result. The specified seed value is used as the initial accumulator value. For aggregation behavior with no intermediate results, see `reduce`. - seealso: [scan operator on reactivex.io](http://reactivex.io/documentation/operators/scan.html) - parameter seed: The initial accumulator value. - parameter accumulator: An accumulator function to be invoked on each element. - returns: An observable sequence containing the accumulated values. */ public func scan(_ seed: A, accumulator: @escaping (A, Element) throws -> A) -> Observable { return Scan(source: self.asObservable(), seed: seed) { acc, element in let currentAcc = acc acc = try accumulator(currentAcc, element) } } } final private class ScanSink: Sink, ObserverType { typealias Accumulate = Observer.Element typealias Parent = Scan private let parent: Parent private var accumulate: Accumulate init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.accumulate = parent.seed super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let element): do { try self.parent.accumulator(&self.accumulate, element) self.forwardOn(.next(self.accumulate)) } catch let error { self.forwardOn(.error(error)) self.dispose() } case .error(let error): self.forwardOn(.error(error)) self.dispose() case .completed: self.forwardOn(.completed) self.dispose() } } } final private class Scan: Producer { typealias Accumulator = (inout Accumulate, Element) throws -> Void private let source: Observable fileprivate let seed: Accumulate fileprivate let accumulator: Accumulator init(source: Observable, seed: Accumulate, accumulator: @escaping Accumulator) { self.source = source self.seed = seed self.accumulator = accumulator } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Accumulate { let sink = ScanSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Sequence.swift ================================================ // // Sequence.swift // RxSwift // // Created by Krunoslav Zaher on 11/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { // MARK: of /** This method creates a new Observable instance with a variable number of elements. - seealso: [from operator on reactivex.io](http://reactivex.io/documentation/operators/from.html) - parameter elements: Elements to generate. - parameter scheduler: Scheduler to send elements on. If `nil`, elements are sent immediately on subscription. - returns: The observable sequence whose elements are pulled from the given arguments. */ public static func of(_ elements: Element ..., scheduler: ImmediateSchedulerType = CurrentThreadScheduler.instance) -> Observable { ObservableSequence(elements: elements, scheduler: scheduler) } } extension ObservableType { /** Converts an array to an observable sequence. - seealso: [from operator on reactivex.io](http://reactivex.io/documentation/operators/from.html) - returns: The observable sequence whose elements are pulled from the given enumerable sequence. */ public static func from(_ array: [Element], scheduler: ImmediateSchedulerType = CurrentThreadScheduler.instance) -> Observable { ObservableSequence(elements: array, scheduler: scheduler) } /** Converts a sequence to an observable sequence. - seealso: [from operator on reactivex.io](http://reactivex.io/documentation/operators/from.html) - returns: The observable sequence whose elements are pulled from the given enumerable sequence. */ public static func from(_ sequence: Sequence, scheduler: ImmediateSchedulerType = CurrentThreadScheduler.instance) -> Observable where Sequence.Element == Element { ObservableSequence(elements: sequence, scheduler: scheduler) } } final private class ObservableSequenceSink: Sink where Sequence.Element == Observer.Element { typealias Parent = ObservableSequence private let parent: Parent init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { return self.parent.scheduler.scheduleRecursive(self.parent.elements.makeIterator()) { iterator, recurse in var mutableIterator = iterator if let next = mutableIterator.next() { self.forwardOn(.next(next)) recurse(mutableIterator) } else { self.forwardOn(.completed) self.dispose() } } } } final private class ObservableSequence: Producer { fileprivate let elements: Sequence fileprivate let scheduler: ImmediateSchedulerType init(elements: Sequence, scheduler: ImmediateSchedulerType) { self.elements = elements self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = ObservableSequenceSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/ShareReplayScope.swift ================================================ // // ShareReplayScope.swift // RxSwift // // Created by Krunoslav Zaher on 5/28/17. // Copyright © 2017 Krunoslav Zaher. All rights reserved. // /// Subject lifetime scope public enum SubjectLifetimeScope { /** **Each connection will have it's own subject instance to store replay events.** **Connections will be isolated from each another.** Configures the underlying implementation to behave equivalent to. ``` source.multicast(makeSubject: { MySubject() }).refCount() ``` **This is the recommended default.** This has the following consequences: * `retry` or `concat` operators will function as expected because terminating the sequence will clear internal state. * Each connection to source observable sequence will use it's own subject. * When the number of subscribers drops from 1 to 0 and connection to source sequence is disposed, subject will be cleared. ``` let xs = Observable.deferred { () -> Observable in print("Performing work ...") return Observable.just(Date().timeIntervalSince1970) } .share(replay: 1, scope: .whileConnected) _ = xs.subscribe(onNext: { print("next \($0)") }, onCompleted: { print("completed\n") }) _ = xs.subscribe(onNext: { print("next \($0)") }, onCompleted: { print("completed\n") }) _ = xs.subscribe(onNext: { print("next \($0)") }, onCompleted: { print("completed\n") }) ``` Notice how time interval is different and `Performing work ...` is printed each time) ``` Performing work ... next 1495998900.82141 completed Performing work ... next 1495998900.82359 completed Performing work ... next 1495998900.82444 completed ``` */ case whileConnected /** **One subject will store replay events for all connections to source.** **Connections won't be isolated from each another.** Configures the underlying implementation behave equivalent to. ``` source.multicast(MySubject()).refCount() ``` This has the following consequences: * Using `retry` or `concat` operators after this operator usually isn't advised. * Each connection to source observable sequence will share the same subject. * After number of subscribers drops from 1 to 0 and connection to source observable sequence is dispose, this operator will continue holding a reference to the same subject. If at some later moment a new observer initiates a new connection to source it can potentially receive some of the stale events received during previous connection. * After source sequence terminates any new observer will always immediately receive replayed elements and terminal event. No new subscriptions to source observable sequence will be attempted. ``` let xs = Observable.deferred { () -> Observable in print("Performing work ...") return Observable.just(Date().timeIntervalSince1970) } .share(replay: 1, scope: .forever) _ = xs.subscribe(onNext: { print("next \($0)") }, onCompleted: { print("completed\n") }) _ = xs.subscribe(onNext: { print("next \($0)") }, onCompleted: { print("completed\n") }) _ = xs.subscribe(onNext: { print("next \($0)") }, onCompleted: { print("completed\n") }) ``` Notice how time interval is the same, replayed, and `Performing work ...` is printed only once ``` Performing work ... next 1495999013.76356 completed next 1495999013.76356 completed next 1495999013.76356 completed ``` */ case forever } extension ObservableType { /** Returns an observable sequence that **shares a single subscription to the underlying sequence**, and immediately upon subscription replays elements in buffer. This operator is equivalent to: * `.whileConnected` ``` // Each connection will have it's own subject instance to store replay events. // Connections will be isolated from each another. source.multicast(makeSubject: { Replay.create(bufferSize: replay) }).refCount() ``` * `.forever` ``` // One subject will store replay events for all connections to source. // Connections won't be isolated from each another. source.multicast(Replay.create(bufferSize: replay)).refCount() ``` It uses optimized versions of the operators for most common operations. - parameter replay: Maximum element count of the replay buffer. - parameter scope: Lifetime scope of sharing subject. For more information see `SubjectLifetimeScope` enum. - seealso: [shareReplay operator on reactivex.io](http://reactivex.io/documentation/operators/replay.html) - returns: An observable sequence that contains the elements of a sequence produced by multicasting the source sequence. */ public func share(replay: Int = 0, scope: SubjectLifetimeScope = .whileConnected) -> Observable { switch scope { case .forever: switch replay { case 0: return self.multicast(PublishSubject()).refCount() default: return self.multicast(ReplaySubject.create(bufferSize: replay)).refCount() } case .whileConnected: switch replay { case 0: return ShareWhileConnected(source: self.asObservable()) case 1: return ShareReplay1WhileConnected(source: self.asObservable()) default: return self.multicast(makeSubject: { ReplaySubject.create(bufferSize: replay) }).refCount() } } } } private final class ShareReplay1WhileConnectedConnection : ObserverType , SynchronizedUnsubscribeType { typealias Observers = AnyObserver.s typealias DisposeKey = Observers.KeyType typealias Parent = ShareReplay1WhileConnected private let parent: Parent private let subscription = SingleAssignmentDisposable() private let lock: RecursiveLock private var disposed: Bool = false fileprivate var observers = Observers() private var element: Element? init(parent: Parent, lock: RecursiveLock) { self.parent = parent self.lock = lock #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif } final func on(_ event: Event) { let observers = self.lock.performLocked { self.synchronized_on(event) } dispatch(observers, event) } final private func synchronized_on(_ event: Event) -> Observers { if self.disposed { return Observers() } switch event { case .next(let element): self.element = element return self.observers case .error, .completed: let observers = self.observers self.synchronized_dispose() return observers } } final func connect() { self.subscription.setDisposable(self.parent.source.subscribe(self)) } final func synchronized_subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { self.lock.performLocked { if let element = self.element { observer.on(.next(element)) } let disposeKey = self.observers.insert(observer.on) return SubscriptionDisposable(owner: self, key: disposeKey) } } final private func synchronized_dispose() { self.disposed = true if self.parent.connection === self { self.parent.connection = nil } self.observers = Observers() } final func synchronizedUnsubscribe(_ disposeKey: DisposeKey) { if self.lock.performLocked({ self.synchronized_unsubscribe(disposeKey) }) { self.subscription.dispose() } } @inline(__always) final private func synchronized_unsubscribe(_ disposeKey: DisposeKey) -> Bool { // if already unsubscribed, just return if self.observers.removeKey(disposeKey) == nil { return false } if self.observers.count == 0 { self.synchronized_dispose() return true } return false } #if TRACE_RESOURCES deinit { _ = Resources.decrementTotal() } #endif } // optimized version of share replay for most common case final private class ShareReplay1WhileConnected : Observable { fileprivate typealias Connection = ShareReplay1WhileConnectedConnection fileprivate let source: Observable private let lock = RecursiveLock() fileprivate var connection: Connection? init(source: Observable) { self.source = source } override func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { self.lock.lock() let connection = self.synchronized_subscribe(observer) let count = connection.observers.count let disposable = connection.synchronized_subscribe(observer) self.lock.unlock() if count == 0 { connection.connect() } return disposable } @inline(__always) private func synchronized_subscribe(_ observer: Observer) -> Connection where Observer.Element == Element { let connection: Connection if let existingConnection = self.connection { connection = existingConnection } else { connection = ShareReplay1WhileConnectedConnection( parent: self, lock: self.lock) self.connection = connection } return connection } } private final class ShareWhileConnectedConnection : ObserverType , SynchronizedUnsubscribeType { typealias Observers = AnyObserver.s typealias DisposeKey = Observers.KeyType typealias Parent = ShareWhileConnected private let parent: Parent private let subscription = SingleAssignmentDisposable() private let lock: RecursiveLock private var disposed: Bool = false fileprivate var observers = Observers() init(parent: Parent, lock: RecursiveLock) { self.parent = parent self.lock = lock #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif } final func on(_ event: Event) { let observers = self.lock.performLocked { self.synchronized_on(event) } dispatch(observers, event) } final private func synchronized_on(_ event: Event) -> Observers { if self.disposed { return Observers() } switch event { case .next: return self.observers case .error, .completed: let observers = self.observers self.synchronized_dispose() return observers } } final func connect() { self.subscription.setDisposable(self.parent.source.subscribe(self)) } final func synchronized_subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { self.lock.performLocked { let disposeKey = self.observers.insert(observer.on) return SubscriptionDisposable(owner: self, key: disposeKey) } } final private func synchronized_dispose() { self.disposed = true if self.parent.connection === self { self.parent.connection = nil } self.observers = Observers() } final func synchronizedUnsubscribe(_ disposeKey: DisposeKey) { if self.lock.performLocked({ self.synchronized_unsubscribe(disposeKey) }) { self.subscription.dispose() } } @inline(__always) final private func synchronized_unsubscribe(_ disposeKey: DisposeKey) -> Bool { // if already unsubscribed, just return if self.observers.removeKey(disposeKey) == nil { return false } if self.observers.count == 0 { self.synchronized_dispose() return true } return false } #if TRACE_RESOURCES deinit { _ = Resources.decrementTotal() } #endif } // optimized version of share replay for most common case final private class ShareWhileConnected : Observable { fileprivate typealias Connection = ShareWhileConnectedConnection fileprivate let source: Observable private let lock = RecursiveLock() fileprivate var connection: Connection? init(source: Observable) { self.source = source } override func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { self.lock.lock() let connection = self.synchronized_subscribe(observer) let count = connection.observers.count let disposable = connection.synchronized_subscribe(observer) self.lock.unlock() if count == 0 { connection.connect() } return disposable } @inline(__always) private func synchronized_subscribe(_ observer: Observer) -> Connection where Observer.Element == Element { let connection: Connection if let existingConnection = self.connection { connection = existingConnection } else { connection = ShareWhileConnectedConnection( parent: self, lock: self.lock) self.connection = connection } return connection } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/SingleAsync.swift ================================================ // // SingleAsync.swift // RxSwift // // Created by Junior B. on 09/11/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** The single operator is similar to first, but throws a `RxError.noElements` or `RxError.moreThanOneElement` if the source Observable does not emit exactly one element before successfully completing. - seealso: [single operator on reactivex.io](http://reactivex.io/documentation/operators/first.html) - returns: An observable sequence that emits a single element or throws an exception if more (or none) of them are emitted. */ public func single() -> Observable { SingleAsync(source: self.asObservable()) } /** The single operator is similar to first, but throws a `RxError.NoElements` or `RxError.MoreThanOneElement` if the source Observable does not emit exactly one element before successfully completing. - seealso: [single operator on reactivex.io](http://reactivex.io/documentation/operators/first.html) - parameter predicate: A function to test each source element for a condition. - returns: An observable sequence that emits a single element or throws an exception if more (or none) of them are emitted. */ public func single(_ predicate: @escaping (Element) throws -> Bool) -> Observable { SingleAsync(source: self.asObservable(), predicate: predicate) } } private final class SingleAsyncSink : Sink, ObserverType { typealias Element = Observer.Element typealias Parent = SingleAsync private let parent: Parent private var seenValue: Bool = false init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): do { let forward = try self.parent.predicate?(value) ?? true if !forward { return } } catch let error { self.forwardOn(.error(error as Swift.Error)) self.dispose() return } if self.seenValue { self.forwardOn(.error(RxError.moreThanOneElement)) self.dispose() return } self.seenValue = true self.forwardOn(.next(value)) case .error: self.forwardOn(event) self.dispose() case .completed: if self.seenValue { self.forwardOn(.completed) } else { self.forwardOn(.error(RxError.noElements)) } self.dispose() } } } final class SingleAsync: Producer { typealias Predicate = (Element) throws -> Bool private let source: Observable fileprivate let predicate: Predicate? init(source: Observable, predicate: Predicate? = nil) { self.source = source self.predicate = predicate } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = SingleAsyncSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Sink.swift ================================================ // // Sink.swift // RxSwift // // Created by Krunoslav Zaher on 2/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // class Sink: Disposable { fileprivate let observer: Observer fileprivate let cancel: Cancelable private let disposed = AtomicInt(0) #if DEBUG private let synchronizationTracker = SynchronizationTracker() #endif init(observer: Observer, cancel: Cancelable) { #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif self.observer = observer self.cancel = cancel } final func forwardOn(_ event: Event) { #if DEBUG self.synchronizationTracker.register(synchronizationErrorMessage: .default) defer { self.synchronizationTracker.unregister() } #endif if isFlagSet(self.disposed, 1) { return } self.observer.on(event) } final func forwarder() -> SinkForward { SinkForward(forward: self) } final var isDisposed: Bool { isFlagSet(self.disposed, 1) } func dispose() { fetchOr(self.disposed, 1) self.cancel.dispose() } deinit { #if TRACE_RESOURCES _ = Resources.decrementTotal() #endif } } final class SinkForward: ObserverType { typealias Element = Observer.Element private let forward: Sink init(forward: Sink) { self.forward = forward } final func on(_ event: Event) { switch event { case .next: self.forward.observer.on(event) case .error, .completed: self.forward.observer.on(event) self.forward.cancel.dispose() } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Skip.swift ================================================ // // Skip.swift // RxSwift // // Created by Krunoslav Zaher on 6/25/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Bypasses a specified number of elements in an observable sequence and then returns the remaining elements. - seealso: [skip operator on reactivex.io](http://reactivex.io/documentation/operators/skip.html) - parameter count: The number of elements to skip before returning the remaining elements. - returns: An observable sequence that contains the elements that occur after the specified index in the input sequence. */ public func skip(_ count: Int) -> Observable { SkipCount(source: self.asObservable(), count: count) } } extension ObservableType { /** Skips elements for the specified duration from the start of the observable source sequence, using the specified scheduler to run timers. - seealso: [skip operator on reactivex.io](http://reactivex.io/documentation/operators/skip.html) - parameter duration: Duration for skipping elements from the start of the sequence. - parameter scheduler: Scheduler to run the timer on. - returns: An observable sequence with the elements skipped during the specified duration from the start of the source sequence. */ public func skip(_ duration: RxTimeInterval, scheduler: SchedulerType) -> Observable { SkipTime(source: self.asObservable(), duration: duration, scheduler: scheduler) } } // count version final private class SkipCountSink: Sink, ObserverType { typealias Element = Observer.Element typealias Parent = SkipCount let parent: Parent var remaining: Int init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.remaining = parent.count super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): if self.remaining <= 0 { self.forwardOn(.next(value)) } else { self.remaining -= 1 } case .error: self.forwardOn(event) self.dispose() case .completed: self.forwardOn(event) self.dispose() } } } final private class SkipCount: Producer { let source: Observable let count: Int init(source: Observable, count: Int) { self.source = source self.count = count } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = SkipCountSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } // time version final private class SkipTimeSink: Sink, ObserverType where Observer.Element == Element { typealias Parent = SkipTime let parent: Parent // state var open = false init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): if self.open { self.forwardOn(.next(value)) } case .error: self.forwardOn(event) self.dispose() case .completed: self.forwardOn(event) self.dispose() } } func tick() { self.open = true } func run() -> Disposable { let disposeTimer = self.parent.scheduler.scheduleRelative((), dueTime: self.parent.duration) { _ in self.tick() return Disposables.create() } let disposeSubscription = self.parent.source.subscribe(self) return Disposables.create(disposeTimer, disposeSubscription) } } final private class SkipTime: Producer { let source: Observable let duration: RxTimeInterval let scheduler: SchedulerType init(source: Observable, duration: RxTimeInterval, scheduler: SchedulerType) { self.source = source self.scheduler = scheduler self.duration = duration } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = SkipTimeSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/SkipUntil.swift ================================================ // // SkipUntil.swift // RxSwift // // Created by Yury Korolev on 10/3/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns the elements from the source observable sequence that are emitted after the other observable sequence produces an element. - seealso: [skipUntil operator on reactivex.io](http://reactivex.io/documentation/operators/skipuntil.html) - parameter other: Observable sequence that starts propagation of elements of the source sequence. - returns: An observable sequence containing the elements of the source sequence that are emitted after the other sequence emits an item. */ public func skip(until other: Source) -> Observable { SkipUntil(source: self.asObservable(), other: other.asObservable()) } /** Returns the elements from the source observable sequence that are emitted after the other observable sequence produces an element. - seealso: [skipUntil operator on reactivex.io](http://reactivex.io/documentation/operators/skipuntil.html) - parameter other: Observable sequence that starts propagation of elements of the source sequence. - returns: An observable sequence containing the elements of the source sequence that are emitted after the other sequence emits an item. */ @available(*, deprecated, renamed: "skip(until:)") public func skipUntil(_ other: Source) -> Observable { skip(until: other) } } final private class SkipUntilSinkOther : ObserverType , LockOwnerType , SynchronizedOnType { typealias Parent = SkipUntilSink typealias Element = Other private let parent: Parent var lock: RecursiveLock { self.parent.lock } let subscription = SingleAssignmentDisposable() init(parent: Parent) { self.parent = parent #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next: self.parent.forwardElements = true self.subscription.dispose() case .error(let e): self.parent.forwardOn(.error(e)) self.parent.dispose() case .completed: self.subscription.dispose() } } #if TRACE_RESOURCES deinit { _ = Resources.decrementTotal() } #endif } final private class SkipUntilSink : Sink , ObserverType , LockOwnerType , SynchronizedOnType { typealias Element = Observer.Element typealias Parent = SkipUntil let lock = RecursiveLock() private let parent: Parent fileprivate var forwardElements = false private let sourceSubscription = SingleAssignmentDisposable() init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next: if self.forwardElements { self.forwardOn(event) } case .error: self.forwardOn(event) self.dispose() case .completed: if self.forwardElements { self.forwardOn(event) } self.dispose() } } func run() -> Disposable { let sourceSubscription = self.parent.source.subscribe(self) let otherObserver = SkipUntilSinkOther(parent: self) let otherSubscription = self.parent.other.subscribe(otherObserver) self.sourceSubscription.setDisposable(sourceSubscription) otherObserver.subscription.setDisposable(otherSubscription) return Disposables.create(sourceSubscription, otherObserver.subscription) } } final private class SkipUntil: Producer { fileprivate let source: Observable fileprivate let other: Observable init(source: Observable, other: Observable) { self.source = source self.other = other } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = SkipUntilSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/SkipWhile.swift ================================================ // // SkipWhile.swift // RxSwift // // Created by Yury Korolev on 10/9/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Bypasses elements in an observable sequence as long as a specified condition is true and then returns the remaining elements. - seealso: [skipWhile operator on reactivex.io](http://reactivex.io/documentation/operators/skipwhile.html) - parameter predicate: A function to test each element for a condition. - returns: An observable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate. */ public func skip(while predicate: @escaping (Element) throws -> Bool) -> Observable { SkipWhile(source: self.asObservable(), predicate: predicate) } /** Bypasses elements in an observable sequence as long as a specified condition is true and then returns the remaining elements. - seealso: [skipWhile operator on reactivex.io](http://reactivex.io/documentation/operators/skipwhile.html) - parameter predicate: A function to test each element for a condition. - returns: An observable sequence that contains the elements from the input sequence starting at the first element in the linear series that does not pass the test specified by predicate. */ @available(*, deprecated, renamed: "skip(while:)") public func skipWhile(_ predicate: @escaping (Element) throws -> Bool) -> Observable { SkipWhile(source: self.asObservable(), predicate: predicate) } } final private class SkipWhileSink: Sink, ObserverType { typealias Element = Observer.Element typealias Parent = SkipWhile private let parent: Parent private var running = false init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): if !self.running { do { self.running = try !self.parent.predicate(value) } catch let e { self.forwardOn(.error(e)) self.dispose() return } } if self.running { self.forwardOn(.next(value)) } case .error, .completed: self.forwardOn(event) self.dispose() } } } final private class SkipWhile: Producer { typealias Predicate = (Element) throws -> Bool private let source: Observable fileprivate let predicate: Predicate init(source: Observable, predicate: @escaping Predicate) { self.source = source self.predicate = predicate } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = SkipWhileSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/StartWith.swift ================================================ // // StartWith.swift // RxSwift // // Created by Krunoslav Zaher on 4/6/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Prepends a sequence of values to an observable sequence. - seealso: [startWith operator on reactivex.io](http://reactivex.io/documentation/operators/startwith.html) - parameter elements: Elements to prepend to the specified sequence. - returns: The source sequence prepended with the specified values. */ public func startWith(_ elements: Element ...) -> Observable { return StartWith(source: self.asObservable(), elements: elements) } } final private class StartWith: Producer { let elements: [Element] let source: Observable init(source: Observable, elements: [Element]) { self.source = source self.elements = elements super.init() } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { for e in self.elements { observer.on(.next(e)) } return (sink: Disposables.create(), subscription: self.source.subscribe(observer)) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/SubscribeOn.swift ================================================ // // SubscribeOn.swift // RxSwift // // Created by Krunoslav Zaher on 6/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Wraps the source sequence in order to run its subscription and unsubscription logic on the specified scheduler. This operation is not commonly used. This only performs the side-effects of subscription and unsubscription on the specified scheduler. In order to invoke observer callbacks on a `scheduler`, use `observeOn`. - seealso: [subscribeOn operator on reactivex.io](http://reactivex.io/documentation/operators/subscribeon.html) - parameter scheduler: Scheduler to perform subscription and unsubscription actions on. - returns: The source sequence whose subscriptions and unsubscriptions happen on the specified scheduler. */ public func subscribe(on scheduler: ImmediateSchedulerType) -> Observable { SubscribeOn(source: self, scheduler: scheduler) } /** Wraps the source sequence in order to run its subscription and unsubscription logic on the specified scheduler. This operation is not commonly used. This only performs the side-effects of subscription and unsubscription on the specified scheduler. In order to invoke observer callbacks on a `scheduler`, use `observeOn`. - seealso: [subscribeOn operator on reactivex.io](http://reactivex.io/documentation/operators/subscribeon.html) - parameter scheduler: Scheduler to perform subscription and unsubscription actions on. - returns: The source sequence whose subscriptions and unsubscriptions happen on the specified scheduler. */ @available(*, deprecated, renamed: "subscribe(on:)") public func subscribeOn(_ scheduler: ImmediateSchedulerType) -> Observable { subscribe(on: scheduler) } } final private class SubscribeOnSink: Sink, ObserverType where Ob.Element == Observer.Element { typealias Element = Observer.Element typealias Parent = SubscribeOn let parent: Parent init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { self.forwardOn(event) if event.isStopEvent { self.dispose() } } func run() -> Disposable { let disposeEverything = SerialDisposable() let cancelSchedule = SingleAssignmentDisposable() disposeEverything.disposable = cancelSchedule let disposeSchedule = self.parent.scheduler.schedule(()) { _ -> Disposable in let subscription = self.parent.source.subscribe(self) disposeEverything.disposable = ScheduledDisposable(scheduler: self.parent.scheduler, disposable: subscription) return Disposables.create() } cancelSchedule.setDisposable(disposeSchedule) return disposeEverything } } final private class SubscribeOn: Producer { let source: Ob let scheduler: ImmediateSchedulerType init(source: Ob, scheduler: ImmediateSchedulerType) { self.source = source self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Ob.Element { let sink = SubscribeOnSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Switch.swift ================================================ // // Switch.swift // RxSwift // // Created by Krunoslav Zaher on 3/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Projects each element of an observable sequence into a new sequence of observable sequences and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. It is a combination of `map` + `switchLatest` operator - seealso: [flatMapLatest operator on reactivex.io](http://reactivex.io/documentation/operators/flatmap.html) - parameter selector: A transform function to apply to each element. - returns: An observable sequence whose elements are the result of invoking the transform function on each element of source producing an Observable of Observable sequences and that at any point in time produces the elements of the most recent inner observable sequence that has been received. */ public func flatMapLatest(_ selector: @escaping (Element) throws -> Source) -> Observable { return FlatMapLatest(source: self.asObservable(), selector: selector) } /** Projects each element of an observable sequence into a new sequence of observable sequences and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. It is a combination of `map` + `switchLatest` operator - seealso: [flatMapLatest operator on reactivex.io](http://reactivex.io/documentation/operators/flatmap.html) - parameter selector: A transform function to apply to each element. - returns: An observable sequence whose elements are the result of invoking the transform function on each element of source producing an Observable of Observable sequences and that at any point in time produces the elements of the most recent inner observable sequence that has been received. */ public func flatMapLatest(_ selector: @escaping (Element) throws -> Source) -> Infallible { return Infallible(flatMapLatest(selector)) } } extension ObservableType where Element: ObservableConvertibleType { /** Transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. Each time a new inner observable sequence is received, unsubscribe from the previous inner observable sequence. - seealso: [switch operator on reactivex.io](http://reactivex.io/documentation/operators/switch.html) - returns: The observable sequence that at any point in time produces the elements of the most recent inner observable sequence that has been received. */ public func switchLatest() -> Observable { Switch(source: self.asObservable()) } } private class SwitchSink : Sink , ObserverType where Source.Element == Observer.Element { typealias Element = SourceType private let subscriptions: SingleAssignmentDisposable = SingleAssignmentDisposable() private let innerSubscription: SerialDisposable = SerialDisposable() let lock = RecursiveLock() // state fileprivate var stopped = false fileprivate var latest = 0 fileprivate var hasLatest = false override init(observer: Observer, cancel: Cancelable) { super.init(observer: observer, cancel: cancel) } func run(_ source: Observable) -> Disposable { let subscription = source.subscribe(self) self.subscriptions.setDisposable(subscription) return Disposables.create(subscriptions, innerSubscription) } func performMap(_ element: SourceType) throws -> Source { rxAbstractMethod() } @inline(__always) final private func nextElementArrived(element: Element) -> (Int, Observable)? { self.lock.lock(); defer { self.lock.unlock() } do { let observable = try self.performMap(element).asObservable() self.hasLatest = true self.latest = self.latest &+ 1 return (self.latest, observable) } catch let error { self.forwardOn(.error(error)) self.dispose() } return nil } func on(_ event: Event) { switch event { case .next(let element): if let (latest, observable) = self.nextElementArrived(element: element) { let d = SingleAssignmentDisposable() self.innerSubscription.disposable = d let observer = SwitchSinkIter(parent: self, id: latest, this: d) let disposable = observable.subscribe(observer) d.setDisposable(disposable) } case .error(let error): self.lock.lock(); defer { self.lock.unlock() } self.forwardOn(.error(error)) self.dispose() case .completed: self.lock.lock(); defer { self.lock.unlock() } self.stopped = true self.subscriptions.dispose() if !self.hasLatest { self.forwardOn(.completed) self.dispose() } } } } final private class SwitchSinkIter : ObserverType , LockOwnerType , SynchronizedOnType where Source.Element == Observer.Element { typealias Element = Source.Element typealias Parent = SwitchSink private let parent: Parent private let id: Int private let this: Disposable var lock: RecursiveLock { self.parent.lock } init(parent: Parent, id: Int, this: Disposable) { self.parent = parent self.id = id self.this = this } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next: break case .error, .completed: self.this.dispose() } if self.parent.latest != self.id { return } switch event { case .next: self.parent.forwardOn(event) case .error: self.parent.forwardOn(event) self.parent.dispose() case .completed: self.parent.hasLatest = false if self.parent.stopped { self.parent.forwardOn(event) self.parent.dispose() } } } } // MARK: Specializations final private class SwitchIdentitySink: SwitchSink where Observer.Element == Source.Element { override init(observer: Observer, cancel: Cancelable) { super.init(observer: observer, cancel: cancel) } override func performMap(_ element: Source) throws -> Source { element } } final private class MapSwitchSink: SwitchSink where Observer.Element == Source.Element { typealias Selector = (SourceType) throws -> Source private let selector: Selector init(selector: @escaping Selector, observer: Observer, cancel: Cancelable) { self.selector = selector super.init(observer: observer, cancel: cancel) } override func performMap(_ element: SourceType) throws -> Source { try self.selector(element) } } // MARK: Producers final private class Switch: Producer { private let source: Observable init(source: Observable) { self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Source.Element { let sink = SwitchIdentitySink(observer: observer, cancel: cancel) let subscription = sink.run(self.source) return (sink: sink, subscription: subscription) } } final private class FlatMapLatest: Producer { typealias Selector = (SourceType) throws -> Source private let source: Observable private let selector: Selector init(source: Observable, selector: @escaping Selector) { self.source = source self.selector = selector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Source.Element { let sink = MapSwitchSink(selector: self.selector, observer: observer, cancel: cancel) let subscription = sink.run(self.source) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/SwitchIfEmpty.swift ================================================ // // SwitchIfEmpty.swift // RxSwift // // Created by sergdort on 23/12/2016. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns the elements of the specified sequence or `switchTo` sequence if the sequence is empty. - seealso: [DefaultIfEmpty operator on reactivex.io](http://reactivex.io/documentation/operators/defaultifempty.html) - parameter other: Observable sequence being returned when source sequence is empty. - returns: Observable sequence that contains elements from switchTo sequence if source is empty, otherwise returns source sequence elements. */ public func ifEmpty(switchTo other: Observable) -> Observable { SwitchIfEmpty(source: self.asObservable(), ifEmpty: other) } } final private class SwitchIfEmpty: Producer { private let source: Observable private let ifEmpty: Observable init(source: Observable, ifEmpty: Observable) { self.source = source self.ifEmpty = ifEmpty } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = SwitchIfEmptySink(ifEmpty: self.ifEmpty, observer: observer, cancel: cancel) let subscription = sink.run(self.source.asObservable()) return (sink: sink, subscription: subscription) } } final private class SwitchIfEmptySink: Sink , ObserverType { typealias Element = Observer.Element private let ifEmpty: Observable private var isEmpty = true private let ifEmptySubscription = SingleAssignmentDisposable() init(ifEmpty: Observable, observer: Observer, cancel: Cancelable) { self.ifEmpty = ifEmpty super.init(observer: observer, cancel: cancel) } func run(_ source: Observable) -> Disposable { let subscription = source.subscribe(self) return Disposables.create(subscription, ifEmptySubscription) } func on(_ event: Event) { switch event { case .next: self.isEmpty = false self.forwardOn(event) case .error: self.forwardOn(event) self.dispose() case .completed: guard self.isEmpty else { self.forwardOn(.completed) self.dispose() return } let ifEmptySink = SwitchIfEmptySinkIter(parent: self) self.ifEmptySubscription.setDisposable(self.ifEmpty.subscribe(ifEmptySink)) } } } final private class SwitchIfEmptySinkIter : ObserverType { typealias Element = Observer.Element typealias Parent = SwitchIfEmptySink private let parent: Parent init(parent: Parent) { self.parent = parent } func on(_ event: Event) { switch event { case .next: self.parent.forwardOn(event) case .error: self.parent.forwardOn(event) self.parent.dispose() case .completed: self.parent.forwardOn(event) self.parent.dispose() } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Take.swift ================================================ // // Take.swift // RxSwift // // Created by Krunoslav Zaher on 6/12/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns a specified number of contiguous elements from the start of an observable sequence. - seealso: [take operator on reactivex.io](http://reactivex.io/documentation/operators/take.html) - parameter count: The number of elements to return. - returns: An observable sequence that contains the specified number of elements from the start of the input sequence. */ public func take(_ count: Int) -> Observable { if count == 0 { return Observable.empty() } else { return TakeCount(source: self.asObservable(), count: count) } } } extension ObservableType { /** Takes elements for the specified duration from the start of the observable source sequence, using the specified scheduler to run timers. - seealso: [take operator on reactivex.io](http://reactivex.io/documentation/operators/take.html) - parameter duration: Duration for taking elements from the start of the sequence. - parameter scheduler: Scheduler to run the timer on. - returns: An observable sequence with the elements taken during the specified duration from the start of the source sequence. */ public func take(for duration: RxTimeInterval, scheduler: SchedulerType) -> Observable { TakeTime(source: self.asObservable(), duration: duration, scheduler: scheduler) } /** Takes elements for the specified duration from the start of the observable source sequence, using the specified scheduler to run timers. - seealso: [take operator on reactivex.io](http://reactivex.io/documentation/operators/take.html) - parameter duration: Duration for taking elements from the start of the sequence. - parameter scheduler: Scheduler to run the timer on. - returns: An observable sequence with the elements taken during the specified duration from the start of the source sequence. */ @available(*, deprecated, renamed: "take(for:scheduler:)") public func take(_ duration: RxTimeInterval, scheduler: SchedulerType) -> Observable { take(for: duration, scheduler: scheduler) } } // count version final private class TakeCountSink: Sink, ObserverType { typealias Element = Observer.Element typealias Parent = TakeCount private let parent: Parent private var remaining: Int init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.remaining = parent.count super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): if self.remaining > 0 { self.remaining -= 1 self.forwardOn(.next(value)) if self.remaining == 0 { self.forwardOn(.completed) self.dispose() } } case .error: self.forwardOn(event) self.dispose() case .completed: self.forwardOn(event) self.dispose() } } } final private class TakeCount: Producer { private let source: Observable fileprivate let count: Int init(source: Observable, count: Int) { if count < 0 { rxFatalError("count can't be negative") } self.source = source self.count = count } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = TakeCountSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } // time version final private class TakeTimeSink : Sink , LockOwnerType , ObserverType , SynchronizedOnType where Observer.Element == Element { typealias Parent = TakeTime private let parent: Parent let lock = RecursiveLock() init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next(let value): self.forwardOn(.next(value)) case .error: self.forwardOn(event) self.dispose() case .completed: self.forwardOn(event) self.dispose() } } func tick() { self.lock.performLocked { self.forwardOn(.completed) self.dispose() } } func run() -> Disposable { let disposeTimer = self.parent.scheduler.scheduleRelative((), dueTime: self.parent.duration) { _ in self.tick() return Disposables.create() } let disposeSubscription = self.parent.source.subscribe(self) return Disposables.create(disposeTimer, disposeSubscription) } } final private class TakeTime: Producer { typealias TimeInterval = RxTimeInterval fileprivate let source: Observable fileprivate let duration: TimeInterval fileprivate let scheduler: SchedulerType init(source: Observable, duration: TimeInterval, scheduler: SchedulerType) { self.source = source self.scheduler = scheduler self.duration = duration } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = TakeTimeSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/TakeLast.swift ================================================ // // TakeLast.swift // RxSwift // // Created by Tomi Koskinen on 25/10/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns a specified number of contiguous elements from the end of an observable sequence. This operator accumulates a buffer with a length enough to store elements count elements. Upon completion of the source sequence, this buffer is drained on the result sequence. This causes the elements to be delayed. - seealso: [takeLast operator on reactivex.io](http://reactivex.io/documentation/operators/takelast.html) - parameter count: Number of elements to take from the end of the source sequence. - returns: An observable sequence containing the specified number of elements from the end of the source sequence. */ public func takeLast(_ count: Int) -> Observable { TakeLast(source: self.asObservable(), count: count) } } final private class TakeLastSink: Sink, ObserverType { typealias Element = Observer.Element typealias Parent = TakeLast private let parent: Parent private var elements: Queue init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.elements = Queue(capacity: parent.count + 1) super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): self.elements.enqueue(value) if self.elements.count > self.parent.count { _ = self.elements.dequeue() } case .error: self.forwardOn(event) self.dispose() case .completed: for e in self.elements { self.forwardOn(.next(e)) } self.forwardOn(.completed) self.dispose() } } } final private class TakeLast: Producer { private let source: Observable fileprivate let count: Int init(source: Observable, count: Int) { if count < 0 { rxFatalError("count can't be negative") } self.source = source self.count = count } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = TakeLastSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/TakeWithPredicate.swift ================================================ // // TakeWithPredicate.swift // RxSwift // // Created by Krunoslav Zaher on 6/7/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Returns the elements from the source observable sequence until the other observable sequence produces an element. - seealso: [takeUntil operator on reactivex.io](http://reactivex.io/documentation/operators/takeuntil.html) - parameter other: Observable sequence that terminates propagation of elements of the source sequence. - returns: An observable sequence containing the elements of the source sequence up to the point the other sequence interrupted further propagation. */ public func take(until other: Source) -> Observable { TakeUntil(source: self.asObservable(), other: other.asObservable()) } /** Returns elements from an observable sequence until the specified condition is true. - seealso: [takeUntil operator on reactivex.io](http://reactivex.io/documentation/operators/takeuntil.html) - parameter predicate: A function to test each element for a condition. - parameter behavior: Whether or not to include the last element matching the predicate. Defaults to `exclusive`. - returns: An observable sequence that contains the elements from the input sequence that occur before the element at which the test passes. */ public func take(until predicate: @escaping (Element) throws -> Bool, behavior: TakeBehavior = .exclusive) -> Observable { TakeUntilPredicate(source: self.asObservable(), behavior: behavior, predicate: predicate) } /** Returns elements from an observable sequence as long as a specified condition is true. - seealso: [takeWhile operator on reactivex.io](http://reactivex.io/documentation/operators/takewhile.html) - parameter predicate: A function to test each element for a condition. - returns: An observable sequence that contains the elements from the input sequence that occur before the element at which the test no longer passes. */ public func take(while predicate: @escaping (Element) throws -> Bool, behavior: TakeBehavior = .exclusive) -> Observable { take(until: { try !predicate($0) }, behavior: behavior) } /** Returns the elements from the source observable sequence until the other observable sequence produces an element. - seealso: [takeUntil operator on reactivex.io](http://reactivex.io/documentation/operators/takeuntil.html) - parameter other: Observable sequence that terminates propagation of elements of the source sequence. - returns: An observable sequence containing the elements of the source sequence up to the point the other sequence interrupted further propagation. */ @available(*, deprecated, renamed: "take(until:)") public func takeUntil(_ other: Source) -> Observable { take(until: other) } /** Returns elements from an observable sequence until the specified condition is true. - seealso: [takeUntil operator on reactivex.io](http://reactivex.io/documentation/operators/takeuntil.html) - parameter behavior: Whether or not to include the last element matching the predicate. - parameter predicate: A function to test each element for a condition. - returns: An observable sequence that contains the elements from the input sequence that occur before the element at which the test passes. */ @available(*, deprecated, renamed: "take(until:behavior:)") public func takeUntil(_ behavior: TakeBehavior, predicate: @escaping (Element) throws -> Bool) -> Observable { take(until: predicate, behavior: behavior) } /** Returns elements from an observable sequence as long as a specified condition is true. - seealso: [takeWhile operator on reactivex.io](http://reactivex.io/documentation/operators/takewhile.html) - parameter predicate: A function to test each element for a condition. - returns: An observable sequence that contains the elements from the input sequence that occur before the element at which the test no longer passes. */ @available(*, deprecated, renamed: "take(while:)") public func takeWhile(_ predicate: @escaping (Element) throws -> Bool) -> Observable { take(until: { try !predicate($0) }, behavior: .exclusive) } } /// Behaviors for the take operator family. public enum TakeBehavior { /// Include the last element matching the predicate. case inclusive /// Exclude the last element matching the predicate. case exclusive } // MARK: - TakeUntil Observable final private class TakeUntilSinkOther : ObserverType , LockOwnerType , SynchronizedOnType { typealias Parent = TakeUntilSink typealias Element = Other private let parent: Parent var lock: RecursiveLock { self.parent.lock } fileprivate let subscription = SingleAssignmentDisposable() init(parent: Parent) { self.parent = parent #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next: self.parent.forwardOn(.completed) self.parent.dispose() case .error(let e): self.parent.forwardOn(.error(e)) self.parent.dispose() case .completed: self.subscription.dispose() } } #if TRACE_RESOURCES deinit { _ = Resources.decrementTotal() } #endif } final private class TakeUntilSink : Sink , LockOwnerType , ObserverType , SynchronizedOnType { typealias Element = Observer.Element typealias Parent = TakeUntil private let parent: Parent let lock = RecursiveLock() init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next: self.forwardOn(event) case .error: self.forwardOn(event) self.dispose() case .completed: self.forwardOn(event) self.dispose() } } func run() -> Disposable { let otherObserver = TakeUntilSinkOther(parent: self) let otherSubscription = self.parent.other.subscribe(otherObserver) otherObserver.subscription.setDisposable(otherSubscription) let sourceSubscription = self.parent.source.subscribe(self) return Disposables.create(sourceSubscription, otherObserver.subscription) } } final private class TakeUntil: Producer { fileprivate let source: Observable fileprivate let other: Observable init(source: Observable, other: Observable) { self.source = source self.other = other } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = TakeUntilSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // MARK: - TakeUntil Predicate final private class TakeUntilPredicateSink : Sink, ObserverType { typealias Element = Observer.Element typealias Parent = TakeUntilPredicate private let parent: Parent private var running = true init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): if !self.running { return } do { self.running = try !self.parent.predicate(value) } catch let e { self.forwardOn(.error(e)) self.dispose() return } if self.running { self.forwardOn(.next(value)) } else { if self.parent.behavior == .inclusive { self.forwardOn(.next(value)) } self.forwardOn(.completed) self.dispose() } case .error, .completed: self.forwardOn(event) self.dispose() } } } final private class TakeUntilPredicate: Producer { typealias Predicate = (Element) throws -> Bool private let source: Observable fileprivate let predicate: Predicate fileprivate let behavior: TakeBehavior init(source: Observable, behavior: TakeBehavior, predicate: @escaping Predicate) { self.source = source self.behavior = behavior self.predicate = predicate } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = TakeUntilPredicateSink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Throttle.swift ================================================ // // Throttle.swift // RxSwift // // Created by Krunoslav Zaher on 3/22/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Foundation extension ObservableType { /** Returns an Observable that emits the first and the latest item emitted by the source Observable during sequential time windows of a specified duration. This operator makes sure that no two elements are emitted in less then dueTime. - seealso: [debounce operator on reactivex.io](http://reactivex.io/documentation/operators/debounce.html) - parameter dueTime: Throttling duration for each element. - parameter latest: Should latest element received in a dueTime wide time window since last element emission be emitted. - parameter scheduler: Scheduler to run the throttle timers on. - returns: The throttled sequence. */ public func throttle(_ dueTime: RxTimeInterval, latest: Bool = true, scheduler: SchedulerType) -> Observable { Throttle(source: self.asObservable(), dueTime: dueTime, latest: latest, scheduler: scheduler) } } final private class ThrottleSink : Sink , ObserverType , LockOwnerType , SynchronizedOnType { typealias Element = Observer.Element typealias ParentType = Throttle private let parent: ParentType let lock = RecursiveLock() // state private var lastUnsentElement: Element? private var lastSentTime: Date? private var completed: Bool = false let cancellable = SerialDisposable() init(parent: ParentType, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { let subscription = self.parent.source.subscribe(self) return Disposables.create(subscription, cancellable) } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case .next(let element): let now = self.parent.scheduler.now let reducedScheduledTime: RxTimeInterval if let lastSendingTime = self.lastSentTime { reducedScheduledTime = self.parent.dueTime.reduceWithSpanBetween(earlierDate: lastSendingTime, laterDate: now) } else { reducedScheduledTime = .nanoseconds(0) } if reducedScheduledTime.isNow { self.sendNow(element: element) return } if !self.parent.latest { return } let isThereAlreadyInFlightRequest = self.lastUnsentElement != nil self.lastUnsentElement = element if isThereAlreadyInFlightRequest { return } let scheduler = self.parent.scheduler let d = SingleAssignmentDisposable() self.cancellable.disposable = d d.setDisposable(scheduler.scheduleRelative(0, dueTime: reducedScheduledTime, action: self.propagate)) case .error: self.lastUnsentElement = nil self.forwardOn(event) self.dispose() case .completed: if self.lastUnsentElement != nil { self.completed = true } else { self.forwardOn(.completed) self.dispose() } } } private func sendNow(element: Element) { self.lastUnsentElement = nil self.forwardOn(.next(element)) // in case element processing takes a while, this should give some more room self.lastSentTime = self.parent.scheduler.now } func propagate(_: Int) -> Disposable { self.lock.performLocked { if let lastUnsentElement = self.lastUnsentElement { self.sendNow(element: lastUnsentElement) } if self.completed { self.forwardOn(.completed) self.dispose() } } return Disposables.create() } } final private class Throttle: Producer { fileprivate let source: Observable fileprivate let dueTime: RxTimeInterval fileprivate let latest: Bool fileprivate let scheduler: SchedulerType init(source: Observable, dueTime: RxTimeInterval, latest: Bool, scheduler: SchedulerType) { self.source = source self.dueTime = dueTime self.latest = latest self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = ThrottleSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Timeout.swift ================================================ // // Timeout.swift // RxSwift // // Created by Tomi Koskinen on 13/11/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Applies a timeout policy for each element in the observable sequence. If the next element isn't received within the specified timeout duration starting from its predecessor, a TimeoutError is propagated to the observer. - seealso: [timeout operator on reactivex.io](http://reactivex.io/documentation/operators/timeout.html) - parameter dueTime: Maximum duration between values before a timeout occurs. - parameter scheduler: Scheduler to run the timeout timer on. - returns: An observable sequence with a `RxError.timeout` in case of a timeout. */ public func timeout(_ dueTime: RxTimeInterval, scheduler: SchedulerType) -> Observable { return Timeout(source: self.asObservable(), dueTime: dueTime, other: Observable.error(RxError.timeout), scheduler: scheduler) } /** Applies a timeout policy for each element in the observable sequence, using the specified scheduler to run timeout timers. If the next element isn't received within the specified timeout duration starting from its predecessor, the other observable sequence is used to produce future messages from that point on. - seealso: [timeout operator on reactivex.io](http://reactivex.io/documentation/operators/timeout.html) - parameter dueTime: Maximum duration between values before a timeout occurs. - parameter other: Sequence to return in case of a timeout. - parameter scheduler: Scheduler to run the timeout timer on. - returns: The source sequence switching to the other sequence in case of a timeout. */ public func timeout(_ dueTime: RxTimeInterval, other: Source, scheduler: SchedulerType) -> Observable where Element == Source.Element { return Timeout(source: self.asObservable(), dueTime: dueTime, other: other.asObservable(), scheduler: scheduler) } } final private class TimeoutSink: Sink, LockOwnerType, ObserverType { typealias Element = Observer.Element typealias Parent = Timeout private let parent: Parent let lock = RecursiveLock() private let timerD = SerialDisposable() private let subscription = SerialDisposable() private var id = 0 private var switched = false init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { let original = SingleAssignmentDisposable() self.subscription.disposable = original self.createTimeoutTimer() original.setDisposable(self.parent.source.subscribe(self)) return Disposables.create(subscription, timerD) } func on(_ event: Event) { switch event { case .next: var onNextWins = false self.lock.performLocked { onNextWins = !self.switched if onNextWins { self.id = self.id &+ 1 } } if onNextWins { self.forwardOn(event) self.createTimeoutTimer() } case .error, .completed: var onEventWins = false self.lock.performLocked { onEventWins = !self.switched if onEventWins { self.id = self.id &+ 1 } } if onEventWins { self.forwardOn(event) self.dispose() } } } private func createTimeoutTimer() { if self.timerD.isDisposed { return } let nextTimer = SingleAssignmentDisposable() self.timerD.disposable = nextTimer let disposeSchedule = self.parent.scheduler.scheduleRelative(self.id, dueTime: self.parent.dueTime) { state in var timerWins = false self.lock.performLocked { self.switched = (state == self.id) timerWins = self.switched } if timerWins { self.subscription.disposable = self.parent.other.subscribe(self.forwarder()) } return Disposables.create() } nextTimer.setDisposable(disposeSchedule) } } final private class Timeout: Producer { fileprivate let source: Observable fileprivate let dueTime: RxTimeInterval fileprivate let other: Observable fileprivate let scheduler: SchedulerType init(source: Observable, dueTime: RxTimeInterval, other: Observable, scheduler: SchedulerType) { self.source = source self.dueTime = dueTime self.other = other self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = TimeoutSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Timer.swift ================================================ // // Timer.swift // RxSwift // // Created by Krunoslav Zaher on 6/7/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType where Element: RxAbstractInteger { /** Returns an observable sequence that produces a value after each period, using the specified scheduler to run timers and to send out observer messages. - seealso: [interval operator on reactivex.io](http://reactivex.io/documentation/operators/interval.html) - parameter period: Period for producing the values in the resulting sequence. - parameter scheduler: Scheduler to run the timer on. - returns: An observable sequence that produces a value after each period. */ public static func interval(_ period: RxTimeInterval, scheduler: SchedulerType) -> Observable { return Timer( dueTime: period, period: period, scheduler: scheduler ) } } extension ObservableType where Element: RxAbstractInteger { /** Returns an observable sequence that periodically produces a value after the specified initial relative due time has elapsed, using the specified scheduler to run timers. - seealso: [timer operator on reactivex.io](http://reactivex.io/documentation/operators/timer.html) - parameter dueTime: Relative time at which to produce the first value. - parameter period: Period to produce subsequent values. - parameter scheduler: Scheduler to run timers on. - returns: An observable sequence that produces a value after due time has elapsed and then each period. */ public static func timer(_ dueTime: RxTimeInterval, period: RxTimeInterval? = nil, scheduler: SchedulerType) -> Observable { return Timer( dueTime: dueTime, period: period, scheduler: scheduler ) } } import Foundation final private class TimerSink : Sink where Observer.Element : RxAbstractInteger { typealias Parent = Timer private let parent: Parent private let lock = RecursiveLock() init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { return self.parent.scheduler.schedulePeriodic(0 as Observer.Element, startAfter: self.parent.dueTime, period: self.parent.period!) { state in self.lock.performLocked { self.forwardOn(.next(state)) return state &+ 1 } } } } final private class TimerOneOffSink: Sink where Observer.Element: RxAbstractInteger { typealias Parent = Timer private let parent: Parent init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { return self.parent.scheduler.scheduleRelative(self, dueTime: self.parent.dueTime) { [unowned self] _ -> Disposable in self.forwardOn(.next(0)) self.forwardOn(.completed) self.dispose() return Disposables.create() } } } final private class Timer: Producer { fileprivate let scheduler: SchedulerType fileprivate let dueTime: RxTimeInterval fileprivate let period: RxTimeInterval? init(dueTime: RxTimeInterval, period: RxTimeInterval?, scheduler: SchedulerType) { self.scheduler = scheduler self.dueTime = dueTime self.period = period } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { if self.period != nil { let sink = TimerSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } else { let sink = TimerOneOffSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/ToArray.swift ================================================ // // ToArray.swift // RxSwift // // Created by Junior B. on 20/10/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Converts an Observable into a Single that emits the whole sequence as a single array and then terminates. For aggregation behavior see `reduce`. - seealso: [toArray operator on reactivex.io](http://reactivex.io/documentation/operators/to.html) - returns: A Single sequence containing all the emitted elements as array. */ public func toArray() -> Single<[Element]> { PrimitiveSequence(raw: ToArray(source: self.asObservable())) } } final private class ToArraySink: Sink, ObserverType where Observer.Element == [SourceType] { typealias Parent = ToArray let parent: Parent var list = [SourceType]() init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func on(_ event: Event) { switch event { case .next(let value): self.list.append(value) case .error(let e): self.forwardOn(.error(e)) self.dispose() case .completed: self.forwardOn(.next(self.list)) self.forwardOn(.completed) self.dispose() } } } final private class ToArray: Producer<[SourceType]> { let source: Observable init(source: Observable) { self.source = source } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == [SourceType] { let sink = ToArraySink(parent: self, observer: observer, cancel: cancel) let subscription = self.source.subscribe(sink) return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Using.swift ================================================ // // Using.swift // RxSwift // // Created by Yury Korolev on 10/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Constructs an observable sequence that depends on a resource object, whose lifetime is tied to the resulting observable sequence's lifetime. - seealso: [using operator on reactivex.io](http://reactivex.io/documentation/operators/using.html) - parameter resourceFactory: Factory function to obtain a resource object. - parameter observableFactory: Factory function to obtain an observable sequence that depends on the obtained resource. - returns: An observable sequence whose lifetime controls the lifetime of the dependent resource object. */ public static func using(_ resourceFactory: @escaping () throws -> Resource, observableFactory: @escaping (Resource) throws -> Observable) -> Observable { Using(resourceFactory: resourceFactory, observableFactory: observableFactory) } } final private class UsingSink: Sink, ObserverType { typealias SourceType = Observer.Element typealias Parent = Using private let parent: Parent init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { var disposable = Disposables.create() do { let resource = try self.parent.resourceFactory() disposable = resource let source = try self.parent.observableFactory(resource) return Disposables.create( source.subscribe(self), disposable ) } catch let error { return Disposables.create( Observable.error(error).subscribe(self), disposable ) } } func on(_ event: Event) { switch event { case let .next(value): self.forwardOn(.next(value)) case let .error(error): self.forwardOn(.error(error)) self.dispose() case .completed: self.forwardOn(.completed) self.dispose() } } } final private class Using: Producer { typealias Element = SourceType typealias ResourceFactory = () throws -> ResourceType typealias ObservableFactory = (ResourceType) throws -> Observable fileprivate let resourceFactory: ResourceFactory fileprivate let observableFactory: ObservableFactory init(resourceFactory: @escaping ResourceFactory, observableFactory: @escaping ObservableFactory) { self.resourceFactory = resourceFactory self.observableFactory = observableFactory } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { let sink = UsingSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Window.swift ================================================ // // Window.swift // RxSwift // // Created by Junior B. on 29/10/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Projects each element of an observable sequence into a window that is completed when either it’s full or a given amount of time has elapsed. - seealso: [window operator on reactivex.io](http://reactivex.io/documentation/operators/window.html) - parameter timeSpan: Maximum time length of a window. - parameter count: Maximum element count of a window. - parameter scheduler: Scheduler to run windowing timers on. - returns: An observable sequence of windows (instances of `Observable`). */ public func window(timeSpan: RxTimeInterval, count: Int, scheduler: SchedulerType) -> Observable> { return WindowTimeCount(source: self.asObservable(), timeSpan: timeSpan, count: count, scheduler: scheduler) } } final private class WindowTimeCountSink : Sink , ObserverType , LockOwnerType , SynchronizedOnType where Observer.Element == Observable { typealias Parent = WindowTimeCount private let parent: Parent let lock = RecursiveLock() private var subject = PublishSubject() private var count = 0 private var windowId = 0 private let timerD = SerialDisposable() private let refCountDisposable: RefCountDisposable private let groupDisposable = CompositeDisposable() init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent _ = self.groupDisposable.insert(self.timerD) self.refCountDisposable = RefCountDisposable(disposable: self.groupDisposable) super.init(observer: observer, cancel: cancel) } func run() -> Disposable { self.forwardOn(.next(AddRef(source: self.subject, refCount: self.refCountDisposable).asObservable())) self.createTimer(self.windowId) _ = self.groupDisposable.insert(self.parent.source.subscribe(self)) return self.refCountDisposable } func startNewWindowAndCompleteCurrentOne() { self.subject.on(.completed) self.subject = PublishSubject() self.forwardOn(.next(AddRef(source: self.subject, refCount: self.refCountDisposable).asObservable())) } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { var newWindow = false var newId = 0 switch event { case .next(let element): self.subject.on(.next(element)) do { _ = try incrementChecked(&self.count) } catch let e { self.subject.on(.error(e as Swift.Error)) self.dispose() } if self.count == self.parent.count { newWindow = true self.count = 0 self.windowId += 1 newId = self.windowId self.startNewWindowAndCompleteCurrentOne() } case .error(let error): self.subject.on(.error(error)) self.forwardOn(.error(error)) self.dispose() case .completed: self.subject.on(.completed) self.forwardOn(.completed) self.dispose() } if newWindow { self.createTimer(newId) } } func createTimer(_ windowId: Int) { if self.timerD.isDisposed { return } if self.windowId != windowId { return } let nextTimer = SingleAssignmentDisposable() self.timerD.disposable = nextTimer let scheduledRelative = self.parent.scheduler.scheduleRelative(windowId, dueTime: self.parent.timeSpan) { previousWindowId in var newId = 0 self.lock.performLocked { if previousWindowId != self.windowId { return } self.count = 0 self.windowId = self.windowId &+ 1 newId = self.windowId self.startNewWindowAndCompleteCurrentOne() } self.createTimer(newId) return Disposables.create() } nextTimer.setDisposable(scheduledRelative) } } final private class WindowTimeCount: Producer> { fileprivate let timeSpan: RxTimeInterval fileprivate let count: Int fileprivate let scheduler: SchedulerType fileprivate let source: Observable init(source: Observable, timeSpan: RxTimeInterval, count: Int, scheduler: SchedulerType) { self.source = source self.timeSpan = timeSpan self.count = count self.scheduler = scheduler } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Observable { let sink = WindowTimeCountSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/WithLatestFrom.swift ================================================ // // WithLatestFrom.swift // RxSwift // // Created by Yury Korolev on 10/19/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Merges two observable sequences into one observable sequence by combining each element from self with the latest element from the second source, if any. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - note: Elements emitted by self before the second source has emitted any values will be omitted. - parameter second: Second observable source. - parameter resultSelector: Function to invoke for each element from the self combined with the latest element from the second source, if any. - returns: An observable sequence containing the result of combining each element of the self with the latest element from the second source, if any, using the specified result selector function. */ public func withLatestFrom(_ second: Source, resultSelector: @escaping (Element, Source.Element) throws -> ResultType) -> Observable { WithLatestFrom(first: self.asObservable(), second: second.asObservable(), resultSelector: resultSelector) } /** Merges two observable sequences into one observable sequence by using latest element from the second sequence every time when `self` emits an element. - seealso: [combineLatest operator on reactivex.io](http://reactivex.io/documentation/operators/combinelatest.html) - note: Elements emitted by self before the second source has emitted any values will be omitted. - parameter second: Second observable source. - returns: An observable sequence containing the result of combining each element of the self with the latest element from the second source, if any, using the specified result selector function. */ public func withLatestFrom(_ second: Source) -> Observable { WithLatestFrom(first: self.asObservable(), second: second.asObservable(), resultSelector: { $1 }) } } final private class WithLatestFromSink : Sink , ObserverType , LockOwnerType , SynchronizedOnType { typealias ResultType = Observer.Element typealias Parent = WithLatestFrom typealias Element = FirstType private let parent: Parent fileprivate var lock = RecursiveLock() fileprivate var latest: SecondType? init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(observer: observer, cancel: cancel) } func run() -> Disposable { let sndSubscription = SingleAssignmentDisposable() let sndO = WithLatestFromSecond(parent: self, disposable: sndSubscription) sndSubscription.setDisposable(self.parent.second.subscribe(sndO)) let fstSubscription = self.parent.first.subscribe(self) return Disposables.create(fstSubscription, sndSubscription) } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case let .next(value): guard let latest = self.latest else { return } do { let res = try self.parent.resultSelector(value, latest) self.forwardOn(.next(res)) } catch let e { self.forwardOn(.error(e)) self.dispose() } case .completed: self.forwardOn(.completed) self.dispose() case let .error(error): self.forwardOn(.error(error)) self.dispose() } } } final private class WithLatestFromSecond : ObserverType , LockOwnerType , SynchronizedOnType { typealias ResultType = Observer.Element typealias Parent = WithLatestFromSink typealias Element = SecondType private let parent: Parent private let disposable: Disposable var lock: RecursiveLock { self.parent.lock } init(parent: Parent, disposable: Disposable) { self.parent = parent self.disposable = disposable } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { switch event { case let .next(value): self.parent.latest = value case .completed: self.disposable.dispose() case let .error(error): self.parent.forwardOn(.error(error)) self.parent.dispose() } } } final private class WithLatestFrom: Producer { typealias ResultSelector = (FirstType, SecondType) throws -> ResultType fileprivate let first: Observable fileprivate let second: Observable fileprivate let resultSelector: ResultSelector init(first: Observable, second: Observable, resultSelector: @escaping ResultSelector) { self.first = first self.second = second self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == ResultType { let sink = WithLatestFromSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/WithUnretained.swift ================================================ // // WithUnretained.swift // RxSwift // // Created by Vincent Pradeilles on 01/01/2021. // Copyright © 2020 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events emitted by the sequence. In the case the provided object cannot be retained successfully, the sequence will complete. - note: Be careful when using this operator in a sequence that has a buffer or replay, for example `share(replay: 1)`, as the sharing buffer will also include the provided object, which could potentially cause a retain cycle. - parameter obj: The object to provide an unretained reference on. - parameter resultSelector: A function to combine the unretained referenced on `obj` and the value of the observable sequence. - returns: An observable sequence that contains the result of `resultSelector` being called with an unretained reference on `obj` and the values of the original sequence. */ public func withUnretained( _ obj: Object, resultSelector: @escaping (Object, Element) -> Out ) -> Observable { map { [weak obj] element -> Out in guard let obj = obj else { throw UnretainedError.failedRetaining } return resultSelector(obj, element) } .catch{ error -> Observable in guard let unretainedError = error as? UnretainedError, unretainedError == .failedRetaining else { return .error(error) } return .empty() } } /** Provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events emitted by the sequence. In the case the provided object cannot be retained successfully, the sequence will complete. - note: Be careful when using this operator in a sequence that has a buffer or replay, for example `share(replay: 1)`, as the sharing buffer will also include the provided object, which could potentially cause a retain cycle. - parameter obj: The object to provide an unretained reference on. - returns: An observable sequence of tuples that contains both an unretained reference on `obj` and the values of the original sequence. */ public func withUnretained(_ obj: Object) -> Observable<(Object, Element)> { return withUnretained(obj) { ($0, $1) } } } private enum UnretainedError: Swift.Error { case failedRetaining } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Zip+Collection.swift ================================================ // // Zip+Collection.swift // RxSwift // // Created by Krunoslav Zaher on 8/30/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip(_ collection: Collection, resultSelector: @escaping ([Collection.Element.Element]) throws -> Element) -> Observable where Collection.Element: ObservableType { ZipCollectionType(sources: collection, resultSelector: resultSelector) } /** Merges the specified observable sequences into one observable sequence whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip(_ collection: Collection) -> Observable<[Element]> where Collection.Element: ObservableType, Collection.Element.Element == Element { ZipCollectionType(sources: collection, resultSelector: { $0 }) } } final private class ZipCollectionTypeSink : Sink where Collection.Element: ObservableConvertibleType { typealias Result = Observer.Element typealias Parent = ZipCollectionType typealias SourceElement = Collection.Element.Element private let parent: Parent private let lock = RecursiveLock() // state private var numberOfValues = 0 private var values: [Queue] private var isDone: [Bool] private var numberOfDone = 0 private var subscriptions: [SingleAssignmentDisposable] init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent self.values = [Queue](repeating: Queue(capacity: 4), count: parent.count) self.isDone = [Bool](repeating: false, count: parent.count) self.subscriptions = [SingleAssignmentDisposable]() self.subscriptions.reserveCapacity(parent.count) for _ in 0 ..< parent.count { self.subscriptions.append(SingleAssignmentDisposable()) } super.init(observer: observer, cancel: cancel) } func on(_ event: Event, atIndex: Int) { self.lock.lock(); defer { self.lock.unlock() } switch event { case .next(let element): self.values[atIndex].enqueue(element) if self.values[atIndex].count == 1 { self.numberOfValues += 1 } if self.numberOfValues < self.parent.count { if self.numberOfDone == self.parent.count - 1 { self.forwardOn(.completed) self.dispose() } return } do { var arguments = [SourceElement]() arguments.reserveCapacity(self.parent.count) // recalculate number of values self.numberOfValues = 0 for i in 0 ..< self.values.count { arguments.append(self.values[i].dequeue()!) if !self.values[i].isEmpty { self.numberOfValues += 1 } } let result = try self.parent.resultSelector(arguments) self.forwardOn(.next(result)) } catch let error { self.forwardOn(.error(error)) self.dispose() } case .error(let error): self.forwardOn(.error(error)) self.dispose() case .completed: if self.isDone[atIndex] { return } self.isDone[atIndex] = true self.numberOfDone += 1 if self.numberOfDone == self.parent.count { self.forwardOn(.completed) self.dispose() } else { self.subscriptions[atIndex].dispose() } } } func run() -> Disposable { var j = 0 for i in self.parent.sources { let index = j let source = i.asObservable() let disposable = source.subscribe(AnyObserver { event in self.on(event, atIndex: index) }) self.subscriptions[j].setDisposable(disposable) j += 1 } if self.parent.sources.isEmpty { self.forwardOn(.completed) } return Disposables.create(subscriptions) } } final private class ZipCollectionType: Producer where Collection.Element: ObservableConvertibleType { typealias ResultSelector = ([Collection.Element.Element]) throws -> Result let sources: Collection let resultSelector: ResultSelector let count: Int init(sources: Collection, resultSelector: @escaping ResultSelector) { self.sources = sources self.resultSelector = resultSelector self.count = self.sources.count } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = ZipCollectionTypeSink(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Zip+arity.swift ================================================ // This file is autogenerated. Take a look at `Preprocessor` target in RxSwift project // // Zip+arity.swift // RxSwift // // Created by Krunoslav Zaher on 5/23/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // // 2 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, resultSelector: @escaping (O1.Element, O2.Element) throws -> Element) -> Observable { return Zip2( source1: source1.asObservable(), source2: source2.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2) -> Observable<(O1.Element, O2.Element)> { return Zip2( source1: source1.asObservable(), source2: source2.asObservable(), resultSelector: { ($0, $1) } ) } } final class ZipSink2_ : ZipSink { typealias Result = Observer.Element typealias Parent = Zip2 let parent: Parent var values1: Queue = Queue(capacity: 2) var values2: Queue = Queue(capacity: 2) init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 2, observer: observer, cancel: cancel) } override func hasElements(_ index: Int) -> Bool { switch index { case 0: return !self.values1.isEmpty case 1: return !self.values2.isEmpty default: rxFatalError("Unhandled case (Function)") } } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let observer1 = ZipObserver(lock: self.lock, parent: self, index: 0, setNextValue: { self.values1.enqueue($0) }, this: subscription1) let observer2 = ZipObserver(lock: self.lock, parent: self, index: 1, setNextValue: { self.values2.enqueue($0) }, this: subscription2) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) return Disposables.create([ subscription1, subscription2 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.values1.dequeue()!, self.values2.dequeue()!) } } final class Zip2 : Producer { typealias ResultSelector = (E1, E2) throws -> Result let source1: Observable let source2: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = ZipSink2_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 3 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, resultSelector: @escaping (O1.Element, O2.Element, O3.Element) throws -> Element) -> Observable { return Zip3( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3) -> Observable<(O1.Element, O2.Element, O3.Element)> { return Zip3( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), resultSelector: { ($0, $1, $2) } ) } } final class ZipSink3_ : ZipSink { typealias Result = Observer.Element typealias Parent = Zip3 let parent: Parent var values1: Queue = Queue(capacity: 2) var values2: Queue = Queue(capacity: 2) var values3: Queue = Queue(capacity: 2) init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 3, observer: observer, cancel: cancel) } override func hasElements(_ index: Int) -> Bool { switch index { case 0: return !self.values1.isEmpty case 1: return !self.values2.isEmpty case 2: return !self.values3.isEmpty default: rxFatalError("Unhandled case (Function)") } } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let observer1 = ZipObserver(lock: self.lock, parent: self, index: 0, setNextValue: { self.values1.enqueue($0) }, this: subscription1) let observer2 = ZipObserver(lock: self.lock, parent: self, index: 1, setNextValue: { self.values2.enqueue($0) }, this: subscription2) let observer3 = ZipObserver(lock: self.lock, parent: self, index: 2, setNextValue: { self.values3.enqueue($0) }, this: subscription3) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) return Disposables.create([ subscription1, subscription2, subscription3 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.values1.dequeue()!, self.values2.dequeue()!, self.values3.dequeue()!) } } final class Zip3 : Producer { typealias ResultSelector = (E1, E2, E3) throws -> Result let source1: Observable let source2: Observable let source3: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = ZipSink3_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 4 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element) throws -> Element) -> Observable { return Zip4( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4) -> Observable<(O1.Element, O2.Element, O3.Element, O4.Element)> { return Zip4( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), resultSelector: { ($0, $1, $2, $3) } ) } } final class ZipSink4_ : ZipSink { typealias Result = Observer.Element typealias Parent = Zip4 let parent: Parent var values1: Queue = Queue(capacity: 2) var values2: Queue = Queue(capacity: 2) var values3: Queue = Queue(capacity: 2) var values4: Queue = Queue(capacity: 2) init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 4, observer: observer, cancel: cancel) } override func hasElements(_ index: Int) -> Bool { switch index { case 0: return !self.values1.isEmpty case 1: return !self.values2.isEmpty case 2: return !self.values3.isEmpty case 3: return !self.values4.isEmpty default: rxFatalError("Unhandled case (Function)") } } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let subscription4 = SingleAssignmentDisposable() let observer1 = ZipObserver(lock: self.lock, parent: self, index: 0, setNextValue: { self.values1.enqueue($0) }, this: subscription1) let observer2 = ZipObserver(lock: self.lock, parent: self, index: 1, setNextValue: { self.values2.enqueue($0) }, this: subscription2) let observer3 = ZipObserver(lock: self.lock, parent: self, index: 2, setNextValue: { self.values3.enqueue($0) }, this: subscription3) let observer4 = ZipObserver(lock: self.lock, parent: self, index: 3, setNextValue: { self.values4.enqueue($0) }, this: subscription4) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) subscription4.setDisposable(self.parent.source4.subscribe(observer4)) return Disposables.create([ subscription1, subscription2, subscription3, subscription4 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.values1.dequeue()!, self.values2.dequeue()!, self.values3.dequeue()!, self.values4.dequeue()!) } } final class Zip4 : Producer { typealias ResultSelector = (E1, E2, E3, E4) throws -> Result let source1: Observable let source2: Observable let source3: Observable let source4: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, source4: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.source4 = source4 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = ZipSink4_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 5 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element) throws -> Element) -> Observable { return Zip5( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5) -> Observable<(O1.Element, O2.Element, O3.Element, O4.Element, O5.Element)> { return Zip5( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), resultSelector: { ($0, $1, $2, $3, $4) } ) } } final class ZipSink5_ : ZipSink { typealias Result = Observer.Element typealias Parent = Zip5 let parent: Parent var values1: Queue = Queue(capacity: 2) var values2: Queue = Queue(capacity: 2) var values3: Queue = Queue(capacity: 2) var values4: Queue = Queue(capacity: 2) var values5: Queue = Queue(capacity: 2) init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 5, observer: observer, cancel: cancel) } override func hasElements(_ index: Int) -> Bool { switch index { case 0: return !self.values1.isEmpty case 1: return !self.values2.isEmpty case 2: return !self.values3.isEmpty case 3: return !self.values4.isEmpty case 4: return !self.values5.isEmpty default: rxFatalError("Unhandled case (Function)") } } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let subscription4 = SingleAssignmentDisposable() let subscription5 = SingleAssignmentDisposable() let observer1 = ZipObserver(lock: self.lock, parent: self, index: 0, setNextValue: { self.values1.enqueue($0) }, this: subscription1) let observer2 = ZipObserver(lock: self.lock, parent: self, index: 1, setNextValue: { self.values2.enqueue($0) }, this: subscription2) let observer3 = ZipObserver(lock: self.lock, parent: self, index: 2, setNextValue: { self.values3.enqueue($0) }, this: subscription3) let observer4 = ZipObserver(lock: self.lock, parent: self, index: 3, setNextValue: { self.values4.enqueue($0) }, this: subscription4) let observer5 = ZipObserver(lock: self.lock, parent: self, index: 4, setNextValue: { self.values5.enqueue($0) }, this: subscription5) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) subscription4.setDisposable(self.parent.source4.subscribe(observer4)) subscription5.setDisposable(self.parent.source5.subscribe(observer5)) return Disposables.create([ subscription1, subscription2, subscription3, subscription4, subscription5 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.values1.dequeue()!, self.values2.dequeue()!, self.values3.dequeue()!, self.values4.dequeue()!, self.values5.dequeue()!) } } final class Zip5 : Producer { typealias ResultSelector = (E1, E2, E3, E4, E5) throws -> Result let source1: Observable let source2: Observable let source3: Observable let source4: Observable let source5: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, source4: Observable, source5: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.source4 = source4 self.source5 = source5 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = ZipSink5_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 6 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element) throws -> Element) -> Observable { return Zip6( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6) -> Observable<(O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element)> { return Zip6( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), resultSelector: { ($0, $1, $2, $3, $4, $5) } ) } } final class ZipSink6_ : ZipSink { typealias Result = Observer.Element typealias Parent = Zip6 let parent: Parent var values1: Queue = Queue(capacity: 2) var values2: Queue = Queue(capacity: 2) var values3: Queue = Queue(capacity: 2) var values4: Queue = Queue(capacity: 2) var values5: Queue = Queue(capacity: 2) var values6: Queue = Queue(capacity: 2) init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 6, observer: observer, cancel: cancel) } override func hasElements(_ index: Int) -> Bool { switch index { case 0: return !self.values1.isEmpty case 1: return !self.values2.isEmpty case 2: return !self.values3.isEmpty case 3: return !self.values4.isEmpty case 4: return !self.values5.isEmpty case 5: return !self.values6.isEmpty default: rxFatalError("Unhandled case (Function)") } } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let subscription4 = SingleAssignmentDisposable() let subscription5 = SingleAssignmentDisposable() let subscription6 = SingleAssignmentDisposable() let observer1 = ZipObserver(lock: self.lock, parent: self, index: 0, setNextValue: { self.values1.enqueue($0) }, this: subscription1) let observer2 = ZipObserver(lock: self.lock, parent: self, index: 1, setNextValue: { self.values2.enqueue($0) }, this: subscription2) let observer3 = ZipObserver(lock: self.lock, parent: self, index: 2, setNextValue: { self.values3.enqueue($0) }, this: subscription3) let observer4 = ZipObserver(lock: self.lock, parent: self, index: 3, setNextValue: { self.values4.enqueue($0) }, this: subscription4) let observer5 = ZipObserver(lock: self.lock, parent: self, index: 4, setNextValue: { self.values5.enqueue($0) }, this: subscription5) let observer6 = ZipObserver(lock: self.lock, parent: self, index: 5, setNextValue: { self.values6.enqueue($0) }, this: subscription6) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) subscription4.setDisposable(self.parent.source4.subscribe(observer4)) subscription5.setDisposable(self.parent.source5.subscribe(observer5)) subscription6.setDisposable(self.parent.source6.subscribe(observer6)) return Disposables.create([ subscription1, subscription2, subscription3, subscription4, subscription5, subscription6 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.values1.dequeue()!, self.values2.dequeue()!, self.values3.dequeue()!, self.values4.dequeue()!, self.values5.dequeue()!, self.values6.dequeue()!) } } final class Zip6 : Producer { typealias ResultSelector = (E1, E2, E3, E4, E5, E6) throws -> Result let source1: Observable let source2: Observable let source3: Observable let source4: Observable let source5: Observable let source6: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, source4: Observable, source5: Observable, source6: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.source4 = source4 self.source5 = source5 self.source6 = source6 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = ZipSink6_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 7 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element) throws -> Element) -> Observable { return Zip7( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), source7: source7.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7) -> Observable<(O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element)> { return Zip7( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), source7: source7.asObservable(), resultSelector: { ($0, $1, $2, $3, $4, $5, $6) } ) } } final class ZipSink7_ : ZipSink { typealias Result = Observer.Element typealias Parent = Zip7 let parent: Parent var values1: Queue = Queue(capacity: 2) var values2: Queue = Queue(capacity: 2) var values3: Queue = Queue(capacity: 2) var values4: Queue = Queue(capacity: 2) var values5: Queue = Queue(capacity: 2) var values6: Queue = Queue(capacity: 2) var values7: Queue = Queue(capacity: 2) init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 7, observer: observer, cancel: cancel) } override func hasElements(_ index: Int) -> Bool { switch index { case 0: return !self.values1.isEmpty case 1: return !self.values2.isEmpty case 2: return !self.values3.isEmpty case 3: return !self.values4.isEmpty case 4: return !self.values5.isEmpty case 5: return !self.values6.isEmpty case 6: return !self.values7.isEmpty default: rxFatalError("Unhandled case (Function)") } } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let subscription4 = SingleAssignmentDisposable() let subscription5 = SingleAssignmentDisposable() let subscription6 = SingleAssignmentDisposable() let subscription7 = SingleAssignmentDisposable() let observer1 = ZipObserver(lock: self.lock, parent: self, index: 0, setNextValue: { self.values1.enqueue($0) }, this: subscription1) let observer2 = ZipObserver(lock: self.lock, parent: self, index: 1, setNextValue: { self.values2.enqueue($0) }, this: subscription2) let observer3 = ZipObserver(lock: self.lock, parent: self, index: 2, setNextValue: { self.values3.enqueue($0) }, this: subscription3) let observer4 = ZipObserver(lock: self.lock, parent: self, index: 3, setNextValue: { self.values4.enqueue($0) }, this: subscription4) let observer5 = ZipObserver(lock: self.lock, parent: self, index: 4, setNextValue: { self.values5.enqueue($0) }, this: subscription5) let observer6 = ZipObserver(lock: self.lock, parent: self, index: 5, setNextValue: { self.values6.enqueue($0) }, this: subscription6) let observer7 = ZipObserver(lock: self.lock, parent: self, index: 6, setNextValue: { self.values7.enqueue($0) }, this: subscription7) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) subscription4.setDisposable(self.parent.source4.subscribe(observer4)) subscription5.setDisposable(self.parent.source5.subscribe(observer5)) subscription6.setDisposable(self.parent.source6.subscribe(observer6)) subscription7.setDisposable(self.parent.source7.subscribe(observer7)) return Disposables.create([ subscription1, subscription2, subscription3, subscription4, subscription5, subscription6, subscription7 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.values1.dequeue()!, self.values2.dequeue()!, self.values3.dequeue()!, self.values4.dequeue()!, self.values5.dequeue()!, self.values6.dequeue()!, self.values7.dequeue()!) } } final class Zip7 : Producer { typealias ResultSelector = (E1, E2, E3, E4, E5, E6, E7) throws -> Result let source1: Observable let source2: Observable let source3: Observable let source4: Observable let source5: Observable let source6: Observable let source7: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, source4: Observable, source5: Observable, source6: Observable, source7: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.source4 = source4 self.source5 = source5 self.source6 = source6 self.source7 = source7 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = ZipSink7_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } // 8 extension ObservableType { /** Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. - returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8, resultSelector: @escaping (O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element, O8.Element) throws -> Element) -> Observable { return Zip8( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), source7: source7.asObservable(), source8: source8.asObservable(), resultSelector: resultSelector ) } } extension ObservableType where Element == Any { /** Merges the specified observable sequences into one observable sequence of tuples whenever all of the observable sequences have produced an element at a corresponding index. - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) - returns: An observable sequence containing the result of combining elements of the sources. */ public static func zip (_ source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8) -> Observable<(O1.Element, O2.Element, O3.Element, O4.Element, O5.Element, O6.Element, O7.Element, O8.Element)> { return Zip8( source1: source1.asObservable(), source2: source2.asObservable(), source3: source3.asObservable(), source4: source4.asObservable(), source5: source5.asObservable(), source6: source6.asObservable(), source7: source7.asObservable(), source8: source8.asObservable(), resultSelector: { ($0, $1, $2, $3, $4, $5, $6, $7) } ) } } final class ZipSink8_ : ZipSink { typealias Result = Observer.Element typealias Parent = Zip8 let parent: Parent var values1: Queue = Queue(capacity: 2) var values2: Queue = Queue(capacity: 2) var values3: Queue = Queue(capacity: 2) var values4: Queue = Queue(capacity: 2) var values5: Queue = Queue(capacity: 2) var values6: Queue = Queue(capacity: 2) var values7: Queue = Queue(capacity: 2) var values8: Queue = Queue(capacity: 2) init(parent: Parent, observer: Observer, cancel: Cancelable) { self.parent = parent super.init(arity: 8, observer: observer, cancel: cancel) } override func hasElements(_ index: Int) -> Bool { switch index { case 0: return !self.values1.isEmpty case 1: return !self.values2.isEmpty case 2: return !self.values3.isEmpty case 3: return !self.values4.isEmpty case 4: return !self.values5.isEmpty case 5: return !self.values6.isEmpty case 6: return !self.values7.isEmpty case 7: return !self.values8.isEmpty default: rxFatalError("Unhandled case (Function)") } } func run() -> Disposable { let subscription1 = SingleAssignmentDisposable() let subscription2 = SingleAssignmentDisposable() let subscription3 = SingleAssignmentDisposable() let subscription4 = SingleAssignmentDisposable() let subscription5 = SingleAssignmentDisposable() let subscription6 = SingleAssignmentDisposable() let subscription7 = SingleAssignmentDisposable() let subscription8 = SingleAssignmentDisposable() let observer1 = ZipObserver(lock: self.lock, parent: self, index: 0, setNextValue: { self.values1.enqueue($0) }, this: subscription1) let observer2 = ZipObserver(lock: self.lock, parent: self, index: 1, setNextValue: { self.values2.enqueue($0) }, this: subscription2) let observer3 = ZipObserver(lock: self.lock, parent: self, index: 2, setNextValue: { self.values3.enqueue($0) }, this: subscription3) let observer4 = ZipObserver(lock: self.lock, parent: self, index: 3, setNextValue: { self.values4.enqueue($0) }, this: subscription4) let observer5 = ZipObserver(lock: self.lock, parent: self, index: 4, setNextValue: { self.values5.enqueue($0) }, this: subscription5) let observer6 = ZipObserver(lock: self.lock, parent: self, index: 5, setNextValue: { self.values6.enqueue($0) }, this: subscription6) let observer7 = ZipObserver(lock: self.lock, parent: self, index: 6, setNextValue: { self.values7.enqueue($0) }, this: subscription7) let observer8 = ZipObserver(lock: self.lock, parent: self, index: 7, setNextValue: { self.values8.enqueue($0) }, this: subscription8) subscription1.setDisposable(self.parent.source1.subscribe(observer1)) subscription2.setDisposable(self.parent.source2.subscribe(observer2)) subscription3.setDisposable(self.parent.source3.subscribe(observer3)) subscription4.setDisposable(self.parent.source4.subscribe(observer4)) subscription5.setDisposable(self.parent.source5.subscribe(observer5)) subscription6.setDisposable(self.parent.source6.subscribe(observer6)) subscription7.setDisposable(self.parent.source7.subscribe(observer7)) subscription8.setDisposable(self.parent.source8.subscribe(observer8)) return Disposables.create([ subscription1, subscription2, subscription3, subscription4, subscription5, subscription6, subscription7, subscription8 ]) } override func getResult() throws -> Result { try self.parent.resultSelector(self.values1.dequeue()!, self.values2.dequeue()!, self.values3.dequeue()!, self.values4.dequeue()!, self.values5.dequeue()!, self.values6.dequeue()!, self.values7.dequeue()!, self.values8.dequeue()!) } } final class Zip8 : Producer { typealias ResultSelector = (E1, E2, E3, E4, E5, E6, E7, E8) throws -> Result let source1: Observable let source2: Observable let source3: Observable let source4: Observable let source5: Observable let source6: Observable let source7: Observable let source8: Observable let resultSelector: ResultSelector init(source1: Observable, source2: Observable, source3: Observable, source4: Observable, source5: Observable, source6: Observable, source7: Observable, source8: Observable, resultSelector: @escaping ResultSelector) { self.source1 = source1 self.source2 = source2 self.source3 = source3 self.source4 = source4 self.source5 = source5 self.source6 = source6 self.source7 = source7 self.source8 = source8 self.resultSelector = resultSelector } override func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Result { let sink = ZipSink8_(parent: self, observer: observer, cancel: cancel) let subscription = sink.run() return (sink: sink, subscription: subscription) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observables/Zip.swift ================================================ // // Zip.swift // RxSwift // // Created by Krunoslav Zaher on 5/23/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // protocol ZipSinkProtocol: AnyObject { func next(_ index: Int) func fail(_ error: Swift.Error) func done(_ index: Int) } class ZipSink : Sink, ZipSinkProtocol { typealias Element = Observer.Element let arity: Int let lock = RecursiveLock() // state private var isDone: [Bool] init(arity: Int, observer: Observer, cancel: Cancelable) { self.isDone = [Bool](repeating: false, count: arity) self.arity = arity super.init(observer: observer, cancel: cancel) } func getResult() throws -> Element { rxAbstractMethod() } func hasElements(_ index: Int) -> Bool { rxAbstractMethod() } func next(_ index: Int) { var hasValueAll = true for i in 0 ..< self.arity { if !self.hasElements(i) { hasValueAll = false break } } if hasValueAll { do { let result = try self.getResult() self.forwardOn(.next(result)) } catch let e { self.forwardOn(.error(e)) self.dispose() } } } func fail(_ error: Swift.Error) { self.forwardOn(.error(error)) self.dispose() } func done(_ index: Int) { self.isDone[index] = true var allDone = true for done in self.isDone where !done { allDone = false break } if allDone { self.forwardOn(.completed) self.dispose() } } } final class ZipObserver : ObserverType , LockOwnerType , SynchronizedOnType { typealias ValueSetter = (Element) -> Void private var parent: ZipSinkProtocol? let lock: RecursiveLock // state private let index: Int private let this: Disposable private let setNextValue: ValueSetter init(lock: RecursiveLock, parent: ZipSinkProtocol, index: Int, setNextValue: @escaping ValueSetter, this: Disposable) { self.lock = lock self.parent = parent self.index = index self.this = this self.setNextValue = setNextValue } func on(_ event: Event) { self.synchronizedOn(event) } func synchronized_on(_ event: Event) { if self.parent != nil { switch event { case .next: break case .error: self.this.dispose() case .completed: self.this.dispose() } } if let parent = self.parent { switch event { case .next(let value): self.setNextValue(value) parent.next(self.index) case .error(let error): parent.fail(error) case .completed: parent.done(self.index) } } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/ObserverType.swift ================================================ // // ObserverType.swift // RxSwift // // Created by Krunoslav Zaher on 2/8/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Supports push-style iteration over an observable sequence. public protocol ObserverType { /// The type of elements in sequence that observer can observe. associatedtype Element /// Notify observer about sequence event. /// /// - parameter event: Event that occurred. func on(_ event: Event) } /// Convenience API extensions to provide alternate next, error, completed events extension ObserverType { /// Convenience method equivalent to `on(.next(element: Element))` /// /// - parameter element: Next element to send to observer(s) public func onNext(_ element: Element) { self.on(.next(element)) } /// Convenience method equivalent to `on(.completed)` public func onCompleted() { self.on(.completed) } /// Convenience method equivalent to `on(.error(Swift.Error))` /// - parameter error: Swift.Error to send to observer(s) public func onError(_ error: Swift.Error) { self.on(.error(error)) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observers/AnonymousObserver.swift ================================================ // // AnonymousObserver.swift // RxSwift // // Created by Krunoslav Zaher on 2/8/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // final class AnonymousObserver: ObserverBase { typealias EventHandler = (Event) -> Void private let eventHandler : EventHandler init(_ eventHandler: @escaping EventHandler) { #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif self.eventHandler = eventHandler } override func onCore(_ event: Event) { self.eventHandler(event) } #if TRACE_RESOURCES deinit { _ = Resources.decrementTotal() } #endif } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observers/ObserverBase.swift ================================================ // // ObserverBase.swift // RxSwift // // Created by Krunoslav Zaher on 2/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // class ObserverBase : Disposable, ObserverType { private let isStopped = AtomicInt(0) func on(_ event: Event) { switch event { case .next: if load(self.isStopped) == 0 { self.onCore(event) } case .error, .completed: if fetchOr(self.isStopped, 1) == 0 { self.onCore(event) } } } func onCore(_ event: Event) { rxAbstractMethod() } func dispose() { fetchOr(self.isStopped, 1) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Observers/TailRecursiveSink.swift ================================================ // // TailRecursiveSink.swift // RxSwift // // Created by Krunoslav Zaher on 3/21/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // enum TailRecursiveSinkCommand { case moveNext case dispose } #if DEBUG || TRACE_RESOURCES public var maxTailRecursiveSinkStackSize = 0 #endif /// This class is usually used with `Generator` version of the operators. class TailRecursiveSink : Sink , InvocableWithValueType where Sequence.Element: ObservableConvertibleType, Sequence.Element.Element == Observer.Element { typealias Value = TailRecursiveSinkCommand typealias Element = Observer.Element typealias SequenceGenerator = (generator: Sequence.Iterator, remaining: IntMax?) var generators: [SequenceGenerator] = [] var disposed = false var subscription = SerialDisposable() // this is thread safe object var gate = AsyncLock>>() override init(observer: Observer, cancel: Cancelable) { super.init(observer: observer, cancel: cancel) } func run(_ sources: SequenceGenerator) -> Disposable { self.generators.append(sources) self.schedule(.moveNext) return self.subscription } func invoke(_ command: TailRecursiveSinkCommand) { switch command { case .dispose: self.disposeCommand() case .moveNext: self.moveNextCommand() } } // simple implementation for now func schedule(_ command: TailRecursiveSinkCommand) { self.gate.invoke(InvocableScheduledItem(invocable: self, state: command)) } func done() { self.forwardOn(.completed) self.dispose() } func extract(_ observable: Observable) -> SequenceGenerator? { rxAbstractMethod() } // should be done on gate locked private func moveNextCommand() { var next: Observable? repeat { guard let (g, left) = self.generators.last else { break } if self.isDisposed { return } self.generators.removeLast() var e = g guard let nextCandidate = e.next()?.asObservable() else { continue } // `left` is a hint of how many elements are left in generator. // In case this is the last element, then there is no need to push // that generator on stack. // // This is an optimization used to make sure in tail recursive case // there is no memory leak in case this operator is used to generate non terminating // sequence. if let knownOriginalLeft = left { // `- 1` because generator.next() has just been called if knownOriginalLeft - 1 >= 1 { self.generators.append((e, knownOriginalLeft - 1)) } } else { self.generators.append((e, nil)) } let nextGenerator = self.extract(nextCandidate) if let nextGenerator = nextGenerator { self.generators.append(nextGenerator) #if DEBUG || TRACE_RESOURCES if maxTailRecursiveSinkStackSize < self.generators.count { maxTailRecursiveSinkStackSize = self.generators.count } #endif } else { next = nextCandidate } } while next == nil guard let existingNext = next else { self.done() return } let disposable = SingleAssignmentDisposable() self.subscription.disposable = disposable disposable.setDisposable(self.subscribeToNext(existingNext)) } func subscribeToNext(_ source: Observable) -> Disposable { rxAbstractMethod() } func disposeCommand() { self.disposed = true self.generators.removeAll(keepingCapacity: false) } override func dispose() { super.dispose() self.subscription.dispose() self.gate.dispose() self.schedule(.dispose) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Reactive.swift ================================================ // // Reactive.swift // RxSwift // // Created by Yury Korolev on 5/2/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // /** Use `Reactive` proxy as customization point for constrained protocol extensions. General pattern would be: // 1. Extend Reactive protocol with constrain on Base // Read as: Reactive Extension where Base is a SomeType extension Reactive where Base: SomeType { // 2. Put any specific reactive extension for SomeType here } With this approach we can have more specialized methods and properties using `Base` and not just specialized on common base type. `Binder`s are also automatically synthesized using `@dynamicMemberLookup` for writable reference properties of the reactive base. */ @dynamicMemberLookup public struct Reactive { /// Base object to extend. public let base: Base /// Creates extensions with base object. /// /// - parameter base: Base object. public init(_ base: Base) { self.base = base } /// Automatically synthesized binder for a key path between the reactive /// base and one of its properties public subscript(dynamicMember keyPath: ReferenceWritableKeyPath) -> Binder where Base: AnyObject { Binder(self.base) { base, value in base[keyPath: keyPath] = value } } } /// A type that has reactive extensions. public protocol ReactiveCompatible { /// Extended type associatedtype ReactiveBase /// Reactive extensions. static var rx: Reactive.Type { get set } /// Reactive extensions. var rx: Reactive { get set } } extension ReactiveCompatible { /// Reactive extensions. public static var rx: Reactive.Type { get { Reactive.self } // this enables using Reactive to "mutate" base type // swiftlint:disable:next unused_setter_value set { } } /// Reactive extensions. public var rx: Reactive { get { Reactive(self) } // this enables using Reactive to "mutate" base object // swiftlint:disable:next unused_setter_value set { } } } import Foundation /// Extend NSObject with `rx` proxy. extension NSObject: ReactiveCompatible { } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Rx.swift ================================================ // // Rx.swift // RxSwift // // Created by Krunoslav Zaher on 2/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if TRACE_RESOURCES private let resourceCount = AtomicInt(0) /// Resource utilization information public struct Resources { /// Counts internal Rx resource allocations (Observables, Observers, Disposables, etc.). This provides a simple way to detect leaks during development. public static var total: Int32 { load(resourceCount) } /// Increments `Resources.total` resource count. /// /// - returns: New resource count public static func incrementTotal() -> Int32 { increment(resourceCount) } /// Decrements `Resources.total` resource count /// /// - returns: New resource count public static func decrementTotal() -> Int32 { decrement(resourceCount) } } #endif /// Swift does not implement abstract methods. This method is used as a runtime check to ensure that methods which intended to be abstract (i.e., they should be implemented in subclasses) are not called directly on the superclass. func rxAbstractMethod(file: StaticString = #file, line: UInt = #line) -> Swift.Never { rxFatalError("Abstract method", file: file, line: line) } func rxFatalError(_ lastMessage: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) -> Swift.Never { fatalError(lastMessage(), file: file, line: line) } func rxFatalErrorInDebug(_ lastMessage: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) { #if DEBUG fatalError(lastMessage(), file: file, line: line) #else print("\(file):\(line): \(lastMessage())") #endif } func incrementChecked(_ i: inout Int) throws -> Int { if i == Int.max { throw RxError.overflow } defer { i += 1 } return i } func decrementChecked(_ i: inout Int) throws -> Int { if i == Int.min { throw RxError.overflow } defer { i -= 1 } return i } #if DEBUG import Foundation final class SynchronizationTracker { private let lock = RecursiveLock() public enum SynchronizationErrorMessages: String { case variable = "Two different threads are trying to assign the same `Variable.value` unsynchronized.\n This is undefined behavior because the end result (variable value) is nondeterministic and depends on the \n operating system thread scheduler. This will cause random behavior of your program.\n" case `default` = "Two different unsynchronized threads are trying to send some event simultaneously.\n This is undefined behavior because the ordering of the effects caused by these events is nondeterministic and depends on the \n operating system thread scheduler. This will result in a random behavior of your program.\n" } private var threads = [UnsafeMutableRawPointer: Int]() private func synchronizationError(_ message: String) { #if FATAL_SYNCHRONIZATION rxFatalError(message) #else print(message) #endif } func register(synchronizationErrorMessage: SynchronizationErrorMessages) { self.lock.lock(); defer { self.lock.unlock() } let pointer = Unmanaged.passUnretained(Thread.current).toOpaque() let count = (self.threads[pointer] ?? 0) + 1 if count > 1 { self.synchronizationError( "⚠️ Reentrancy anomaly was detected.\n" + " > Debugging: To debug this issue you can set a breakpoint in \(#file):\(#line) and observe the call stack.\n" + " > Problem: This behavior is breaking the observable sequence grammar. `next (error | completed)?`\n" + " This behavior breaks the grammar because there is overlapping between sequence events.\n" + " Observable sequence is trying to send an event before sending of previous event has finished.\n" + " > Interpretation: This could mean that there is some kind of unexpected cyclic dependency in your code,\n" + " or that the system is not behaving in the expected way.\n" + " > Remedy: If this is the expected behavior this message can be suppressed by adding `.observe(on:MainScheduler.asyncInstance)`\n" + " or by enqueuing sequence events in some other way.\n" ) } self.threads[pointer] = count if self.threads.count > 1 { self.synchronizationError( "⚠️ Synchronization anomaly was detected.\n" + " > Debugging: To debug this issue you can set a breakpoint in \(#file):\(#line) and observe the call stack.\n" + " > Problem: This behavior is breaking the observable sequence grammar. `next (error | completed)?`\n" + " This behavior breaks the grammar because there is overlapping between sequence events.\n" + " Observable sequence is trying to send an event before sending of previous event has finished.\n" + " > Interpretation: " + synchronizationErrorMessage.rawValue + " > Remedy: If this is the expected behavior this message can be suppressed by adding `.observe(on:MainScheduler.asyncInstance)`\n" + " or by synchronizing sequence events in some other way.\n" ) } } func unregister() { self.lock.performLocked { let pointer = Unmanaged.passUnretained(Thread.current).toOpaque() self.threads[pointer] = (self.threads[pointer] ?? 1) - 1 if self.threads[pointer] == 0 { self.threads[pointer] = nil } } } } #endif /// RxSwift global hooks public enum Hooks { // Should capture call stack public static var recordCallStackOnError: Bool = false } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/RxMutableBox.swift ================================================ // // RxMutableBox.swift // RxSwift // // Created by Krunoslav Zaher on 5/22/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // #if os(Linux) /// As Swift 5 was released, A patch to `Thread` for Linux /// changed `threadDictionary` to a `NSMutableDictionary` instead of /// a `Dictionary`: https://github.com/apple/swift-corelibs-foundation/pull/1762/files /// /// This means that on Linux specifically, `RxMutableBox` must be a `NSObject` /// or it won't be possible to store it in `Thread.threadDictionary`. /// /// For more information, read the discussion at: /// https://github.com/ReactiveX/RxSwift/issues/1911#issuecomment-479723298 import Foundation /// Creates mutable reference wrapper for any type. final class RxMutableBox: NSObject { /// Wrapped value var value: T /// Creates reference wrapper for `value`. /// /// - parameter value: Value to wrap. init (_ value: T) { self.value = value } } #else /// Creates mutable reference wrapper for any type. final class RxMutableBox: CustomDebugStringConvertible { /// Wrapped value var value: T /// Creates reference wrapper for `value`. /// /// - parameter value: Value to wrap. init (_ value: T) { self.value = value } } extension RxMutableBox { /// - returns: Box description. var debugDescription: String { "MutatingBox(\(self.value))" } } #endif ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/SchedulerType.swift ================================================ // // SchedulerType.swift // RxSwift // // Created by Krunoslav Zaher on 2/8/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Dispatch import Foundation // Type that represents time interval in the context of RxSwift. public typealias RxTimeInterval = DispatchTimeInterval /// Type that represents absolute time in the context of RxSwift. public typealias RxTime = Date /// Represents an object that schedules units of work. public protocol SchedulerType: ImmediateSchedulerType { /// - returns: Current time. var now : RxTime { get } /** Schedules an action to be executed. - parameter state: State passed to the action to be executed. - parameter dueTime: Relative time after which to execute the action. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ func scheduleRelative(_ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable /** Schedules a periodic piece of work. - parameter state: State passed to the action to be executed. - parameter startAfter: Period after which initial work should be run. - parameter period: Period for running the work periodically. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ func schedulePeriodic(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable } extension SchedulerType { /** Periodic task will be emulated using recursive scheduling. - parameter state: Initial state passed to the action upon the first iteration. - parameter startAfter: Period after which initial work should be run. - parameter period: Period for running the work periodically. - returns: The disposable object used to cancel the scheduled recurring action (best effort). */ public func schedulePeriodic(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable { let schedule = SchedulePeriodicRecursive(scheduler: self, startAfter: startAfter, period: period, action: action, state: state) return schedule.start() } func scheduleRecursive(_ state: State, dueTime: RxTimeInterval, action: @escaping (State, AnyRecursiveScheduler) -> Void) -> Disposable { let scheduler = AnyRecursiveScheduler(scheduler: self, action: action) scheduler.schedule(state, dueTime: dueTime) return Disposables.create(with: scheduler.dispose) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/ConcurrentDispatchQueueScheduler.swift ================================================ // // ConcurrentDispatchQueueScheduler.swift // RxSwift // // Created by Krunoslav Zaher on 7/5/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Dispatch import Foundation /// Abstracts the work that needs to be performed on a specific `dispatch_queue_t`. You can also pass a serial dispatch queue, it shouldn't cause any problems. /// /// This scheduler is suitable when some work needs to be performed in background. public class ConcurrentDispatchQueueScheduler: SchedulerType { public typealias TimeInterval = Foundation.TimeInterval public typealias Time = Date public var now : Date { Date() } let configuration: DispatchQueueConfiguration /// Constructs new `ConcurrentDispatchQueueScheduler` that wraps `queue`. /// /// - parameter queue: Target dispatch queue. /// - parameter leeway: The amount of time, in nanoseconds, that the system will defer the timer. public init(queue: DispatchQueue, leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) { self.configuration = DispatchQueueConfiguration(queue: queue, leeway: leeway) } /// Convenience init for scheduler that wraps one of the global concurrent dispatch queues. /// /// - parameter qos: Target global dispatch queue, by quality of service class. /// - parameter leeway: The amount of time, in nanoseconds, that the system will defer the timer. public convenience init(qos: DispatchQoS, leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) { self.init(queue: DispatchQueue( label: "rxswift.queue.\(qos)", qos: qos, attributes: [DispatchQueue.Attributes.concurrent], target: nil), leeway: leeway ) } /** Schedules an action to be executed immediately. - parameter state: State passed to the action to be executed. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public final func schedule(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable { self.configuration.schedule(state, action: action) } /** Schedules an action to be executed. - parameter state: State passed to the action to be executed. - parameter dueTime: Relative time after which to execute the action. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public final func scheduleRelative(_ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable { self.configuration.scheduleRelative(state, dueTime: dueTime, action: action) } /** Schedules a periodic piece of work. - parameter state: State passed to the action to be executed. - parameter startAfter: Period after which initial work should be run. - parameter period: Period for running the work periodically. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func schedulePeriodic(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable { self.configuration.schedulePeriodic(state, startAfter: startAfter, period: period, action: action) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/ConcurrentMainScheduler.swift ================================================ // // ConcurrentMainScheduler.swift // RxSwift // // Created by Krunoslav Zaher on 10/17/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Dispatch import Foundation /** Abstracts work that needs to be performed on `MainThread`. In case `schedule` methods are called from main thread, it will perform action immediately without scheduling. This scheduler is optimized for `subscribeOn` operator. If you want to observe observable sequence elements on main thread using `observeOn` operator, `MainScheduler` is more suitable for that purpose. */ public final class ConcurrentMainScheduler : SchedulerType { public typealias TimeInterval = Foundation.TimeInterval public typealias Time = Date private let mainScheduler: MainScheduler private let mainQueue: DispatchQueue /// - returns: Current time. public var now: Date { self.mainScheduler.now as Date } private init(mainScheduler: MainScheduler) { self.mainQueue = DispatchQueue.main self.mainScheduler = mainScheduler } /// Singleton instance of `ConcurrentMainScheduler` public static let instance = ConcurrentMainScheduler(mainScheduler: MainScheduler.instance) /** Schedules an action to be executed immediately. - parameter state: State passed to the action to be executed. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func schedule(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable { if DispatchQueue.isMain { return action(state) } let cancel = SingleAssignmentDisposable() self.mainQueue.async { if cancel.isDisposed { return } cancel.setDisposable(action(state)) } return cancel } /** Schedules an action to be executed. - parameter state: State passed to the action to be executed. - parameter dueTime: Relative time after which to execute the action. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public final func scheduleRelative(_ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable { self.mainScheduler.scheduleRelative(state, dueTime: dueTime, action: action) } /** Schedules a periodic piece of work. - parameter state: State passed to the action to be executed. - parameter startAfter: Period after which initial work should be run. - parameter period: Period for running the work periodically. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func schedulePeriodic(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable { self.mainScheduler.schedulePeriodic(state, startAfter: startAfter, period: period, action: action) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/CurrentThreadScheduler.swift ================================================ // // CurrentThreadScheduler.swift // RxSwift // // Created by Krunoslav Zaher on 8/30/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Dispatch import Foundation #if os(Linux) fileprivate enum CurrentThreadSchedulerQueueKey { fileprivate static let instance = "RxSwift.CurrentThreadScheduler.Queue" } #else private class CurrentThreadSchedulerQueueKey: NSObject, NSCopying { static let instance = CurrentThreadSchedulerQueueKey() private override init() { super.init() } override var hash: Int { return 0 } public func copy(with zone: NSZone? = nil) -> Any { return self } } #endif /// Represents an object that schedules units of work on the current thread. /// /// This is the default scheduler for operators that generate elements. /// /// This scheduler is also sometimes called `trampoline scheduler`. public class CurrentThreadScheduler : ImmediateSchedulerType { typealias ScheduleQueue = RxMutableBox> /// The singleton instance of the current thread scheduler. public static let instance = CurrentThreadScheduler() private static var isScheduleRequiredKey: pthread_key_t = { () -> pthread_key_t in let key = UnsafeMutablePointer.allocate(capacity: 1) defer { key.deallocate() } guard pthread_key_create(key, nil) == 0 else { rxFatalError("isScheduleRequired key creation failed") } return key.pointee }() private static var scheduleInProgressSentinel: UnsafeRawPointer = { () -> UnsafeRawPointer in return UnsafeRawPointer(UnsafeMutablePointer.allocate(capacity: 1)) }() static var queue : ScheduleQueue? { get { return Thread.getThreadLocalStorageValueForKey(CurrentThreadSchedulerQueueKey.instance) } set { Thread.setThreadLocalStorageValue(newValue, forKey: CurrentThreadSchedulerQueueKey.instance) } } /// Gets a value that indicates whether the caller must call a `schedule` method. public static private(set) var isScheduleRequired: Bool { get { return pthread_getspecific(CurrentThreadScheduler.isScheduleRequiredKey) == nil } set(isScheduleRequired) { if pthread_setspecific(CurrentThreadScheduler.isScheduleRequiredKey, isScheduleRequired ? nil : scheduleInProgressSentinel) != 0 { rxFatalError("pthread_setspecific failed") } } } /** Schedules an action to be executed as soon as possible on current thread. If this method is called on some thread that doesn't have `CurrentThreadScheduler` installed, scheduler will be automatically installed and uninstalled after all work is performed. - parameter state: State passed to the action to be executed. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func schedule(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable { if CurrentThreadScheduler.isScheduleRequired { CurrentThreadScheduler.isScheduleRequired = false let disposable = action(state) defer { CurrentThreadScheduler.isScheduleRequired = true CurrentThreadScheduler.queue = nil } guard let queue = CurrentThreadScheduler.queue else { return disposable } while let latest = queue.value.dequeue() { if latest.isDisposed { continue } latest.invoke() } return disposable } let existingQueue = CurrentThreadScheduler.queue let queue: RxMutableBox> if let existingQueue = existingQueue { queue = existingQueue } else { queue = RxMutableBox(Queue(capacity: 1)) CurrentThreadScheduler.queue = queue } let scheduledItem = ScheduledItem(action: action, state: state) queue.value.enqueue(scheduledItem) return scheduledItem } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/HistoricalScheduler.swift ================================================ // // HistoricalScheduler.swift // RxSwift // // Created by Krunoslav Zaher on 12/27/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Foundation /// Provides a virtual time scheduler that uses `Date` for absolute time and `NSTimeInterval` for relative time. public class HistoricalScheduler : VirtualTimeScheduler { /** Creates a new historical scheduler with initial clock value. - parameter initialClock: Initial value for virtual clock. */ public init(initialClock: RxTime = Date(timeIntervalSince1970: 0)) { super.init(initialClock: initialClock, converter: HistoricalSchedulerTimeConverter()) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/HistoricalSchedulerTimeConverter.swift ================================================ // // HistoricalSchedulerTimeConverter.swift // RxSwift // // Created by Krunoslav Zaher on 12/27/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Foundation /// Converts historical virtual time into real time. /// /// Since historical virtual time is also measured in `Date`, this converter is identity function. public struct HistoricalSchedulerTimeConverter : VirtualTimeConverterType { /// Virtual time unit used that represents ticks of virtual clock. public typealias VirtualTimeUnit = RxTime /// Virtual time unit used to represent differences of virtual times. public typealias VirtualTimeIntervalUnit = TimeInterval /// Returns identical value of argument passed because historical virtual time is equal to real time, just /// decoupled from local machine clock. public func convertFromVirtualTime(_ virtualTime: VirtualTimeUnit) -> RxTime { virtualTime } /// Returns identical value of argument passed because historical virtual time is equal to real time, just /// decoupled from local machine clock. public func convertToVirtualTime(_ time: RxTime) -> VirtualTimeUnit { time } /// Returns identical value of argument passed because historical virtual time is equal to real time, just /// decoupled from local machine clock. public func convertFromVirtualTimeInterval(_ virtualTimeInterval: VirtualTimeIntervalUnit) -> TimeInterval { virtualTimeInterval } /// Returns identical value of argument passed because historical virtual time is equal to real time, just /// decoupled from local machine clock. public func convertToVirtualTimeInterval(_ timeInterval: TimeInterval) -> VirtualTimeIntervalUnit { timeInterval } /** Offsets `Date` by time interval. - parameter time: Time. - parameter timeInterval: Time interval offset. - returns: Time offsetted by time interval. */ public func offsetVirtualTime(_ time: VirtualTimeUnit, offset: VirtualTimeIntervalUnit) -> VirtualTimeUnit { time.addingTimeInterval(offset) } /// Compares two `Date`s. public func compareVirtualTime(_ lhs: VirtualTimeUnit, _ rhs: VirtualTimeUnit) -> VirtualTimeComparison { switch lhs.compare(rhs as Date) { case .orderedAscending: return .lessThan case .orderedSame: return .equal case .orderedDescending: return .greaterThan } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/Internal/DispatchQueueConfiguration.swift ================================================ // // DispatchQueueConfiguration.swift // RxSwift // // Created by Krunoslav Zaher on 7/23/16. // Copyright © 2016 Krunoslav Zaher. All rights reserved. // import Dispatch import Foundation struct DispatchQueueConfiguration { let queue: DispatchQueue let leeway: DispatchTimeInterval } extension DispatchQueueConfiguration { func schedule(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable { let cancel = SingleAssignmentDisposable() self.queue.async { if cancel.isDisposed { return } cancel.setDisposable(action(state)) } return cancel } func scheduleRelative(_ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable { let deadline = DispatchTime.now() + dueTime let compositeDisposable = CompositeDisposable() let timer = DispatchSource.makeTimerSource(queue: self.queue) timer.schedule(deadline: deadline, leeway: self.leeway) // TODO: // This looks horrible, and yes, it is. // It looks like Apple has made a conceptual change here, and I'm unsure why. // Need more info on this. // It looks like just setting timer to fire and not holding a reference to it // until deadline causes timer cancellation. var timerReference: DispatchSourceTimer? = timer let cancelTimer = Disposables.create { timerReference?.cancel() timerReference = nil } timer.setEventHandler(handler: { if compositeDisposable.isDisposed { return } _ = compositeDisposable.insert(action(state)) cancelTimer.dispose() }) timer.resume() _ = compositeDisposable.insert(cancelTimer) return compositeDisposable } func schedulePeriodic(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable { let initial = DispatchTime.now() + startAfter var timerState = state let timer = DispatchSource.makeTimerSource(queue: self.queue) timer.schedule(deadline: initial, repeating: period, leeway: self.leeway) // TODO: // This looks horrible, and yes, it is. // It looks like Apple has made a conceptual change here, and I'm unsure why. // Need more info on this. // It looks like just setting timer to fire and not holding a reference to it // until deadline causes timer cancellation. var timerReference: DispatchSourceTimer? = timer let cancelTimer = Disposables.create { timerReference?.cancel() timerReference = nil } timer.setEventHandler(handler: { if cancelTimer.isDisposed { return } timerState = action(timerState) }) timer.resume() return cancelTimer } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/Internal/InvocableScheduledItem.swift ================================================ // // InvocableScheduledItem.swift // RxSwift // // Created by Krunoslav Zaher on 11/7/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // struct InvocableScheduledItem : InvocableType { let invocable: I let state: I.Value init(invocable: I, state: I.Value) { self.invocable = invocable self.state = state } func invoke() { self.invocable.invoke(self.state) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/Internal/InvocableType.swift ================================================ // // InvocableType.swift // RxSwift // // Created by Krunoslav Zaher on 11/7/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // protocol InvocableType { func invoke() } protocol InvocableWithValueType { associatedtype Value func invoke(_ value: Value) } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/Internal/ScheduledItem.swift ================================================ // // ScheduledItem.swift // RxSwift // // Created by Krunoslav Zaher on 9/2/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // struct ScheduledItem : ScheduledItemType , InvocableType { typealias Action = (T) -> Disposable private let action: Action private let state: T private let disposable = SingleAssignmentDisposable() var isDisposed: Bool { self.disposable.isDisposed } init(action: @escaping Action, state: T) { self.action = action self.state = state } func invoke() { self.disposable.setDisposable(self.action(self.state)) } func dispose() { self.disposable.dispose() } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/Internal/ScheduledItemType.swift ================================================ // // ScheduledItemType.swift // RxSwift // // Created by Krunoslav Zaher on 11/7/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // protocol ScheduledItemType : Cancelable , InvocableType { func invoke() } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/MainScheduler.swift ================================================ // // MainScheduler.swift // RxSwift // // Created by Krunoslav Zaher on 2/8/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Dispatch #if !os(Linux) import Foundation #endif /** Abstracts work that needs to be performed on `DispatchQueue.main`. In case `schedule` methods are called from `DispatchQueue.main`, it will perform action immediately without scheduling. This scheduler is usually used to perform UI work. Main scheduler is a specialization of `SerialDispatchQueueScheduler`. This scheduler is optimized for `observeOn` operator. To ensure observable sequence is subscribed on main thread using `subscribeOn` operator please use `ConcurrentMainScheduler` because it is more optimized for that purpose. */ public final class MainScheduler : SerialDispatchQueueScheduler { private let mainQueue: DispatchQueue let numberEnqueued = AtomicInt(0) /// Initializes new instance of `MainScheduler`. public init() { self.mainQueue = DispatchQueue.main super.init(serialQueue: self.mainQueue) } /// Singleton instance of `MainScheduler` public static let instance = MainScheduler() /// Singleton instance of `MainScheduler` that always schedules work asynchronously /// and doesn't perform optimizations for calls scheduled from main queue. public static let asyncInstance = SerialDispatchQueueScheduler(serialQueue: DispatchQueue.main) /// In case this method is called on a background thread it will throw an exception. public static func ensureExecutingOnScheduler(errorMessage: String? = nil) { if !DispatchQueue.isMain { rxFatalError(errorMessage ?? "Executing on background thread. Please use `MainScheduler.instance.schedule` to schedule work on main thread.") } } /// In case this method is running on a background thread it will throw an exception. public static func ensureRunningOnMainThread(errorMessage: String? = nil) { #if !os(Linux) // isMainThread is not implemented in Linux Foundation guard Thread.isMainThread else { rxFatalError(errorMessage ?? "Running on background thread.") } #endif } override func scheduleInternal(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable { let previousNumberEnqueued = increment(self.numberEnqueued) if DispatchQueue.isMain && previousNumberEnqueued == 0 { let disposable = action(state) decrement(self.numberEnqueued) return disposable } let cancel = SingleAssignmentDisposable() self.mainQueue.async { if !cancel.isDisposed { cancel.setDisposable(action(state)) } decrement(self.numberEnqueued) } return cancel } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/OperationQueueScheduler.swift ================================================ // // OperationQueueScheduler.swift // RxSwift // // Created by Krunoslav Zaher on 4/4/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Dispatch import Foundation /// Abstracts the work that needs to be performed on a specific `NSOperationQueue`. /// /// This scheduler is suitable for cases when there is some bigger chunk of work that needs to be performed in background and you want to fine tune concurrent processing using `maxConcurrentOperationCount`. public class OperationQueueScheduler: ImmediateSchedulerType { public let operationQueue: OperationQueue public let queuePriority: Operation.QueuePriority /// Constructs new instance of `OperationQueueScheduler` that performs work on `operationQueue`. /// /// - parameter operationQueue: Operation queue targeted to perform work on. /// - parameter queuePriority: Queue priority which will be assigned to new operations. public init(operationQueue: OperationQueue, queuePriority: Operation.QueuePriority = .normal) { self.operationQueue = operationQueue self.queuePriority = queuePriority } /** Schedules an action to be executed recursively. - parameter state: State passed to the action to be executed. - parameter action: Action to execute recursively. The last parameter passed to the action is used to trigger recursive scheduling of the action, passing in recursive invocation state. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func schedule(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable { let cancel = SingleAssignmentDisposable() let operation = BlockOperation { if cancel.isDisposed { return } cancel.setDisposable(action(state)) } operation.queuePriority = self.queuePriority self.operationQueue.addOperation(operation) return cancel } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/RecursiveScheduler.swift ================================================ // // RecursiveScheduler.swift // RxSwift // // Created by Krunoslav Zaher on 6/7/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // private enum ScheduleState { case initial case added(CompositeDisposable.DisposeKey) case done } /// Type erased recursive scheduler. final class AnyRecursiveScheduler { typealias Action = (State, AnyRecursiveScheduler) -> Void private let lock = RecursiveLock() // state private let group = CompositeDisposable() private var scheduler: SchedulerType private var action: Action? init(scheduler: SchedulerType, action: @escaping Action) { self.action = action self.scheduler = scheduler } /** Schedules an action to be executed recursively. - parameter state: State passed to the action to be executed. - parameter dueTime: Relative time after which to execute the recursive action. */ func schedule(_ state: State, dueTime: RxTimeInterval) { var scheduleState: ScheduleState = .initial let d = self.scheduler.scheduleRelative(state, dueTime: dueTime) { state -> Disposable in // best effort if self.group.isDisposed { return Disposables.create() } let action = self.lock.performLocked { () -> Action? in switch scheduleState { case let .added(removeKey): self.group.remove(for: removeKey) case .initial: break case .done: break } scheduleState = .done return self.action } if let action = action { action(state, self) } return Disposables.create() } self.lock.performLocked { switch scheduleState { case .added: rxFatalError("Invalid state") case .initial: if let removeKey = self.group.insert(d) { scheduleState = .added(removeKey) } else { scheduleState = .done } case .done: break } } } /// Schedules an action to be executed recursively. /// /// - parameter state: State passed to the action to be executed. func schedule(_ state: State) { var scheduleState: ScheduleState = .initial let d = self.scheduler.schedule(state) { state -> Disposable in // best effort if self.group.isDisposed { return Disposables.create() } let action = self.lock.performLocked { () -> Action? in switch scheduleState { case let .added(removeKey): self.group.remove(for: removeKey) case .initial: break case .done: break } scheduleState = .done return self.action } if let action = action { action(state, self) } return Disposables.create() } self.lock.performLocked { switch scheduleState { case .added: rxFatalError("Invalid state") case .initial: if let removeKey = self.group.insert(d) { scheduleState = .added(removeKey) } else { scheduleState = .done } case .done: break } } } func dispose() { self.lock.performLocked { self.action = nil } self.group.dispose() } } /// Type erased recursive scheduler. final class RecursiveImmediateScheduler { typealias Action = (_ state: State, _ recurse: (State) -> Void) -> Void private var lock = SpinLock() private let group = CompositeDisposable() private var action: Action? private let scheduler: ImmediateSchedulerType init(action: @escaping Action, scheduler: ImmediateSchedulerType) { self.action = action self.scheduler = scheduler } // immediate scheduling /// Schedules an action to be executed recursively. /// /// - parameter state: State passed to the action to be executed. func schedule(_ state: State) { var scheduleState: ScheduleState = .initial let d = self.scheduler.schedule(state) { state -> Disposable in // best effort if self.group.isDisposed { return Disposables.create() } let action = self.lock.performLocked { () -> Action? in switch scheduleState { case let .added(removeKey): self.group.remove(for: removeKey) case .initial: break case .done: break } scheduleState = .done return self.action } if let action = action { action(state, self.schedule) } return Disposables.create() } self.lock.performLocked { switch scheduleState { case .added: rxFatalError("Invalid state") case .initial: if let removeKey = self.group.insert(d) { scheduleState = .added(removeKey) } else { scheduleState = .done } case .done: break } } } func dispose() { self.lock.performLocked { self.action = nil } self.group.dispose() } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/SchedulerServices+Emulation.swift ================================================ // // SchedulerServices+Emulation.swift // RxSwift // // Created by Krunoslav Zaher on 6/6/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // enum SchedulePeriodicRecursiveCommand { case tick case dispatchStart } final class SchedulePeriodicRecursive { typealias RecursiveAction = (State) -> State typealias RecursiveScheduler = AnyRecursiveScheduler private let scheduler: SchedulerType private let startAfter: RxTimeInterval private let period: RxTimeInterval private let action: RecursiveAction private var state: State private let pendingTickCount = AtomicInt(0) init(scheduler: SchedulerType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping RecursiveAction, state: State) { self.scheduler = scheduler self.startAfter = startAfter self.period = period self.action = action self.state = state } func start() -> Disposable { self.scheduler.scheduleRecursive(SchedulePeriodicRecursiveCommand.tick, dueTime: self.startAfter, action: self.tick) } func tick(_ command: SchedulePeriodicRecursiveCommand, scheduler: RecursiveScheduler) { // Tries to emulate periodic scheduling as best as possible. // The problem that could arise is if handling periodic ticks take too long, or // tick interval is short. switch command { case .tick: scheduler.schedule(.tick, dueTime: self.period) // The idea is that if on tick there wasn't any item enqueued, schedule to perform work immediately. // Else work will be scheduled after previous enqueued work completes. if increment(self.pendingTickCount) == 0 { self.tick(.dispatchStart, scheduler: scheduler) } case .dispatchStart: self.state = self.action(self.state) // Start work and schedule check is this last batch of work if decrement(self.pendingTickCount) > 1 { // This gives priority to scheduler emulation, it's not perfect, but helps scheduler.schedule(SchedulePeriodicRecursiveCommand.dispatchStart) } } } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/SerialDispatchQueueScheduler.swift ================================================ // // SerialDispatchQueueScheduler.swift // RxSwift // // Created by Krunoslav Zaher on 2/8/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Dispatch import Foundation /** Abstracts the work that needs to be performed on a specific `dispatch_queue_t`. It will make sure that even if concurrent dispatch queue is passed, it's transformed into a serial one. It is extremely important that this scheduler is serial, because certain operator perform optimizations that rely on that property. Because there is no way of detecting is passed dispatch queue serial or concurrent, for every queue that is being passed, worst case (concurrent) will be assumed, and internal serial proxy dispatch queue will be created. This scheduler can also be used with internal serial queue alone. In case some customization need to be made on it before usage, internal serial queue can be customized using `serialQueueConfiguration` callback. */ public class SerialDispatchQueueScheduler : SchedulerType { public typealias TimeInterval = Foundation.TimeInterval public typealias Time = Date /// - returns: Current time. public var now : Date { Date() } let configuration: DispatchQueueConfiguration /** Constructs new `SerialDispatchQueueScheduler` that wraps `serialQueue`. - parameter serialQueue: Target dispatch queue. - parameter leeway: The amount of time, in nanoseconds, that the system will defer the timer. */ init(serialQueue: DispatchQueue, leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) { self.configuration = DispatchQueueConfiguration(queue: serialQueue, leeway: leeway) } /** Constructs new `SerialDispatchQueueScheduler` with internal serial queue named `internalSerialQueueName`. Additional dispatch queue properties can be set after dispatch queue is created using `serialQueueConfiguration`. - parameter internalSerialQueueName: Name of internal serial dispatch queue. - parameter serialQueueConfiguration: Additional configuration of internal serial dispatch queue. - parameter leeway: The amount of time, in nanoseconds, that the system will defer the timer. */ public convenience init(internalSerialQueueName: String, serialQueueConfiguration: ((DispatchQueue) -> Void)? = nil, leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) { let queue = DispatchQueue(label: internalSerialQueueName, attributes: []) serialQueueConfiguration?(queue) self.init(serialQueue: queue, leeway: leeway) } /** Constructs new `SerialDispatchQueueScheduler` named `internalSerialQueueName` that wraps `queue`. - parameter queue: Possibly concurrent dispatch queue used to perform work. - parameter internalSerialQueueName: Name of internal serial dispatch queue proxy. - parameter leeway: The amount of time, in nanoseconds, that the system will defer the timer. */ public convenience init(queue: DispatchQueue, internalSerialQueueName: String, leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) { // Swift 3.0 IUO let serialQueue = DispatchQueue(label: internalSerialQueueName, attributes: [], target: queue) self.init(serialQueue: serialQueue, leeway: leeway) } /** Constructs new `SerialDispatchQueueScheduler` that wraps one of the global concurrent dispatch queues. - parameter qos: Identifier for global dispatch queue with specified quality of service class. - parameter internalSerialQueueName: Custom name for internal serial dispatch queue proxy. - parameter leeway: The amount of time, in nanoseconds, that the system will defer the timer. */ @available(macOS 10.10, *) public convenience init(qos: DispatchQoS, internalSerialQueueName: String = "rx.global_dispatch_queue.serial", leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) { self.init(queue: DispatchQueue.global(qos: qos.qosClass), internalSerialQueueName: internalSerialQueueName, leeway: leeway) } /** Schedules an action to be executed immediately. - parameter state: State passed to the action to be executed. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public final func schedule(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable { self.scheduleInternal(state, action: action) } func scheduleInternal(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable { self.configuration.schedule(state, action: action) } /** Schedules an action to be executed. - parameter state: State passed to the action to be executed. - parameter dueTime: Relative time after which to execute the action. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public final func scheduleRelative(_ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable { self.configuration.scheduleRelative(state, dueTime: dueTime, action: action) } /** Schedules a periodic piece of work. - parameter state: State passed to the action to be executed. - parameter startAfter: Period after which initial work should be run. - parameter period: Period for running the work periodically. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func schedulePeriodic(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable { self.configuration.schedulePeriodic(state, startAfter: startAfter, period: period, action: action) } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/VirtualTimeConverterType.swift ================================================ // // VirtualTimeConverterType.swift // RxSwift // // Created by Krunoslav Zaher on 12/23/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import Foundation /// Parametrization for virtual time used by `VirtualTimeScheduler`s. public protocol VirtualTimeConverterType { /// Virtual time unit used that represents ticks of virtual clock. associatedtype VirtualTimeUnit /// Virtual time unit used to represent differences of virtual times. associatedtype VirtualTimeIntervalUnit /** Converts virtual time to real time. - parameter virtualTime: Virtual time to convert to `Date`. - returns: `Date` corresponding to virtual time. */ func convertFromVirtualTime(_ virtualTime: VirtualTimeUnit) -> RxTime /** Converts real time to virtual time. - parameter time: `Date` to convert to virtual time. - returns: Virtual time corresponding to `Date`. */ func convertToVirtualTime(_ time: RxTime) -> VirtualTimeUnit /** Converts from virtual time interval to `NSTimeInterval`. - parameter virtualTimeInterval: Virtual time interval to convert to `NSTimeInterval`. - returns: `NSTimeInterval` corresponding to virtual time interval. */ func convertFromVirtualTimeInterval(_ virtualTimeInterval: VirtualTimeIntervalUnit) -> TimeInterval /** Converts from `NSTimeInterval` to virtual time interval. - parameter timeInterval: `NSTimeInterval` to convert to virtual time interval. - returns: Virtual time interval corresponding to time interval. */ func convertToVirtualTimeInterval(_ timeInterval: TimeInterval) -> VirtualTimeIntervalUnit /** Offsets virtual time by virtual time interval. - parameter time: Virtual time. - parameter offset: Virtual time interval. - returns: Time corresponding to time offsetted by virtual time interval. */ func offsetVirtualTime(_ time: VirtualTimeUnit, offset: VirtualTimeIntervalUnit) -> VirtualTimeUnit /** This is additional abstraction because `Date` is unfortunately not comparable. Extending `Date` with `Comparable` would be too risky because of possible collisions with other libraries. */ func compareVirtualTime(_ lhs: VirtualTimeUnit, _ rhs: VirtualTimeUnit) -> VirtualTimeComparison } /** Virtual time comparison result. This is additional abstraction because `Date` is unfortunately not comparable. Extending `Date` with `Comparable` would be too risky because of possible collisions with other libraries. */ public enum VirtualTimeComparison { /// lhs < rhs. case lessThan /// lhs == rhs. case equal /// lhs > rhs. case greaterThan } extension VirtualTimeComparison { /// lhs < rhs. var lessThen: Bool { self == .lessThan } /// lhs > rhs var greaterThan: Bool { self == .greaterThan } /// lhs == rhs var equal: Bool { self == .equal } } ================================================ FILE: JetChat/Pods/RxSwift/RxSwift/Schedulers/VirtualTimeScheduler.swift ================================================ // // VirtualTimeScheduler.swift // RxSwift // // Created by Krunoslav Zaher on 2/14/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // /// Base class for virtual time schedulers using a priority queue for scheduled items. open class VirtualTimeScheduler : SchedulerType { public typealias VirtualTime = Converter.VirtualTimeUnit public typealias VirtualTimeInterval = Converter.VirtualTimeIntervalUnit private var running : Bool private var currentClock: VirtualTime private var schedulerQueue : PriorityQueue> private var converter: Converter private var nextId = 0 /// - returns: Current time. public var now: RxTime { self.converter.convertFromVirtualTime(self.clock) } /// - returns: Scheduler's absolute time clock value. public var clock: VirtualTime { self.currentClock } /// Creates a new virtual time scheduler. /// /// - parameter initialClock: Initial value for the clock. public init(initialClock: VirtualTime, converter: Converter) { self.currentClock = initialClock self.running = false self.converter = converter self.schedulerQueue = PriorityQueue(hasHigherPriority: { switch converter.compareVirtualTime($0.time, $1.time) { case .lessThan: return true case .equal: return $0.id < $1.id case .greaterThan: return false } }, isEqual: { $0 === $1 }) #if TRACE_RESOURCES _ = Resources.incrementTotal() #endif } /** Schedules an action to be executed immediately. - parameter state: State passed to the action to be executed. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func schedule(_ state: StateType, action: @escaping (StateType) -> Disposable) -> Disposable { return self.scheduleRelative(state, dueTime: .microseconds(0)) { a in return action(a) } } /** Schedules an action to be executed. - parameter state: State passed to the action to be executed. - parameter dueTime: Relative time after which to execute the action. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func scheduleRelative(_ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable { let time = self.now.addingDispatchInterval(dueTime) let absoluteTime = self.converter.convertToVirtualTime(time) let adjustedTime = self.adjustScheduledTime(absoluteTime) return self.scheduleAbsoluteVirtual(state, time: adjustedTime, action: action) } /** Schedules an action to be executed after relative time has passed. - parameter state: State passed to the action to be executed. - parameter time: Absolute time when to execute the action. If this is less or equal then `now`, `now + 1` will be used. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func scheduleRelativeVirtual(_ state: StateType, dueTime: VirtualTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable { let time = self.converter.offsetVirtualTime(self.clock, offset: dueTime) return self.scheduleAbsoluteVirtual(state, time: time, action: action) } /** Schedules an action to be executed at absolute virtual time. - parameter state: State passed to the action to be executed. - parameter time: Absolute time when to execute the action. - parameter action: Action to be executed. - returns: The disposable object used to cancel the scheduled action (best effort). */ public func scheduleAbsoluteVirtual(_ state: StateType, time: VirtualTime, action: @escaping (StateType) -> Disposable) -> Disposable { MainScheduler.ensureExecutingOnScheduler() let compositeDisposable = CompositeDisposable() let item = VirtualSchedulerItem(action: { return action(state) }, time: time, id: self.nextId) self.nextId += 1 self.schedulerQueue.enqueue(item) _ = compositeDisposable.insert(item) return compositeDisposable } /// Adjusts time of scheduling before adding item to schedule queue. open func adjustScheduledTime(_ time: VirtualTime) -> VirtualTime { time } /// Starts the virtual time scheduler. public func start() { MainScheduler.ensureExecutingOnScheduler() if self.running { return } self.running = true repeat { guard let next = self.findNext() else { break } if self.converter.compareVirtualTime(next.time, self.clock).greaterThan { self.currentClock = next.time } next.invoke() self.schedulerQueue.remove(next) } while self.running self.running = false } func findNext() -> VirtualSchedulerItem? { while let front = self.schedulerQueue.peek() { if front.isDisposed { self.schedulerQueue.remove(front) continue } return front } return nil } /// Advances the scheduler's clock to the specified time, running all work till that point. /// /// - parameter virtualTime: Absolute time to advance the scheduler's clock to. public func advanceTo(_ virtualTime: VirtualTime) { MainScheduler.ensureExecutingOnScheduler() if self.running { fatalError("Scheduler is already running") } self.running = true repeat { guard let next = self.findNext() else { break } if self.converter.compareVirtualTime(next.time, virtualTime).greaterThan { break } if self.converter.compareVirtualTime(next.time, self.clock).greaterThan { self.currentClock = next.time } next.invoke() self.schedulerQueue.remove(next) } while self.running self.currentClock = virtualTime self.running = false } /// Advances the scheduler's clock by the specified relative time. public func sleep(_ virtualInterval: VirtualTimeInterval) { MainScheduler.ensureExecutingOnScheduler() let sleepTo = self.converter.offsetVirtualTime(self.clock, offset: virtualInterval) if self.converter.compareVirtualTime(sleepTo, self.clock).lessThen { fatalError("Can't sleep to past.") } self.currentClock = sleepTo } /// Stops the virtual time scheduler. public func stop() { MainScheduler.ensureExecutingOnScheduler() self.running = false } #if TRACE_RESOURCES deinit { _ = Resources.decrementTotal() } #endif } // MARK: description extension VirtualTimeScheduler: CustomDebugStringConvertible { /// A textual representation of `self`, suitable for debugging. public var debugDescription: String { self.schedulerQueue.debugDescription } } final class VirtualSchedulerItem ### 1. Date Parsing SwiftDate can recognize all the major datetime formats automatically (ISO8601, RSS, Alt RSS, .NET, SQL, HTTP...) and you can also provide your own formats. Creating a new date has never been so easy! ```swift // All default datetime formats (15+) are recognized automatically let _ = "2010-05-20 15:30:00".toDate() // You can also provide your own format! let _ = "2010-05-20 15:30".toDate("yyyy-MM-dd HH:mm") // All ISO8601 variants are supported too with timezone parsing! let _ = "2017-09-17T11:59:29+02:00".toISODate() // RSS, Extended, HTTP, SQL, .NET and all the major variants are supported! let _ = "19 Nov 2015 22:20:40 +0100".toRSS(alt: true) ``` ### 2. Date Manipulation Date can be manipulated by adding or removing time components using a natural language; time unit extraction is also easy and includes the support for timezone, calendar and locales! Manipulation can be done with standard math operators and between dates, time intervals, date components and relevant time units! ```swift // Math operations support time units let _ = ("2010-05-20 15:30:00".toDate() + 3.months - 2.days) let _ = Date() + 3.hours let _ = date1 + [.year:1, .month:2, .hour:5] let _ = date1 + date2 // extract single time unit components from date manipulation let over1Year = (date3 - date2).year > 1 ``` ### 3. Date Comparison SwiftDate include an extensive set of comparison functions; you can compare two dates by granularity, check if a date is an particular day, range and practically any other comparison you ever need. Comparison is also available via standard math operators like (`>, >=, <, <=`). ```swift // Standard math comparison is allowed let _ = dateA >= dateB || dateC < dateB // Complex comparisons includes granularity support let _ = dateA.compare(toDate: dateB, granularity: .hour) == .orderedSame let _ = dateA.isAfterDate(dateB, orEqual: true, granularity: .month) // > until month granularity let _ = dateC.isInRange(date: dateA, and: dateB, orEqual: true, granularity: .day) // > until day granularity let _ = dateA.earlierDate(dateB) // earlier date let _ = dateA.laterDate(dateB) // later date // Check if date is close to another with a given precision let _ = dateA.compareCloseTo(dateB, precision: 1.hours.timeInterval // Compare for relevant events: // .isToday, .isYesterday, .isTomorrow, .isWeekend, isNextWeek // .isSameDay, .isMorning, .isWeekday ... let _ = date.compare(.isToday) let _ = date.compare(.isNight) let _ = date.compare(.isNextWeek) let _ = date.compare(.isThisMonth) let _ = date.compare(.startOfWeek) let _ = date.compare(.isNextYear) // ...and MORE THAN 30 OTHER COMPARISONS BUILT IN // Operation in arrays (oldestIn, newestIn, sortedByNewest, sortedByOldest...) let _ = DateInRegion.oldestIn(list: datesArray) let _ = DateInRegion.sortedByNewest(list: datesArray) ``` ### 4. Date Creation with Region (Timezone, Calendar & Locale) You can create new dates from a string, time intervals or using date components. SwiftDate offers a wide set of functions to create and derivate your dates even with random generation! ```swift // All dates includes timezone, calendar and locales! // Create from string let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let date1 = DateInRegion("2010-01-01 00:00:00", region: rome)! // Create date from intervals let _ = DateInRegion(seconds: 39940, region: rome) let _ = DateInRegion(milliseconds: 5000, region: rome) // Date from components let _ = DateInRegion(components: { $0.year = 2001 $0.month = 9 $0.day = 11 $0.hour = 12 $0.minute = 0 }, region: rome) let _ = DateInRegion(year: 2001, month: 1, day: 5, hour: 23, minute: 30, second: 0, region: rome) // Random date generation with/without bounds let _ = DateInRegion.randomDate(region: rome) let _ = DateInRegion.randomDate(withinDaysBeforeToday: 5) let _ = DateInRegion.randomDates(count: 50, between: lowerLimitDate, and: upperLimitDate, region: rome) ``` ### 5. Derivated Dates Date can be also generated starting from other dates; SwiftDate includes an extensive set of functions to generate. Over 20 different derivated dates can be created easily using `dateAt()` function. ```swift let _ = DateInRegion().dateAt(.endOfDay) // today at the end of the day // Over 20 different relevant dates including .startOfDay, // .endOfDay, .startOfWeek, .tomorrow, .nextWeekday, .nextMonth, .prevYear, .nearestMinute and many others! let _ = dateA.nextWeekday(.friday) // the next friday after dateA let _ = (date.dateAt(.startOfMonth) - 3.days) let _ = dateA.compare(.endOfWeek) // Enumerate dates in range by providing your own custom // increment expressed in date components let from = DateInRegion("2015-01-01 10:00:00", region: rome)! let to = DateInRegion("2015-01-02 03:00:00", region: rome)! let increment2 = DateComponents.create { $0.hour = 1 $0.minute = 30 $0.second = 10 } // generate dates in range by incrementing +1h,30m,10s each new date let dates = DateInRegion.enumerateDates(from: fromDate2, to: toDate2, increment: increment2) // Get all mondays in Jan 2019 let mondaysInJan2019 = Date.datesForWeekday(.monday, inMonth: 1, ofYear: 2019) // Altering time components let _ = dateA.dateBySet(hour: 10, min: 0, secs: 0) // Truncating a date let _ = dateA.dateTruncated(at: [.year,.month,.day]) // reset all time components keeping only date // Rounding a date let _ = dateA.dateRoundedAt(.toMins(10)) let _ = dateA.dateRoundedAt(.toFloor30Mins) // Adding components let _ = dateA.dateByAdding(5,.year) // Date at the start/end of any time component let _ = dateA.dateAtEndOf(.year) // 31 of Dec at 23:59:59 let _ = dateA.dateAtStartOf(.day) // at 00:00:00 of the same day let _ = dateA.dateAtStartOf(.month) // at 00:00:00 of the first day of the month ``` ### 6. Components Extraction You can extract components directly from dates and it includes the right value expressed in date's region (the right timezone and set locale!). ```swift // Create a date in a region, London but with the lcoale set to IT let london = Region(calendar: .gregorian, zone: .europeLondon, locale: .italian) let date = DateInRegion("2018-02-05 23:14:45", format: dateFormat, region: london)! // You can extract any of the all available time units. // VALUES ARE EXPRESSED IN THE REGION OF THE DATE (THE RIGHT TIMEZONE). // (you can still get the UTC/absolute value by getting the inner's absoluteDate). let _ = date.year // 2018 let _ = date.month // 2 let _ = date.monthNameDefault // 'Febbraio' as the locale is the to IT! let _ = date.firstDayOfWeek // 5 let _ = date.weekdayNameShort // 'Lun' as locale is the to IT // ... all components are supported: .year, .month, .day, .hour, .minute, .second, // .monthName, .weekday, .nearestHour, .firstDayOfWeek. .quarter and so on... ``` ### 7. Switch between timezones/locale and calendars You can easily convert any date to another region (aka another calendar, locale or timezone) easily! New date contains all values expressed into the destination reason ```swift // Conversion between timezones is easy using convertTo(region:) function let rNY = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.english) let rRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateInNY = "2017-01-01 00:00:00".toDate(region: rNY) let dateInRome = dateInNY?.convertTo(region: rRome)! print(dateInRome.toString()) // "dom gen 01 06:00:00 +0100 2017\n" // You can also convert single region's attributes let dateInIndia = dateInNY?.convertTo(timezone: Zones.indianChristmas, locale: Locales.nepaliIndia) print("\(dateInIndia!.toString())") // "आइत जनवरी ०१ १२:००:०० +0700 २०१७\n" ``` ### 8. Date Formatting Date formatting is easy, you can specify your own format, locale or use any of the provided ones. ```swift // Date Formatting let london = Region(calendar: .gregorian, zone: .europeLondon, locale: .english) let date = ... // 2017-07-22T18:27:02+02:00 in london region let _ = date.toDotNET() // /Date(1500740822000+0200)/ let _ = date.toISODate() // 2017-07-22T18:27:02+02:00 let _ = date.toFormat("dd MMM yyyy 'at' HH:mm") // "22 July 2017 at 18:27" // You can also easily change locale when formatting a region let _ = date.toFormat("dd MMM", locale: .italian) // "22 Luglio" // Time Interval Formatting as Countdown let interval: TimeInterval = (2.hours.timeInterval) + (34.minutes.timeInterval) + (5.seconds.timeInterval) let _ = interval.toClock() // "2:34:05" // Time Interval Formatting by Components let _ = interval.toString { $0.maximumUnitCount = 4 $0.allowedUnits = [.day, .hour, .minute] $0.collapsesLargestUnit = true $0.unitsStyle = .abbreviated } // "2h 34m" ``` ### 9. Relative Date Formatting (fully customizable!) Relative formatting is all new in SwiftDate; it supports 120+ languages with two different styles (`.default, .twitter`), 9 flavours (`.long, .longTime, .longConvenient, .short, .shortTime, .shortConvenient, .narrow, .tiny, .quantify`) and all of them are customizable as you need. The extensible format allows you to provide your own translations and rules to override the default behaviour. ```swift // Twitter Style let _ = (Date() - 3.minutes).toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.english) // "3m" let _ = (Date() - 6.minutes).toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.italian) // "6 min fa" // Default Style let _ = (now2 - 5.hours).toRelative(style: RelativeFormatter.defaultStyle(), locale: Locales.english) // "5 hours ago" let y = (now2 - 40.minutes).toRelative(style: RelativeFormatter.defaultStyle(), locale: Locales.italian) // "45 minuti fa" ``` ### 10. Codable Support Both `DateInRegion` and `Region` fully support the new Swift's `Codable` protocol. This mean you can safely encode/decode them: ```swift // Encoding/Decoding a Region let region = Region(calendar: Calendars.gregorian, zone: Zones.europeOslo, locale: Locales.english) let encodedJSON = try JSONEncoder().encode(region) let decodedRegion = try JSONDecoder().decode(Region.self, from: encodedJSON) // Encoding/Decoding a DateInRegion let date = DateInRegion("2015-09-24T13:20:55", region: region) let encodedDate = try JSONEncoder().encode(date) let decodedDate = try JSONDecoder().decode(DateInRegion.self, from: encodedDate) ``` ### 11. Time Periods SwiftDate integrates the great Matthew York's [DateTools](https://github.com/MatthewYork/DateTools) module in order to support Time Periods. See [Time Periods](/Documentation/12.Timer_Periods.md) section of the documentation. ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Date/Date+Compare.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public extension Date { // MARK: - Comparing Close /// Decides whether a Date is "close by" another one passed in parameter, /// where "Being close" is measured using a precision argument /// which is initialized a 300 seconds, or 5 minutes. /// /// - Parameters: /// - refDate: reference date compare against to. /// - precision: The precision of the comparison (default is 5 minutes, or 300 seconds). /// - Returns: A boolean; true if close by, false otherwise. func compareCloseTo(_ refDate: Date, precision: TimeInterval = 300) -> Bool { return (abs(timeIntervalSince(refDate)) < precision) } // MARK: - Extendend Compare /// Compare the date with the rule specified in the `compareType` parameter. /// /// - Parameter compareType: comparison type. /// - Returns: `true` if comparison succeded, `false` otherwise func compare(_ compareType: DateComparisonType) -> Bool { return inDefaultRegion().compare(compareType) } /// Returns a ComparisonResult value that indicates the ordering of two given dates based on /// their components down to a given unit granularity. /// /// - parameter date: date to compare. /// - parameter granularity: The smallest unit that must, along with all larger units be less for the given dates /// - returns: `ComparisonResult` func compare(toDate refDate: Date, granularity: Calendar.Component) -> ComparisonResult { return inDefaultRegion().compare(toDate: refDate.inDefaultRegion(), granularity: granularity) } /// Compares whether the receiver is before/before equal `date` based on their components down to a given unit granularity. /// /// - Parameters: /// - refDate: reference date /// - orEqual: `true` to also check for equality /// - granularity: smallest unit that must, along with all larger units, be less for the given dates /// - Returns: Boolean func isBeforeDate(_ refDate: Date, orEqual: Bool = false, granularity: Calendar.Component) -> Bool { return inDefaultRegion().isBeforeDate(refDate.inDefaultRegion(), orEqual: orEqual, granularity: granularity) } /// Compares whether the receiver is after `date` based on their components down to a given unit granularity. /// /// - Parameters: /// - refDate: reference date /// - orEqual: `true` to also check for equality /// - granularity: Smallest unit that must, along with all larger units, be greater for the given dates. /// - Returns: Boolean func isAfterDate(_ refDate: Date, orEqual: Bool = false, granularity: Calendar.Component) -> Bool { return inDefaultRegion().isAfterDate(refDate.inDefaultRegion(), orEqual: orEqual, granularity: granularity) } /// Returns a value between 0.0 and 1.0 or nil, that is the position of current date between 2 other dates. /// /// - Parameters: /// - startDate: range upper bound date /// - endDate: range lower bound date /// - Returns: `nil` if current date is not between `startDate` and `endDate`. Otherwise returns position between `startDate` and `endDate`. func positionInRange(date startDate: Date, and endDate: Date) -> Double? { return inDefaultRegion().positionInRange(date: startDate.inDefaultRegion(), and: endDate.inDefaultRegion()) } /// Return true if receiver date is contained in the range specified by two dates. /// /// - Parameters: /// - startDate: range upper bound date /// - endDate: range lower bound date /// - orEqual: `true` to also check for equality on date and date2 /// - granularity: smallest unit that must, along with all larger units, be greater for the given dates. /// - Returns: Boolean func isInRange(date startDate: Date, and endDate: Date, orEqual: Bool = false, granularity: Calendar.Component = .nanosecond) -> Bool { return inDefaultRegion().isInRange(date: startDate.inDefaultRegion(), and: endDate.inDefaultRegion(), orEqual: orEqual, granularity: granularity) } /// Compares equality of two given dates based on their components down to a given unit /// granularity. /// /// - parameter date: date to compare /// - parameter granularity: The smallest unit that must, along with all larger units, be equal for the given /// dates to be considered the same. /// /// - returns: `true` if the dates are the same down to the given granularity, otherwise `false` func isInside(date: Date, granularity: Calendar.Component) -> Bool { return (compare(toDate: date, granularity: granularity) == .orderedSame) } // MARK: - Date Earlier/Later /// Return the earlier of two dates, between self and a given date. /// /// - Parameter date: The date to compare to self /// - Returns: The date that is earlier func earlierDate(_ date: Date) -> Date { return timeIntervalSince(date) <= 0 ? self : date } /// Return the later of two dates, between self and a given date. /// /// - Parameter date: The date to compare to self /// - Returns: The date that is later func laterDate(_ date: Date) -> Date { return timeIntervalSince(date) >= 0 ? self : date } } extension Date { /// Returns the difference in the calendar component given (like day, month or year) /// with respect to the other date as a positive integer public func difference(in component: Calendar.Component, from other: Date) -> Int? { let (max, min) = orderDate(with: other) let result = calendar.dateComponents([component], from: min, to: max) return getValue(of: component, from: result) } /// Returns the differences in the calendar components given (like day, month and year) /// with respect to the other date as dictionary with the calendar component as the key /// and the diffrence as a positive integer as the value public func differences(in components: Set, from other: Date) -> [Calendar.Component: Int] { let (max, min) = orderDate(with: other) let differenceInDates = calendar.dateComponents(components, from: min, to: max) var result = [Calendar.Component: Int]() for component in components { if let value = getValue(of: component, from: differenceInDates) { result[component] = value } } return result } private func getValue(of component: Calendar.Component, from dateComponents: DateComponents) -> Int? { switch component { case .era: return dateComponents.era case .year: return dateComponents.year case .month: return dateComponents.month case .day: return dateComponents.day case .hour: return dateComponents.hour case .minute: return dateComponents.minute case .second: return dateComponents.second case .weekday: return dateComponents.weekday case .weekdayOrdinal: return dateComponents.weekdayOrdinal case .quarter: return dateComponents.quarter case .weekOfMonth: return dateComponents.weekOfMonth case .weekOfYear: return dateComponents.weekOfYear case .yearForWeekOfYear: return dateComponents.yearForWeekOfYear case .nanosecond: return dateComponents.nanosecond case .calendar, .timeZone: return nil @unknown default: assert(false, "unknown date component") } return nil } private func orderDate(with other: Date) -> (Date, Date) { let first = self.timeIntervalSince1970 let second = other.timeIntervalSince1970 if first >= second { return (self, other) } return (other, self) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Date/Date+Components.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public extension Date { /// Indicates whether the month is a leap month. var isLeapMonth: Bool { return inDefaultRegion().isLeapMonth } /// Indicates whether the year is a leap year. var isLeapYear: Bool { return inDefaultRegion().isLeapYear } /// Julian day is the continuous count of days since the beginning of /// the Julian Period used primarily by astronomers. var julianDay: Double { return inDefaultRegion().julianDay } /// The Modified Julian Date (MJD) was introduced by the Smithsonian Astrophysical Observatory /// in 1957 to record the orbit of Sputnik via an IBM 704 (36-bit machine) /// and using only 18 bits until August 7, 2576. var modifiedJulianDay: Double { return inDefaultRegion().modifiedJulianDay } /// Return elapsed time expressed in given components since the current receiver and a reference date. /// /// - Parameters: /// - refDate: reference date (`nil` to use current date in the same region of the receiver) /// - component: time unit to extract. /// - Returns: value func getInterval(toDate: Date?, component: Calendar.Component) -> Int64 { return inDefaultRegion().getInterval(toDate: toDate?.inDefaultRegion(), component: component) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Date/Date+Create.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public extension Date { /// Return the oldest date in given list. /// /// - Parameter list: list of dates /// - Returns: a tuple with the index of the oldest date and its instance. static func oldestIn(list: [Date]) -> Date? { guard list.count > 0 else { return nil } guard list.count > 1 else { return list.first! } return list.min(by: { return $0 < $1 }) } /// Return the newest date in given list. /// /// - Parameter list: list of dates /// - Returns: a tuple with the index of the oldest date and its instance. static func newestIn(list: [Date]) -> Date? { guard list.count > 0 else { return nil } guard list.count > 1 else { return list.first! } return list.max(by: { return $0 < $1 }) } /// Enumerate dates between two intervals by adding specified time components defined by a function and return an array of dates. /// `startDate` interval will be the first item of the resulting array. /// The last item of the array is evaluated automatically and maybe not equal to `endDate`. /// /// - Parameters: /// - start: starting date /// - endDate: ending date /// - increment: increment function. It get the last generated date and require a valida `DateComponents` instance which define the increment /// - Returns: array of dates static func enumerateDates(from startDate: Date, to endDate: Date, increment: ((Date) -> (DateComponents))) -> [Date] { var dates: [Date] = [] var currentDate = startDate while currentDate <= endDate { dates.append(currentDate) currentDate = (currentDate + increment(currentDate)) } return dates } /// Enumerate dates between two intervals by adding specified time components and return an array of dates. /// `startDate` interval will be the first item of the resulting array. /// The last item of the array is evaluated automatically and maybe not equal to `endDate`. /// /// - Parameters: /// - start: starting date /// - endDate: ending date /// - increment: components to add /// - Returns: array of dates static func enumerateDates(from startDate: Date, to endDate: Date, increment: DateComponents) -> [Date] { return Date.enumerateDates(from: startDate, to: endDate, increment: { _ in return increment }) } /// Round a given date time to the passed style (off|up|down). /// /// - Parameter style: rounding mode. /// - Returns: rounded date func dateRoundedAt(at style: RoundDateMode) -> Date { return inDefaultRegion().dateRoundedAt(style).date } /// Returns a new DateInRegion that is initialized at the start of a specified unit of time. /// /// - Parameter unit: time unit value. /// - Returns: instance at the beginning of the time unit; `self` if fails. func dateAtStartOf(_ unit: Calendar.Component) -> Date { return inDefaultRegion().dateAtStartOf(unit).date } /// Return a new DateInRegion that is initialized at the start of the specified components /// executed in order. /// /// - Parameter units: sequence of transformations as time unit components /// - Returns: new date at the beginning of the passed components, intermediate results if fails. func dateAtStartOf(_ units: [Calendar.Component]) -> Date { return units.reduce(self) { (currentDate, currentUnit) -> Date in return currentDate.dateAtStartOf(currentUnit) } } /// Returns a new Moment that is initialized at the end of a specified unit of time. /// /// - parameter unit: A TimeUnit value. /// /// - returns: A new Moment instance. func dateAtEndOf(_ unit: Calendar.Component) -> Date { return inDefaultRegion().dateAtEndOf(unit).date } /// Return a new DateInRegion that is initialized at the end of the specified components /// executed in order. /// /// - Parameter units: sequence of transformations as time unit components /// - Returns: new date at the end of the passed components, intermediate results if fails. func dateAtEndOf(_ units: [Calendar.Component]) -> Date { return units.reduce(self) { (currentDate, currentUnit) -> Date in return currentDate.dateAtEndOf(currentUnit) } } /// Create a new date by altering specified components of the receiver. /// /// - Parameter components: components to alter with their new values. /// - Returns: new altered `DateInRegion` instance func dateBySet(_ components: [Calendar.Component: Int]) -> Date? { return DateInRegion(self, region: SwiftDate.defaultRegion).dateBySet(components)?.date } /// Create a new date by altering specified time components. /// /// - Parameters: /// - hour: hour to set (`nil` to leave it unaltered) /// - min: min to set (`nil` to leave it unaltered) /// - secs: sec to set (`nil` to leave it unaltered) /// - ms: milliseconds to set (`nil` to leave it unaltered) /// - options: options for calculation /// - Returns: new altered `DateInRegion` instance func dateBySet(hour: Int?, min: Int?, secs: Int?, ms: Int? = nil, options: TimeCalculationOptions = TimeCalculationOptions()) -> Date? { let srcDate = DateInRegion(self, region: SwiftDate.defaultRegion) return srcDate.dateBySet(hour: hour, min: min, secs: secs, ms: ms, options: options)?.date } /// Creates a new instance by truncating the components /// /// - Parameter components: components to truncate. /// - Returns: new date with truncated components. func dateTruncated(_ components: [Calendar.Component]) -> Date? { return DateInRegion(self, region: SwiftDate.defaultRegion).dateTruncated(at: components)?.date } /// Creates a new instance by truncating the components starting from given components down the granurality. /// /// - Parameter component: The component to be truncated from. /// - Returns: new date with truncated components. func dateTruncated(from component: Calendar.Component) -> Date? { return DateInRegion(self, region: SwiftDate.defaultRegion).dateTruncated(from: component)?.date } /// Offset a date by n calendar components. /// Note: This operation can be functionally chained. /// /// - Parameters: /// - count: value of the offset. /// - component: component to offset. /// - Returns: new altered date. func dateByAdding(_ count: Int, _ component: Calendar.Component) -> DateInRegion { return DateInRegion(self, region: SwiftDate.defaultRegion).dateByAdding(count, component) } /// Return related date starting from the receiver attributes. /// /// - Parameter type: related date to obtain. /// - Returns: instance of the related date. func dateAt(_ type: DateRelatedType) -> Date { return inDefaultRegion().dateAt(type).date } /// Create a new date at now and extract the related date using passed rule type. /// /// - Parameter type: related date to obtain. /// - Returns: instance of the related date. static func nowAt(_ type: DateRelatedType) -> Date { return Date().dateAt(type) } /// Return the dates for a specific weekday inside given month of specified year. /// Ie. get me all the saturdays of Feb 2018. /// NOTE: Values are returned in order. /// /// - Parameters: /// - weekday: weekday target. /// - month: month target. /// - year: year target. /// - region: region target, omit to use `SwiftDate.defaultRegion` /// - Returns: Ordered list of the dates for given weekday into given month. static func datesForWeekday(_ weekday: WeekDay, inMonth month: Int, ofYear year: Int, region: Region = SwiftDate.defaultRegion) -> [Date] { let fromDate = DateInRegion(Date(year: year, month: month, day: 1, hour: 0, minute: 0), region: region) let toDate = fromDate.dateAt(.endOfMonth) return DateInRegion.datesForWeekday(weekday, from: fromDate, to: toDate, region: region).map { $0.date } } /// Return the dates for a specific weekday inside a specified date range. /// NOTE: Values are returned in order. /// /// - Parameters: /// - weekday: weekday target. /// - startDate: from date of the range. /// - endDate: to date of the range. /// - region: region target, omit to use `SwiftDate.defaultRegion` /// - Returns: Ordered list of the dates for given weekday in passed range. static func datesForWeekday(_ weekday: WeekDay, from startDate: Date, to endDate: Date, region: Region = SwiftDate.defaultRegion) -> [Date] { let fromDate = DateInRegion(startDate, region: region) let toDate = DateInRegion(endDate, region: region) return DateInRegion.datesForWeekday(weekday, from: fromDate, to: toDate, region: region).map { $0.date } } /// Returns the date at the given week number and week day preserving smaller components (hour, minute, seconds) /// /// For example: to get the third friday of next month /// let today = DateInRegion() /// let result = today.dateAt(weekdayOrdinal: 3, weekday: .friday, monthNumber: today.month + 1) /// /// - Parameters: /// - weekdayOrdinal: the week number (by set position in a recurrence rule) /// - weekday: WeekDay /// - monthNumber: a number from 1 to 12 representing the month, optional parameter /// - yearNumber: a number representing the year, optional parameter /// - Returns: new date created with the given parameters func dateAt(weekdayOrdinal: Int, weekday: WeekDay, monthNumber: Int? = nil, yearNumber: Int? = nil) -> Date { let date = DateInRegion(self, region: region) return date.dateAt(weekdayOrdinal: weekdayOrdinal, weekday: weekday, monthNumber: monthNumber, yearNumber: yearNumber).date } /// Returns the next weekday preserving smaller components (hour, minute, seconds) /// /// - Parameters: /// - weekday: weekday to get. /// - region: region target, omit to use `SwiftDate.defaultRegion` /// - Returns: `Date` func nextWeekday(_ weekday: WeekDay, region: Region = SwiftDate.defaultRegion) -> Date { let date = DateInRegion(self, region: region) return date.nextWeekday(weekday).date } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Date/Date+Math.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation /// Subtracts two dates and returns the relative components from `lhs` to `rhs`. /// Follows this mathematical pattern: /// let difference = lhs - rhs /// rhs + difference = lhs public func - (lhs: Date, rhs: Date) -> DateComponents { return SwiftDate.defaultRegion.calendar.dateComponents(DateComponents.allComponentsSet, from: rhs, to: lhs) } /// Adds date components to a date and returns a new date. public func + (lhs: Date, rhs: DateComponents) -> Date { return rhs.from(lhs)! } /// Adds date components to a date and returns a new date. public func + (lhs: DateComponents, rhs: Date) -> Date { return (rhs + lhs) } /// Subtracts date components from a date and returns a new date. public func - (lhs: Date, rhs: DateComponents) -> Date { return (lhs + (-rhs)) } public func + (lhs: Date, rhs: TimeInterval) -> Date { return lhs.addingTimeInterval(rhs) } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Date/Date.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation #if os(Linux) #else internal enum AssociatedKeys: String { case customDateFormatter = "SwiftDate.CustomDateFormatter" } #endif extension Date: DateRepresentable { /// Just return itself to be compliant with `DateRepresentable` protocol. public var date: Date { return self } /// For absolute Date object the default region is obtained from the global `defaultRegion` variable. public var region: Region { return SwiftDate.defaultRegion } #if os(Linux) public var customFormatter: DateFormatter? { get { debugPrint("Not supported on Linux") return nil } set { debugPrint("Not supported on Linux") } } #else /// Assign a custom formatter if you need a special behaviour during formatting of the object. /// Usually you will not need to do it, SwiftDate uses the local thread date formatter in order to /// optimize the formatting process. By default is `nil`. public var customFormatter: DateFormatter? { get { let formatter: DateFormatter? = getAssociatedValue(key: AssociatedKeys.customDateFormatter.rawValue, object: self as AnyObject) return formatter } set { set(associatedValue: newValue, key: AssociatedKeys.customDateFormatter.rawValue, object: self as AnyObject) } } #endif /// Extract the date components. public var dateComponents: DateComponents { return region.calendar.dateComponents(DateComponents.allComponentsSet, from: self) } /// Initialize a new date object from string expressed in given region. /// /// - Parameters: /// - string: date expressed as string. /// - format: format of the date (`nil` uses provided list of auto formats patterns. /// Pass it if you can in order to optimize the parse task). /// - region: region in which the date is expressed. `nil` uses the `SwiftDate.defaultRegion`. public init?(_ string: String, format: String? = nil, region: Region = SwiftDate.defaultRegion) { guard let dateInRegion = DateInRegion(string, format: format, region: region) else { return nil } self = dateInRegion.date } /// Initialize a new date from the number of seconds passed since Unix Epoch. /// /// - Parameter interval: seconds /// Initialize a new date from the number of seconds passed since Unix Epoch. /// /// - Parameters: /// - interval: seconds from Unix epoch time. /// - region: region in which the date, `nil` uses the default region at UTC timezone public init(seconds interval: TimeInterval, region: Region = Region.UTC) { self = DateInRegion(seconds: interval, region: region).date } /// Initialize a new date corresponding to the number of milliseconds since the Unix Epoch. /// /// - Parameters: /// - interval: seconds since the Unix Epoch timestamp. /// - region: region in which the date must be expressed, `nil` uses the default region at UTC timezone public init(milliseconds interval: Int, region: Region = Region.UTC) { self = DateInRegion(milliseconds: interval, region: region).date } /// Initialize a new date with the opportunity to configure single date components via builder pattern. /// Date is therfore expressed in passed region (`DateComponents`'s `timezone`,`calendar` and `locale` are ignored /// and overwritten by the region if not `nil`). /// /// - Parameters: /// - configuration: configuration callback /// - region: region in which the date is expressed. Ignore to use `SwiftDate.defaultRegion`, `nil` to use `DateComponents` data. public init?(components configuration: ((inout DateComponents) -> Void), region: Region? = SwiftDate.defaultRegion) { guard let date = DateInRegion(components: configuration, region: region)?.date else { return nil } self = date } /// Initialize a new date with given components. /// /// - Parameters: /// - components: components of the date. /// - region: region in which the date is expressed. /// Ignore to use `SwiftDate.defaultRegion`, `nil` to use `DateComponents` data. public init?(components: DateComponents, region: Region?) { guard let date = DateInRegion(components: components, region: region)?.date else { return nil } self = date } /// Initialize a new date with given components. public init(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0, region: Region = SwiftDate.defaultRegion) { var components = DateComponents() components.year = year components.month = month components.day = day components.hour = hour components.minute = minute components.second = second components.nanosecond = nanosecond components.timeZone = region.timeZone components.calendar = region.calendar self = region.calendar.date(from: components)! } /// Express given absolute date in the context of the default region. /// /// - Returns: `DateInRegion` public func inDefaultRegion() -> DateInRegion { return DateInRegion(self, region: SwiftDate.defaultRegion) } /// Express given absolute date in the context of passed region. /// /// - Parameter region: destination region. /// - Returns: `DateInRegion` public func `in`(region: Region) -> DateInRegion { return DateInRegion(self, region: region) } /// Return a date in the distant past. /// /// - Returns: Date instance. public static func past() -> Date { return Date.distantPast } /// Return a date in the distant future. /// /// - Returns: Date instance. public static func future() -> Date { return Date.distantFuture } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/DateInRegion/DateInRegion+Compare.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation // MARK: - Comparing DateInRegion public func == (lhs: DateInRegion, rhs: DateInRegion) -> Bool { return (lhs.date.timeIntervalSince1970 == rhs.date.timeIntervalSince1970) } public func <= (lhs: DateInRegion, rhs: DateInRegion) -> Bool { let result = lhs.date.compare(rhs.date) return (result == .orderedAscending || result == .orderedSame) } public func >= (lhs: DateInRegion, rhs: DateInRegion) -> Bool { let result = lhs.date.compare(rhs.date) return (result == .orderedDescending || result == .orderedSame) } public func < (lhs: DateInRegion, rhs: DateInRegion) -> Bool { return lhs.date.compare(rhs.date) == .orderedAscending } public func > (lhs: DateInRegion, rhs: DateInRegion) -> Bool { return lhs.date.compare(rhs.date) == .orderedDescending } // The type of comparison to do against today's date or with the suplied date. /// /// - isToday: hecks if date today. /// - isTomorrow: Checks if date is tomorrow. /// - isYesterday: Checks if date is yesterday. /// - isSameDay: Compares date days /// - isThisWeek: Checks if date is in this week. /// - isNextWeek: Checks if date is in next week. /// - isLastWeek: Checks if date is in last week. /// - isSameWeek: Compares date weeks /// - isThisMonth: Checks if date is in this month. /// - isNextMonth: Checks if date is in next month. /// - isLastMonth: Checks if date is in last month. /// - isSameMonth: Compares date months /// - isThisYear: Checks if date is in this year. /// - isNextYear: Checks if date is in next year. /// - isLastYear: Checks if date is in last year. /// - isSameYear: Compare date years /// - isInTheFuture: Checks if it's a future date /// - isInThePast: Checks if the date has passed /// - isEarlier: Checks if earlier than date /// - isLater: Checks if later than date /// - isWeekday: Checks if it's a weekday /// - isWeekend: Checks if it's a weekend /// - isInDST: Indicates whether the represented date uses daylight saving time. /// - isMorning: Return true if date is in the morning (>=5 - <12) /// - isAfternoon: Return true if date is in the afternoon (>=12 - <17) /// - isEvening: Return true if date is in the morning (>=17 - <21) /// - isNight: Return true if date is in the morning (>=21 - <5) public enum DateComparisonType { // Days case isToday case isTomorrow case isYesterday case isSameDay(_ : DateRepresentable) // Weeks case isThisWeek case isNextWeek case isLastWeek case isSameWeek(_: DateRepresentable) // Months case isThisMonth case isNextMonth case isLastMonth case isSameMonth(_: DateRepresentable) // Years case isThisYear case isNextYear case isLastYear case isSameYear(_: DateRepresentable) // Relative Time case isInTheFuture case isInThePast case isEarlier(than: DateRepresentable) case isLater(than: DateRepresentable) case isWeekday case isWeekend // Day time case isMorning case isAfternoon case isEvening case isNight // TZ case isInDST } public extension DateInRegion { /// Decides whether a DATE is "close by" another one passed in parameter, /// where "Being close" is measured using a precision argument /// which is initialized a 300 seconds, or 5 minutes. /// /// - Parameters: /// - refDate: reference date compare against to. /// - precision: The precision of the comparison (default is 5 minutes, or 300 seconds). /// - Returns: A boolean; true if close by, false otherwise. func compareCloseTo(_ refDate: DateInRegion, precision: TimeInterval = 300) -> Bool { return (abs(date.timeIntervalSince(refDate.date)) <= precision) } /// Compare the date with the rule specified in the `compareType` parameter. /// /// - Parameter compareType: comparison type. /// - Returns: `true` if comparison succeded, `false` otherwise func compare(_ compareType: DateComparisonType) -> Bool { switch compareType { case .isToday: return compare(.isSameDay(region.nowInThisRegion())) case .isTomorrow: let tomorrow = DateInRegion(region: region).dateByAdding(1, .day) return compare(.isSameDay(tomorrow)) case .isYesterday: let yesterday = DateInRegion(region: region).dateByAdding(-1, .day) return compare(.isSameDay(yesterday)) case .isSameDay(let refDate): return calendar.isDate(date, inSameDayAs: refDate.date) case .isThisWeek: return compare(.isSameWeek(region.nowInThisRegion())) case .isNextWeek: let nextWeek = region.nowInThisRegion().dateByAdding(1, .weekOfYear) return compare(.isSameWeek(nextWeek)) case .isLastWeek: let lastWeek = region.nowInThisRegion().dateByAdding(-1, .weekOfYear) return compare(.isSameWeek(lastWeek)) case .isSameWeek(let refDate): guard weekOfYear == refDate.weekOfYear else { return false } // Ensure time interval is under 1 week return (abs(date.timeIntervalSince(refDate.date)) < 1.weeks.timeInterval) case .isThisMonth: return compare(.isSameMonth(region.nowInThisRegion())) case .isNextMonth: let nextMonth = region.nowInThisRegion().dateByAdding(1, .month) return compare(.isSameMonth(nextMonth)) case .isLastMonth: let lastMonth = region.nowInThisRegion().dateByAdding(-1, .month) return compare(.isSameMonth(lastMonth)) case .isSameMonth(let refDate): return (year == refDate.year) && (month == refDate.month) case .isThisYear: return compare(.isSameYear(region.nowInThisRegion())) case .isNextYear: let nextYear = region.nowInThisRegion().dateByAdding(1, .year) return compare(.isSameYear(nextYear)) case .isLastYear: let lastYear = region.nowInThisRegion().dateByAdding(-1, .year) return compare(.isSameYear(lastYear)) case .isSameYear(let refDate): return (year == refDate.year) case .isInTheFuture: return compare(.isLater(than: region.nowInThisRegion())) case .isInThePast: return compare(.isEarlier(than: region.nowInThisRegion())) case .isEarlier(let refDate): return ((date as NSDate).earlierDate(refDate.date) == date) case .isLater(let refDate): return ((date as NSDate).laterDate(refDate.date) == date) case .isWeekday: return !compare(.isWeekend) case .isWeekend: let range = calendar.maximumRange(of: Calendar.Component.weekday)! return (weekday == range.lowerBound || weekday == range.upperBound - range.lowerBound) case .isInDST: return region.timeZone.isDaylightSavingTime(for: date) case .isMorning: return (hour >= 5 && hour < 12) case .isAfternoon: return (hour >= 12 && hour < 17) case .isEvening: return (hour >= 17 && hour < 21) case .isNight: return (hour >= 21 || hour < 5) } } /// Returns a ComparisonResult value that indicates the ordering of two given dates based on /// their components down to a given unit granularity. /// /// - parameter date: date to compare. /// - parameter granularity: The smallest unit that must, along with all larger units /// - returns: `ComparisonResult` func compare(toDate refDate: DateInRegion, granularity: Calendar.Component) -> ComparisonResult { switch granularity { case .nanosecond: // There is a possible rounding error using Calendar to compare two dates below the minute granularity // So we've added this trick and use standard Date compare which return correct results in this case // https://github.com/malcommac/SwiftDate/issues/346 return date.compare(refDate.date) default: return region.calendar.compare(date, to: refDate.date, toGranularity: granularity) } } /// Compares whether the receiver is before/before equal `date` based on their components down to a given unit granularity. /// /// - Parameters: /// - refDate: reference date /// - orEqual: `true` to also check for equality /// - granularity: smallest unit that must, along with all larger units, be less for the given dates /// - Returns: Boolean func isBeforeDate(_ date: DateInRegion, orEqual: Bool = false, granularity: Calendar.Component) -> Bool { let result = compare(toDate: date, granularity: granularity) return (orEqual ? (result == .orderedSame || result == .orderedAscending) : result == .orderedAscending) } /// Compares whether the receiver is after `date` based on their components down to a given unit granularity. /// /// - Parameters: /// - refDate: reference date /// - orEqual: `true` to also check for equality /// - granularity: Smallest unit that must, along with all larger units, be greater for the given dates. /// - Returns: Boolean func isAfterDate(_ refDate: DateInRegion, orEqual: Bool = false, granularity: Calendar.Component) -> Bool { let result = compare(toDate: refDate, granularity: granularity) return (orEqual ? (result == .orderedSame || result == .orderedDescending) : result == .orderedDescending) } /// Compares equality of two given dates based on their components down to a given unit /// granularity. /// /// - parameter date: date to compare /// - parameter granularity: The smallest unit that must, along with all larger units, be equal for the given /// dates to be considered the same. /// /// - returns: `true` if the dates are the same down to the given granularity, otherwise `false` func isInside(date: DateInRegion, granularity: Calendar.Component) -> Bool { return (compare(toDate: date, granularity: granularity) == .orderedSame) } /// Returns a value between 0.0 and 1.0 or nil, that is the position of current date between 2 other dates. /// /// - Parameters: /// - startDate: range upper bound date /// - endDate: range lower bound date /// - Returns: `nil` if current date is not between `startDate` and `endDate`. Otherwise returns position between `startDate` and `endDate`. func positionInRange(date startDate: DateInRegion, and endDate: DateInRegion) -> Double? { let diffCurrentDateAndStartDate = self - startDate guard diffCurrentDateAndStartDate >= 0 else { return nil } let diffEndDateAndStartDate = endDate - startDate guard diffEndDateAndStartDate > 0, diffCurrentDateAndStartDate <= diffEndDateAndStartDate else { return nil } return diffCurrentDateAndStartDate / diffEndDateAndStartDate } /// Return `true` if receiver data is contained in the range specified by two dates. /// /// - Parameters: /// - startDate: range upper bound date /// - endDate: range lower bound date /// - orEqual: `true` to also check for equality on date and date2, default is `true` /// - granularity: smallest unit that must, along with all larger units, be greater /// - Returns: Boolean func isInRange(date startDate: DateInRegion, and endDate: DateInRegion, orEqual: Bool = true, granularity: Calendar.Component = .nanosecond) -> Bool { return isAfterDate(startDate, orEqual: orEqual, granularity: granularity) && isBeforeDate(endDate, orEqual: orEqual, granularity: granularity) } // MARK: - Date Earlier/Later /// Return the earlier of two dates, between self and a given date. /// /// - Parameter date: The date to compare to self /// - Returns: The date that is earlier func earlierDate(_ date: DateInRegion) -> DateInRegion { return self.date.timeIntervalSince(date.date) <= 0 ? self : date } /// Return the later of two dates, between self and a given date. /// /// - Parameter date: The date to compare to self /// - Returns: The date that is later func laterDate(_ date: DateInRegion) -> DateInRegion { return self.date.timeIntervalSince(date.date) >= 0 ? self : date } /// Returns the difference in the calendar component given (like day, month or year) /// with respect to the other date as a positive integer func difference(in component: Calendar.Component, from other: DateInRegion) -> Int? { return self.date.difference(in: component, from: other.date) } /// Returns the differences in the calendar components given (like day, month and year) /// with respect to the other date as dictionary with the calendar component as the key /// and the diffrence as a positive integer as the value func differences(in components: Set, from other: DateInRegion) -> [Calendar.Component: Int] { return self.date.differences(in: components, from: other.date) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/DateInRegion/DateInRegion+Components.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public extension DateInRegion { /// Indicates whether the month is a leap month. var isLeapMonth: Bool { let calendar = region.calendar // Library function for leap contains a bug for Gregorian calendars, implemented workaround if calendar.identifier == Calendar.Identifier.gregorian && year > 1582 { guard let range: Range = calendar.range(of: .day, in: .month, for: date) else { return false } return ((range.upperBound - range.lowerBound) == 29) } // For other calendars: return calendar.dateComponents([.day, .month, .year], from: date).isLeapMonth! } /// Indicates whether the year is a leap year. var isLeapYear: Bool { let calendar = region.calendar // Library function for leap contains a bug for Gregorian calendars, implemented workaround if calendar.identifier == Calendar.Identifier.gregorian { var newComponents = dateComponents newComponents.month = 2 newComponents.day = 10 let testDate = DateInRegion(components: newComponents, region: region) return testDate!.isLeapMonth } else if calendar.identifier == Calendar.Identifier.chinese { /// There are 12 or 13 months in each year and 29 or 30 days in each month. /// A 13-month year is a leap year, which meaning more than 376 days is a leap year. return ( dateAtStartOf(.year).toUnit(.day, to: dateAtEndOf(.year)) > 375 ) } // For other calendars: return calendar.dateComponents([.day, .month, .year], from: date).isLeapMonth! } /// Julian day is the continuous count of days since the beginning of /// the Julian Period used primarily by astronomers. var julianDay: Double { let destRegion = Region(calendar: Calendars.gregorian, zone: Zones.gmt, locale: Locales.english) let utc = convertTo(region: destRegion) let year = Double(utc.year) let month = Double(utc.month) let day = Double(utc.day) let hour = Double(utc.hour) + Double(utc.minute) / 60.0 + (Double(utc.second) + Double(utc.nanosecond) / 1e9) / 3600.0 var jd = 367.0 * year - floor( 7.0 * ( year + floor((month + 9.0) / 12.0)) / 4.0 ) jd -= floor( 3.0 * (floor( (year + (month - 9.0) / 7.0) / 100.0 ) + 1.0) / 4.0 ) jd += floor(275.0 * month / 9.0) + day + 1_721_028.5 + hour / 24.0 return jd } /// The Modified Julian Date (MJD) was introduced by the Smithsonian Astrophysical Observatory /// in 1957 to record the orbit of Sputnik via an IBM 704 (36-bit machine) /// and using only 18 bits until August 7, 2576. var modifiedJulianDay: Double { return julianDay - 2_400_000.5 } /// Return elapsed time expressed in given components since the current receiver and a reference date. /// Time is evaluated with the fixed measumerent of each unity. /// /// - Parameters: /// - refDate: reference date (`nil` to use current date in the same region of the receiver) /// - component: time unit to extract. /// - Returns: value func getInterval(toDate: DateInRegion?, component: Calendar.Component) -> Int64 { let refDate = (toDate ?? region.nowInThisRegion()) switch component { case .year: let end = calendar.ordinality(of: .year, in: .era, for: refDate.date) let start = calendar.ordinality(of: .year, in: .era, for: date) return Int64(end! - start!) case .month: let end = calendar.ordinality(of: .month, in: .era, for: refDate.date) let start = calendar.ordinality(of: .month, in: .era, for: date) return Int64(end! - start!) case .day: let end = calendar.ordinality(of: .day, in: .era, for: refDate.date) let start = calendar.ordinality(of: .day, in: .era, for: date) return Int64(end! - start!) case .hour: let interval = refDate.date.timeIntervalSince(date) return Int64(interval / 1.hours.timeInterval) case .minute: let interval = refDate.date.timeIntervalSince(date) return Int64(interval / 1.minutes.timeInterval) case .second: return Int64(refDate.date.timeIntervalSince(date)) case .weekday: let end = calendar.ordinality(of: .weekday, in: .era, for: refDate.date) let start = calendar.ordinality(of: .weekday, in: .era, for: date) return Int64(end! - start!) case .weekdayOrdinal: let end = calendar.ordinality(of: .weekdayOrdinal, in: .era, for: refDate.date) let start = calendar.ordinality(of: .weekdayOrdinal, in: .era, for: date) return Int64(end! - start!) case .weekOfYear: let end = calendar.ordinality(of: .weekOfYear, in: .era, for: refDate.date) let start = calendar.ordinality(of: .weekOfYear, in: .era, for: date) return Int64(end! - start!) default: debugPrint("Passed component cannot be used to extract values using interval() function between two dates. Returning 0.") return 0 } } /// The interval between the receiver and the another parameter. /// If the receiver is earlier than anotherDate, the return value is negative. /// If anotherDate is nil, the results are undefined. /// /// - Parameter date: The date with which to compare the receiver. /// - Returns: time interval between two dates func timeIntervalSince(_ date: DateInRegion) -> TimeInterval { return self.date.timeIntervalSince(date.date) } /// Extract DateComponents from the difference between two dates. /// /// - Parameter rhs: date to compare /// - Returns: components func componentsTo(_ rhs: DateInRegion) -> DateComponents { return calendar.dateComponents(DateComponents.allComponentsSet, from: rhs.date, to: date) } /// Returns the difference between two dates (`date - self`) expressed as date components. /// /// - Parameters: /// - date: reference date as initial date (left operand) /// - components: components to extract, `nil` to use default `DateComponents.allComponentsSet` /// - Returns: extracted date components func componentsSince(_ date: DateInRegion, components: [Calendar.Component]? = nil) -> DateComponents { if date.calendar != calendar { debugPrint("Date has different calendar, results maybe wrong") } let cmps = (components != nil ? Calendar.Component.toSet(components!) : DateComponents.allComponentsSet) return date.calendar.dateComponents(cmps, from: date.date, to: self.date) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/DateInRegion/DateInRegion+Create.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public extension DateInRegion { // MARK: - Random Date Generator /// Generate a sequence of dates between a range. /// /// - Parameters: /// - count: number of dates to generate. /// - initial: lower date bound. /// - final: upper date bound. /// - region: region of the dates. /// - Returns: array of dates static func randomDates(count: Int, between initial: DateInRegion, and final: DateInRegion, region: Region = SwiftDate.defaultRegion) -> [DateInRegion] { var list: [DateInRegion] = [] for _ in 0.. DateInRegion { let today = DateInRegion(region: region) let earliest = DateInRegion(today.date.addingTimeInterval(TimeInterval(-days * 24 * 60 * 60)), region: region) return DateInRegion.randomDate(between: earliest, and: today) } /// Generate a random date in given region. /// /// - Parameter region: destination region, `nil` to use the default region /// - Returns: random date static func randomDate(region: Region = SwiftDate.defaultRegion) -> DateInRegion { let randomTime = TimeInterval(UInt32.random(in: UInt32.min.. DateInRegion { let interval = final.timeIntervalSince(initial) let randomInterval = TimeInterval(UInt32.random(in: UInt32.min.. DateInRegion? { guard list.count > 0 else { return nil } guard list.count > 1 else { return list.first! } return list.min(by: { return $0 < $1 }) } /// Sort date by oldest, with the oldest date on top. /// /// - Parameter list: list to sort /// - Returns: sorted array static func sortedByOldest(list: [DateInRegion]) -> [DateInRegion] { return list.sorted(by: { $0.date.compare($1.date) == .orderedAscending }) } /// Sort date by newest, with the newest date on top. /// /// - Parameter list: list to sort /// - Returns: sorted array static func sortedByNewest(list: [DateInRegion]) -> [DateInRegion] { return list.sorted(by: { $0.date.compare($1.date) == .orderedDescending }) } /// Return the newest date in given list (timezone is ignored, comparison uses absolute date). /// /// - Parameter list: list of dates /// - Returns: a tuple with the index of the newest date and its instance. static func newestIn(list: [DateInRegion]) -> DateInRegion? { guard list.count > 0 else { return nil } guard list.count > 1 else { return list.first! } return list.max(by: { return $0 < $1 }) } /// Enumerate dates between two intervals by adding specified time components and return an array of dates. /// `startDate` interval will be the first item of the resulting array. /// The last item of the array is evaluated automatically and maybe not equal to `endDate`. /// /// - Parameters: /// - start: starting date /// - endDate: ending date /// - increment: components to add /// - Returns: array of dates static func enumerateDates(from startDate: DateInRegion, to endDate: DateInRegion, increment: DateComponents) -> [DateInRegion] { return DateInRegion.enumerateDates(from: startDate, to: endDate, increment: { _ in return increment }) } /// Enumerate dates between two intervals by adding specified time components defined in a closure and return an array of dates. /// `startDate` interval will be the first item of the resulting array. /// The last item of the array is evaluated automatically and maybe not equal to `endDate`. /// /// - Parameters: /// - start: starting date /// - endDate: ending date /// - increment: increment function. It get the last generated date and require a valida `DateComponents` instance which define the increment /// - Returns: array of dates static func enumerateDates(from startDate: DateInRegion, to endDate: DateInRegion, increment: ((DateInRegion) -> (DateComponents))) -> [DateInRegion] { guard startDate.calendar == endDate.calendar else { debugPrint("Cannot enumerate dates between two different region's calendars. Return empty array.") return [] } var dates: [DateInRegion] = [] var currentDate = startDate while currentDate <= endDate { dates.append(currentDate) currentDate = (currentDate + increment(currentDate)) } return dates } /// Returns a new DateInRegion that is initialized at the start of a specified unit of time. /// /// - Parameter unit: time unit value. /// - Returns: instance at the beginning of the time unit; `self` if fails. func dateAtStartOf(_ unit: Calendar.Component) -> DateInRegion { #if os(Linux) guard let result = (region.calendar as NSCalendar).range(of: unit.nsCalendarUnit, for: date) else { return self } return DateInRegion(result.start, region: region) #else var start: NSDate? var interval: TimeInterval = 0 guard (region.calendar as NSCalendar).range(of: unit.nsCalendarUnit, start: &start, interval: &interval, for: date), let startDate = start else { return self } return DateInRegion(startDate as Date, region: region) #endif } /// Return a new DateInRegion that is initialized at the start of the specified components /// executed in order. /// /// - Parameter units: sequence of transformations as time unit components /// - Returns: new date at the beginning of the passed components, intermediate results if fails. func dateAtStartOf(_ units: [Calendar.Component]) -> DateInRegion { return units.reduce(self) { (currentDate, currentUnit) -> DateInRegion in return currentDate.dateAtStartOf(currentUnit) } } /// Returns a new Moment that is initialized at the end of a specified unit of time. /// /// - parameter unit: time unit value. /// /// - returns: A new Moment instance. func dateAtEndOf(_ unit: Calendar.Component) -> DateInRegion { // RangeOfUnit returns the start of the next unit; we will subtract one thousandth of a second #if os(Linux) guard let result = (region.calendar as NSCalendar).range(of: unit.nsCalendarUnit, for: date) else { return self } let startOfNextUnit = result.start.addingTimeInterval(result.duration) let endOfThisUnit = Date(timeInterval: -0.001, since: startOfNextUnit) return DateInRegion(endOfThisUnit, region: region) #else var start: NSDate? var interval: TimeInterval = 0 guard (self.region.calendar as NSCalendar).range(of: unit.nsCalendarUnit, start: &start, interval: &interval, for: date), let startDate = start else { return self } let startOfNextUnit = startDate.addingTimeInterval(interval) let endOfThisUnit = Date(timeInterval: -0.001, since: startOfNextUnit as Date) return DateInRegion(endOfThisUnit, region: region) #endif } /// Return a new DateInRegion that is initialized at the end of the specified components /// executed in order. /// /// - Parameter units: sequence of transformations as time unit components /// - Returns: new date at the end of the passed components, intermediate results if fails. func dateAtEndOf(_ units: [Calendar.Component]) -> DateInRegion { return units.reduce(self) { (currentDate, currentUnit) -> DateInRegion in return currentDate.dateAtEndOf(currentUnit) } } /// Create a new date by altering specified components of the receiver. /// Note: `calendar` and `timezone` are ignored. /// Note: some components may alter the date cyclically (like setting both `.year` and `.yearForWeekOfYear`) and /// may results in a wrong evaluated date. /// /// - Parameter components: components to alter with their new values. /// - Returns: new altered `DateInRegion` instance func dateBySet(_ components: [Calendar.Component: Int?]) -> DateInRegion? { var dateComponents = DateComponents() dateComponents.year = (components[.year] ?? year) dateComponents.month = (components[.month] ?? month) dateComponents.day = (components[.day] ?? day) dateComponents.hour = (components[.hour] ?? hour) dateComponents.minute = (components[.minute] ?? minute) dateComponents.second = (components[.second] ?? second) dateComponents.nanosecond = (components[.nanosecond] ?? nanosecond) // Some components may interfer with others, so we'll set it them only if explicitly set. if let weekday = components[.weekday] { dateComponents.weekday = weekday } if let weekOfYear = components[.weekOfYear] { dateComponents.weekOfYear = weekOfYear } if let weekdayOrdinal = components[.weekdayOrdinal] { dateComponents.weekdayOrdinal = weekdayOrdinal } if let yearForWeekOfYear = components[.yearForWeekOfYear] { dateComponents.yearForWeekOfYear = yearForWeekOfYear } guard let newDate = calendar.date(from: dateComponents) else { return nil } return DateInRegion(newDate, region: region) } /// Create a new date by altering specified time components. /// /// - Parameters: /// - hour: hour to set (`nil` to leave it unaltered) /// - min: min to set (`nil` to leave it unaltered) /// - secs: sec to set (`nil` to leave it unaltered) /// - ms: milliseconds to set (`nil` to leave it unaltered) /// - options: options for calculation /// - Returns: new altered `DateInRegion` instance func dateBySet(hour: Int?, min: Int?, secs: Int?, ms: Int? = nil, options: TimeCalculationOptions = TimeCalculationOptions()) -> DateInRegion? { guard let date = calendar.date(bySettingHour: (hour ?? self.hour), minute: (min ?? self.minute), second: (secs ?? self.second), of: self.date, matchingPolicy: options.matchingPolicy, repeatedTimePolicy: options.repeatedTimePolicy, direction: options.direction) else { return nil } guard let ms = ms else { return DateInRegion(date, region: region) } var timestamp = date.timeIntervalSince1970.rounded(.down) timestamp += Double(ms) / 1000.0 return DateInRegion(Date(timeIntervalSince1970: timestamp), region: region) } /// Creates a new instance by truncating the components /// /// - Parameter components: components to truncate. /// - Returns: new date with truncated components. func dateTruncated(at components: [Calendar.Component]) -> DateInRegion? { var dateComponents = self.dateComponents for component in components { switch component { case .month: dateComponents.month = 1 case .day: dateComponents.day = 1 case .hour: dateComponents.hour = 0 case .minute: dateComponents.minute = 0 case .second: dateComponents.second = 0 case .nanosecond: dateComponents.nanosecond = 0 default: continue } } guard let newDate = calendar.date(from: dateComponents) else { return nil } return DateInRegion(newDate, region: region) } /// Creates a new instance by truncating the components starting from given components down the granurality. /// /// - Parameter component: The component to be truncated from. /// - Returns: new date with truncated components. func dateTruncated(from component: Calendar.Component) -> DateInRegion? { switch component { case .month: return dateTruncated(at: [.month, .day, .hour, .minute, .second, .nanosecond]) case .day: return dateTruncated(at: [.day, .hour, .minute, .second, .nanosecond]) case .hour: return dateTruncated(at: [.hour, .minute, .second, .nanosecond]) case .minute: return dateTruncated(at: [.minute, .second, .nanosecond]) case .second: return dateTruncated(at: [.second, .nanosecond]) case .nanosecond: return dateTruncated(at: [.nanosecond]) default: return self } } /// Round a given date time to the passed style (off|up|down). /// /// - Parameter style: rounding mode. /// - Returns: rounded date func dateRoundedAt(_ style: RoundDateMode) -> DateInRegion { switch style { case .to5Mins: return dateRoundedAt(.toMins(5)) case .to10Mins: return dateRoundedAt(.toMins(10)) case .to30Mins: return dateRoundedAt(.toMins(30)) case .toCeil5Mins: return dateRoundedAt(.toCeilMins(5)) case .toCeil10Mins: return dateRoundedAt(.toCeilMins(10)) case .toCeil30Mins: return dateRoundedAt(.toCeilMins(30)) case .toFloor5Mins: return dateRoundedAt(.toFloorMins(5)) case .toFloor10Mins: return dateRoundedAt(.toFloorMins(10)) case .toFloor30Mins: return dateRoundedAt(.toFloorMins(30)) case .toMins(let minuteInterval): let onesDigit: Int = (minute % 10) if onesDigit < 5 { return dateRoundedAt(.toFloorMins(minuteInterval)) } else { return dateRoundedAt(.toCeilMins(minuteInterval)) } case .toCeilMins(let minuteInterval): let remain: Int = (minute % minuteInterval) let value = (( Int(1.minutes.timeInterval) * (minuteInterval - remain)) - second) return dateByAdding(value, .second) case .toFloorMins(let minuteInterval): let remain: Int = (minute % minuteInterval) let value = -((Int(1.minutes.timeInterval) * remain) + second) return dateByAdding(value, .second) } } /// Offset a date by n calendar components. /// Note: This operation can be functionally chained. /// /// - Parameters: /// - count: value of the offset (maybe negative). /// - component: component to offset. /// - Returns: new altered date. func dateByAdding(_ count: Int, _ component: Calendar.Component) -> DateInRegion { var newComponent = DateComponents(second: 0) switch component { case .era: newComponent = DateComponents(era: count) case .year: newComponent = DateComponents(year: count) case .month: newComponent = DateComponents(month: count) case .day: newComponent = DateComponents(day: count) case .hour: newComponent = DateComponents(hour: count) case .minute: newComponent = DateComponents(minute: count) case .second: newComponent = DateComponents(second: count) case .weekday: newComponent = DateComponents(weekday: count) case .weekdayOrdinal: newComponent = DateComponents(weekdayOrdinal: count) case .quarter: newComponent = DateComponents(quarter: count) case .weekOfMonth: newComponent = DateComponents(weekOfMonth: count) case .weekOfYear: newComponent = DateComponents(weekOfYear: count) case .yearForWeekOfYear: newComponent = DateComponents(yearForWeekOfYear: count) case .nanosecond: newComponent = DateComponents(nanosecond: count) default: break // .calendar and .timezone does nothing in this context } guard let newDate = region.calendar.date(byAdding: newComponent, to: date) else { return self // failed to add component, return unmodified date } return DateInRegion(newDate, region: region) } /// Return related date starting from the receiver attributes. /// /// - Parameter type: related date to obtain. /// - Returns: instance of the related date; if fails the same unmodified date is returned func dateAt(_ type: DateRelatedType) -> DateInRegion { switch type { case .startOfDay: return calendar.startOfDay(for: date).in(region: region) case .endOfDay: return dateByAdding(1, .day).dateAt(.startOfDay).dateByAdding(-1, .second) case .startOfWeek: let components = calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: date) return calendar.date(from: components)!.in(region: region) case .endOfWeek: return dateAt(.startOfWeek).dateByAdding(7, .day).dateByAdding(-1, .second) case .startOfMonth: return dateBySet([.day: 1, .hour: 0, .minute: 0, .second: 0, .nanosecond: 0])! case .endOfMonth: return dateByAdding((monthDays - day), .day).dateAtEndOf(.day) case .tomorrow: return dateByAdding(1, .day) case .tomorrowAtStart: return dateByAdding(1, .day).dateAtStartOf(.day) case .yesterday: return dateByAdding(-1, .day) case .yesterdayAtStart: return dateByAdding(-1, .day).dateAtStartOf(.day) case .nearestMinute(let nearest): let minutes = (minute + nearest / 2) / nearest * nearest return dateBySet([.minute: minutes])! case .nearestHour(let nearest): let hours = (hour + nearest / 2) / nearest * nearest return dateBySet([.hour: hours, .minute: 0])! case .nextWeekday(let weekday): var cal = Calendar(identifier: calendar.identifier) cal.firstWeekday = 2 // Sunday = 1, Saturday = 7 var components = DateComponents() components.weekday = weekday.rawValue guard let next = cal.nextDate(after: date, matching: components, matchingPolicy: .nextTimePreservingSmallerComponents) else { return self } return DateInRegion(next, region: region) case .nextDSTDate: guard let nextDate = region.timeZone.nextDaylightSavingTimeTransition(after: date) else { return self } return DateInRegion(nextDate, region: region) case .prevMonth: return dateByAdding(-1, .month).dateAtStartOf(.month).dateAtStartOf(.day) case .nextMonth: return dateByAdding(1, .month).dateAtStartOf(.month).dateAtStartOf(.day) case .prevWeek: return dateByAdding(-1, .weekOfYear).dateAtStartOf(.weekOfYear).dateAtStartOf(.day) case .nextWeek: return dateByAdding(1, .weekOfYear).dateAtStartOf(.weekOfYear).dateAtStartOf(.day) case .nextYear: return dateByAdding(1, .year).dateAtStartOf(.year) case .prevYear: return dateByAdding(-1, .year).dateAtStartOf(.year) case .nextDSTTransition: guard let transitionDate = region.timeZone.nextDaylightSavingTimeTransition(after: date) else { return self } return DateInRegion(transitionDate, region: region) } } /// Create a new instance of the date in the same region with time shifted by given time interval. /// /// - Parameter interval: time interval to shift; maybe negative. /// - Returns: new instance of the `DateInRegion` func addingTimeInterval(_ interval: TimeInterval) -> DateInRegion { return DateInRegion(date.addingTimeInterval(interval), region: region) } // MARK: - Conversion /// Convert a date to a new calendar/timezone/locale. /// Only non `nil` values are used, other values are inherithed by the receiver's region. /// /// - Parameters: /// - calendar: non `nil` value to change the calendar /// - timezone: non `nil` value to change the timezone /// - locale: non `nil` value to change the locale /// - Returns: converted date func convertTo(calendar: CalendarConvertible? = nil, timezone: ZoneConvertible? = nil, locale: LocaleConvertible? = nil) -> DateInRegion { let newRegion = Region(calendar: (calendar ?? region.calendar), zone: (timezone ?? region.timeZone), locale: (locale ?? region.locale)) return convertTo(region: newRegion) } /// Return the dates for a specific weekday inside given month of specified year. /// Ie. get me all the saturdays of Feb 2018. /// NOTE: Values are returned in order. /// /// - Parameters: /// - weekday: weekday target. /// - month: month target. /// - year: year target. /// - region: region target, omit to use `SwiftDate.defaultRegion` /// - Returns: Ordered list of the dates for given weekday into given month. static func datesForWeekday(_ weekday: WeekDay, inMonth month: Int, ofYear year: Int, region: Region = SwiftDate.defaultRegion) -> [DateInRegion] { let fromDate = DateInRegion(year: year, month: month, day: 1, hour: 0, minute: 0, second: 0, nanosecond: 0, region: region) let toDate = fromDate.dateAt(.endOfMonth) return DateInRegion.datesForWeekday(weekday, from: fromDate, to: toDate, region: region) } /// Return the dates for a specific weekday inside a specified date range. /// NOTE: Values are returned in order. /// /// - Parameters: /// - weekday: weekday target. /// - startDate: from date of the range. /// - endDate: to date of the range. /// - region: region target, omit to use `SwiftDate.defaultRegion` /// - Returns: Ordered list of the dates for given weekday in passed range. static func datesForWeekday(_ weekday: WeekDay, from startDate: DateInRegion, to endDate: DateInRegion, region: Region = SwiftDate.defaultRegion) -> [DateInRegion] { let calendarObj = region.calendar let startDateWeekDay = Int(calendarObj.component(.weekday, from: startDate.date)) let desiredDay = weekday.rawValue let offset = (desiredDay - startDateWeekDay + 7) % 7 let firstOccurrence = calendarObj.startOfDay(for: calendarObj.date(byAdding: DateComponents(day: offset), to: startDate.date)!) guard firstOccurrence.timeIntervalSince1970 < endDate.timeIntervalSince1970 else { return [] } var dateOccurrences = [DateInRegion(firstOccurrence, region: region)] while true { let nextDate = DateInRegion(calendarObj.date(byAdding: DateComponents(day: 7), to: dateOccurrences.last!.date)!, region: region) guard nextDate < endDate else { break } dateOccurrences.append(nextDate) } return dateOccurrences } } public extension DateInRegion { /// Returns the date at the given week number and week day preserving smaller components (hour, minute, seconds) /// /// For example: to get the third friday of next month /// let today = DateInRegion() /// let result = today.dateAt(weekdayOrdinal: 3, weekday: .friday, monthNumber: today.month + 1) /// /// - Parameters: /// - weekdayOrdinal: the week number (by set position in a recurrence rule) /// - weekday: WeekDay /// - monthNumber: a number from 1 to 12 representing the month, optional parameter /// - yearNumber: a number representing the year, optional parameter /// - Returns: new date created with the given parameters func dateAt(weekdayOrdinal: Int, weekday: WeekDay, monthNumber: Int? = nil, yearNumber: Int? = nil) -> DateInRegion { let monthNum = monthNumber ?? month let yearNum = yearNumber ?? year var requiredWeekNum = weekdayOrdinal var result = DateInRegion(year: yearNum, month: monthNum, day: 1, hour: hour, minute: minute, second: second, nanosecond: nanosecond, region: region) if result.weekday == weekday.rawValue { requiredWeekNum -= 1 } while requiredWeekNum > 0 { result = result.nextWeekday(weekday) requiredWeekNum -= 1 } return result } /// Returns the date on the given day of month preserving smaller components func dateAt(dayOfMonth: Int, monthNumber: Int? = nil, yearNumber: Int? = nil) -> DateInRegion { let monthNum = monthNumber ?? month let yearNum = yearNumber ?? year let result = DateInRegion(year: yearNum, month: monthNum, day: dayOfMonth, hour: hour, minute: minute, second: second, nanosecond: nanosecond, region: region) return result } /// Returns the date after given number of weeks on the given day of week func dateAfter(weeks count: Int, on weekday: WeekDay) -> DateInRegion { var result = self.dateByAdding(count, .weekOfMonth) if result.weekday == weekday.rawValue { return result } else if result.weekday > weekday.rawValue { result = result.dateByAdding(-1, .weekOfMonth) } return result.nextWeekday(weekday) } /// Returns the next weekday preserving smaller components /// /// - Parameters: /// - weekday: weekday to get. /// - region: region target, omit to use `SwiftDate.defaultRegion` /// - Returns: `DateInRegion` func nextWeekday(_ weekday: WeekDay) -> DateInRegion { var components = DateComponents() components.weekday = weekday.rawValue components.hour = hour components.second = second components.minute = minute guard let next = region.calendar.nextDate(after: date, matching: components, matchingPolicy: .nextTimePreservingSmallerComponents) else { return self } return DateInRegion(next, region: region) } /// Returns next date with the given weekday and the given week number func next(_ weekday: WeekDay, withWeekOfMonth weekNumber: Int, andMonthNumber monthNumber: Int? = nil) -> DateInRegion { var result = self.dateAt(weekdayOrdinal: weekNumber, weekday: weekday, monthNumber: monthNumber) if result <= self { if let monthNum = monthNumber { result = self.dateAt(weekdayOrdinal: weekNumber, weekday: weekday, monthNumber: monthNum, yearNumber: self.year + 1) } else { result = self.dateAt(weekdayOrdinal: weekNumber, weekday: weekday, monthNumber: self.month + 1) } } return result } /// Returns the next day of month preserving smaller components (hour, minute, seconds) func next(dayOfMonth: Int, monthOfYear: Int? = nil) -> DateInRegion { var components = DateComponents() components.day = dayOfMonth components.month = monthOfYear components.hour = hour components.second = second components.minute = minute guard let next = region.calendar.nextDate(after: date, matching: components, matchingPolicy: .nextTimePreservingSmallerComponents) else { return self } return DateInRegion(next, region: region) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/DateInRegion/DateInRegion+Math.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation // MARK: - Math Operation DateInRegion - DateInRegion public func - (lhs: DateInRegion, rhs: DateInRegion) -> TimeInterval { return lhs.timeIntervalSince(rhs) } // MARK: - Math Operation DateInRegion - Date Components public func + (lhs: DateInRegion, rhs: DateComponents) -> DateInRegion { let nextDate = lhs.calendar.date(byAdding: rhs, to: lhs.date) return DateInRegion(nextDate!, region: lhs.region) } public func - (lhs: DateInRegion, rhs: DateComponents) -> DateInRegion { return lhs + (-rhs) } // MARK: - Math Operation DateInRegion - Calendar.Component public func + (lhs: DateInRegion, rhs: [Calendar.Component: Int]) -> DateInRegion { let cmps = DateInRegion.componentsFrom(values: rhs) return lhs + cmps } public func - (lhs: DateInRegion, rhs: [Calendar.Component: Int]) -> DateInRegion { var invertedCmps: [Calendar.Component: Int] = [:] rhs.forEach { invertedCmps[$0.key] = -$0.value } return lhs + invertedCmps } // MARK: - Internal DateInRegion Extension extension DateInRegion { /// Return a `DateComponent` object from a given set of `Calendar.Component` object with associated values and a specific region /// /// - parameter values: calendar components to set (with their values) /// - parameter multipler: optional multipler (by default is nil; to make an inverse component value it should be multipled by -1) /// - parameter region: optional region to set /// /// - returns: a `DateComponents` object internal static func componentsFrom(values: [Calendar.Component: Int], multipler: Int? = nil, setRegion region: Region? = nil) -> DateComponents { var cmps = DateComponents() if region != nil { cmps.calendar = region!.calendar cmps.calendar!.locale = region!.locale cmps.timeZone = region!.timeZone } values.forEach { pair in if pair.key != .timeZone && pair.key != .calendar { cmps.setValue( (multipler == nil ? pair.value : pair.value * multipler!), for: pair.key) } } return cmps } /// Adds a time interval to this date. /// WARNING: /// This only adjusts an absolute value. If you wish to add calendrical concepts like hours, /// days, months then you must use a Calendar. /// That will take into account complexities like daylight saving time, /// months with different numbers of days, and more. /// /// - Parameter timeInterval: The value to add, in seconds. public mutating func addTimeInterval(_ timeInterval: TimeInterval) { date.addTimeInterval(timeInterval) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/DateInRegion/DateInRegion.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public struct DateInRegion: DateRepresentable, Decodable, Encodable, CustomStringConvertible, Comparable, Hashable { /// Absolute date represented. This date is not associated with any timezone or calendar /// but represent the absolute number of seconds since Jan 1, 2001 at 00:00:00 UTC. public internal(set) var date: Date /// Associated region which define where the date is represented into the world. public let region: Region /// Formatter used to transform this object in a string. By default is `nil` because SwiftDate /// uses the thread shared formatter in order to avoid expensive init of the `DateFormatter` object. /// However, if you need of a custom behaviour you can set a valid value. public var customFormatter: DateFormatter? /// Extract date components by taking care of the region in which the date is expressed. public var dateComponents: DateComponents { return region.calendar.dateComponents(DateComponents.allComponentsSet, from: date) } /// Description of the date public var description: String { let absISODate = DateFormatter.sharedFormatter(forRegion: Region.UTC).string(from: date) let representedDate = formatter(format: DateFormats.iso8601).string(from: date) return "{abs_date='\(absISODate)', rep_date='\(representedDate)', region=\(region.description)" } /// The interval between the date value and 00:00:00 UTC on 1 January 1970. public var timeIntervalSince1970: TimeInterval { return date.timeIntervalSince1970 } /// Initialize with an absolute date and represent it into given geographic region. /// /// - Parameters: /// - date: absolute date to represent. /// - region: region in which the date is represented. If ignored `defaultRegion` is used instead. public init(_ date: Date = Date(), region: Region = SwiftDate.defaultRegion) { self.date = date self.region = region } /// Initialize a new `DateInRegion` by parsing given string. /// If you know the format of the string you should pass it in order to speed up the parsing process. /// If you don't know the format leave it `nil` and parse is done between all formats in `DateFormats.builtInAutoFormats` /// and the ordered list you can provide in `SwiftDate.autoParseFormats` (with attempt priority set on your list). /// /// - Parameters: /// - string: string with the date. /// - format: format of the date. /// - region: region in which the date is expressed. public init?(_ string: String, format: String? = nil, region: Region = SwiftDate.defaultRegion) { guard let date = DateFormats.parse(string: string, format: format, region: region) else { return nil // failed to parse date } self.date = date self.region = region } /// Initialize a new `DateInRegion` by parsing given string with the ordered list of passed formats. /// If you know the format of the string you should pass it in order to speed up the parsing process. /// If you don't know the format leave it `nil` and parse is done between all formats in `DateFormats.builtInAutoFormats` /// and the ordered list you can provide in `SwiftDate.autoParseFormats` (with attempt priority set on your list). /// /// - Parameters: /// - string: string with the date. /// - formats: ordered list of formats to use. /// - region: region in which the date is expressed. public init?(_ string: String, formats: [String]?, region: Region = SwiftDate.defaultRegion) { guard let date = DateFormats.parse(string: string, formats: (formats ?? SwiftDate.autoFormats), region: region) else { return nil // failed to parse date } self.date = date self.region = region } /// Initialize a new date from the number of seconds passed since Unix Epoch. /// /// - Parameters: /// - interval: seconds since Unix Epoch. /// - region: the region in which the date must be expressed, `nil` uses the default region at UTC timezone public init(seconds interval: TimeInterval, region: Region = Region.UTC) { self.date = Date(timeIntervalSince1970: interval) self.region = region } /// Initialize a new date corresponding to the number of milliseconds since the Unix Epoch. /// /// - Parameters: /// - interval: seconds since the Unix Epoch timestamp. /// - region: region in which the date must be expressed, `nil` uses the default region at UTC timezone public init(milliseconds interval: Int, region: Region = Region.UTC) { self.date = Date(timeIntervalSince1970: TimeInterval(interval) / 1000) self.region = region } /// Initialize a new date with the opportunity to configure single date components via builder pattern. /// Date is therfore expressed in passed region (`DateComponents`'s `timezone`,`calendar` and `locale` are ignored /// and overwritten by the region if not `nil`). /// /// - Parameters: /// - configuration: configuration callback /// - region: region in which the date is expressed. /// Ignore to use `SwiftDate.defaultRegion`, `nil` to use `DateComponents` data. public init?(components configuration: ((inout DateComponents) -> Void), region: Region? = SwiftDate.defaultRegion) { var components = DateComponents() configuration(&components) let r = (region ?? Region(fromDateComponents: components)) guard let date = r.calendar.date(from: components) else { return nil } self.date = date self.region = r } /// Initialize a new date with given components. /// /// - Parameters: /// - components: components of the date. /// - region: region in which the date is expressed. /// Ignore to use `SwiftDate.defaultRegion`, `nil` to use `DateComponents` data. public init?(components: DateComponents, region: Region?) { let r = (region ?? Region(fromDateComponents: components)) guard let date = r.calendar.date(from: components) else { return nil } self.date = date self.region = r } /// Initialize a new date with given components. public init(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0, nanosecond: Int = 0, region: Region = SwiftDate.defaultRegion) { var components = DateComponents() components.year = year components.month = month components.day = day components.hour = hour components.minute = minute components.second = second components.nanosecond = nanosecond components.timeZone = region.timeZone components.calendar = region.calendar self.date = region.calendar.date(from: components)! self.region = region } /// Return a date in the distant past. /// /// - Returns: Date instance. public static func past() -> DateInRegion { return DateInRegion(Date.distantPast, region: SwiftDate.defaultRegion) } /// Return a date in the distant future. /// /// - Returns: Date instance. public static func future() -> DateInRegion { return DateInRegion(Date.distantFuture, region: SwiftDate.defaultRegion) } // MARK: - Codable Support enum CodingKeys: String, CodingKey { case date case region } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/DateInRegion/Region.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation /// Region define a context both for `Date` and `DateInRegion`. /// Each `Date` is assigned to the currently set `SwiftDate.default public struct Region: Decodable, Encodable, Equatable, Hashable, CustomStringConvertible { // MARK: - Properties /// Calendar associated with region public let calendar: Calendar /// Locale associated with region public var locale: Locale { return calendar.locale! } /// Timezone associated with region public var timeZone: TimeZone { return calendar.timeZone } /// Description of the object public var description: String { return "{calendar='\(calendar.identifier)', timezone='\(timeZone.identifier)', locale='\(locale.identifier)'}" } public func hash(into hasher: inout Hasher) { hasher.combine(calendar) } // MARK: Initialization /// Initialize a new region with given parameters. /// /// - Parameters: /// - calendar: calendar for region, if not specified `defaultRegions`'s calendar is used instead. /// - timezone: timezone for region, if not specified `defaultRegions`'s timezone is used instead. /// - locale: locale for region, if not specified `defaultRegions`'s locale is used instead. public init(calendar: CalendarConvertible = SwiftDate.defaultRegion.calendar, zone: ZoneConvertible = SwiftDate.defaultRegion.timeZone, locale: LocaleConvertible = SwiftDate.defaultRegion.locale) { self.calendar = Calendar.newCalendar(calendar, configure: { $0.timeZone = zone.toTimezone() $0.locale = locale.toLocale() }) } /// Initialize a new Region by reading the `timeZone`,`calendar` and `locale` /// parameters from the passed `DateComponents` instance. /// For any `nil` parameter the correspondent `SwiftDate.defaultRegion` is used instead. /// /// - Parameter fromDateComponents: date components public init(fromDateComponents components: DateComponents) { let tz = (components.timeZone ?? Zones.current.toTimezone()) let cal = (components.calendar ?? Calendars.gregorian.toCalendar()) let loc = (cal.locale ?? Locales.current.toLocale()) self.init(calendar: cal, zone: tz, locale: loc) } public static var UTC: Region { return Region(calendar: Calendar.autoupdatingCurrent, zone: Zones.gmt.toTimezone(), locale: Locale.autoupdatingCurrent) } /// Return the current local device's region where all attributes are set to the device's values. /// /// - Returns: Region public static var local: Region { return Region(calendar: Calendar.autoupdatingCurrent, zone: TimeZone.autoupdatingCurrent, locale: Locale.autoupdatingCurrent) } /// ISO Region is defined by the gregorian calendar, gmt timezone and english posix locale public static var ISO: Region { return Region(calendar: Calendars.gregorian.toCalendar(), zone: Zones.gmt.toTimezone(), locale: Locales.englishUnitedStatesComputer) } /// Return an auto updating region where all settings are obtained from the current's device settings. /// /// - Returns: Region public static var current: Region { return Region(calendar: Calendar.autoupdatingCurrent, zone: TimeZone.autoupdatingCurrent, locale: Locale.autoupdatingCurrent) } /// Return a new region in current's device timezone with optional adjust of the calendar and locale. /// /// - Parameters: /// - locale: locale to set /// - calendar: calendar to set /// - Returns: region public static func currentIn(locale: LocaleConvertible? = nil, calendar: CalendarConvertible? = nil) -> Region { return Region(calendar: (calendar ?? SwiftDate.defaultRegion.calendar), zone: SwiftDate.defaultRegion.timeZone, locale: (locale ?? SwiftDate.defaultRegion.locale)) } /// Return the current date expressed into the receiver region. /// /// - Returns: `DateInRegion` instance public func nowInThisRegion() -> DateInRegion { return DateInRegion(Date(), region: self) } // MARK: - Codable Support enum CodingKeys: String, CodingKey { case calendar case locale case timezone } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(calendar.timeZone.identifier, forKey: .timezone) try container.encode(calendar.locale!.identifier, forKey: .locale) try container.encode(calendar.identifier.description, forKey: .calendar) } public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) let calId = Calendar.Identifier( try values.decode(String.self, forKey: .calendar)) let tz = (TimeZone(identifier: try values.decode(String.self, forKey: .timezone)) ?? SwiftDate.defaultRegion.timeZone) let lc = Locale(identifier: try values.decode(String.self, forKey: .locale)) calendar = Calendar.newCalendar(calId, configure: { $0.timeZone = tz $0.locale = lc }) } // MARK: - Comparable public static func == (lhs: Region, rhs: Region) -> Bool { // Note: equality does not consider other parameters than the identifier of the major // attributes (calendar, timezone and locale). Deeper comparisor must be made directly // between Calendar (it may fail when you encode/decode autoUpdating calendars). return (lhs.calendar.identifier == rhs.calendar.identifier) && (lhs.timeZone.identifier == rhs.timeZone.identifier) && (lhs.locale.identifier == rhs.locale.identifier) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/DateRepresentable.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public protocol DateRepresentable { // MARK: - Date Components var year: Int { get } /// Represented month var month: Int { get } /// Represented month name with given style. /// /// - Parameter style: style in which the name must be formatted. /// - Returns: name of the month func monthName(_ style: SymbolFormatStyle) -> String /// Number of the days in the receiver. var monthDays: Int { get } /// Day unit of the receiver. var day: Int { get } /// Day of year unit of the receiver var dayOfYear: Int { get } /// The number of day in ordinal style format for the receiver in current locale. /// For example, in the en_US locale, the number 3 is represented as 3rd; /// in the fr_FR locale, the number 3 is represented as 3e. @available(iOS 9.0, macOS 10.11, *) var ordinalDay: String { get } /// Hour unit of the receiver. var hour: Int { get } /// Nearest rounded hour from the date var nearestHour: Int { get } /// Minute unit of the receiver. var minute: Int { get } /// Second unit of the receiver. var second: Int { get } /// Nanosecond unit of the receiver. var nanosecond: Int { get } /// Milliseconds in day of the receiver /// This field behaves exactly like a composite of all time-related fields, not including the zone fields. /// As such, it also reflects discontinuities of those fields on DST transition days. /// On a day of DST onset, it will jump forward. On a day of DST cessation, it will jump backward. /// This reflects the fact that is must be combined with the offset field to obtain a unique local time value. var msInDay: Int { get } /// Weekday unit of the receiver. /// The weekday units are the numbers 1-N (where for the Gregorian calendar N=7 and 1 is Sunday). var weekday: Int { get } /// Name of the weekday expressed in given format style. /// /// - Parameter style: style to express the value. /// - Parameter locale: locale to use; ignore it to use default's region locale. /// - Returns: weekday name func weekdayName(_ style: SymbolFormatStyle, locale: LocaleConvertible?) -> String /// Week of a year of the receiver. var weekOfYear: Int { get } /// Week of a month of the receiver. var weekOfMonth: Int { get } /// Ordinal position within the month unit of the corresponding weekday unit. /// For example, in the Gregorian calendar a weekday ordinal unit of 2 for a /// weekday unit 3 indicates "the second Tuesday in the month". var weekdayOrdinal: Int { get } /// Return the first day number of the week where the receiver date is located. var firstDayOfWeek: Int { get } /// Return the last day number of the week where the receiver date is located. var lastDayOfWeek: Int { get } /// Relative year for a week within a year calendar unit. var yearForWeekOfYear: Int { get } /// Quarter value of the receiver. var quarter: Int { get } /// Quarter name expressed in given format style. /// /// - Parameter style: style to express the value. /// - Parameter locale: locale to use; ignore it to use default's region locale. /// - Returns: quarter name func quarterName(_ style: SymbolFormatStyle, locale: LocaleConvertible?) -> String /// Era value of the receiver. var era: Int { get } /// Name of the era expressed in given format style. /// /// - Parameter style: style to express the value. /// - Parameter locale: locale to use; ignore it to use default's region locale. /// - Returns: era func eraName(_ style: SymbolFormatStyle, locale: LocaleConvertible?) -> String /// The current daylight saving time offset of the represented date. var DSTOffset: TimeInterval { get } // MARK: - Common Properties /// Absolute representation of the date var date: Date { get } /// Associated region var region: Region { get } /// Associated calendar var calendar: Calendar { get } /// Extract the date components from the date var dateComponents: DateComponents { get } /// Returns whether the given date is in today as boolean. var isToday: Bool { get } /// Returns whether the given date is in yesterday. var isYesterday: Bool { get } /// Returns whether the given date is in tomorrow. var isTomorrow: Bool { get } /// Returns whether the given date is in the weekend. var isInWeekend: Bool { get } /// Return true if given date represent a passed date var isInPast: Bool { get } /// Return true if given date represent a future date var isInFuture: Bool { get } /// Use this object to format the date object. /// By default this object return the `customFormatter` instance (if set) or the /// local thread shared formatter (via `sharedFormatter()` func; this is the most typical scenario). /// /// - Parameters: /// - format: format string to set. /// - configuration: optional callback used to configure the object inline. /// - Returns: formatter instance func formatter(format: String?, configuration: ((DateFormatter) -> Void)?) -> DateFormatter /// User this object to get an DateFormatter already configured to format the data object with the associated region. /// By default this object return the `customFormatter` instance (if set) configured for region or the /// local thread shared formatter even configured for region (via `sharedFormatter()` func; this is the most typical scenario). /// /// - format: format string to set. /// - configuration: optional callback used to configure the object inline. /// - Returns: formatter instance func formatterForRegion(format: String?, configuration: ((inout DateFormatter) -> Void)?) -> DateFormatter /// Set a custom formatter for this object. /// Typically you should not need to set a value for this property. /// With a `nil` value SwiftDate will uses the threa shared formatter returned by `sharedFormatter()` function. /// In case you need to a custom formatter instance you can override the default behaviour by setting a value here. var customFormatter: DateFormatter? { get set } /// Return a formatter instance created as singleton into the current caller's thread. /// This object is used for formatting when no `dateFormatter` is set for the object /// (this is the common scenario where you want to avoid multiple formatter instances to /// parse dates; instances of DateFormatter are very expensive to create and you should /// use a single instance in each thread to perform this kind of tasks). /// /// - Returns: formatter instance var sharedFormatter: DateFormatter { get } // MARK: - Init /// Initialize a new date by parsing a string. /// /// - Parameters: /// - string: string with the date. /// - format: format used to parse date. Pass `nil` to use built-in formats /// (if you know you should pass it to optimize the parsing process) /// - region: region in which the date in `string` is expressed. init?(_ string: String, format: String?, region: Region) /// Initialize a new date from a number of seconds since the Unix Epoch. /// /// - Parameters: /// - interval: seconds since the Unix Epoch timestamp. /// - region: region in which the date must be expressed. init(seconds interval: TimeInterval, region: Region) /// Initialize a new date corresponding to the number of milliseconds since the Unix Epoch. /// /// - Parameters: /// - interval: seconds since the Unix Epoch timestamp. /// - region: region in which the date must be expressed. init(milliseconds interval: Int, region: Region) /// Initialize a new date with the opportunity to configure single date components via builder pattern. /// Date is therfore expressed in passed region (`DateComponents`'s `timezone`,`calendar` and `locale` are ignored /// and overwritten by the region if not `nil`). /// /// - Parameters: /// - configuration: configuration callback /// - region: region in which the date is expressed. Ignore to use `SwiftDate.defaultRegion`, /// `nil` to use `DateComponents` data. init?(components configuration: ((inout DateComponents) -> Void), region: Region?) /// Initialize a new date with time components passed. /// /// - Parameters: /// - components: date components /// - region: region in which the date is expressed. Ignore to use `SwiftDate.defaultRegion`, /// `nil` to use `DateComponents` data. init?(components: DateComponents, region: Region?) /// Initialize a new date with given components. init(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int, nanosecond: Int, region: Region) // MARK: - Conversion /// Convert a date to another region. /// /// - Parameter region: destination region in which the date must be represented. /// - Returns: converted date func convertTo(region: Region) -> DateInRegion // MARK: - To String Formatting /// Convert date to a string using passed pre-defined style. /// /// - Parameter style: formatter style, `nil` to use `standard` style /// - Returns: string representation of the date func toString(_ style: DateToStringStyles?) -> String /// Convert date to a string using custom date format. /// /// - Parameters: /// - format: format of the string representation /// - locale: locale to fix a custom locale, `nil` to use associated region's locale /// - Returns: string representation of the date func toFormat(_ format: String, locale: LocaleConvertible?) -> String /// Convert a date to a string representation relative to another reference date (or current /// if not passed). /// /// - Parameters: /// - since: reference date, if `nil` current is used. /// - style: style to use to format relative date. /// - locale: force locale print, `nil` to use the date own region's locale /// - Returns: string representation of the date. func toRelative(since: DateInRegion?, style: RelativeFormatter.Style?, locale: LocaleConvertible?) -> String /// Return ISO8601 representation of the date /// /// - Parameter options: optional options, if nil extended iso format is used func toISO(_ options: ISOFormatter.Options?) -> String /// Return DOTNET compatible representation of the date. /// /// - Returns: string representation of the date func toDotNET() -> String /// Return SQL compatible representation of the date. /// /// - Returns: string represenation of the date func toSQL() -> String /// Return RSS compatible representation of the date /// /// - Parameter alt: `true` to return altRSS version, `false` to return the standard RSS representation /// - Returns: string representation of the date func toRSS(alt: Bool) -> String // MARK: - Extract Components /// Extract time components for elapsed interval between the receiver date /// and a reference date. /// /// - Parameters: /// - units: units to extract. /// - refDate: reference date /// - Returns: extracted time units func toUnits(_ units: Set, to refDate: DateRepresentable) -> [Calendar.Component: Int] /// Extract time unit component from given date. /// /// - Parameters: /// - unit: time component to extract /// - refDate: reference date /// - Returns: extracted time unit value func toUnit(_ unit: Calendar.Component, to refDate: DateRepresentable) -> Int } public extension DateRepresentable { // MARK: - Common Properties var calendar: Calendar { return region.calendar } // MARK: - Date Components Properties var year: Int { return dateComponents.year! } var month: Int { return dateComponents.month! } var monthDays: Int { return calendar.range(of: .day, in: .month, for: date)!.count } func monthName(_ style: SymbolFormatStyle) -> String { let formatter = self.formatter(format: nil) let idx = (month - 1) switch style { case .default: return formatter.monthSymbols[idx] case .defaultStandalone: return formatter.standaloneMonthSymbols[idx] case .short: return formatter.shortMonthSymbols[idx] case .standaloneShort: return formatter.shortStandaloneMonthSymbols[idx] case .veryShort: return formatter.veryShortMonthSymbols[idx] case .standaloneVeryShort: return formatter.veryShortStandaloneMonthSymbols[idx] } } var day: Int { return dateComponents.day! } var dayOfYear: Int { return calendar.ordinality(of: .day, in: .year, for: date)! } @available(iOS 9.0, macOS 10.11, *) var ordinalDay: String { let day = self.day return DateFormatter.sharedOrdinalNumberFormatter(locale: region.locale).string(from: day as NSNumber) ?? "\(day)" } var hour: Int { return dateComponents.hour! } var nearestHour: Int { let newDate = (date + (date.minute >= 30 ? 60 - date.minute : -date.minute).minutes) return newDate.in(region: region).hour } var minute: Int { return dateComponents.minute! } var second: Int { return dateComponents.second! } var nanosecond: Int { return dateComponents.nanosecond! } var msInDay: Int { return (calendar.ordinality(of: .second, in: .day, for: date)! * 1000) } var weekday: Int { return dateComponents.weekday! } func weekdayName(_ style: SymbolFormatStyle, locale: LocaleConvertible? = nil) -> String { let formatter = self.formatter(format: nil) { $0.locale = (locale ?? self.region.locale).toLocale() } let idx = (weekday - 1) switch style { case .default: return formatter.weekdaySymbols[idx] case .defaultStandalone: return formatter.standaloneWeekdaySymbols[idx] case .short: return formatter.shortWeekdaySymbols[idx] case .standaloneShort: return formatter.shortStandaloneWeekdaySymbols[idx] case .veryShort: return formatter.veryShortWeekdaySymbols[idx] case .standaloneVeryShort: return formatter.veryShortStandaloneWeekdaySymbols[idx] } } var weekOfYear: Int { return dateComponents.weekOfYear! } var weekOfMonth: Int { return dateComponents.weekOfMonth! } var weekdayOrdinal: Int { return dateComponents.weekdayOrdinal! } var yearForWeekOfYear: Int { return dateComponents.yearForWeekOfYear! } var firstDayOfWeek: Int { return date.dateAt(.startOfWeek).day } var lastDayOfWeek: Int { return date.dateAt(.endOfWeek).day } var quarter: Int { let monthsInQuarter = Double(Calendar.current.monthSymbols.count) / 4.0 return Int(ceil( Double(month) / monthsInQuarter)) } var isToday: Bool { return calendar.isDateInToday(date) } var isYesterday: Bool { return calendar.isDateInYesterday(date) } var isTomorrow: Bool { return calendar.isDateInTomorrow(date) } var isInWeekend: Bool { return calendar.isDateInWeekend(date) } var isInPast: Bool { return date < Date() } var isInFuture: Bool { return date > Date() } func quarterName(_ style: SymbolFormatStyle, locale: LocaleConvertible? = nil) -> String { let formatter = self.formatter(format: nil) { $0.locale = (locale ?? self.region.locale).toLocale() } let idx = (quarter - 1) switch style { case .default: return formatter.quarterSymbols[idx] case .defaultStandalone: return formatter.standaloneQuarterSymbols[idx] case .short, .veryShort: return formatter.shortQuarterSymbols[idx] case .standaloneShort, .standaloneVeryShort: return formatter.shortStandaloneQuarterSymbols[idx] } } var era: Int { return dateComponents.era! } func eraName(_ style: SymbolFormatStyle, locale: LocaleConvertible? = nil) -> String { let formatter = self.formatter(format: nil) { $0.locale = (locale ?? self.region.locale).toLocale() } let idx = (era - 1) switch style { case .default, .defaultStandalone: return formatter.longEraSymbols[idx] case .short, .standaloneShort, .veryShort, .standaloneVeryShort: return formatter.eraSymbols[idx] } } var DSTOffset: TimeInterval { return region.timeZone.daylightSavingTimeOffset(for: date) } // MARK: - Date Formatters func formatter(format: String? = nil, configuration: ((DateFormatter) -> Void)? = nil) -> DateFormatter { let formatter = (customFormatter ?? sharedFormatter) if let dFormat = format { formatter.dateFormat = dFormat } configuration?(formatter) return formatter } func formatterForRegion(format: String? = nil, configuration: ((inout DateFormatter) -> Void)? = nil) -> DateFormatter { var formatter = self.formatter(format: format, configuration: { $0.timeZone = self.region.timeZone $0.calendar = self.calendar $0.locale = self.region.locale }) configuration?(&formatter) return formatter } var sharedFormatter: DateFormatter { return DateFormatter.sharedFormatter(forRegion: region) } func toString(_ style: DateToStringStyles? = nil) -> String { guard let style = style else { return DateToStringStyles.standard.toString(self) } return style.toString(self) } func toFormat(_ format: String, locale: LocaleConvertible? = nil) -> String { guard let fixedLocale = locale else { return DateToStringStyles.custom(format).toString(self) } let fixedRegion = Region(calendar: region.calendar, zone: region.timeZone, locale: fixedLocale) let fixedDate = DateInRegion(date.date, region: fixedRegion) return DateToStringStyles.custom(format).toString(fixedDate) } func toRelative(since: DateInRegion? = nil, style: RelativeFormatter.Style? = nil, locale: LocaleConvertible? = nil) -> String { return RelativeFormatter.format(date: self, to: since, style: style, locale: locale?.toLocale()) } func toISO(_ options: ISOFormatter.Options? = nil) -> String { return DateToStringStyles.iso( (options ?? ISOFormatter.Options([.withInternetDateTime])) ).toString(self) } func toDotNET() -> String { return DOTNETFormatter.format(self, options: nil) } func toRSS(alt: Bool) -> String { switch alt { case true: return DateToStringStyles.altRSS.toString(self) case false: return DateToStringStyles.rss.toString(self) } } func toSQL() -> String { return DateToStringStyles.sql.toString(self) } // MARK: - Conversion func convertTo(region: Region) -> DateInRegion { return DateInRegion(date, region: region) } // MARK: - Extract Time Components func toUnits(_ units: Set, to refDate: DateRepresentable) -> [Calendar.Component: Int] { let cal = region.calendar let components = cal.dateComponents(units, from: date, to: refDate.date) return components.toDict() } func toUnit(_ unit: Calendar.Component, to refDate: DateRepresentable) -> Int { let cal = region.calendar let components = cal.dateComponents([unit], from: date, to: refDate.date) return components.value(for: unit)! } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/DotNetParserFormatter.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public class DOTNETParser: StringToDateTransformable { internal static let pattern = "\\/Date\\((-?\\d+)((?:[\\+\\-]\\d+)?)\\)\\/" public static func parse(_ string: String) -> (seconds: TimeInterval, tz: TimeZone)? { do { let parser = try NSRegularExpression(pattern: DOTNETParser.pattern, options: .caseInsensitive) guard let match = parser.firstMatch(in: string, options: .reportCompletion, range: NSRange(location: 0, length: string.count)) else { return nil } guard let milliseconds = TimeInterval((string as NSString).substring(with: match.range(at: 1))) else { return nil } // Parse timezone let raw_tz = ((string as NSString).substring(with: match.range(at: 2)) as NSString) guard raw_tz.length > 1 else { return nil } let tz_sign: String = raw_tz.substring(to: 1) if tz_sign != "+" && tz_sign != "-" { return nil } let tz_hours: String = raw_tz.substring(with: NSRange(location: 1, length: 2)) let tz_minutes: String = raw_tz.substring(with: NSRange(location: 3, length: 2)) let tz_offset = (Int(tz_hours)! * 60 * 60) + ( Int(tz_minutes)! * 60 ) guard let tz_obj = TimeZone(secondsFromGMT: tz_offset) else { return nil } return ( (milliseconds / 1000), tz_obj ) } catch { return nil } } public static func parse(_ string: String, region: Region?, options: Any?) -> DateInRegion? { guard let result = DOTNETParser.parse(string) else { return nil } let regionSet = region ?? Region.ISO let adaptedRegion = Region(calendar: regionSet.calendar, zone: regionSet.timeZone, locale: regionSet.locale) return DateInRegion(seconds: result.seconds, region: adaptedRegion) } } public class DOTNETFormatter: DateToStringTrasformable { public static func format(_ date: DateRepresentable, options: Any?) -> String { let milliseconds = (date.date.timeIntervalSince1970 * 1000.0) let tzOffsets = (date.region.timeZone.secondsFromGMT(for: date.date) / 3600) let formattedStr = String(format: "/Date(%.0f%+03d00)/", milliseconds, tzOffsets) return formattedStr } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/Formatter+Protocols.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public protocol DateToStringTrasformable { static func format(_ date: DateRepresentable, options: Any?) -> String } public protocol StringToDateTransformable { static func parse(_ string: String, region: Region?, options: Any?) -> DateInRegion? } // MARK: - Formatters /// Format to represent a date to string /// /// - iso: standard iso format. The ISO8601 formatted date, time and millisec "yyyy-MM-dd'T'HH:mm:ssZZZZZ" /// - extended: Extended format. "eee dd-MMM-yyyy GG HH:mm:ss.SSS zzz" /// - rss: The RSS formatted date "EEE, d MMM yyyy HH:mm:ss ZZZ" i.e. "Fri, 09 Sep 2011 15:26:08 +0200" /// - altRSS: The Alternative RSS formatted date "d MMM yyyy HH:mm:ss ZZZ" i.e. "09 Sep 2011 15:26:08 +0200" /// - dotNet: The dotNet formatted date "/Date(%d%d)/" i.e. "/Date(1268123281843)/" /// - httpHeader: The http header formatted date "EEE, dd MMM yyyy HH:mm:ss zzz" i.e. "Tue, 15 Nov 1994 12:45:26 GMT" /// - custom: custom string format /// - standard: A generic standard format date i.e. "EEE MMM dd HH:mm:ss Z yyyy" /// - date: Date only format (short = "2/27/17", medium = "Feb 27, 2017", long = "February 27, 2017", full = "Monday, February 27, 2017" /// - time: Time only format (short = "2:22 PM", medium = "2:22:06 PM", long = "2:22:06 PM EST", full = "2:22:06 PM Eastern Standard Time" /// - dateTime: Date/Time format (short = "2/27/17, 2:22 PM", medium = "Feb 27, 2017, 2:22:06 PM", long = "February 27, 2017 at 2:22:06 PM EST", full = "Monday, February 27, 2017 at 2:22:06 PM Eastern Standard Time" public enum DateToStringStyles { case iso(_: ISOFormatter.Options) case extended case rss case altRSS case dotNet case httpHeader case sql case date(_: DateFormatter.Style) case time(_: DateFormatter.Style) case dateTime(_: DateFormatter.Style) case dateTimeMixed(dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style) case custom(_: String) case standard case relative(style: RelativeFormatter.Style?) public func toString(_ date: DateRepresentable) -> String { switch self { case .iso(let opts): return ISOFormatter.format(date, options: opts) case .extended: return date.formatterForRegion(format: DateFormats.extended).string(from: date.date) case .rss: return date.formatterForRegion(format: DateFormats.rss).string(from: date.date) case .altRSS: return date.formatterForRegion(format: DateFormats.altRSS).string(from: date.date) case .sql: return date.formatterForRegion(format: DateFormats.sql).string(from: date.date) case .dotNet: return DOTNETFormatter.format(date, options: nil) case .httpHeader: return date.formatterForRegion(format: DateFormats.httpHeader).string(from: date.date) case .custom(let format): return date.formatterForRegion(format: format).string(from: date.date) case .standard: return date.formatterForRegion(format: DateFormats.standard).string(from: date.date) case .date(let style): return date.formatterForRegion(format: nil, configuration: { $0.dateStyle = style $0.timeStyle = .none }).string(from: date.date) case .time(let style): return date.formatterForRegion(format: nil, configuration: { $0.dateStyle = .none $0.timeStyle = style }).string(from: date.date) case .dateTime(let style): return date.formatterForRegion(format: nil, configuration: { $0.dateStyle = style $0.timeStyle = style }).string(from: date.date) case .dateTimeMixed(let dateStyle, let timeStyle): return date.formatterForRegion(format: nil, configuration: { $0.dateStyle = dateStyle $0.timeStyle = timeStyle }).string(from: date.date) case .relative(let style): return RelativeFormatter.format(date, options: style) } } } // MARK: - Parsers /// String to date transform /// /// - iso: standard automatic iso parser (evaluate the date components automatically) /// - extended: Extended format. "eee dd-MMM-yyyy GG HH:mm:ss.SSS zzz" /// - rss: The RSS formatted date "EEE, d MMM yyyy HH:mm:ss ZZZ" i.e. "Fri, 09 Sep 2011 15:26:08 +0200" /// - altRSS: The Alternative RSS formatted date "d MMM yyyy HH:mm:ss ZZZ" i.e. "09 Sep 2011 15:26:08 +0200" /// - dotNet: The dotNet formatted date "/Date(%d%d)/" i.e. "/Date(1268123281843)/" /// - httpHeader: The http header formatted date "EEE, dd MMM yyyy HH:mm:ss zzz" i.e. "Tue, 15 Nov 1994 12:45:26 GMT" /// - strict: custom string format with lenient options active /// - custom: custom string format /// - standard: A generic standard format date i.e. "EEE MMM dd HH:mm:ss Z yyyy" public enum StringToDateStyles { case iso(_: ISOParser.Options) case extended case rss case altRSS case dotNet case sql case httpHeader case strict(_: String) case custom(_: String) case standard public func toDate(_ string: String, region: Region) -> DateInRegion? { switch self { case .iso(let options): return ISOParser.parse(string, region: region, options: options) case .custom(let format): return DateInRegion(string, format: format, region: region) case .extended: return DateInRegion(string, format: DateFormats.extended, region: region) case .sql: return DateInRegion(string, format: DateFormats.sql, region: region) case .rss: return DateInRegion(string, format: DateFormats.rss, region: Region.ISO)?.convertTo(locale: region.locale) case .altRSS: return DateInRegion(string, format: DateFormats.altRSS, region: Region.ISO)?.convertTo(locale: region.locale) case .dotNet: return DOTNETParser.parse(string, region: region, options: nil) case .httpHeader: return DateInRegion(string, format: DateFormats.httpHeader, region: region) case .standard: return DateInRegion(string, format: DateFormats.standard, region: region) case .strict(let format): let formatter = DateFormatter.sharedFormatter(forRegion: region, format: format) formatter.isLenient = false guard let absDate = formatter.date(from: string) else { return nil } return DateInRegion(absDate, region: region) } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/ISOFormatter.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public class ISOFormatter: DateToStringTrasformable { public struct Options: OptionSet { public let rawValue: Int public init(rawValue: Int) { self.rawValue = rawValue } /// The date representation includes the year. The format for year is inferred based on the other specified options. /// - If withWeekOfYear is specified, YYYY is used. /// - Otherwise, yyyy is used. public static let withYear = ISOFormatter.Options(rawValue: 1 << 0) /// The date representation includes the month. The format for month is MM. public static let withMonth = ISOFormatter.Options(rawValue: 1 << 1) /// The date representation includes the week of the year. /// The format for week of year is ww, including the W prefix. public static let withWeekOfYear = ISOFormatter.Options(rawValue: 1 << 2) /// The date representation includes the day. The format for day is inferred based on provided options: /// - If withMonth is specified, dd is used. /// - If withWeekOfYear is specified, ee is used. /// - Otherwise, DDD is used. public static let withDay = ISOFormatter.Options(rawValue: 1 << 3) /// The date representation includes the time. The format for time is HH:mm:ss. public static let withTime = ISOFormatter.Options(rawValue: 1 << 4) /// The date representation includes the timezone. The format for timezone is ZZZZZ. public static let withTimeZone = ISOFormatter.Options(rawValue: 1 << 5) /// The date representation uses a space ( ) instead of T between the date and time. public static let withSpaceBetweenDateAndTime = ISOFormatter.Options(rawValue: 1 << 6) /// The date representation uses the dash separator (-) in the date. public static let withDashSeparatorInDate = ISOFormatter.Options(rawValue: 1 << 7) /// The date representation uses the colon separator (:) in the time. public static let withFullDate = ISOFormatter.Options(rawValue: 1 << 8) /// The date representation includes the hour, minute, and second. public static let withFullTime = ISOFormatter.Options(rawValue: 1 << 9) /// The format used for internet date times, according to the RFC 3339 standard. /// Equivalent to specifying withFullDate, withFullTime, withDashSeparatorInDate, /// withColonSeparatorInTime, and withColonSeparatorInTimeZone. public static let withInternetDateTime = ISOFormatter.Options(rawValue: 1 << 10) // The format used for internet date times; it's similar to .withInternetDateTime // but include milliseconds ('yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ'). public static let withInternetDateTimeExtended = ISOFormatter.Options(rawValue: 1 << 11) /// Print the timezone in format `ZZZ` instead of `ZZZZZ` /// An example outout maybe be `+0200` instead of `+02:00`. public static let withoutTZSeparators = ISOFormatter.Options(rawValue: 1 << 12) /// Evaluate formatting string public var dateFormat: String { if contains(.withInternetDateTimeExtended) || contains(.withoutTZSeparators) { if contains(.withoutTZSeparators) { return "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ" } return "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" } if contains(.withInternetDateTime) { if contains(.withoutTZSeparators) { return "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ" } return "yyyy-MM-dd'T'HH:mm:ssZZZZZ" } var format: String = "" if contains(.withFullDate) { format += "yyyy-MM-dd" } else { if contains(.withYear) { if contains(.withWeekOfYear) { format += "YYYY" } else if contains(.withMonth) || contains(.withDay) { format += "yyyy" } else { // not valid } } if contains(.withMonth) { if contains(.withYear) || contains(.withDay) || contains(.withWeekOfYear) { format += "MM" } else { // not valid } } if contains(.withWeekOfYear) { if contains(.withDay) { format += "'W'ww" } else { if contains(.withYear) || contains(.withMonth) { if contains(.withDashSeparatorInDate) { format += "-'W'ww" } else { format += "'W'ww" } } else { // not valid } } } if contains(.withDay) { if contains(.withWeekOfYear) { format += "FF" } else if contains(.withMonth) { format += "dd" } else if contains(.withYear) { if contains(.withDashSeparatorInDate) { format += "-DDD" } else { format += "DDD" } } else { // not valid } } } let hasDate = (contains(.withFullDate) || contains(.withMonth) || contains(.withDay) || contains(.withWeekOfYear) || contains(.withYear)) if hasDate && (contains(.withFullTime) || contains(.withTimeZone) || contains(.withTime)) { if contains(.withSpaceBetweenDateAndTime) { format += " " } else { format += "'T'" } } if contains(.withFullTime) { format += "HH:mm:ssZZZZZ" } else { if contains(.withTime) { format += "HH:mm:ss" } if contains(.withTimeZone) { if contains(.withoutTZSeparators) { return "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ" } format += "ZZZZZ" } } return format } } public static func format(_ date: DateRepresentable, options: Any?) -> String { let formatOptions = ((options as? ISOFormatter.Options) ?? ISOFormatter.Options([.withInternetDateTime])) let formatter = date.formatter(format: formatOptions.dateFormat) { $0.locale = Locales.englishUnitedStatesComputer.toLocale() // fix for 12/24h $0.timeZone = date.region.timeZone $0.calendar = Calendars.gregorian.toCalendar() } return formatter.string(from: date.date) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/ISOParser.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // // swiftlint:disable file_length import Foundation /// This defines all possible errors you can encounter parsing ISO8601 string /// /// - eof: end of file /// - notDigit: expected digit, value cannot be parsed as int /// - notDouble: expected double digit, value cannot be parsed as double /// - invalid: invalid state reached. Something in the format is not correct public enum ISO8601ParserError: Error { case eof case notDigit case notDouble case invalid } fileprivate extension Int { /// Return `true` if current year is a leap year, `false` otherwise var isLeapYear: Bool { return ((self % 4) == 0) && (((self % 100) != 0) || ((self % 400) == 0)) } } // MARK: - Internal Extension for UnicodeScalar type internal extension UnicodeScalar { /// return `true` if current character is a digit (arabic), `false` otherwise var isDigit: Bool { return "0"..."9" ~= self } /// return `true` if current character is a space var isSpace: Bool { return CharacterSet.whitespaces.contains(self) } } /// This is the ISO8601 Parser class: it evaluates automatically the format of the ISO8601 date /// and attempt to parse it in a valid `Date` object. /// Resulting date also includes Time Zone settings and a property which allows you to inspect /// single date components. /// /// This work is inspired to the original ISO8601DateFormatter class written in ObjC by /// Peter Hosey (available here https://bitbucket.org/boredzo/iso-8601-parser-unparser). /// I've made a Swift porting and fixed some issues when parsing several ISO8601 date variants. // swiftlint:disable type_body_length public class ISOParser: StringToDateTransformable { /// Internal structure internal enum Weekday: Int { case monday = 0 case tuesday = 1 case wednesday = 2 case thursday = 3 } public struct Options { /// Time separator character. By default is `:`. var time_separator: ISOParser.ISOChar = ":" /// Strict parsing. By default is `false`. var strict: Bool = false public init(strict: Bool = false) { self.strict = strict } } /// Some typealias to make the code cleaner public typealias ISOString = String.UnicodeScalarView public typealias ISOIndex = String.UnicodeScalarView.Index public typealias ISOChar = UnicodeScalar public typealias ISOParsedDate = (date: Date?, timezone: TimeZone?) /// This represent the internal parser status representation public struct ParsedDate { /// Type of date parsed /// /// - monthAndDate: month and date style /// - week: date with week number /// - dateOnly: date only // swiftlint:disable nesting public enum DateStyle { case monthAndDate case week case dateOnly } /// Parsed year value var year: Int = 0 /// Parsed month or week number var month_or_week: Int = 0 /// Parsed day value var day: Int = 0 /// Parsed hour value var hour: Int = 0 /// Parsed minutes value var minute: TimeInterval = 0.0 /// Parsed seconds value var seconds: TimeInterval = 0.0 /// Parsed nanoseconds value var nanoseconds: TimeInterval = 0.0 /// Parsed weekday number (1=monday, 7=sunday) /// If `nil` source string has not specs about weekday. var weekday: Int? /// Timezone parsed hour value var tz_hour: Int = 0 /// Timezone parsed minute value var tz_minute: Int = 0 /// Type of parsed date var type: DateStyle = .monthAndDate /// Parsed timezone object var timezone: TimeZone? } /// Source generation calendar. private var srcCalendar = Calendars.gregorian.toCalendar() /// Source raw parsed values private var date = ParsedDate() /// Source string represented as unicode scalars private var string: ISOString /// Current position of the parser in source string. /// Initially is equal to `string.startIndex` private var cIdx: ISOIndex /// Just a shortcut to the last index in source string private var eIdx: ISOIndex /// Lenght of the string private var length: Int /// Number of hyphens characters found before any value /// Consequential "-" are used to define implicit values in dates. private var hyphens: Int = 0 /// Private date components used for default values private var now_cmps: DateComponents /// Configuration used for parser private var options: ISOParser.Options /// Date components parsed private(set) var date_components: DateComponents? /// Parsed date private(set) var parsedDate: Date? /// Parsed timezone private(set) var parsedTimeZone: TimeZone? /// Date adjusted at parsed timezone private var dateInTimezone: Date? { get { srcCalendar.timeZone = date.timezone ?? TimeZone(identifier: "UTC")! return srcCalendar.date(from: date_components!) } } /// Initialize a new parser with a source ISO8601 string to parse /// Parsing is done during initialization; any exception is reported /// before allocating. /// /// - Parameters: /// - src: source ISO8601 string /// - config: configuration used for parsing /// - Throws: throw an `ISO8601Error` if parsing operation fails public init?(_ src: String, options: ISOParser.Options? = nil) { let src_trimmed = src.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) guard src_trimmed.count > 0 else { return nil } string = src_trimmed.unicodeScalars length = src_trimmed.count cIdx = string.startIndex eIdx = string.endIndex self.options = (options ?? ISOParser.Options()) self.now_cmps = srcCalendar.dateComponents([.year, .month, .day], from: Date()) var idx = cIdx while idx < eIdx { if string[idx] == "-" { hyphens += 1 } else { break } idx = string.index(after: idx) } do { try parse() } catch { return nil } } // MARK: - Internal Parser /// Private parsing function /// /// - Throws: throw an `ISO8601Error` if parsing operation fails @discardableResult private func parse() throws -> ISOParsedDate { // PARSE DATE if current() == "T" { // There is no date here, only a time. // Set the date to now; then we'll parse the time. next() guard current()?.isDigit ?? false else { throw ISO8601ParserError.invalid } date.year = now_cmps.year! date.month_or_week = now_cmps.month! date.day = now_cmps.day! } else { moveUntil(is: "-") let is_time_only = (string.contains("T") == false && string.contains(":") && !string.contains("-")) if is_time_only == false { var (num_digits, segment) = try read_int() switch num_digits { case 0: try parse_digits_0(num_digits, &segment) case 8: try parse_digits_8(num_digits, &segment) case 6: try parse_digits_6(num_digits, &segment) case 4: try parse_digits_4(num_digits, &segment) case 5: try parse_digits_5(num_digits, &segment) case 1: try parse_digits_1(num_digits, &segment) case 2: try parse_digits_2(num_digits, &segment) case 7: try parse_digits_7(num_digits, &segment) //YYYY DDD (ordinal date) case 3: try parse_digits_3(num_digits, &segment) //--DDD (ordinal date, implicit year) default: throw ISO8601ParserError.invalid } } else { date.year = now_cmps.year! date.month_or_week = now_cmps.month! date.day = now_cmps.day! } } var hasTime = false if current()?.isSpace ?? false || current() == "T" { hasTime = true next() } // PARSE TIME if current()?.isDigit ?? false == true { let time_sep = options.time_separator let hasTimeSeparator = string.contains(time_sep) date.hour = try read_int(2).value if hasTimeSeparator == false && hasTime { date.minute = TimeInterval(try read_int(2).value) } else if current() == time_sep { next() if time_sep == "," || time_sep == "." { //We can't do fractional minutes when '.' is the segment separator. //Only allow whole minutes and whole seconds. date.minute = TimeInterval(try read_int(2).value) if current() == time_sep { next() date.seconds = TimeInterval(try read_int(2).value) } } else { //Allow a fractional minute. //If we don't get a fraction, look for a seconds segment. //Otherwise, the fraction of a minute is the seconds. date.minute = try read_double().value if current() != ":" { var int_part: Double = 0.0 var frac_part: Double = 0.0 frac_part = modf(date.minute, &int_part) date.minute = int_part date.seconds = frac_part if date.seconds > Double.ulpOfOne { // Convert fraction (e.g. .5) into seconds (e.g. 30). date.seconds *= 60 } else if current() == time_sep { next() // date.seconds = try read_double().value let value = try modf(read_double().value) date.nanoseconds = TimeInterval(round(value.1 * 1000) * 1_000_000) date.seconds = TimeInterval(value.0) } } else { // fractional minutes next() let value = try modf(read_double().value) date.nanoseconds = TimeInterval(round(value.1 * 1000) * 1_000_000) date.seconds = TimeInterval(value.0) } } } if options.strict == false { if cIdx != eIdx && current()?.isSpace ?? false == true { next() } } if cIdx != eIdx { switch current() { case "Z": date.timezone = TimeZone(abbreviation: "UTC") case "+", "-": let is_negative = current() == "-" next() if current()?.isDigit ?? false == true { //Read hour offset. date.tz_hour = try read_int(2).value if is_negative == true { date.tz_hour = -date.tz_hour } // Optional separator if current() == time_sep { next() } if current()?.isDigit ?? false { // Read minute offset date.tz_minute = try read_int(2).value if is_negative == true { date.tz_minute = -date.tz_minute } } let timezone_offset = (date.tz_hour * 3600) + (date.tz_minute * 60) date.timezone = TimeZone(secondsFromGMT: timezone_offset) } default: break } } } date_components = DateComponents() date_components!.year = date.year date_components!.day = date.day date_components!.hour = date.hour date_components!.minute = Int(date.minute) date_components!.second = Int(date.seconds) date_components!.nanosecond = Int(date.nanoseconds) switch date.type { case .monthAndDate: date_components!.month = date.month_or_week case .week: //Adapted from . //This works by converting the week date into an ordinal date, then letting the next case handle it. let prevYear = date.year - 1 let YY = prevYear % 100 let prevC = prevYear - YY let prevG = YY + YY / 4 let isLeapYear = (((prevC / 100) % 4) * 5) let jan1Weekday = ((isLeapYear + prevG) % 7) var day = ((8 - jan1Weekday) + (7 * (jan1Weekday > Weekday.thursday.rawValue ? 1 : 0))) day += (date.day - 1) + (7 * (date.month_or_week - 2)) if let weekday = date.weekday { //date_components!.weekday = weekday date_components!.day = day + weekday } else { date_components!.day = day } case .dateOnly: //An "ordinal date". break } //cfg.calendar.timeZone = date.timezone ?? TimeZone(identifier: "UTC")! //parsedDate = cfg.calendar.date(from: date_components!) let tz = date.timezone ?? TimeZone(identifier: "UTC")! parsedTimeZone = tz srcCalendar.timeZone = tz parsedDate = srcCalendar.date(from: date_components!) return (parsedDate, parsedTimeZone) } private func parse_digits_3(_ num_digits: Int, _ segment: inout Int) throws { //Technically, the standard only allows one hyphen. But it says that two hyphens is the logical implementation, and one was dropped for brevity. So I have chosen to allow the missing hyphen. if hyphens < 1 || (hyphens > 2 && options.strict == false) { throw ISO8601ParserError.invalid } date.day = segment date.year = now_cmps.year! date.type = .dateOnly if options.strict == true && (date.day > (365 + (date.year.isLeapYear ? 1 : 0))) { throw ISO8601ParserError.invalid } } private func parse_digits_7(_ num_digits: Int, _ segment: inout Int) throws { guard hyphens == 0 else { throw ISO8601ParserError.invalid } date.day = segment % 1000 date.year = segment / 1000 date.type = .dateOnly if options.strict == true && (date.day > (365 + (date.year.isLeapYear ? 1 : 0))) { throw ISO8601ParserError.invalid } } private func parse_digits_2(_ num_digits: Int, _ segment: inout Int) throws { func parse_hyphens_3(_ num_digits: Int, _ segment: inout Int) throws { date.year = now_cmps.year! date.month_or_week = now_cmps.month! date.day = segment } func parse_hyphens_2(_ num_digits: Int, _ segment: inout Int) throws { date.year = now_cmps.year! date.month_or_week = segment if current() == "-" { next() date.day = try read_int(2).value } else { date.day = 1 } } func parse_hyphens_1(_ num_digits: Int, _ segment: inout Int) throws { let current_year = now_cmps.year! let current_century = (current_year % 100) date.year = segment + (current_year - current_century) if num_digits == 1 { // implied decade date.year += current_century - (current_year % 10) } if current() == "-" { next() if current() == "W" { next() date.type = .week } date.month_or_week = try read_int(2).value if current() == "-" { next() if date.type == .week { // weekday number let weekday = try read_int().value if weekday > 7 { throw ISO8601ParserError.invalid } date.weekday = weekday } else { date.day = try read_int().value if date.day == 0 { date.day = 1 } if date.month_or_week == 0 { date.month_or_week = 1 } } } else { date.day = 1 } } else { date.month_or_week = 1 date.day = 1 } } func parse_hyphens_0(_ num_digits: Int, _ segment: inout Int) throws { if current() == "-" { // Implicit century date.year = now_cmps.year! date.year -= (date.year % 100) date.year += segment next() if current() == "W" { try parseWeekAndDay() } else if current()?.isDigit ?? false == false { try centuryOnly(&segment) } else { // Get month and/or date. let (v_count, v_seg) = try read_int() switch v_count { case 4: // YY-MMDD date.day = v_seg % 100 date.month_or_week = v_seg / 100 case 1: // YY-M; YY-M-DD (extension) if options.strict == true { throw ISO8601ParserError.invalid } case 2: // YY-MM; YY-MM-DD date.month_or_week = v_seg if current() == "-" { next() if current()?.isDigit ?? false == true { date.day = try read_int(2).value } else { date.day = 1 } } else { date.day = 1 } case 3: // Ordinal date date.day = v_seg date.type = .dateOnly default: break } } } else if current() == "W" { date.year = now_cmps.year! date.year -= (date.year % 100) date.year += segment try parseWeekAndDay() } else { try centuryOnly(&segment) } } switch hyphens { case 0: try parse_hyphens_0(num_digits, &segment) case 1: try parse_hyphens_1(num_digits, &segment) //-YY; -YY-MM (implicit century) case 2: try parse_hyphens_2(num_digits, &segment) //--MM; --MM-DD case 3: try parse_hyphens_3(num_digits, &segment) //---DD default: throw ISO8601ParserError.invalid } } private func parse_digits_1(_ num_digits: Int, _ segment: inout Int) throws { if options.strict == true { // Two digits only - never just one. guard hyphens == 1 else { throw ISO8601ParserError.invalid } if current() == "-" { next() } next() guard current() == "W" else { throw ISO8601ParserError.invalid } date.year = now_cmps.year! date.year -= (date.year % 10) date.year += segment } else { try parse_digits_2(num_digits, &segment) } } private func parse_digits_5(_ num_digits: Int, _ segment: inout Int) throws { guard hyphens == 0 else { throw ISO8601ParserError.invalid } // YYDDD date.year = now_cmps.year! date.year -= (date.year % 100) date.year += segment / 1000 date.day = segment % 1000 date.type = .dateOnly } private func parse_digits_4(_ num_digits: Int, _ segment: inout Int) throws { func parse_hyphens_0(_ num_digits: Int, _ segment: inout Int) throws { date.year = segment if current() == "-" { next() } if current()?.isDigit ?? false == false { if current() == "W" { try parseWeekAndDay() } else { date.month_or_week = 1 date.day = 1 } } else { let (v_num, v_seg) = try read_int() switch v_num { case 4: // MMDD date.day = v_seg % 100 date.month_or_week = v_seg / 100 case 2: // MM date.month_or_week = v_seg if current() == "-" { next() } if current()?.isDigit ?? false == false { date.day = 1 } else { date.day = try read_int().value } case 3: // DDD date.day = v_seg % 1000 date.type = .dateOnly if options.strict == true && (date.day > 365 + (date.year.isLeapYear ? 1 : 0)) { throw ISO8601ParserError.invalid } default: throw ISO8601ParserError.invalid } } } func parse_hyphens_1(_ num_digits: Int, _ segment: inout Int) throws { date.month_or_week = segment % 100 date.year = segment / 100 if current() == "-" { next() } if current()?.isDigit ?? false == false { date.day = 1 } else { date.day = try read_int().value } } func parse_hyphens_2(_ num_digits: Int, _ segment: inout Int) throws { date.day = segment % 100 date.month_or_week = segment / 100 date.year = now_cmps.year! } switch hyphens { case 0: try parse_hyphens_0(num_digits, &segment) // YYYY case 1: try parse_hyphens_1(num_digits, &segment) // YYMM case 2: try parse_hyphens_2(num_digits, &segment) // MMDD default: throw ISO8601ParserError.invalid } } private func parse_digits_6(_ num_digits: Int, _ segment: inout Int) throws { // YYMMDD (implicit century) guard hyphens == 0 else { throw ISO8601ParserError.invalid } date.day = segment % 100 segment /= 100 date.month_or_week = segment % 100 date.year = now_cmps.year! date.year -= (date.year % 100) date.year += (segment / 100) } private func parse_digits_8(_ num_digits: Int, _ segment: inout Int) throws { // YYYY MM DD guard hyphens == 0 else { throw ISO8601ParserError.invalid } date.day = segment % 100 segment /= 100 date.month_or_week = segment % 100 date.year = segment / 100 } private func parse_digits_0(_ num_digits: Int, _ segment: inout Int) throws { guard current() == "W" else { throw ISO8601ParserError.invalid } if seek(1) == "-" && isDigit(seek(2)) && ((hyphens == 1 || hyphens == 2) && options.strict == false) { date.year = now_cmps.year! date.month_or_week = 1 next(2) try parseDayAfterWeek() } else if hyphens == 1 { date.year = now_cmps.year! if current() == "W" { next() date.month_or_week = try read_int(2).value date.type = .week try parseWeekday() } else { try parseDayAfterWeek() } } else { throw ISO8601ParserError.invalid } } private func parseWeekday() throws { if current() == "-" { next() } let weekday = try read_int().value if weekday > 7 { throw ISO8601ParserError.invalid } date.type = .week date.weekday = weekday } private func parseWeekAndDay() throws { next() if current()?.isDigit ?? false == false { //Not really a week-based date; just a year followed by '-W'. guard options.strict == false else { throw ISO8601ParserError.invalid } date.month_or_week = 1 date.day = 1 } else { date.month_or_week = try read_int(2).value try parseWeekday() } } private func parseDayAfterWeek() throws { date.day = current()?.isDigit ?? false == true ? try read_int(2).value : 1 date.type = .week } private func centuryOnly(_ segment: inout Int) throws { date.year = segment * 100 + now_cmps.year! % 100 date.month_or_week = 1 date.day = 1 } /// Return `true` if given character is a char /// /// - Parameter char: char to evaluate /// - Returns: `true` if char is a digit, `false` otherwise private func isDigit(_ char: UnicodeScalar?) -> Bool { guard let char = char else { return false } return char.isDigit } // MARK: - Scanner internal functions /// Get the value at specified offset from current scanner position without /// moving the current scanner's index. /// /// - Parameter offset: offset to move /// - Returns: char at given position, `nil` if not found @discardableResult public func seek(_ offset: Int = 1) -> ISOChar? { let move_idx = string.index(cIdx, offsetBy: offset) guard move_idx < eIdx else { return nil } return string[move_idx] } /// Return the char at the current position of the scanner /// /// - Parameter next: if `true` return the current char and move to the next position /// - Returns: the char sat the current position of the scanner @discardableResult public func current(_ next: Bool = false) -> ISOChar? { guard cIdx != eIdx else { return nil } let current = string[cIdx] if next == true { cIdx = string.index(after: cIdx) } return current } /// Move by `offset` characters the index of the scanner and return the char at the current /// position. If EOF is reached `nil` is returned. /// /// - Parameter offset: offset value (use negative number to move backwards) /// - Returns: character at the current position. @discardableResult private func next(_ offset: Int = 1) -> ISOChar? { let next = string.index(cIdx, offsetBy: offset) guard next < eIdx else { return nil } cIdx = next return string[cIdx] } /// Read from the current scanner index and parse the value as Int. /// /// - Parameter max_count: number of characters to move. If nil scanners continues until a non /// digit value is encountered. /// - Returns: parsed value /// - Throws: throw an exception if parser fails @discardableResult private func read_int(_ max_count: Int? = nil) throws -> (count: Int, value: Int) { var move_idx = cIdx var count = 0 while move_idx < eIdx { if let max = max_count, count >= max { break } if string[move_idx].isDigit == false { break } count += 1 move_idx = string.index(after: move_idx) } let raw_value = String(string[cIdx.. (count: Int, value: Double) { var move_idx = cIdx var count = 0 var fractional_start = false while move_idx < eIdx { let char = string[move_idx] if char == "." || char == "," { if fractional_start == true { throw ISO8601ParserError.notDouble } else { fractional_start = true } } else { if char.isDigit == false { break } } count += 1 move_idx = string.index(after: move_idx) } let raw_value = String(string[cIdx.. Int { var move_idx = cIdx var count = 0 while move_idx < eIdx { guard string[move_idx] == char else { break } move_idx = string.index(after: move_idx) count += 1 } cIdx = move_idx return count } /// Move the current scanner index to the next position until passed `char` value is /// encountered or `eof` is reached. /// /// - Parameter char: char /// - Returns: the number of characters passed @discardableResult private func moveUntil(isNot char: UnicodeScalar) -> Int { var move_idx = cIdx var count = 0 while move_idx < eIdx { guard string[move_idx] != char else { break } move_idx = string.index(after: move_idx) count += 1 } cIdx = move_idx return count } /// Return a date parsed from a valid ISO8601 string /// /// - Parameter string: source string /// - Returns: a valid `Date` object or `nil` if date cannot be parsed public static func date(from string: String) -> ISOParsedDate? { guard let parser = ISOParser(string) else { return nil } return (parser.parsedDate, parser.parsedTimeZone) } public static func parse(_ string: String, region: Region?, options: Any?) -> DateInRegion? { let formatOptions = options as? ISOParser.Options guard let parser = ISOParser(string, options: formatOptions), let date = parser.parsedDate else { return nil } let parsedRegion = Region(calendar: region?.calendar ?? Region.ISO.calendar, zone: (region?.timeZone ?? parser.parsedTimeZone ?? Region.ISO.timeZone), locale: region?.locale ?? Region.ISO.locale) return DateInRegion(date, region: parsedRegion) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatter+Style.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation #if os(Linux) import Glibc #else import Darwin #endif // MARK: - Style public extension RelativeFormatter { enum PluralForm: String { case zero, one, two, few, many, other } /// Style for formatter struct Style { /// Flavours supported by the style, specified in order. /// The first available flavour for specified locale is used. /// If no flavour is available `.long` is used instead (this flavour /// MUST be part of every lang structure). public var flavours: [Flavour] /// Gradation specify how the unit are evaluated in order to get the /// best one to represent a given amount of time interval. /// By default `convenient()` is used. public var gradation: Gradation = .convenient() /// Allowed time units the style can use. Some styles may not include /// some time units (ie. `.quarter`) because they are not useful for /// a given representation. /// If not specified all the following units are set: /// `.now, .minute, .hour, .day, .week, .month, .year` public var allowedUnits: [Unit]? /// Create a new style. /// /// - Parameters: /// - flavours: flavours of the style. /// - gradation: gradation rules. /// - units: allowed units. public init(flavours: [Flavour], gradation: Gradation, allowedUnits units: [Unit]? = nil) { self.flavours = flavours self.gradation = gradation allowedUnits = (units ?? [.now, .minute, .hour, .day, .week, .month, .year]) } } /// Return the default style for relative formatter. /// /// - Returns: style instance. static func defaultStyle() -> Style { return Style(flavours: [.longConvenient, .long], gradation: .convenient()) } /// Return the time-only style for relative formatter. /// /// - Returns: style instance. static func timeStyle() -> Style { return Style(flavours: [.longTime], gradation: .convenient()) } /// Return the twitter style for relative formatter. /// /// - Returns: style instance. static func twitterStyle() -> Style { return Style(flavours: [.tiny, .shortTime, .narrow, .shortTime], gradation: .twitter()) } } // MARK: - Flavour public extension RelativeFormatter { /// Supported flavours enum Flavour: String { case long = "long" case longTime = "long_time" case longConvenient = "long_convenient" case short = "short" case shortTime = "short_time" case shortConvenient = "short_convenient" case narrow = "narrow" case tiny = "tiny" case quantify = "quantify" } } // MARK: - Gradation public extension RelativeFormatter { /// Gradation is used to define a set of rules used to get the best /// representation of a given elapsed time interval (ie. the best /// representation for 300 seconds is in minutes, 5 minutes specifically). /// Rules are executed in order by the parser and the best one (< elapsed interval) /// is returned to be used by the formatter. struct Gradation { /// A single Gradation rule specification // swiftlint:disable nesting public struct Rule { public enum ThresholdType { case value(_: Double?) case function(_: ((TimeInterval) -> (Double?))) func evaluateForTimeInterval(_ elapsed: TimeInterval) -> Double? { switch self { case .value(let value): return value case .function(let function): return function(elapsed) } } } public enum RoundingStrategy { case regularRound case ceiling case flooring case custom((Double) -> Double) func roundValue(_ value: Double) -> Double { switch self { case .regularRound: return round(value) case .ceiling: return ceil(value) case .flooring: return floor(value) case .custom(let roundingFunction): return roundingFunction(value) } } } /// The time unit to which the rule refers. /// It's used to evaluate the factor. public var unit: Unit /// Threhsold value of the unit. When a difference between two dates /// is less than the threshold the unit before this is the best /// candidate to represent the time interval. public var threshold: ThresholdType? /// Granuality threshold of the unit public var granularity: Double? /// The rounding strategy that should be used prior to generating the relative time public var roundingStrategy: RoundingStrategy /// Relation with a previous threshold public var thresholdPrevious: [Unit: Double]? /// You can specify a custom formatter for a rule which return the /// string representation of a data with your own pattern. // swiftlint:disable nesting public typealias CustomFormatter = ((DateRepresentable) -> (String)) public var customFormatter: CustomFormatter? /// Create a new rule. /// /// - Parameters: /// - unit: target time unit. /// - threshold: threshold value. /// - granularity: granularity value. /// - prev: relation with a previous rule in gradation lsit. /// - formatter: custom formatter. public init(_ unit: Unit, threshold: ThresholdType?, granularity: Double? = nil, roundingStrategy: RoundingStrategy = .regularRound, prev: [Unit: Double]? = nil, formatter: CustomFormatter? = nil ) { self.unit = unit self.threshold = threshold self.granularity = granularity self.roundingStrategy = roundingStrategy self.thresholdPrevious = prev self.customFormatter = formatter } } /// Gradation rules var rules: [Rule] /// Number of gradation rules var count: Int { return rules.count } /// Subscript by unit. /// Return the first rule for given unit. /// /// - Parameter unit: unit to get. public subscript(_ unit: Unit) -> Rule? { return rules.first(where: { $0.unit == unit }) } /// Subscript by index. /// Return the rule at given index, `nil` if index is invalid. /// /// - Parameter index: index public subscript(_ index: Int) -> Rule? { guard index < rules.count, index >= 0 else { return nil } return rules[index] } /// Create a new gradition with a given set of ordered rules. /// /// - Parameter rules: ordered rules. public init(_ rules: [Rule]) { self.rules = rules } /// Create a new gradation by removing the units from receiver which are not part of the given array. /// /// - Parameter units: units to keep. /// - Returns: a new filtered `Gradation` instance. public func filtered(byUnits units: [Unit]) -> Gradation { return Gradation(rules.filter { units.contains($0.unit) }) } /// Canonical gradation rules public static func canonical() -> Gradation { return Gradation([ Rule(.now, threshold: .value(0)), Rule(.second, threshold: .value(0.5)), Rule(.minute, threshold: .value(59.5)), Rule(.hour, threshold: .value(59.5 * 60.0)), Rule(.day, threshold: .value(23.5 * 60 * 60)), Rule(.week, threshold: .value(6.5 * Unit.day.factor)), Rule(.month, threshold: .value(3.5 * 7 * Unit.day.factor)), Rule(.year, threshold: .value(1.5 * Unit.month.factor)) ]) } /// Convenient gradation rules public static func convenient() -> Gradation { let list = Gradation([ Rule(.now, threshold: .value(0)), Rule(.second, threshold: .value(1), prev: [.now: 1]), Rule(.minute, threshold: .value(45)), Rule(.minute, threshold: .value(2.5 * 60), granularity: 5), Rule(.halfHour, threshold: .value(22.5 * 60), granularity: 5), Rule(.hour, threshold: .value(42.5 * 60), prev: [.minute: 52.5 * 60]), Rule(.day, threshold: .value((20.5 / 24) * Unit.day.factor)), Rule(.week, threshold: .value(5.5 * Unit.day.factor)), Rule(.month, threshold: .value(3.5 * 7 * Unit.day.factor)), Rule(.year, threshold: .value(10.5 * Unit.month.factor)) ]) return list } /// Twitter gradation rules public static func twitter() -> Gradation { return Gradation([ Rule(.now, threshold: .value(0)), Rule(.second, threshold: .value(1), prev: [.now: 1]), Rule(.minute, threshold: .value(45)), Rule(.hour, threshold: .value(59.5 * 60.0)), Rule(.hour, threshold: .value((1.days.timeInterval - 0.5 * 1.hours.timeInterval))), Rule(.day, threshold: .value((20.5 / 24) * Unit.day.factor)), Rule(.other, threshold: .function({ now in // Jan 1st of the next year. let nextYear = (Date(timeIntervalSince1970: now) + 1.years).dateAtStartOf(.year) return (nextYear.timeIntervalSince1970 - now) }), formatter: { date in // "Apr 11, 2017" return date.toFormat("MMM dd, yyyy") }) ]) } } } // MARK: - Unit public extension RelativeFormatter { /// Units for relative formatter enum Unit: String { case now = "now" case second = "second" case minute = "minute" case hour = "hour" case halfHour = "half_hour" case day = "day" case week = "week" case month = "month" case year = "year" case quarter = "quarter" case other = "" /// Factor of conversion of the unit to seconds public var factor: Double { switch self { case .now, .second: return 1 case .minute: return 1.minutes.timeInterval case .hour: return 1.hours.timeInterval case .halfHour: return (1.hours.timeInterval * 0.5) case .day: return 1.days.timeInterval case .week: return 1.weeks.timeInterval case .month: return 1.months.timeInterval case .year: return 1.years.timeInterval case .quarter: return (91.days.timeInterval + 6.hours.timeInterval) case .other: return 0 } } } } internal extension Double { /// Return -1 if number is negative, 1 if positive var sign: Int { return (self < 0 ? -1 : 1) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatter.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public class RelativeFormatter: DateToStringTrasformable { /// Private singleton for relative formatter private static let shared = RelativeFormatter() /// Return all languages supported by the library for relative date formatting public static var allLanguages: [RelativeFormatterLanguage] { return RelativeFormatterLanguage.allCases } private init() {} /// Return the language table for a specified locale. /// If not loaded yet a new instance of the table is loaded and cached. /// /// - Parameter locale: locale to load /// - Returns: language table private func tableForLocale(_ locale: Locale) -> RelativeFormatterLanguage { let localeId = (locale.collatorIdentifier ?? Locales.english.toLocale().collatorIdentifier!) if let lang = RelativeFormatterLanguage(rawValue: localeId) { return lang } guard let fallbackFlavours = RelativeFormatterLanguage(rawValue: localeId.components(separatedBy: "_").first!) ?? RelativeFormatterLanguage(rawValue: localeId.components(separatedBy: "-").first!) else { return tableForLocale(Locales.english.toLocale()) // fallback not found, return english } return fallbackFlavours // return fallback } /// Implementation of the protocol for DateToStringTransformable. public static func format(_ date: DateRepresentable, options: Any?) -> String { let dateToFormat = (date as? DateInRegion ?? DateInRegion(date.date, region: SwiftDate.defaultRegion)) return RelativeFormatter.format(date: dateToFormat, style: (options as? Style), locale: date.region.locale) } /// Return relative formatted string result of comparison of two passed dates. /// /// - Parameters: /// - date: date to compare /// - toDate: date to compare against for (if `nil` current date in the same region of `date` is used) /// - style: style of the relative formatter. /// - locale: locale to use; if not passed the `date`'s region locale is used. /// - Returns: formatted string, empty string if formatting fails public static func format(date: DateRepresentable, to toDate: DateRepresentable? = nil, style: Style?, locale fixedLocale: Locale? = nil) -> String { let refDate = (toDate ?? date.region.nowInThisRegion()) // a now() date is created if no reference is passed let options = (style ?? RelativeFormatter.defaultStyle()) // default style if not used let locale = (fixedLocale ?? date.region.locale) // date's locale is used if no value is forced // how much time elapsed (in seconds) let elapsed = (refDate.date.timeIntervalSince1970 - date.date.timeIntervalSince1970) // get first suitable flavour for a given locale let (flavour, localeData) = suitableFlavour(inList: options.flavours, forLocale: locale) // get all units which can be represented by the locale data for required style let allUnits = suitableUnits(inLocaleData: localeData, requiredUnits: options.allowedUnits) guard allUnits.count > 0 else { debugPrint("Required units in style were not found in locale spec. Returning empty string") return "" } guard let suitableRule = ruleToRepresent(timeInterval: abs(elapsed), referenceInterval: refDate.date.timeIntervalSince1970, units: allUnits, gradation: options.gradation) else { // If no time unit is suitable, just output an empty string. // E.g. when "now" unit is not available // and "second" has a threshold of `0.5` // (e.g. the "canonical" grading scale). return "" } if let customFormat = suitableRule.customFormatter { return customFormat(date) } var amount = (abs(elapsed) / suitableRule.unit.factor) // Apply granularity to the time amount // (and fallback to the previous step // if the first level of granularity // isn't met by this amount) if let granularity = suitableRule.granularity { // Recalculate the elapsed time amount based on granularity amount = round(amount / granularity) * granularity } let value: Double = -1.0 * Double(elapsed.sign) * suitableRule.roundingStrategy.roundValue(amount) let formatString = relativeFormat(locale: locale, flavour: flavour, value: value, unit: suitableRule.unit) return formatString.replacingOccurrences(of: "{0}", with: String(Int(abs(value)))) } private static func relativeFormat(locale: Locale, flavour: Flavour, value: Double, unit: Unit) -> String { let table = RelativeFormatter.shared.tableForLocale(locale) guard let styleTable = table.flavours[flavour.rawValue] as? [String: Any] else { return "" } if let fixedValue = styleTable[unit.rawValue] as? String { return fixedValue } guard let unitRules = styleTable[unit.rawValue] as? [String: Any] else { return "" } // Choose either "previous", "past", "current", "next" or "future" based on time `value` sign. // If "next" is not present, we fallback on "future" // If "previous" is not present, we fallback on "past" // If "current" is not present, we fallback on "past" // If "past" is same as "future" then they're stored as "other". // If there's only "other" then it's being collapsed. let quantifierKey: String switch value { case -1 where unitRules["previous"] != nil: // If it is previous value -1, and previous unitRule exist quantifierKey = "previous" case 0 where unitRules["current"] != nil: // If it is current value 0, and current unitRule exist quantifierKey = "current" case ...0: // If value is up to 0 included, also fallback when current or previous isn't found quantifierKey = "past" case 1 where unitRules["next"] != nil: // If it is next value 1, and next unitRule exist quantifierKey = "next" case 1...: // If it is future value >0, and fallback if next isn't found quantifierKey = "future" default: // Should never happen fatalError() } if let fixedValue = unitRules[quantifierKey] as? String { return fixedValue } else if let quantifierRules = unitRules[quantifierKey] as? [String: Any] { // plurar/translations forms // "other" rule is supposed to always be present. // If only "other" rule is present then "rules" is not an object and is a string. let quantifier = (table.quantifyKey(forValue: abs(value)) ?? .other).rawValue if let relativeFormat = quantifierRules[quantifier] as? String { return relativeFormat } else { return quantifierRules[RelativeFormatter.PluralForm.other.rawValue] as? String ?? "" } } else { return "" } } /// Return the first suitable flavour into the list which is available for a given locale. /// /// - Parameters: /// - flavours: ordered flavours. /// - locale: locale to use. /// - Returns: a pair of found flavor and locale table private static func suitableFlavour(inList flavours: [Flavour], forLocale locale: Locale) -> (flavour: Flavour, locale: [String: Any]) { let localeData = RelativeFormatter.shared.tableForLocale(locale) // get the locale table for flavour in flavours { if let flavourData = localeData.flavours[flavour.rawValue] as? [String: Any] { return (flavour, flavourData) // found our required flavor in passed locale } } // long must be always present // swiftlint:disable force_cast return (.long, localeData.flavours[Flavour.long.rawValue] as! [String: Any]) } /// Return a list of available time units in locale filtered by required units of style. /// If resulting array if empty there is not any time unit which can be rapresented with given locale /// so formatting fails. /// /// - Parameters: /// - localeData: local table. /// - styleUnits: required time units. /// - Returns: available units. private static func suitableUnits(inLocaleData localeData: [String: Any], requiredUnits styleUnits: [Unit]?) -> [Unit] { let localeUnits: [Unit] = localeData.keys.compactMap { Unit(rawValue: $0) } guard let restrictedStyleUnits = styleUnits else { return localeUnits } // no restrictions return localeUnits.filter({ restrictedStyleUnits.contains($0) }) } /// Return the best rule in gradation to represent given time interval. /// /// - Parameters: /// - elapsed: elapsed interval to represent /// - referenceInterval: reference interval /// - units: units /// - gradation: gradation /// - Returns: best rule to represent private static func ruleToRepresent(timeInterval elapsed: TimeInterval, referenceInterval: TimeInterval, units: [Unit], gradation: Gradation) -> Gradation.Rule? { // Leave only allowed time measurement units. // E.g. omit "quarter" unit. let filteredGradation = gradation.filtered(byUnits: units) // If no steps of gradation fit the conditions // then return nothing. guard gradation.count > 0 else { return nil } // Find the most appropriate gradation step let i = findGradationStep(elapsed: elapsed, now: referenceInterval, gradation: filteredGradation) guard i >= 0 else { return nil } let step = filteredGradation[i]! // Apply granularity to the time amount // (and fall back to the previous step // if the first level of granularity // isn't met by this amount) if let granurality = step.granularity { // Recalculate the elapsed time amount based on granularity let amount = round( (elapsed / step.unit.factor) / granurality) * granurality // If the granularity for this step // is too high, then fallback // to the previous step of gradation. // (if there is any previous step of gradation) if amount == 0 && i > 0 { return filteredGradation[i - 1] } } return step } private static func findGradationStep(elapsed: TimeInterval, now: TimeInterval, gradation: Gradation, step: Int = 0) -> Int { // If the threshold for moving from previous step // to this step is too high then return the previous step. let fromGradation = gradation[step - 1] let currentGradation = gradation[step]! let thresholdValue = threshold(from: fromGradation, to: currentGradation, now: now) if let t = thresholdValue, elapsed < t { return step - 1 } // If it's the last step of gradation then return it. if step == (gradation.count - 1) { return step } // Move to the next step. return findGradationStep(elapsed: elapsed, now: now, gradation: gradation, step: step + 1) } /// Evaluate threshold. private static func threshold(from fromRule: Gradation.Rule?, to toRule: Gradation.Rule, now: TimeInterval) -> Double? { var threshold: Double? // Allows custom thresholds when moving // from a specific step to a specific step. if let fromStepUnit = fromRule?.unit { threshold = toRule.thresholdPrevious?[fromStepUnit] } // If no custom threshold is set for this transition // then use the usual threshold for the next step. if threshold == nil { threshold = toRule.threshold?.evaluateForTimeInterval(now) } return threshold } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatterLanguage.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation internal class RelativeFormatterLanguagesCache { static let shared = RelativeFormatterLanguagesCache() @Atomic private(set) var cachedValues = [String: [String: Any]]() func flavoursForLocaleID(_ langID: String) -> [String: Any]? { do { guard let cachedValue = cachedValues[langID] else { var fileURL = Bundle.appModule?.url(forResource: langID, withExtension: "json", subdirectory: "langs") if fileURL == nil { fileURL = Bundle(for: RelativeFormatter.self).resourceURL?.appendingPathComponent("langs/\(langID).json") } guard let fullURL = fileURL else { return nil } let data = try Data(contentsOf: fullURL) let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) if let value = json as? [String: Any] { cachedValues[langID] = value return value } return nil } return cachedValue } catch { debugPrint("Failed to read data for language id: \(langID)") return nil } } } public enum RelativeFormatterLanguage: String, CaseIterable { case af = "af" // Locales.afrikaans case am = "am" // Locales.amharic case ar_AE = "ar_AE" // Locales.arabicUnitedArabEmirates case ar = "ar" // Locales.arabic case `as` = "as" // Locales.assamese case az = "az" // Locales.assamese case be = "be" // Locales.belarusian case bg = "bg" // Locales.bulgarian case bn = "bn" // Locales.bengali case br = "br" // Locales.breton case bs = "bs" // Locales.bosnian case bs_Cyrl = "bs-Cyrl" // Locales.belarusian case ca = "ca" // Locales.catalan case cz = "cz" // Locales.czech case cy = "cy" // Locales.welsh case cs = "cs" // Locales.czech case da = "da" // Locales.danish case de = "de" // Locales.dutch case dsb = "dsb" // Locales.lowerSorbian case dz = "dz" // Locales.dzongkha case ee = "ee" // Locales.ewe case el = "el" // Locales.greek case en = "en" // Locales.english case es_AR = "es_AR" // Locales.spanishArgentina case es_PY = "es_PY" // Locales.spanishParaguay case es_MX = "es_MX" // Locales.spanishMexico case es_US = "es_US" // Locales.spanishUnitedStates case es = "es" // Locales.spanish case et = "et" // Locales.estonian case eu = "eu" // Locales.basque case fa = "fa" // Locales.persian case fi = "fi" // Locales.finnish case fil = "fil" // Locales.filipino case fo = "fo" // Locales.faroese case fr_CA = "fr_CA" // French (Canada) case fr = "fr" // French case fur = "fur" // Friulian case fy = "fy" // Western Frisian case ga = "ga" // Irish case gd = "gd" // Scottish Gaelic case gl = "gl" // Galician case gu = "gu" // Gujarati case he = "he" // Hebrew case hi = "hi" // Hindi case hr = "hr" // Croatian case hsb = "hsb" // Upper Sorbian case hu = "hu" // Hungarian case hy = "hy" // Armenian case id = "id" // Indonesian case `is` = "is" // Icelandic case it = "it" // Locales.italian case ja = "ja" // Japanese case jgo = "jgo" // Ngomba case ka = "ka" // Georgian case kea = "kea" // Kabuverdianu case kk = "kk" // Kazakh case kl = "kl" // Kalaallisut case km = "km" // Khmer case kn = "kn" // Kannada case ko = "ko" // Korean case kok = "kok" // Konkani case ksh = "ksh" // Colognian case ky = "ky" // Kyrgyz case lb = "lb" // Luxembourgish case lkt = "lkt" // Lakota case lo = "lo" // Lao case lt = "lt" // Lithuanian case lv = "lv" // Latvian case mk = "mk" // Macedonian case ml = "ml" // Malayalam case mn = "mn" // Mongolian case mr = "mr" // Marathi case ms = "ms" // Malay case mt = "mt" // Maltese case my = "my" // Burmese case mzn = "mzn" // Mazanderani case nb = "nb" // Norwegian Bokmål case ne = "ne" // Nepali case nl = "nl" // Netherland case nn = "nn" // Norwegian Nynorsk case or = "or" // Odia case pa = "pa" // Punjabi case pl = "pl" // Polish case ps = "ps" // Pashto case pt = "pt" // Portuguese case ro = "ro" // Romanian case ru = "ru" // Russian case sah = "sah" // Sakha case sd = "sd" // Sindhi case se_FI = "se_FI" // Northern Sami (Finland) case se = "se" // Northern Sami case si = "si" // Sinhala case sk = "sk" // Slovak case sl = "sl" // Slovenian case sq = "sq" // Albanian case sr_Latn = "sr_Latn" // Serbian (Latin) case sr = "sr" // Serbian case sv = "sv" // Swedish case sw = "sw" // Swedish case ta = "ta" // Tamil case te = "te" // Telugu case th = "th" // Thai case ti = "ti" // Tigrinya case tk = "tk" // Turkmen case to = "to" // Tongan case tr = "tr" // Turkish case ug = "ug" // Uyghur case uk = "uk" // Ukrainian case ur_IN = "ur_IN" // Urdu (India) case ur = "ur" // Urdu case uz_Cyrl = "uz_Cyrl" // Uzbek (Cyrillic) case uz = "uz" // Uzbek (Cyrillic) case vi = "vi" // Vietnamese case wae = "wae" // Walser case yue_Hans = "yue_Hans" // Cantonese (Simplified) case yue_Hant = "yue_Hant" // Cantonese (Traditional) case zh_Hans_HK = "zh_Hans_HK" // Chinese (Simplified, Hong Kong [China]) case zh_Hans_MO = "zh_Hans_MO" // Chinese (Simplified, Macau [China]) case zh_Hans_SG = "zh_Hans_SG" // Chinese (Simplified, Singapore) case zh_Hant_HK = "zh_Hant_HK" // Chinese (Traditional, Hong Kong [China]) case zh_Hant_MO = "zh_Hant_MO" // Chinese (Traditional, Macau [China]) case zh_Hans = "zh_Hans" // Chinese (Simplified) case zh_Hant = "zh_Hant" // Chinese (Traditional) case zh = "zh" // Chinese case zu = "zu" // Zulu /// Table with the data of the language. /// Data is structured in: /// { flavour: { unit : { data } } } public var flavours: [String: Any] { return RelativeFormatterLanguagesCache.shared.flavoursForLocaleID(self.rawValue) ?? [:] } public var identifier: String { return self.rawValue } public func quantifyKey(forValue value: Double) -> RelativeFormatter.PluralForm? { switch self { case .sr_Latn, .sr, .uk: let mod10 = Int(value) % 10 let mod100 = Int(value) % 100 switch mod10 { case 1: switch mod100 { case 11: break default: return .one } case 2, 3, 4: switch mod100 { case 12, 13, 14: break default: return .few } default: break } return .many case .ru, .sk, .sl: let mod10 = Int(value) % 10 let mod100 = Int(value) % 100 switch mod100 { case 11...14: break default: switch mod10 { case 1: return .one case 2...4: return .few default: break } } return .many case .ro: let mod100 = Int(value) % 100 switch value { case 0: return .few case 1: return .one default: if mod100 > 1 && mod100 <= 19 { return .few } } return .other case .pa: switch value { case 0, 1: return .one default: return .other } case .mt: switch value { case 1: return .one case 0: return .few case 2...10: return .few case 11...19: return .many default: return .other } case .lt, .lv: let mod10 = Int(value) % 10 let mod100 = Int(value) % 100 if value == 0 { return .zero } if value == 1 { return .one } switch mod10 { case 1: if mod100 != 11 { return .one } return .many default: return .many } case .ksh, .se: switch value { case 0: return .zero case 1: return .one default: return .other } case .`is`: let mod10 = Int(value) % 10 let mod100 = Int(value) % 100 if value == 0 { return .zero } if value == 1 { return .one } switch mod10 { case 1: if mod100 != 11 { return .one } default: break } return .many case .id, .ja, .ms, .my, .mzn, .sah, .se_FI, .si, .th, .yue_Hans, .yue_Hant, .zh_Hans_HK, .zh_Hans_MO, .zh_Hans_SG, .zh_Hant_HK, .zh_Hant_MO, .zh: return .other case .hy: return (value >= 0 && value < 2 ? .one : .other) case .ga, .gd: switch Int(value) { case 1: return .one case 2: return .two case 3...6: return .few case 7...10: return .many default: return .other } case .fr_CA, .fr: return (value >= 0 && value < 2 ? .one : .other) case .dz, .kea, .ko, .kok, .lkt, .lo: return nil case .cs: // Locales.czech switch value { case 1: return .one case 2, 3, 4: return .few default: return .other } case .cy: switch value { case 0: return .zero case 1: return .one case 2: return .two case 3: return .few case 6: return .many default: return .other } case .cz, .dsb: switch value { case 1: return .one case 2, 3, 4: return .few default: return .other } case .br: let n = Int(value) return n % 10 == 1 && n % 100 != 11 && n % 100 != 71 && n % 100 != 91 ? .zero : n % 10 == 2 && n % 100 != 12 && n % 100 != 72 && n % 100 != 92 ? .one : (n % 10 == 3 || n % 10 == 4 || n % 10 == 9) && n % 100 != 13 && n % 100 != 14 && n % 100 != 19 && n % 100 != 73 && n % 100 != 74 && n % 100 != 79 && n % 100 != 93 && n % 100 != 94 && n % 100 != 99 ? .two : n % 1_000_000 == 0 && n != 0 ? .many : .other case .be, .bs, .bs_Cyrl, .hr, .hsb, .pl: let mod10 = Int(value) % 10 let mod100 = Int(value) % 100 switch mod10 { case 1: switch mod100 { case 11: break default: return .one } case 2, 3, 4: switch mod100 { case 12, 13, 14: break default: return .few } default: break } return .many case .ar, .ar_AE, .he: switch value { case 0: return .zero case 1: return .one case 2: return .two default: let mod100 = Int(value) % 100 if mod100 >= 3 && mod100 <= 10 { return .few } else if mod100 >= 11 { return .many } else { return .other } } case .am, .bn, .fa, .gu, .kn, .mr, .zu: return (value >= 0 && value <= 1 ? .one : .other) default: return (value == 1 ? .one : .other) } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/af.json ================================================ { "narrow" : { "quarter" : { "previous" : "vorige kwartaal", "current" : "hierdie kwartaal", "next" : "volgende kwartaal", "past" : "{0} kwartale gelede", "future" : "oor {0} kwartale" }, "month" : { "past" : "{0} md. gelede", "next" : "volgende maand", "future" : "oor {0} md.", "previous" : "verlede maand", "current" : "vandeesmaand" }, "now" : "nou", "hour" : { "future" : "oor {0} uur", "past" : "{0} uur gelede", "current" : "hierdie uur" }, "minute" : { "past" : "{0} min. gelede", "current" : "hierdie minuut", "future" : "oor {0} min." }, "day" : { "previous" : "gister", "future" : { "one" : "oor {0} dag", "other" : "oor {0} dae" }, "past" : { "one" : "{0} dag gelede", "other" : "{0} dae gelede" }, "current" : "vandag", "next" : "môre" }, "year" : { "next" : "volgende jaar", "past" : "{0} jaar gelede", "future" : "oor {0} jaar", "previous" : "verlede jaar", "current" : "hierdie jaar" }, "week" : { "next" : "volgende week", "previous" : "verlede week", "past" : "{0} w. gelede", "future" : "oor {0} w.", "current" : "vandeesweek" }, "second" : { "future" : "oor {0} sek.", "current" : "nou", "past" : "{0} sek. gelede" } }, "short" : { "minute" : { "future" : "oor {0} min.", "current" : "hierdie minuut", "past" : "{0} min. gelede" }, "month" : { "current" : "vandeesmaand", "past" : "{0} md. gelede", "future" : "oor {0} md.", "next" : "volgende maand", "previous" : "verlede maand" }, "week" : { "current" : "vandeesweek", "past" : "{0} w. gelede", "future" : "oor {0} w.", "next" : "volgende week", "previous" : "verlede week" }, "hour" : { "future" : "oor {0} uur", "current" : "hierdie uur", "past" : "{0} uur gelede" }, "day" : { "next" : "môre", "current" : "vandag", "previous" : "gister", "past" : { "one" : "{0} dag gelede", "other" : "{0} dae gelede" }, "future" : { "one" : "oor {0} dag", "other" : "oor {0} dae" } }, "second" : { "future" : "oor {0} sek.", "current" : "nou", "past" : "{0} sek. gelede" }, "now" : "nou", "year" : { "current" : "hierdie jaar", "future" : "oor {0} jaar", "past" : "{0} jaar gelede", "next" : "volgende jaar", "previous" : "verlede jaar" }, "quarter" : { "current" : "hierdie kwartaal", "future" : { "one" : "oor {0} kwartaal", "other" : "oor {0} kwartale" }, "previous" : "vorige kwartaal", "next" : "volgende kwartaal", "past" : { "other" : "{0} kwartale gelede", "one" : "{0} kwartaal gelede" } } }, "long" : { "day" : { "previous" : "gister", "past" : { "one" : "{0} dag gelede", "other" : "{0} dae gelede" }, "future" : { "other" : "oor {0} dae", "one" : "oor {0} dag" }, "next" : "môre", "current" : "vandag" }, "hour" : { "current" : "hierdie uur", "future" : "oor {0} uur", "past" : "{0} uur gelede" }, "month" : { "next" : "volgende maand", "current" : "vandeesmaand", "past" : { "one" : "{0} maand gelede", "other" : "{0} maande gelede" }, "previous" : "verlede maand", "future" : "oor {0} minuut" }, "quarter" : { "next" : "volgende kwartaal", "past" : { "one" : "{0} kwartaal gelede", "other" : "{0} kwartale gelede" }, "previous" : "vorige kwartaal", "current" : "hierdie kwartaal", "future" : { "one" : "oor {0} kwartaal", "other" : "oor {0} kwartale" } }, "year" : { "previous" : "verlede jaar", "current" : "hierdie jaar", "next" : "volgende jaar", "past" : "{0} jaar gelede", "future" : "oor {0} jaar" }, "minute" : { "past" : { "other" : "{0} minute gelede", "one" : "{0} minuut gelede" }, "future" : "oor {0} minuut", "current" : "hierdie minuut" }, "second" : { "current" : "nou", "future" : { "one" : "oor {0} sekonde", "other" : "oor {0} sekondes" }, "past" : { "other" : "{0} sekondes gelede", "one" : "{0} sekonde gelede" } }, "week" : { "future" : { "other" : "oor {0} weke", "one" : "oor {0} week" }, "previous" : "verlede week", "current" : "vandeesweek", "next" : "volgende week", "past" : { "one" : "{0} week gelede", "other" : "{0} weke gelede" } }, "now" : "nou" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/am.json ================================================ { "short" : { "month" : { "previous" : "ያለፈው ወር", "current" : "በዚህ ወር", "next" : "የሚቀጥለው ወር", "past" : "ከ{0} ወራት በፊት", "future" : "በ{0} ወራት ውስጥ" }, "now" : "አሁን", "day" : { "next" : "ነገ", "current" : "ዛሬ", "previous" : "ትላንትና", "past" : { "other" : "ከ{0} ቀኖች በፊት", "one" : "ከ {0} ቀን በፊት" }, "future" : { "other" : "በ{0} ቀኖች ውስጥ", "one" : "በ{0} ቀን ውስጥ" } }, "year" : { "current" : "በዚህ ዓመት", "future" : "በ{0} ዓመታት ውስጥ", "previous" : "ያለፈው ዓመት", "next" : "የሚቀጥለው ዓመት", "past" : "ከ{0} ዓመታት በፊት" }, "hour" : { "past" : { "one" : "ከ{0} ሰዓት በፊት", "other" : "ከ{0} ሰዓቶች በፊት" }, "future" : { "one" : "በ{0} ሰዓት ውስጥ", "other" : "በ{0} ሰዓቶች ውስጥ" }, "current" : "ይህ ሰዓት" }, "minute" : { "current" : "ይህ ደቂቃ", "future" : { "other" : "በ{0} ደቂቃዎች ውስጥ", "one" : "በ{0} ደቂቃ ውስጥ" }, "past" : { "other" : "ከ{0} ደቂቃዎች በፊት", "one" : "ከ{0} ደቂቃ በፊት" } }, "second" : { "current" : "አሁን", "future" : { "other" : "በ{0} ሰከንዶች ውስጥ", "one" : "በ{0} ሰከንድ ውስጥ" }, "past" : { "one" : "ከ{0} ሰከንድ በፊት", "other" : "ከ{0} ሰከንዶች በፊት" } }, "quarter" : { "future" : "+{0} ሩብ", "previous" : "የመጨረሻው ሩብ", "next" : "የሚቀጥለው ሩብ", "past" : "{0} ሩብ በፊት", "current" : "ይህ ሩብ" }, "week" : { "next" : "የሚቀጥለው ሳምንት", "past" : "ከ{0} ሳምንታት በፊት", "current" : "በዚህ ሣምንት", "previous" : "ባለፈው ሳምንት", "future" : "በ{0} ሳምንታት ውስጥ" } }, "narrow" : { "now" : "አሁን", "year" : { "current" : "በዚህ ዓመት", "next" : "የሚቀጥለው ዓመት", "past" : "ከ{0} ዓመታት በፊት", "future" : "በ{0} ዓመታት ውስጥ", "previous" : "ያለፈው ዓመት" }, "day" : { "past" : { "other" : "ከ{0} ቀኖች በፊት", "one" : "ከ {0} ቀን በፊት" }, "future" : { "one" : "በ{0} ቀን ውስጥ", "other" : "በ{0} ቀኖች ውስጥ" }, "next" : "ነገ", "current" : "ዛሬ", "previous" : "ትላንትና" }, "month" : { "current" : "በዚህ ወር", "future" : "በ{0} ወራት ውስጥ", "next" : "የሚቀጥለው ወር", "past" : "ከ{0} ወራት በፊት", "previous" : "ያለፈው ወር" }, "second" : { "future" : { "one" : "በ{0} ሰከንድ ውስጥ", "other" : "በ{0} ሰከንዶች ውስጥ" }, "current" : "አሁን", "past" : { "one" : "ከ{0} ሰከንድ በፊት", "other" : "ከ{0} ሰከንዶች በፊት" } }, "minute" : { "future" : { "one" : "በ{0} ደቂቃ ውስጥ", "other" : "በ{0} ደቂቃዎች ውስጥ" }, "current" : "ይህ ደቂቃ", "past" : { "one" : "ከ{0} ደቂቃ በፊት", "other" : "ከ{0} ደቂቃዎች በፊት" } }, "week" : { "future" : "በ{0} ሳምንታት ውስጥ", "next" : "የሚቀጥለው ሳምንት", "previous" : "ባለፈው ሳምንት", "past" : "ከ{0} ሳምንታት በፊት", "current" : "በዚህ ሣምንት" }, "quarter" : { "future" : "+{0} ሩብ", "current" : "ይህ ሩብ", "past" : "{0} ሩብ በፊት", "previous" : "የመጨረሻው ሩብ", "next" : "የሚቀጥለው ሩብ" }, "hour" : { "current" : "ይህ ሰዓት", "past" : { "one" : "ከ{0} ሰዓት በፊት", "other" : "ከ{0} ሰዓቶች በፊት" }, "future" : { "other" : "በ{0} ሰዓቶች ውስጥ", "one" : "በ{0} ሰዓት ውስጥ" } } }, "long" : { "now" : "አሁን", "minute" : { "current" : "ይህ ደቂቃ", "future" : { "one" : "በ{0} ደቂቃ ውስጥ", "other" : "በ{0} ደቂቃዎች ውስጥ" }, "past" : { "one" : "ከ{0} ደቂቃ በፊት", "other" : "ከ{0} ደቂቃዎች በፊት" } }, "year" : { "past" : { "other" : "ከ{0} ዓመታት በፊት", "one" : "ከ{0} ዓመት በፊት" }, "previous" : "ያለፈው ዓመት", "current" : "በዚህ ዓመት", "next" : "የሚቀጥለው ዓመት", "future" : "በ{0} ዓመታት ውስጥ" }, "hour" : { "current" : "ይህ ሰዓት", "future" : { "one" : "በ{0} ሰዓት ውስጥ", "other" : "በ{0} ሰዓቶች ውስጥ" }, "past" : { "one" : "ከ{0} ሰዓት በፊት", "other" : "ከ{0} ሰዓቶች በፊት" } }, "second" : { "current" : "አሁን", "past" : { "one" : "ከ{0} ሰከንድ በፊት", "other" : "ከ{0} ሰከንዶች በፊት" }, "future" : { "other" : "በ{0} ሰከንዶች ውስጥ", "one" : "በ{0} ሰከንድ ውስጥ" } }, "day" : { "previous" : "ትናንት", "current" : "ዛሬ", "future" : { "other" : "በ{0} ቀናት ውስጥ", "one" : "በ{0} ቀን ውስጥ" }, "past" : { "one" : "ከ{0} ቀን በፊት", "other" : "ከ{0} ቀናት በፊት" }, "next" : "ነገ" }, "month" : { "previous" : "ያለፈው ወር", "current" : "በዚህ ወር", "next" : "የሚቀጥለው ወር", "future" : { "one" : "በ{0} ወር ውስጥ", "other" : "በ{0} ወራት ውስጥ" }, "past" : { "other" : "ከ{0} ወራት በፊት", "one" : "ከ{0} ወር በፊት" } }, "quarter" : { "next" : "የሚቀጥለው ሩብ", "current" : "ይህ ሩብ", "past" : "{0} ሩብ በፊት", "future" : "+{0} ሩብ", "previous" : "የመጨረሻው ሩብ" }, "week" : { "future" : { "one" : "በ{0} ሳምንት ውስጥ", "other" : "በ{0} ሳምንታት ውስጥ" }, "current" : "በዚህ ሳምንት", "next" : "የሚቀጥለው ሳምንት", "previous" : "ያለፈው ሳምንት", "past" : { "other" : "ከ{0} ሳምንታት በፊት", "one" : "ከ{0} ሳምንት በፊት" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ar.json ================================================ { "narrow" : { "second" : { "past" : { "other" : "قبل {0} ثانية", "one" : "قبل ثانية واحدة", "two" : "قبل ثانيتين", "few" : "قبل {0} ثوانٍ" }, "current" : "الآن", "future" : { "two" : "خلال ثانيتين", "one" : "خلال ثانية واحدة", "other" : "خلال {0} ثانية", "few" : "خلال {0} ثوانٍ" } }, "week" : { "past" : { "one" : "قبل أسبوع واحد", "few" : "قبل {0} أسابيع", "two" : "قبل أسبوعين", "many" : "قبل {0} أسبوعًا", "other" : "قبل {0} أسبوع" }, "previous" : "الأسبوع الماضي", "future" : { "one" : "خلال أسبوع واحد", "many" : "خلال {0} أسبوعًا", "other" : "خلال {0} أسبوع", "two" : "خلال أسبوعين", "few" : "خلال {0} أسابيع" }, "current" : "هذا الأسبوع", "next" : "الأسبوع القادم" }, "day" : { "current" : "اليوم", "next" : "غدًا", "past" : { "one" : "قبل يوم واحد", "many" : "قبل {0} يومًا", "other" : "قبل {0} يوم", "few" : "قبل {0} أيام", "two" : "قبل يومين" }, "previous" : "أمس", "future" : { "two" : "خلال يومين", "few" : "خلال {0} أيام", "other" : "خلال {0} يوم", "one" : "خلال يوم واحد", "many" : "خلال {0} يومًا" } }, "hour" : { "past" : { "two" : "قبل ساعتين", "other" : "قبل {0} ساعة", "few" : "قبل {0} ساعات", "one" : "قبل ساعة واحدة" }, "current" : "الساعة الحالية", "future" : { "few" : "خلال {0} ساعات", "other" : "خلال {0} ساعة", "one" : "خلال ساعة واحدة", "two" : "خلال ساعتين" } }, "year" : { "past" : { "two" : "قبل سنتين", "other" : "قبل {0} سنة", "few" : "قبل {0} سنوات", "one" : "قبل سنة واحدة" }, "previous" : "السنة الماضية", "next" : "السنة القادمة", "future" : { "one" : "خلال سنة واحدة", "two" : "خلال سنتين", "other" : "خلال {0} سنة", "few" : "خلال {0} سنوات" }, "current" : "السنة الحالية" }, "quarter" : { "next" : "الربع القادم", "future" : { "other" : "خلال {0} ربع سنة", "one" : "خلال ربع سنة واحد", "few" : "خلال {0} أرباع سنة", "two" : "خلال ربعي سنة" }, "previous" : "الربع الأخير", "current" : "هذا الربع", "past" : { "one" : "قبل ربع سنة واحد", "other" : "قبل {0} ربع سنة", "two" : "قبل ربعي سنة", "few" : "قبل {0} أرباع سنة" } }, "month" : { "past" : { "few" : "قبل {0} أشهر", "many" : "قبل {0} شهرًا", "other" : "قبل {0} شهر", "one" : "قبل شهر واحد", "two" : "قبل شهرين" }, "next" : "الشهر القادم", "current" : "هذا الشهر", "previous" : "الشهر الماضي", "future" : { "many" : "خلال {0} شهرًا", "two" : "خلال شهرين", "other" : "خلال {0} شهر", "one" : "خلال شهر واحد", "few" : "خلال {0} أشهر" } }, "now" : "الآن", "minute" : { "future" : { "two" : "خلال دقيقتين", "few" : "خلال {0} دقائق", "other" : "خلال {0} دقيقة", "one" : "خلال دقيقة واحدة" }, "past" : { "one" : "قبل دقيقة واحدة", "few" : "قبل {0} دقائق", "other" : "قبل {0} دقيقة", "two" : "قبل دقيقتين" }, "current" : "هذه الدقيقة" } }, "long" : { "now" : "الآن", "day" : { "current" : "اليوم", "previous" : "أمس", "next" : "غدًا", "past" : { "one" : "قبل يوم واحد", "two" : "قبل يومين", "many" : "قبل {0} يومًا", "few" : "قبل {0} أيام", "other" : "قبل {0} يوم" }, "future" : { "two" : "خلال يومين", "many" : "خلال {0} يومًا", "few" : "خلال {0} أيام", "one" : "خلال يوم واحد", "other" : "خلال {0} يوم" } }, "month" : { "future" : { "few" : "خلال {0} أشهر", "many" : "خلال {0} شهرًا", "other" : "خلال {0} شهر", "one" : "خلال شهر واحد", "two" : "خلال شهرين" }, "past" : { "two" : "قبل شهرين", "few" : "قبل {0} أشهر", "one" : "قبل شهر واحد", "many" : "قبل {0} شهرًا", "other" : "قبل {0} شهر" }, "current" : "هذا الشهر", "previous" : "الشهر الماضي", "next" : "الشهر القادم" }, "minute" : { "future" : { "two" : "خلال دقيقتين", "few" : "خلال {0} دقائق", "other" : "خلال {0} دقيقة", "one" : "خلال دقيقة واحدة" }, "past" : { "two" : "قبل دقيقتين", "other" : "قبل {0} دقيقة", "few" : "قبل {0} دقائق", "one" : "قبل دقيقة واحدة" }, "current" : "هذه الدقيقة" }, "second" : { "future" : { "two" : "خلال ثانيتين", "other" : "خلال {0} ثانية", "one" : "خلال ثانية واحدة", "few" : "خلال {0} ثوانٍ" }, "current" : "الآن", "past" : { "two" : "قبل ثانيتين", "one" : "قبل ثانية واحدة", "other" : "قبل {0} ثانية", "few" : "قبل {0} ثوانِ" } }, "hour" : { "current" : "الساعة الحالية", "past" : { "few" : "قبل {0} ساعات", "other" : "قبل {0} ساعة", "one" : "قبل ساعة واحدة", "two" : "قبل ساعتين" }, "future" : { "other" : "خلال {0} ساعة", "one" : "خلال ساعة واحدة", "two" : "خلال ساعتين", "few" : "خلال {0} ساعات" } }, "year" : { "previous" : "السنة الماضية", "future" : { "one" : "خلال سنة واحدة", "two" : "خلال سنتين", "few" : "خلال {0} سنوات", "other" : "خلال {0} سنة" }, "current" : "السنة الحالية", "next" : "السنة القادمة", "past" : { "one" : "قبل سنة واحدة", "other" : "قبل {0} سنة", "two" : "قبل سنتين", "few" : "قبل {0} سنوات" } }, "quarter" : { "current" : "هذا الربع", "past" : { "other" : "قبل {0} ربع سنة", "two" : "قبل ربعي سنة", "few" : "قبل {0} أرباع سنة", "one" : "قبل ربع سنة واحد" }, "future" : { "two" : "خلال ربعي سنة", "few" : "خلال {0} أرباع سنة", "other" : "خلال {0} ربع سنة", "one" : "خلال ربع سنة واحد" }, "next" : "الربع القادم", "previous" : "الربع الأخير" }, "week" : { "next" : "الأسبوع القادم", "current" : "هذا الأسبوع", "past" : { "one" : "قبل أسبوع واحد", "many" : "قبل {0} أسبوعًا", "two" : "قبل أسبوعين", "other" : "قبل {0} أسبوع", "few" : "قبل {0} أسابيع" }, "future" : { "other" : "خلال {0} أسبوع", "two" : "خلال أسبوعين", "few" : "خلال {0} أسابيع", "one" : "خلال أسبوع واحد", "many" : "خلال {0} أسبوعًا" }, "previous" : "الأسبوع الماضي" } }, "short" : { "now" : "الآن", "year" : { "current" : "السنة الحالية", "past" : { "few" : "قبل {0} سنوات", "two" : "قبل سنتين", "other" : "قبل {0} سنة", "one" : "قبل سنة واحدة" }, "previous" : "السنة الماضية", "next" : "السنة القادمة", "future" : { "few" : "خلال {0} سنوات", "one" : "خلال سنة واحدة", "two" : "خلال سنتين", "other" : "خلال {0} سنة" } }, "quarter" : { "past" : { "two" : "قبل ربعي سنة", "few" : "قبل {0} أرباع سنة", "other" : "قبل {0} ربع سنة", "one" : "قبل ربع سنة واحد" }, "next" : "الربع القادم", "previous" : "الربع الأخير", "current" : "هذا الربع", "future" : { "few" : "خلال {0} أرباع سنة", "one" : "خلال ربع سنة واحد", "other" : "خلال {0} ربع سنة", "two" : "خلال ربعي سنة" } }, "second" : { "past" : { "few" : "قبل {0} ثوانٍ", "other" : "قبل {0} ثانية", "one" : "قبل ثانية واحدة", "two" : "قبل ثانيتين" }, "future" : { "one" : "خلال ثانية واحدة", "two" : "خلال ثانيتين", "few" : "خلال {0} ثوانٍ", "other" : "خلال {0} ثانية" }, "current" : "الآن" }, "hour" : { "current" : "الساعة الحالية", "past" : { "other" : "قبل {0} ساعة", "two" : "قبل ساعتين", "one" : "قبل ساعة واحدة", "few" : "قبل {0} ساعات" }, "future" : { "two" : "خلال ساعتين", "one" : "خلال ساعة واحدة", "other" : "خلال {0} ساعة", "few" : "خلال {0} ساعات" } }, "day" : { "future" : { "other" : "خلال {0} يوم", "one" : "خلال يوم واحد", "few" : "خلال {0} أيام", "two" : "خلال يومين", "many" : "خلال {0} يومًا" }, "previous" : "أمس", "current" : "اليوم", "past" : { "many" : "قبل {0} يومًا", "other" : "قبل {0} يوم", "two" : "قبل يومين", "few" : "قبل {0} أيام", "one" : "قبل يوم واحد" }, "next" : "غدًا" }, "month" : { "next" : "الشهر القادم", "past" : { "two" : "قبل شهرين", "other" : "قبل {0} شهر", "few" : "خلال {0} أشهر", "many" : "قبل {0} شهرًا", "one" : "قبل شهر واحد" }, "future" : { "one" : "خلال شهر واحد", "two" : "خلال شهرين", "other" : "خلال {0} شهر", "many" : "خلال {0} شهرًا", "few" : "خلال {0} أشهر" }, "previous" : "الشهر الماضي", "current" : "هذا الشهر" }, "week" : { "future" : { "one" : "خلال أسبوع واحد", "two" : "خلال {0} أسبوعين", "few" : "خلال {0} أسابيع", "many" : "خلال {0} أسبوعًا", "other" : "خلال {0} أسبوع" }, "next" : "الأسبوع القادم", "previous" : "الأسبوع الماضي", "past" : { "many" : "قبل {0} أسبوعًا", "other" : "قبل {0} أسبوع", "two" : "قبل أسبوعين", "one" : "قبل أسبوع واحد", "few" : "قبل {0} أسابيع" }, "current" : "هذا الأسبوع" }, "minute" : { "current" : "هذه الدقيقة", "past" : { "few" : "قبل {0} دقائق", "two" : "قبل دقيقتين", "one" : "قبل دقيقة واحدة", "other" : "قبل {0} دقيقة" }, "future" : { "few" : "خلال {0} دقائق", "other" : "خلال {0} دقيقة", "one" : "خلال دقيقة واحدة", "two" : "خلال دقيقتين" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ar_AE.json ================================================ { "short" : { "month" : { "future" : { "other" : "خلال {0} شهر", "one" : "خلال شهر واحد", "two" : "خلال شهرين", "few" : "خلال {0} أشهر", "many" : "خلال {0} شهرًا" }, "previous" : "الشهر الماضي", "next" : "الشهر القادم", "past" : { "one" : "قبل شهر واحد", "two" : "قبل شهرين", "few" : "خلال {0} أشهر", "other" : "قبل {0} شهر", "many" : "قبل {0} شهرًا" }, "current" : "هذا الشهر" }, "day" : { "future" : { "two" : "خلال يومين", "one" : "خلال يوم واحد", "few" : "خلال {0} أيام", "other" : "خلال {0} يوم", "many" : "خلال {0} يومًا" }, "previous" : "أمس", "next" : "غدًا", "past" : { "two" : "قبل يومين", "many" : "قبل {0} يومًا", "other" : "قبل {0} يوم", "one" : "قبل يوم واحد", "few" : "قبل {0} أيام" }, "current" : "اليوم" }, "second" : { "future" : { "one" : "خلال ثانية واحدة", "few" : "خلال {0} ثوانٍ", "two" : "خلال ثانيتين", "other" : "خلال {0} ثانية" }, "current" : "الآن", "past" : { "two" : "قبل ثانيتين", "other" : "قبل {0} ثانية", "few" : "قبل {0} ثوانٍ", "one" : "قبل ثانية واحدة" } }, "quarter" : { "past" : { "other" : "قبل {0} ربع سنة", "two" : "قبل ربعي سنة", "one" : "قبل ربع سنة واحد", "few" : "قبل {0} أرباع سنة" }, "next" : "الربع القادم", "future" : { "other" : "خلال {0} ربع سنة", "few" : "خلال {0} أرباع سنة", "two" : "خلال ربعي سنة", "one" : "خلال ربع سنة واحد" }, "previous" : "الربع الأخير", "current" : "هذا الربع" }, "year" : { "future" : { "few" : "خلال {0} سنوات", "other" : "خلال {0} سنة", "one" : "خلال سنة واحدة", "two" : "خلال سنتين" }, "current" : "هذه السنة", "previous" : "السنة الماضية", "past" : { "few" : "قبل {0} سنوات", "other" : "قبل {0} سنة", "two" : "قبل سنتين", "one" : "قبل سنة واحدة" }, "next" : "السنة التالية" }, "hour" : { "past" : { "few" : "قبل {0} ساعات", "other" : "قبل {0} ساعة", "two" : "قبل ساعتين", "one" : "قبل ساعة واحدة" }, "future" : { "two" : "خلال ساعتين", "one" : "خلال ساعة واحدة", "other" : "خلال {0} ساعة", "few" : "خلال {0} ساعات" }, "current" : "الساعة الحالية" }, "week" : { "current" : "هذا الأسبوع", "previous" : "الأسبوع الماضي", "past" : { "two" : "قبل أسبوعين", "other" : "قبل {0} أسبوع", "few" : "قبل {0} أسابيع", "one" : "قبل أسبوع واحد", "many" : "قبل {0} أسبوعًا" }, "next" : "الأسبوع القادم", "future" : { "two" : "خلال {0} أسبوعين", "few" : "خلال {0} أسابيع", "other" : "خلال {0} أسبوع", "one" : "خلال أسبوع واحد", "many" : "خلال {0} أسبوعًا" } }, "minute" : { "future" : { "other" : "خلال {0} دقيقة", "one" : "خلال دقيقة واحدة", "few" : "خلال {0} دقائق", "two" : "خلال دقيقتين" }, "past" : { "other" : "قبل {0} دقيقة", "two" : "قبل دقيقتين", "one" : "قبل دقيقة واحدة", "few" : "قبل {0} دقائق" }, "current" : "هذه الدقيقة" }, "now" : "الآن" }, "narrow" : { "second" : { "future" : { "few" : "خلال {0} ثوانٍ", "two" : "خلال ثانيتين", "other" : "خلال {0} ثانية", "one" : "خلال ثانية واحدة" }, "current" : "الآن", "past" : { "one" : "قبل ثانية واحدة", "few" : "قبل {0} ثوانٍ", "two" : "قبل ثانيتين", "other" : "قبل {0} ثانية" } }, "now" : "الآن", "day" : { "future" : { "other" : "خلال {0} يوم", "one" : "خلال يوم واحد", "two" : "خلال يومين", "few" : "خلال {0} أيام", "many" : "خلال {0} يومًا" }, "current" : "اليوم", "past" : { "one" : "قبل يوم واحد", "many" : "قبل {0} يومًا", "two" : "قبل يومين", "other" : "قبل {0} يوم", "few" : "قبل {0} أيام" }, "next" : "غدًا", "previous" : "أمس" }, "hour" : { "past" : { "few" : "قبل {0} ساعات", "two" : "قبل ساعتين", "other" : "قبل {0} ساعة", "one" : "قبل ساعة واحدة" }, "future" : { "two" : "خلال ساعتين", "one" : "خلال ساعة واحدة", "few" : "خلال {0} ساعات", "other" : "خلال {0} ساعة" }, "current" : "الساعة الحالية" }, "quarter" : { "past" : { "one" : "قبل ربع سنة واحد", "few" : "قبل {0} أرباع سنة", "other" : "قبل {0} ربع سنة", "two" : "قبل ربعي سنة" }, "current" : "هذا الربع", "future" : { "one" : "خلال ربع سنة واحد", "two" : "خلال ربعي سنة", "few" : "خلال {0} أرباع سنة", "other" : "خلال {0} ربع سنة" }, "next" : "الربع القادم", "previous" : "الربع الأخير" }, "year" : { "future" : { "other" : "خلال {0} سنة", "one" : "خلال سنة واحدة", "few" : "خلال {0} سنوات", "two" : "خلال سنتين" }, "next" : "السنة التالية", "previous" : "السنة الماضية", "current" : "هذه السنة", "past" : { "two" : "قبل سنتين", "few" : "قبل {0} سنوات", "other" : "قبل {0} سنة", "one" : "قبل سنة واحدة" } }, "month" : { "next" : "الشهر القادم", "previous" : "الشهر الماضي", "future" : { "other" : "خلال {0} شهر", "few" : "خلال {0} أشهر", "one" : "خلال شهر واحد", "many" : "خلال {0} شهرًا", "two" : "خلال شهرين" }, "current" : "هذا الشهر", "past" : { "many" : "قبل {0} شهرًا", "other" : "قبل {0} شهر", "one" : "قبل شهر واحد", "two" : "قبل شهرين", "few" : "قبل {0} أشهر" } }, "week" : { "previous" : "الأسبوع الماضي", "next" : "الأسبوع القادم", "past" : { "many" : "قبل {0} أسبوعًا", "few" : "قبل {0} أسابيع", "other" : "قبل {0} أسبوع", "two" : "قبل أسبوعين", "one" : "قبل أسبوع واحد" }, "future" : { "one" : "خلال أسبوع واحد", "many" : "خلال {0} أسبوعًا", "few" : "خلال {0} أسابيع", "two" : "خلال أسبوعين", "other" : "خلال {0} أسبوع" }, "current" : "هذا الأسبوع" }, "minute" : { "past" : { "two" : "قبل دقيقتين", "other" : "قبل {0} دقيقة", "one" : "قبل دقيقة واحدة", "few" : "قبل {0} دقائق" }, "future" : { "other" : "خلال {0} دقيقة", "few" : "خلال {0} دقائق", "two" : "خلال دقيقتين", "one" : "خلال دقيقة واحدة" }, "current" : "هذه الدقيقة" } }, "long" : { "now" : "الآن", "second" : { "past" : { "two" : "قبل ثانيتين", "one" : "قبل ثانية واحدة", "other" : "قبل {0} ثانية", "few" : "قبل {0} ثوانِ" }, "future" : { "one" : "خلال ثانية واحدة", "two" : "خلال ثانيتين", "few" : "خلال {0} ثوانٍ", "other" : "خلال {0} ثانية" }, "current" : "الآن" }, "minute" : { "current" : "هذه الدقيقة", "past" : { "one" : "قبل دقيقة واحدة", "few" : "قبل {0} دقائق", "two" : "قبل دقيقتين", "other" : "قبل {0} دقيقة" }, "future" : { "one" : "خلال دقيقة واحدة", "few" : "خلال {0} دقائق", "two" : "خلال دقيقتين", "other" : "خلال {0} دقيقة" } }, "week" : { "previous" : "الأسبوع الماضي", "current" : "هذا الأسبوع", "past" : { "one" : "قبل أسبوع واحد", "two" : "قبل أسبوعين", "few" : "قبل {0} أسابيع", "other" : "قبل {0} أسبوع", "many" : "قبل {0} أسبوعًا" }, "next" : "الأسبوع القادم", "future" : { "one" : "خلال أسبوع واحد", "many" : "خلال {0} أسبوعًا", "other" : "خلال {0} أسبوع", "two" : "خلال أسبوعين", "few" : "خلال {0} أسابيع" } }, "day" : { "future" : { "one" : "خلال يوم واحد", "few" : "خلال {0} أيام", "two" : "خلال يومين", "many" : "خلال {0} يومًا", "other" : "خلال {0} يوم" }, "next" : "غدًا", "previous" : "أمس", "current" : "اليوم", "past" : { "many" : "قبل {0} يومًا", "two" : "قبل يومين", "one" : "قبل يوم واحد", "few" : "قبل {0} أيام", "other" : "قبل {0} يوم" } }, "month" : { "current" : "هذا الشهر", "next" : "الشهر القادم", "future" : { "many" : "خلال {0} شهرًا", "few" : "خلال {0} أشهر", "other" : "خلال {0} شهر", "one" : "خلال شهر واحد", "two" : "خلال شهرين" }, "previous" : "الشهر الماضي", "past" : { "other" : "قبل {0} شهر", "two" : "قبل شهرين", "many" : "قبل {0} شهرًا", "one" : "قبل شهر واحد", "few" : "قبل {0} أشهر" } }, "year" : { "future" : { "one" : "خلال سنة واحدة", "few" : "خلال {0} سنوات", "two" : "خلال سنتين", "other" : "خلال {0} سنة" }, "previous" : "السنة الماضية", "next" : "السنة التالية", "current" : "هذه السنة", "past" : { "two" : "قبل سنتين", "other" : "قبل {0} سنة", "one" : "قبل سنة واحدة", "few" : "قبل {0} سنوات" } }, "hour" : { "future" : { "other" : "خلال {0} ساعة", "one" : "خلال ساعة واحدة", "two" : "خلال ساعتين", "few" : "خلال {0} ساعات" }, "past" : { "two" : "قبل ساعتين", "few" : "قبل {0} ساعات", "one" : "قبل ساعة واحدة", "other" : "قبل {0} ساعة" }, "current" : "الساعة الحالية" }, "quarter" : { "past" : { "one" : "قبل ربع سنة واحد", "two" : "قبل ربعي سنة", "other" : "قبل {0} ربع سنة", "few" : "قبل {0} أرباع سنة" }, "future" : { "few" : "خلال {0} أرباع سنة", "one" : "خلال ربع سنة واحد", "other" : "خلال {0} ربع سنة", "two" : "خلال ربعي سنة" }, "previous" : "الربع الأخير", "current" : "هذا الربع", "next" : "الربع القادم" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/as.json ================================================ { "narrow" : { "quarter" : { "past" : "{0} তিনি মাহ পূৰ্বে", "future" : "{0} তিনি মাহত", "current" : "এই তিনি মাহ", "previous" : "যোৱা তিনি মাহ", "next" : "অহা তিনি মাহ" }, "year" : { "previous" : "যোৱা বছৰ", "next" : "অহা বছৰ", "future" : "{0} বছৰত", "current" : "এই বছৰ", "past" : "{0} বছৰৰ পূৰ্বে" }, "week" : { "future" : "{0} সপ্তাহত", "previous" : "যোৱা সপ্তাহ", "next" : "অহা সপ্তাহ", "past" : "{0} সপ্তাহ পূৰ্বে", "current" : "এই সপ্তাহ" }, "day" : { "current" : "আজি", "past" : "{0} দিন পূৰ্বে", "next" : "কাইলৈ", "future" : "{0} দিনত", "previous" : "কালি" }, "hour" : { "current" : "এইটো ঘণ্টাত", "past" : "{0} ঘণ্টা পূৰ্বে", "future" : "{0} ঘণ্টাত" }, "minute" : { "future" : "{0} মিনিটত", "current" : "এইটো মিনিটত", "past" : "{0} মিনিট পূৰ্বে" }, "month" : { "next" : "অহা মাহ", "current" : "এই মাহ", "past" : "{0} মাহ পূৰ্বে", "future" : "{0} মাহত", "previous" : "যোৱা মাহ" }, "now" : "এতিয়া", "second" : { "future" : "{0} ছেকেণ্ডত", "current" : "এতিয়া", "past" : "{0} ছেকেণ্ড পূৰ্বে" } }, "short" : { "minute" : { "past" : "{0} মিনিট পূৰ্বে", "future" : "{0} মিনিটত", "current" : "এইটো মিনিটত" }, "week" : { "current" : "এই সপ্তাহ", "past" : "{0} সপ্তাহ পূৰ্বে", "future" : "{0} সপ্তাহত", "next" : "অহা সপ্তাহ", "previous" : "যোৱা সপ্তাহ" }, "year" : { "future" : "{0} বছৰত", "previous" : "যোৱা বছৰ", "next" : "অহা বছৰ", "current" : "এই বছৰ", "past" : "{0} বছৰৰ পূৰ্বে" }, "day" : { "next" : "কাইলৈ", "current" : "আজি", "previous" : "কালি", "past" : "{0} দিন পূৰ্বে", "future" : "{0} দিনত" }, "hour" : { "future" : "{0} ঘণ্টাত", "current" : "এইটো ঘণ্টাত", "past" : "{0} ঘণ্টা পূৰ্বে" }, "quarter" : { "current" : "এই তিনি মাহ", "future" : "{0} তিনি মাহত", "previous" : "যোৱা তিনি মাহ", "next" : "অহা তিনি মাহ", "past" : "{0} তিনি মাহ পূৰ্বে" }, "second" : { "past" : "{0} ছেকেণ্ড পূৰ্বে", "current" : "এতিয়া", "future" : "{0} ছেকেণ্ডত" }, "month" : { "current" : "এই মাহ", "past" : "{0} মাহ পূৰ্বে", "future" : "{0} মাহত", "next" : "অহা মাহ", "previous" : "যোৱা মাহ" }, "now" : "এতিয়া" }, "long" : { "hour" : { "future" : "{0} ঘণ্টাত", "current" : "এইটো ঘণ্টাত", "past" : "{0} ঘণ্টা পূৰ্বে" }, "day" : { "next" : "কাইলৈ", "past" : "{0} দিন পূৰ্বে", "previous" : "কালি", "future" : "{0} দিনত", "current" : "আজি" }, "second" : { "past" : "{0} ছেকেণ্ড পূৰ্বে", "current" : "এতিয়া", "future" : "{0} ছেকেণ্ডত" }, "week" : { "future" : "{0} সপ্তাহত", "past" : "{0} সপ্তাহ পূৰ্বে", "current" : "এই সপ্তাহ", "next" : "অহা সপ্তাহ", "previous" : "যোৱা সপ্তাহ" }, "minute" : { "future" : "{0} মিনিটত", "past" : "{0} মিনিট পূৰ্বে", "current" : "এইটো মিনিটত" }, "month" : { "future" : "{0} মাহত", "past" : "{0} মাহ পূৰ্বে", "current" : "এই মাহ", "previous" : "যোৱা মাহ", "next" : "অহা মাহ" }, "now" : "এতিয়া", "year" : { "current" : "এই বছৰ", "next" : "অহা বছৰ", "previous" : "যোৱা বছৰ", "future" : "{0} বছৰত", "past" : "{0} বছৰৰ পূৰ্বে" }, "quarter" : { "future" : "{0} তিনি মাহত", "previous" : "যোৱা তিনি মাহ", "past" : "{0} তিনি মাহ পূৰ্বে", "next" : "অহা তিনি মাহ", "current" : "এই তিনি মাহ" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/be.json ================================================ { "long" : { "quarter" : { "previous" : "у мінулым квартале", "next" : "у наступным квартале", "past" : { "one" : "{0} квартал таму", "few" : "{0} кварталы таму", "many" : "{0} кварталаў таму", "other" : "{0} квартала таму" }, "future" : { "many" : "праз {0} кварталаў", "one" : "праз {0} квартал", "other" : "праз {0} квартала", "few" : "праз {0} кварталы" }, "current" : "у гэтым квартале" }, "year" : { "future" : { "one" : "праз {0} год", "many" : "праз {0} гадоў", "few" : "праз {0} гады", "other" : "праз {0} года" }, "current" : "у гэтым годзе", "next" : "у наступным годзе", "past" : { "one" : "{0} год таму", "other" : "{0} года таму", "many" : "{0} гадоў таму", "few" : "{0} гады таму" }, "previous" : "у мінулым годзе" }, "month" : { "current" : "у гэтым месяцы", "past" : { "one" : "{0} месяц таму", "few" : "{0} месяцы таму", "many" : "{0} месяцаў таму", "other" : "{0} месяца таму" }, "previous" : "у мінулым месяцы", "next" : "у наступным месяцы", "future" : { "many" : "праз {0} месяцаў", "other" : "праз {0} месяца", "few" : "праз {0} месяцы", "one" : "праз {0} месяц" } }, "hour" : { "current" : "у гэту гадзіну", "future" : { "one" : "праз {0} гадзіну", "other" : "праз {0} гадзіны", "many" : "праз {0} гадзін" }, "past" : { "one" : "{0} гадзіну таму", "other" : "{0} гадзіны таму", "many" : "{0} гадзін таму" } }, "week" : { "future" : { "other" : "праз {0} тыдня", "few" : "праз {0} тыдні", "one" : "праз {0} тыдзень", "many" : "праз {0} тыдняў" }, "next" : "на наступным тыдні", "previous" : "на мінулым тыдні", "past" : { "many" : "{0} тыдняў таму", "one" : "{0} тыдзень таму", "few" : "{0} тыдні таму", "other" : "{0} тыдня таму" }, "current" : "на гэтым тыдні" }, "minute" : { "future" : { "one" : "праз {0} хвіліну", "other" : "праз {0} хвіліны", "many" : "праз {0} хвілін" }, "current" : "у гэту хвіліну", "past" : { "one" : "{0} хвіліну таму", "many" : "{0} хвілін таму", "other" : "{0} хвіліны таму" } }, "now" : "цяпер", "second" : { "past" : { "many" : "{0} секунд таму", "other" : "{0} секунды таму", "one" : "{0} секунду таму" }, "future" : { "many" : "праз {0} секунд", "one" : "праз {0} секунду", "other" : "праз {0} секунды" }, "current" : "цяпер" }, "day" : { "current" : "сёння", "next" : "заўтра", "past" : { "one" : "{0} дзень таму", "many" : "{0} дзён таму", "few" : "{0} дні таму", "other" : "{0} дня таму" }, "future" : { "other" : "праз {0} дня", "one" : "праз {0} дзень", "many" : "праз {0} дзён", "few" : "праз {0} дні" }, "previous" : "учора" } }, "narrow" : { "now" : "цяпер", "quarter" : { "current" : "у гэтым квартале", "next" : "у наступным квартале", "past" : "{0} кв. таму", "previous" : "у мінулым квартале", "future" : "праз {0} кв." }, "day" : { "past" : { "one" : "{0} дзень таму", "few" : "{0} дні таму", "many" : "{0} дзён таму", "other" : "{0} дня таму" }, "previous" : "учора", "current" : "сёння", "next" : "заўтра", "future" : { "few" : "праз {0} дні", "many" : "праз {0} дзён", "other" : "праз {0} дня", "one" : "праз {0} дзень" } }, "week" : { "current" : "на гэтым тыдні", "next" : "на наступным тыдні", "future" : "праз {0} тыд", "previous" : "на мінулым тыдні", "past" : "{0} тыд таму" }, "year" : { "current" : "у гэтым годзе", "previous" : "у мінулым годзе", "past" : "{0} г. таму", "next" : "у наступным годзе", "future" : "праз {0} г." }, "month" : { "past" : "{0} мес. таму", "current" : "у гэтым месяцы", "future" : "праз {0} мес.", "previous" : "у мінулым месяцы", "next" : "у наступным месяцы" }, "second" : { "current" : "цяпер", "past" : "{0} с таму", "future" : "праз {0} с" }, "minute" : { "current" : "у гэту хвіліну", "future" : "праз {0} хв", "past" : "{0} хв таму" }, "hour" : { "future" : "праз {0} гадз", "past" : "{0} гадз таму", "current" : "у гэту гадзіну" } }, "short" : { "month" : { "current" : "у гэтым месяцы", "next" : "у наступным месяцы", "past" : "{0} мес. таму", "future" : "праз {0} мес.", "previous" : "у мінулым месяцы" }, "now" : "цяпер", "week" : { "next" : "на наступным тыдні", "past" : "{0} тыд таму", "current" : "на гэтым тыдні", "previous" : "на мінулым тыдні", "future" : "праз {0} тыд" }, "day" : { "future" : { "other" : "праз {0} дня", "few" : "праз {0} дні", "one" : "праз {0} дзень", "many" : "праз {0} дзён" }, "previous" : "учора", "current" : "сёння", "next" : "заўтра", "past" : { "one" : "{0} дзень таму", "few" : "{0} дні таму", "other" : "{0} дня таму", "many" : "{0} дзён таму" } }, "year" : { "current" : "у гэтым годзе", "future" : "праз {0} г.", "previous" : "у мінулым годзе", "next" : "у наступным годзе", "past" : "{0} г. таму" }, "hour" : { "past" : "{0} гадз таму", "current" : "у гэту гадзіну", "future" : "праз {0} гадз" }, "minute" : { "past" : "{0} хв таму", "future" : "праз {0} хв", "current" : "у гэту хвіліну" }, "second" : { "future" : "праз {0} с", "current" : "цяпер", "past" : "{0} с таму" }, "quarter" : { "future" : "праз {0} кв.", "previous" : "у мінулым квартале", "next" : "у наступным квартале", "past" : "{0} кв. таму", "current" : "у гэтым квартале" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/bg.json ================================================ { "narrow" : { "week" : { "next" : "сл. седм.", "past" : "пр. {0} седм.", "future" : "сл. {0} седм.", "previous" : "мин. седм.", "current" : "тази седм." }, "quarter" : { "next" : "следв. трим.", "past" : "пр. {0} трим.", "previous" : "мин. трим.", "current" : "това трим.", "future" : "сл. {0} трим." }, "minute" : { "current" : "в тази минута", "past" : "пр. {0} мин", "future" : "сл. {0} мин" }, "day" : { "previous" : "вчера", "next" : "утре", "current" : "днес", "future" : "сл. {0} д", "past" : "пр. {0} д" }, "year" : { "current" : "т. г.", "previous" : "мин. г.", "next" : "сл. г.", "past" : "пр. {0} г.", "future" : "сл. {0} г." }, "second" : { "past" : "пр. {0} сек", "future" : "сл. {0} сек", "current" : "сега" }, "month" : { "past" : "пр. {0} м.", "future" : "сл. {0} м.", "next" : "сл. м.", "current" : "т. м.", "previous" : "мин. м." }, "now" : "сега", "hour" : { "past" : "пр. {0} ч", "current" : "в този час", "future" : "сл. {0} ч" } }, "short" : { "year" : { "next" : "следв. г.", "future" : "след {0} г.", "current" : "т. г.", "past" : "преди {0} г.", "previous" : "мин. г." }, "quarter" : { "future" : "след {0} трим.", "next" : "следв. трим.", "previous" : "мин. трим.", "current" : "това трим.", "past" : "преди {0} трим." }, "second" : { "future" : "след {0} сек", "current" : "сега", "past" : "преди {0} сек" }, "minute" : { "current" : "в тази минута", "past" : "преди {0} мин", "future" : "след {0} мин" }, "day" : { "past" : { "one" : "преди {0} ден", "other" : "преди {0} дни" }, "next" : "утре", "future" : { "one" : "след {0} ден", "other" : "след {0} дни" }, "previous" : "вчера", "current" : "днес" }, "month" : { "previous" : "мин. мес.", "next" : "следв. мес.", "past" : "преди {0} м.", "current" : "този мес.", "future" : "след {0} м." }, "hour" : { "current" : "в този час", "past" : "преди {0} ч", "future" : "след {0} ч" }, "now" : "сега", "week" : { "previous" : "миналата седмица", "current" : "тази седм.", "next" : "следв. седм.", "past" : "преди {0} седм.", "future" : "след {0} седм." } }, "long" : { "month" : { "previous" : "предходен месец", "past" : { "one" : "преди {0} месец", "other" : "преди {0} месеца" }, "current" : "този месец", "future" : { "one" : "след {0} месец", "other" : "след {0} месеца" }, "next" : "следващ месец" }, "week" : { "previous" : "предходната седмица", "current" : "тази седмица", "next" : "следващата седмица", "past" : { "other" : "преди {0} седмици", "one" : "преди {0} седмица" }, "future" : { "one" : "след {0} седмица", "other" : "след {0} седмици" } }, "quarter" : { "future" : { "one" : "след {0} тримесечие", "other" : "след {0} тримесечия" }, "next" : "следващо тримесечие", "previous" : "предходно тримесечие", "current" : "това тримесечие", "past" : { "other" : "преди {0} тримесечия", "one" : "преди {0} тримесечие" } }, "hour" : { "future" : { "one" : "след {0} час", "other" : "след {0} часа" }, "current" : "в този час", "past" : { "one" : "преди {0} час", "other" : "преди {0} часа" } }, "second" : { "future" : { "one" : "след {0} секунда", "other" : "след {0} секунди" }, "current" : "сега", "past" : { "one" : "преди {0} секунда", "other" : "преди {0} секунди" } }, "now" : "сега", "minute" : { "future" : { "one" : "след {0} минута", "other" : "след {0} минути" }, "past" : { "one" : "преди {0} минута", "other" : "преди {0} минути" }, "current" : "в тази минута" }, "year" : { "current" : "тази година", "next" : "следващата година", "future" : { "other" : "след {0} години", "one" : "след {0} година" }, "past" : { "one" : "преди {0} година", "other" : "преди {0} години" }, "previous" : "миналата година" }, "day" : { "current" : "днес", "previous" : "вчера", "future" : { "other" : "след {0} дни", "one" : "след {0} ден" }, "past" : { "one" : "преди {0} ден", "other" : "преди {0} дни" }, "next" : "утре" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/bn.json ================================================ { "narrow" : { "quarter" : { "past" : "[0] ত্রৈমাসিক আগে", "future" : "[0] ত্রৈমাসিকে", "current" : "এই ত্রৈমাসিক", "previous" : "গত ত্রৈমাসিক", "next" : "পরের ত্রৈমাসিক" }, "year" : { "previous" : "গত বছর", "next" : "পরের বছর", "future" : "[0] বছরে", "current" : "এই বছর", "past" : "[0] বছর পূর্বে" }, "week" : { "future" : "[0] সপ্তাহে", "previous" : "গত সপ্তাহ", "next" : "পরের সপ্তাহ", "past" : "[0] সপ্তাহ আগে", "current" : "এই সপ্তাহ" }, "day" : { "current" : "আজ", "past" : "[0] দিন আগে", "next" : "আগামীকাল", "future" : "[0] দিনের মধ্যে", "previous" : "গতকাল" }, "hour" : { "current" : "এই ঘণ্টায়", "past" : "[0] ঘন্টা আগে", "future" : "[0] ঘন্টায়" }, "minute" : { "future" : "[0] মিনিটে", "current" : "এই মিনিট", "past" : "[0] মিনিট আগে" }, "month" : { "next" : "পরের মাস", "current" : "এই মাস", "past" : "[0] মাস আগে", "future" : "[0] মাসে", "previous" : "গত মাস" }, "now" : "এখন", "second" : { "future" : "[0] সেকেন্ডে", "current" : "এখন", "past" : "[0] সেকেন্ড আগে" } }, "short" : { "minute" : { "past" : "[0] মিনিট আগে", "future" : "[0] মিনিটে", "current" : "এই মিনিট" }, "week" : { "current" : "এই সপ্তাহ", "past" : "[0] সপ্তাহ আগে", "future" : "[0] সপ্তাহে", "next" : "পরের সপ্তাহ", "previous" : "গত সপ্তাহ" }, "year" : { "current" : "এই বছর", "future" : "[0] বছরে", "past" : "[0] বছর পূর্বে", "next" : "পরের বছর", "previous" : "গত বছর" }, "day" : { "next" : "আগামীকাল", "current" : "আজ", "previous" : "গতকাল", "past" : "[0] দিন আগে", "future" : "[0] দিনের মধ্যে" }, "hour" : { "future" : "[0] ঘন্টায়", "current" : "এই ঘণ্টায়", "past" : "[0] ঘন্টা আগে" }, "quarter" : { "current" : "এই ত্রৈমাসিক", "future" : "[0] ত্রৈমাসিকে", "previous" : "গত ত্রৈমাসিক", "next" : "পরের ত্রৈমাসিক", "past" : "[0] ত্রৈমাসিক আগে" }, "second" : { "past" : "[0] সেকেন্ড পূর্বে", "current" : "এখন", "future" : "[0] সেকেন্ডে" }, "month" : { "current" : "এই মাস", "past" : "[0] মাস আগে", "future" : "[0] মাসে", "next" : "পরের মাস", "previous" : "গত মাস" }, "now" : "এখন" }, "long" : { "hour" : { "future" : "[0] ঘন্টায়", "current" : "এই ঘণ্টায়", "past" : "[0] ঘন্টা আগে" }, "day" : { "next" : "আগামীকাল", "past" : "[0] দিন আগে", "previous" : "গতকাল", "future" : "[0] দিনের মধ্যে", "current" : "আজ" }, "second" : { "past" : "[0] সেকেন্ড পূর্বে", "current" : "এখন", "future" : "[0] সেকেন্ডে" }, "week" : { "future" : "[0] সপ্তাহে", "past" : "[0] সপ্তাহ আগে", "current" : "এই সপ্তাহ", "next" : "পরের সপ্তাহ", "previous" : "গত সপ্তাহ" }, "minute" : { "future" : "[0] মিনিটে", "past" : "[0] মিনিট আগে", "current" : "এই মিনিট" }, "month" : { "future" : "[0] মাসে", "past" : "[0] মাস আগে", "current" : "এই মাস", "previous" : "গত মাস", "next" : "পরের মাস" }, "now" : "এখন", "year" : { "current" : "এই বছর", "next" : "পরের বছর", "previous" : "গত বছর", "future" : "[0] বছরে", "past" : "[0] বছর পূর্বে" }, "quarter" : { "future" : "[0] ত্রৈমাসিকে", "previous" : "গত ত্রৈমাসিক", "past" : "[0] ত্রৈমাসিক আগে", "next" : "পরের ত্রৈমাসিক", "current" : "এই ত্রৈমাসিক" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/br.json ================================================ { "narrow" : { "quarter" : { "previous" : "last quarter", "current" : "this quarter", "next" : "next quarter", "past" : "-{0} trim.", "future" : "+{0} trim." }, "month" : { "past" : "-{0} miz", "next" : "ar miz a zeu", "future" : "+{0} miz", "previous" : "ar miz diaraok", "current" : "ar miz-mañ" }, "now" : "brem.", "hour" : { "future" : "+{0} h", "past" : "-{0} h", "current" : "this hour" }, "minute" : { "past" : "-{0} min", "current" : "this minute", "future" : "+{0} min" }, "day" : { "current" : "hiziv", "future" : "+{0} d", "next" : "warcʼhoazh", "previous" : "decʼh", "past" : "-{0} d" }, "year" : { "next" : "ar bl. a zeu", "past" : "-{0} bl.", "future" : "+{0} bl.", "previous" : "warlene", "current" : "hevlene" }, "week" : { "next" : "ar sizhun a zeu", "previous" : "ar sizhun diaraok", "past" : { "many" : "{0} a sizhunioù zo", "other" : "{0} sizhun zo" }, "future" : { "many" : "a-benn {0} a sizhunioù", "other" : "a-benn {0} sizhun" }, "current" : "ar sizhun-mañ" }, "second" : { "future" : "+{0} s", "current" : "brem.", "past" : "-{0} s" } }, "short" : { "minute" : { "future" : "a-benn {0} min", "current" : "this minute", "past" : "{0} min zo" }, "month" : { "current" : "ar miz-mañ", "past" : { "two" : "{0} viz zo", "many" : "{0} a vizioù zo", "other" : "{0} miz zo" }, "future" : { "two" : "a-benn {0} viz", "many" : "a-benn {0} a vizioù", "other" : "a-benn {0} miz" }, "next" : "ar miz a zeu", "previous" : "ar miz diaraok" }, "week" : { "next" : "ar sizhun a zeu", "current" : "ar sizhun-mañ", "previous" : "ar sizhun diaraok", "past" : { "many" : "{0} a sizhunioù zo", "other" : "{0} sizhun zo" }, "future" : { "other" : "a-benn {0} sizhun", "many" : "a-benn {0} a sizhunioù" } }, "hour" : { "future" : "a-benn {0} e", "current" : "this hour", "past" : "{0} e zo" }, "day" : { "future" : "a-benn {0} d", "previous" : "decʼh", "current" : "hiziv", "next" : "warcʼhoazh", "past" : "{0} d zo" }, "second" : { "future" : "a-benn {0} s", "current" : "brem.", "past" : "{0} s zo" }, "now" : "brem.", "year" : { "current" : "hevlene", "future" : "a-benn {0} bl.", "past" : "{0} bl. zo", "next" : "ar bl. a zeu", "previous" : "warlene" }, "quarter" : { "current" : "this quarter", "future" : "a-benn {0} trim.", "previous" : "last quarter", "next" : "next quarter", "past" : "{0} trim. zo" } }, "long" : { "minute" : { "past" : { "other" : "{0} munut zo", "two" : "{0} vunut zo", "many" : "{0} a vunutoù zo" }, "future" : { "two" : "a-benn {0} vunut", "many" : "a-benn {0} a vunutoù", "other" : "a-benn {0} munut" }, "current" : "this minute" }, "week" : { "past" : { "many" : "{0} a sizhunioù zo", "other" : "{0} sizhun zo" }, "future" : { "many" : "a-benn {0} a sizhunioù", "other" : "a-benn {0} sizhun" }, "next" : "ar sizhun a zeu", "previous" : "ar sizhun diaraok", "current" : "ar sizhun-mañ" }, "now" : "bremañ", "hour" : { "current" : "this hour", "future" : { "other" : "a-benn {0} eur", "many" : "a-benn {0} a eurioù" }, "past" : { "other" : "{0} eur zo", "many" : "{0} a eurioù zo" } }, "second" : { "future" : { "other" : "a-benn {0} eilenn", "many" : "a-benn {0} a eilennoù" }, "current" : "bremañ", "past" : "{0} eilenn zo" }, "day" : { "next" : "warcʼhoazh", "previous" : "decʼh", "future" : { "many" : "a-benn {0} a zeizioù", "two" : "a-benn {0} zeiz", "other" : "a-benn {0} deiz" }, "current" : "hiziv", "past" : { "other" : "{0} deiz zo", "many" : "{0} a zeizioù zo", "two" : "{0} zeiz zo" } }, "year" : { "next" : "ar bloaz a zeu", "past" : { "many" : "{0} a vloazioù zo", "other" : "{0} vloaz zo", "one" : "{0} bloaz zo", "few" : "{0} bloaz zo" }, "future" : { "other" : "a-benn {0} vloaz", "one" : "a-benn {0} bloaz", "many" : "a-benn {0} a vloazioù", "few" : "a-benn {0} bloaz" }, "previous" : "warlene", "current" : "hevlene" }, "quarter" : { "past" : { "few" : "{0} zrimiziad zo", "other" : "{0} trimiziad zo", "many" : "{0} a zrimiziadoù zo", "two" : "{0} drimiziad zo" }, "current" : "this quarter", "next" : "next quarter", "future" : { "many" : "a-benn {0} a drimiziadoù", "few" : "a-benn {0} zrimiziad", "two" : "a-benn {0} drimiziad", "other" : "a-benn {0} trimiziad" }, "previous" : "last quarter" }, "month" : { "previous" : "ar miz diaraok", "next" : "ar miz a zeu", "future" : { "two" : "a-benn {0} viz", "many" : "a-benn {0} a vizioù", "other" : "a-benn {0} miz" }, "current" : "ar miz-mañ", "past" : { "two" : "{0} viz zo", "other" : "{0} miz zo", "many" : "{0} a vizioù zo" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/bs-Cyrl.json ================================================ { "narrow" : { "hour" : { "future" : { "other" : "за {0} сати", "few" : "за {0} сата", "one" : "за {0} сат" }, "past" : { "few" : "пре {0} сата", "other" : "пре {0} сати", "one" : "пре {0} сат" }, "current" : "this hour" }, "quarter" : { "next" : "next quarter", "current" : "this quarter", "past" : "-{0} Q", "future" : "+{0} Q", "previous" : "last quarter" }, "week" : { "previous" : "Прошле недеље", "current" : "Ове недеље", "future" : { "other" : "за {0} недеља", "few" : "за {0} недеље", "one" : "за {0} недељу" }, "past" : { "one" : "пре {0} недељу", "other" : "пре {0} недеља", "few" : "пре {0} недеље" }, "next" : "Следеће недеље" }, "day" : { "next" : "сутра", "past" : { "one" : "пре {0} дан", "other" : "пре {0} дана" }, "future" : { "one" : "за {0} дан", "other" : "за {0} дана" }, "current" : "данас", "previous" : "јуче" }, "second" : { "past" : { "few" : "пре {0} секунде", "other" : "пре {0} секунди", "one" : "пре {0} секунд" }, "current" : "now", "future" : { "one" : "за {0} секунд", "few" : "за {0} секунде", "other" : "за {0} секунди" } }, "year" : { "past" : { "one" : "пре {0} годину", "few" : "пре {0} године", "other" : "пре {0} година" }, "future" : { "one" : "за {0} годину", "few" : "за {0} године", "other" : "за {0} година" }, "previous" : "Прошле године", "current" : "Ове године", "next" : "Следеће године" }, "month" : { "previous" : "Прошлог месеца", "current" : "Овог месеца", "past" : { "other" : "пре {0} месеци", "one" : "пре {0} месец", "few" : "пре {0} месеца" }, "next" : "Следећег месеца", "future" : { "one" : "за {0} месец", "few" : "за {0} месеца", "other" : "за {0} месеци" } }, "now" : "now", "minute" : { "past" : { "other" : "пре {0} минута", "one" : "пре {0} минут" }, "future" : { "other" : "за {0} минута", "one" : "за {0} минут" }, "current" : "this minute" } }, "long" : { "month" : { "next" : "Следећег месеца", "future" : { "one" : "за {0} месец", "other" : "за {0} месеци", "few" : "за {0} месеца" }, "previous" : "Прошлог месеца", "current" : "Овог месеца", "past" : { "few" : "пре {0} месеца", "one" : "пре {0} месец", "other" : "пре {0} месеци" } }, "minute" : { "current" : "this minute", "past" : { "one" : "пре {0} минут", "other" : "пре {0} минута" }, "future" : { "other" : "за {0} минута", "one" : "за {0} минут" } }, "week" : { "next" : "Следеће недеље", "future" : { "few" : "за {0} недеље", "other" : "за {0} недеља", "one" : "за {0} недељу" }, "previous" : "Прошле недеље", "past" : { "few" : "пре {0} недеље", "other" : "пре {0} недеља", "one" : "пре {0} недељу" }, "current" : "Ове недеље" }, "second" : { "past" : { "other" : "пре {0} секунди", "one" : "пре {0} секунд", "few" : "пре {0} секунде" }, "future" : { "other" : "за {0} секунди", "few" : "за {0} секунде", "one" : "за {0} секунд" }, "current" : "now" }, "hour" : { "future" : { "other" : "за {0} сати", "few" : "за {0} сата", "one" : "за {0} сат" }, "past" : { "one" : "пре {0} сат", "other" : "пре {0} сати", "few" : "пре {0} сата" }, "current" : "this hour" }, "now" : "now", "quarter" : { "next" : "next quarter", "past" : "-{0} Q", "previous" : "last quarter", "future" : "+{0} Q", "current" : "this quarter" }, "year" : { "next" : "Следеће године", "past" : { "one" : "пре {0} годину", "few" : "пре {0} године", "other" : "пре {0} година" }, "current" : "Ове године", "future" : { "few" : "за {0} године", "other" : "за {0} година", "one" : "за {0} годину" }, "previous" : "Прошле године" }, "day" : { "past" : { "one" : "пре {0} дан", "other" : "пре {0} дана" }, "future" : { "one" : "за {0} дан", "other" : "за {0} дана" }, "current" : "данас", "previous" : "јуче", "next" : "сутра" } }, "short" : { "quarter" : { "future" : "+{0} Q", "previous" : "last quarter", "current" : "this quarter", "next" : "next quarter", "past" : "-{0} Q" }, "month" : { "future" : { "other" : "за {0} месеци", "few" : "за {0} месеца", "one" : "за {0} месец" }, "previous" : "Прошлог месеца", "current" : "Овог месеца", "next" : "Следећег месеца", "past" : { "other" : "пре {0} месеци", "one" : "пре {0} месец", "few" : "пре {0} месеца" } }, "year" : { "previous" : "Прошле године", "next" : "Следеће године", "current" : "Ове године", "future" : { "few" : "за {0} године", "other" : "за {0} година", "one" : "за {0} годину" }, "past" : { "one" : "пре {0} годину", "few" : "пре {0} године", "other" : "пре {0} година" } }, "week" : { "past" : { "other" : "пре {0} недеља", "one" : "пре {0} недељу", "few" : "пре {0} недеље" }, "future" : { "one" : "за {0} недељу", "other" : "за {0} недеља", "few" : "за {0} недеље" }, "next" : "Следеће недеље", "current" : "Ове недеље", "previous" : "Прошле недеље" }, "day" : { "past" : { "other" : "пре {0} дана", "one" : "пре {0} дан" }, "next" : "сутра", "previous" : "јуче", "current" : "данас", "future" : { "one" : "за {0} дан", "other" : "за {0} дана" } }, "hour" : { "current" : "this hour", "past" : { "one" : "пре {0} сат", "few" : "пре {0} сата", "other" : "пре {0} сати" }, "future" : { "few" : "за {0} сата", "other" : "за {0} сати", "one" : "за {0} сат" } }, "minute" : { "past" : { "one" : "пре {0} минут", "other" : "пре {0} минута" }, "future" : { "one" : "за {0} минут", "other" : "за {0} минута" }, "current" : "this minute" }, "now" : "now", "second" : { "current" : "now", "past" : { "one" : "пре {0} секунд", "few" : "пре {0} секунде", "other" : "пре {0} секунди" }, "future" : { "other" : "за {0} секунди", "few" : "за {0} секунде", "one" : "за {0} секунд" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/bs.json ================================================ { "narrow" : { "second" : { "past" : "prije {0} sek.", "future" : "za {0} sek.", "current" : "sada" }, "week" : { "current" : "ove sedmice", "next" : "sljedeće sedmice", "past" : "prije {0} sed.", "previous" : "prošle sedmice", "future" : "za {0} sed." }, "year" : { "next" : "sljedeće godine", "previous" : "prošle godine", "current" : "ove godine", "past" : "prije {0} g.", "future" : "za {0} g." }, "minute" : { "current" : "ova minuta", "past" : "prije {0} min.", "future" : "za {0} min." }, "quarter" : { "previous" : "posljednji kvartal", "future" : "za {0} kv.", "next" : "sljedeći kvartal", "current" : "ovaj kvartal", "past" : "prije {0} kv." }, "hour" : { "future" : { "other" : "za {0} sati", "one" : "za {0} sat", "few" : "za {0} sata" }, "past" : { "other" : "prije {0} sati", "one" : "prije {0} sat", "few" : "prije {0} sata" }, "current" : "ovaj sat" }, "day" : { "previous" : "jučer", "current" : "danas", "next" : "sutra", "past" : "prije {0} d.", "future" : "za {0} d." }, "now" : "sada", "month" : { "future" : "za {0} mj.", "previous" : "prošli mjesec", "next" : "sljedeći mjesec", "current" : "ovaj mjesec", "past" : "prije {0} mj." } }, "long" : { "week" : { "previous" : "prošle sedmice", "future" : { "other" : "za {0} sedmica", "one" : "za {0} sedmicu", "few" : "za {0} sedmice" }, "past" : { "few" : "prije {0} sedmice", "one" : "prije {0} sedmicu", "other" : "prije {0} sedmica" }, "current" : "ove sedmice", "next" : "sljedeće sedmice" }, "second" : { "future" : { "few" : "za {0} sekunde", "one" : "za {0} sekundu", "other" : "za {0} sekundi" }, "current" : "sada", "past" : { "one" : "prije {0} sekundu", "other" : "prije {0} sekundi", "few" : "prije {0} sekunde" } }, "minute" : { "future" : { "one" : "za {0} minutu", "few" : "za {0} minute", "other" : "za {0} minuta" }, "current" : "ova minuta", "past" : { "one" : "prije {0} minutu", "other" : "prije {0} minuta", "few" : "prije {0} minute" } }, "quarter" : { "future" : { "one" : "za {0} kvartal", "other" : "za {0} kvartala" }, "current" : "ovaj kvartal", "next" : "sljedeći kvartal", "past" : { "other" : "prije {0} kvartala", "one" : "prije {0} kvartal" }, "previous" : "posljednji kvartal" }, "day" : { "previous" : "jučer", "current" : "danas", "next" : "sutra", "past" : { "one" : "prije {0} dan", "other" : "prije {0} dana" }, "future" : { "other" : "za {0} dana", "one" : "za {0} dan" } }, "now" : "sada", "month" : { "next" : "sljedeći mjesec", "current" : "ovaj mjesec", "future" : { "one" : "za {0} mjesec", "other" : "za {0} mjeseci", "few" : "za {0} mjeseca" }, "previous" : "prošli mjesec", "past" : { "few" : "prije {0} mjeseca", "one" : "prije {0} mjesec", "other" : "prije {0} mjeseci" } }, "hour" : { "past" : { "few" : "prije {0} sata", "other" : "prije {0} sati", "one" : "prije {0} sat" }, "current" : "ovaj sat", "future" : { "few" : "za {0} sata", "other" : "za {0} sati", "one" : "za {0} sat" } }, "year" : { "current" : "ove godine", "past" : { "one" : "prije {0} godinu", "few" : "prije {0} godine", "other" : "prije {0} godina" }, "previous" : "prošle godine", "future" : { "one" : "za {0} godinu", "other" : "za {0} godina", "few" : "za {0} godine" }, "next" : "sljedeće godine" } }, "short" : { "week" : { "previous" : "prošle sedmice", "current" : "ove sedmice", "next" : "sljedeće sedmice", "past" : "prije {0} sed.", "future" : "za {0} sed." }, "day" : { "next" : "sutra", "past" : "prije {0} d.", "future" : "za {0} d.", "previous" : "jučer", "current" : "danas" }, "second" : { "future" : "za {0} sek.", "current" : "sada", "past" : "prije {0} sek." }, "month" : { "previous" : "prošli mjesec", "next" : "sljedeći mjesec", "past" : "prije {0} mj.", "current" : "ovaj mjesec", "future" : "za {0} mj." }, "hour" : { "future" : { "few" : "za {0} sata", "other" : "za {0} sati", "one" : "za {0} sat" }, "current" : "ovaj sat", "past" : { "other" : "prije {0} sati", "one" : "prije {0} sat", "few" : "prije {0} sata" } }, "now" : "sada", "quarter" : { "future" : "za {0} kv.", "next" : "sljedeći kvartal", "previous" : "posljednji kvartal", "current" : "ovaj kvartal", "past" : "prije {0} kv." }, "minute" : { "current" : "ova minuta", "past" : "prije {0} min.", "future" : "za {0} min." }, "year" : { "next" : "sljedeće godine", "future" : "za {0} god.", "current" : "ove godine", "past" : "prije {0} god.", "previous" : "prošle godine" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ca.json ================================================ { "long" : { "minute" : { "current" : "aquest minut", "past" : { "one" : "fa {0} minut", "other" : "fa {0} minuts" }, "future" : { "other" : "d’aquí a {0} minuts", "one" : "d’aquí a {0} minut" } }, "hour" : { "past" : { "other" : "fa {0} hores", "one" : "fa {0} hora" }, "future" : { "one" : "d’aquí a {0} hora", "other" : "d’aquí a {0} hores" }, "current" : "aquesta hora" }, "quarter" : { "current" : "aquest trimestre", "next" : "el trimestre que ve", "previous" : "el trimestre passat", "past" : { "other" : "fa {0} trimestres", "one" : "fa {0} trimestre" }, "future" : { "one" : "d’aquí a {0} trimestre", "other" : "d’aquí a {0} trimestres" } }, "now" : "ara", "year" : { "next" : "l’any que ve", "future" : { "one" : "d’aquí a {0} any", "other" : "d’aquí a {0} anys" }, "past" : { "one" : "fa {0} any", "other" : "fa {0} anys" }, "current" : "enguany", "previous" : "l’any passat" }, "month" : { "future" : { "other" : "d’aquí a {0} mesos", "one" : "d’aquí a {0} mes" }, "previous" : "el mes passat", "next" : "el mes que ve", "past" : { "other" : "fa {0} mesos", "one" : "fa {0} mes" }, "current" : "aquest mes" }, "second" : { "future" : { "one" : "d’aquí a {0} segon", "other" : "d’aquí a {0} segons" }, "current" : "ara", "past" : { "other" : "fa {0} segons", "one" : "fa {0} segon" } }, "day" : { "previous" : "ahir", "current" : "avui", "next" : "demà", "future" : { "other" : "d’aquí a {0} dies", "one" : "d’aquí a {0} dia" }, "past" : { "one" : "fa {0} dia", "other" : "fa {0} dies" } }, "week" : { "current" : "aquesta setmana", "previous" : "la setmana passada", "past" : { "other" : "fa {0} setmanes", "one" : "fa {0} setmana" }, "future" : { "one" : "d’aquí a {0} setmana", "other" : "d’aquí a {0} setmanes" }, "next" : "la setmana que ve" } }, "narrow" : { "year" : { "previous" : "l’any passat", "current" : "enguany", "next" : "l’any que ve", "past" : { "one" : "fa {0} any", "other" : "fa {0} anys" }, "future" : { "other" : "d’aquí a {0} anys", "one" : "d’aquí a {0} any" } }, "second" : { "current" : "ara", "future" : "d’aquí a {0} s", "past" : "fa {0} s" }, "hour" : { "past" : "fa {0} h", "current" : "aquesta hora", "future" : "d‘aquí a {0} h" }, "week" : { "current" : "aquesta setm.", "previous" : "setm. passada", "past" : "fa {0} setm.", "next" : "setm. vinent", "future" : "d’aquí a {0} setm." }, "day" : { "current" : "avui", "next" : "demà", "past" : { "other" : "fa {0} dies", "one" : "fa {0} dia" }, "previous" : "ahir", "future" : { "one" : "d’aquí a {0} dia", "other" : "d’aquí a {0} dies" } }, "month" : { "current" : "aquest mes", "next" : "mes vinent", "past" : { "one" : "fa {0} mes", "other" : "fa {0} mesos" }, "previous" : "mes passat", "future" : { "one" : "d’aquí a {0} mes", "other" : "d’aquí a {0} mesos" } }, "now" : "ara", "minute" : { "current" : "aquest minut", "past" : "fa {0} min", "future" : "d’aquí a {0} min" }, "quarter" : { "previous" : "trim. passat", "current" : "aquest trim.", "past" : "fa {0} trim.", "future" : "d’aquí a {0} trim.", "next" : "trim. vinent" } }, "short" : { "month" : { "next" : "el mes que ve", "past" : { "other" : "fa {0} mesos", "one" : "fa {0} mes" }, "current" : "aquest mes", "previous" : "el mes passat", "future" : { "other" : "d’aquí a {0} mesos", "one" : "d’aquí a {0} mes" } }, "now" : "ara", "day" : { "next" : "demà", "current" : "avui", "previous" : "ahir", "past" : { "one" : "fa {0} dia", "other" : "fa {0} dies" }, "future" : { "one" : "d’aquí a {0} dia", "other" : "d’aquí a {0} dies" } }, "year" : { "current" : "enguany", "next" : "l’any que ve", "past" : { "other" : "fa {0} anys", "one" : "fa {0} any" }, "future" : { "one" : "d’aquí a {0} any", "other" : "d’aquí a {0} anys" }, "previous" : "l’any passat" }, "hour" : { "current" : "aquesta hora", "past" : "fa {0} h", "future" : "d’aquí a {0} h" }, "minute" : { "past" : "fa {0} min", "future" : "d’aquí a {0} min", "current" : "aquest minut" }, "second" : { "future" : "d’aquí a {0} s", "past" : "fa {0} s", "current" : "ara" }, "quarter" : { "current" : "aquest trim.", "future" : "d’aquí a {0} trim.", "previous" : "el trim. passat", "next" : "el trim. que ve", "past" : "fa {0} trim." }, "week" : { "future" : "d’aquí a {0} setm.", "previous" : "la setm. passada", "next" : "la setm. que ve", "past" : "fa {0} setm.", "current" : "aquesta setm." } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/cs.json ================================================ { "long" : { "month" : { "past" : { "many" : "před {0} měsíce", "other" : "před {0} měsíci", "one" : "před {0} měsícem" }, "previous" : "minulý měsíc", "current" : "tento měsíc", "future" : { "one" : "za {0} měsíc", "many" : "za {0} měsíce", "few" : "za {0} měsíce", "other" : "za {0} měsíců" }, "next" : "příští měsíc" }, "year" : { "future" : { "other" : "za {0} let", "many" : "za {0} roku", "one" : "za {0} rok", "few" : "za {0} roky" }, "past" : { "one" : "před {0} rokem", "many" : "před {0} roku", "other" : "před {0} lety" }, "current" : "tento rok", "next" : "příští rok", "previous" : "minulý rok" }, "second" : { "current" : "nyní", "past" : { "one" : "před {0} sekundou", "other" : "před {0} sekundami", "many" : "před {0} sekundy" }, "future" : { "many" : "za {0} sekundy", "other" : "za {0} sekund", "one" : "za {0} sekundu", "few" : "za {0} sekundy" } }, "quarter" : { "future" : "za {0} čtvrtletí", "previous" : "minulé čtvrtletí", "current" : "toto čtvrtletí", "past" : { "one" : "před {0} čtvrtletím", "many" : "před {0} čtvrtletí", "other" : "před {0} čtvrtletími" }, "next" : "příští čtvrtletí" }, "week" : { "past" : { "many" : "před {0} týdne", "one" : "před {0} týdnem", "other" : "před {0} týdny" }, "current" : "tento týden", "future" : { "other" : "za {0} týdnů", "one" : "za {0} týden", "few" : "za {0} týdny", "many" : "za {0} týdne" }, "previous" : "minulý týden", "next" : "příští týden" }, "minute" : { "current" : "tuto minutu", "past" : { "one" : "před {0} minutou", "other" : "před {0} minutami", "many" : "před {0} minuty" }, "future" : { "few" : "za {0} minuty", "one" : "za {0} minutu", "many" : "za {0} minuty", "other" : "za {0} minut" } }, "day" : { "past" : { "other" : "před {0} dny", "one" : "před {0} dnem", "many" : "před {0} dne" }, "previous" : "včera", "current" : "dnes", "next" : "zítra", "future" : { "few" : "za {0} dny", "many" : "za {0} dne", "other" : "za {0} dní", "one" : "za {0} den" } }, "hour" : { "future" : { "many" : "za {0} hodiny", "other" : "za {0} hodin", "one" : "za {0} hodinu", "few" : "za {0} hodiny" }, "current" : "tuto hodinu", "past" : { "many" : "před {0} hodiny", "one" : "před {0} hodinou", "other" : "před {0} hodinami" } }, "now" : "nyní" }, "narrow" : { "hour" : { "future" : "za {0} h", "current" : "tuto hodinu", "past" : "před {0} h" }, "minute" : { "past" : "před {0} min", "future" : "za {0} min", "current" : "tuto minutu" }, "second" : { "past" : "před {0} s", "current" : "nyní", "future" : "za {0} s" }, "now" : "nyní", "quarter" : { "past" : "-{0} Q", "previous" : "minulé čtvrtletí", "next" : "příští čtvrtletí", "future" : "+{0} Q", "current" : "toto čtvrtletí" }, "week" : { "previous" : "minulý týd.", "past" : "před {0} týd.", "next" : "příští týd.", "current" : "tento týd.", "future" : "za {0} týd." }, "year" : { "past" : { "other" : "před {0} l.", "many" : "před {0} r.", "one" : "před {0} r.", "few" : "před {0} r." }, "previous" : "minulý rok", "current" : "tento rok", "next" : "příští rok", "future" : { "few" : "za {0} r.", "many" : "za {0} r.", "one" : "za {0} r.", "other" : "za {0} l." } }, "day" : { "past" : { "one" : "před {0} dnem", "other" : "před {0} dny", "many" : "před {0} dne" }, "previous" : "včera", "future" : { "one" : "za {0} den", "few" : "za {0} dny", "many" : "za {0} dne", "other" : "za {0} dní" }, "next" : "zítra", "current" : "dnes" }, "month" : { "current" : "tento měs.", "next" : "příští měs.", "future" : "za {0} měs.", "past" : "před {0} měs.", "previous" : "minuý měs." } }, "short" : { "year" : { "current" : "tento rok", "previous" : "minulý rok", "next" : "příští rok", "past" : { "few" : "před {0} r.", "one" : "před {0} r.", "many" : "před {0} r.", "other" : "před {0} l." }, "future" : { "few" : "za {0} r.", "many" : "za {0} r.", "other" : "za {0} l.", "one" : "za {0} r." } }, "quarter" : { "next" : "příští čtvrtletí", "future" : "+{0} Q", "current" : "toto čtvrtletí", "past" : "-{0} Q", "previous" : "minulé čtvrtletí" }, "second" : { "past" : "před {0} s", "future" : "za {0} s", "current" : "nyní" }, "minute" : { "future" : "za {0} min", "current" : "tuto minutu", "past" : "před {0} min" }, "day" : { "past" : { "many" : "před {0} dne", "other" : "před {0} dny", "one" : "před {0} dnem" }, "next" : "zítra", "future" : { "one" : "za {0} den", "many" : "za {0} dne", "other" : "za {0} dní", "few" : "za {0} dny" }, "previous" : "včera", "current" : "dnes" }, "month" : { "future" : "za {0} měs.", "next" : "příští měs.", "previous" : "minulý měs.", "current" : "tento měs.", "past" : "před {0} měs." }, "hour" : { "future" : "za {0} h", "current" : "tuto hodinu", "past" : "před {0} h" }, "now" : "nyní", "week" : { "previous" : "minulý týd.", "next" : "příští týd.", "past" : "před {0} týd.", "current" : "tento týd.", "future" : "za {0} týd." } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/cy.json ================================================ { "narrow" : { "now" : "nawr", "minute" : { "past" : "{0} mun. yn ôl", "current" : "y funud hon", "future" : "ymhen {0} mun." }, "day" : { "current" : "heddiw", "future" : "ymhen {0} diwrnod", "next" : "yfory", "past" : { "two" : "{0} ddiwrnod yn ôl", "other" : "{0} diwrnod yn ôl" }, "previous" : "ddoe" }, "month" : { "next" : "mis nesaf", "past" : { "two" : "{0} fis yn ôl", "other" : "{0} mis yn ôl" }, "future" : { "one" : "ymhen mis", "two" : "ymhen deufis", "other" : "ymhen {0} mis" }, "previous" : "mis diwethaf", "current" : "y mis hwn" }, "week" : { "future" : "ymhen {0} wythnos", "previous" : "wythnos ddiwethaf", "current" : "yr wythnos hon", "next" : "wythnos nesaf", "past" : { "two" : "pythefnos yn ôl", "other" : "{0} wythnos yn ôl" } }, "quarter" : { "previous" : "chwarter olaf", "future" : "ymhen {0} chwarter", "next" : "chwarter nesaf", "current" : "chwarter hwn", "past" : { "one" : "{0} chwarter yn ôl", "few" : "{0} chwarter yn ôl", "many" : "{0} chwarter yn ôl", "other" : "{0} o chwarteri yn ôl", "two" : "{0} chwarter yn ôl" } }, "year" : { "future" : { "few" : "ymhen {0} blynedd", "one" : "ymhen blwyddyn", "many" : "ymhen {0} blynedd", "other" : "ymhen {0} mlynedd", "two" : "ymhen {0} flynedd" }, "next" : "blwyddyn nesaf", "past" : { "other" : "{0} o flynyddoedd yn ôl", "two" : "{0} flynedd yn ôl", "few" : "{0} blynedd yn ôl", "many" : "{0} blynedd yn ôl", "one" : "blwyddyn yn ôl" }, "previous" : "llynedd", "current" : "eleni" }, "hour" : { "current" : "yr awr hon", "past" : "{0} awr yn ôl", "future" : "ymhen {0} awr" }, "second" : { "past" : "{0} eiliad yn ôl", "current" : "nawr", "future" : "ymhen {0} eiliad" } }, "long" : { "now" : "nawr", "year" : { "past" : { "one" : "blwyddyn yn ôl", "two" : "{0} flynedd yn ôl", "few" : "{0} blynedd yn ôl", "many" : "{0} blynedd yn ôl", "other" : "{0} o flynyddoedd yn ôl" }, "previous" : "llynedd", "current" : "eleni", "next" : "blwyddyn nesaf", "future" : { "many" : "ymhen {0} blynedd", "few" : "ymhen {0} blynedd", "one" : "ymhen blwyddyn", "other" : "ymhen {0} mlynedd", "two" : "ymhen {0} flynedd" } }, "month" : { "next" : "mis nesaf", "current" : "y mis hwn", "past" : { "two" : "{0} fis yn ôl", "other" : "{0} mis yn ôl" }, "previous" : "mis diwethaf", "future" : { "two" : "ymhen deufis", "other" : "ymhen {0} mis", "one" : "ymhen mis" } }, "week" : { "next" : "wythnos nesaf", "future" : { "other" : "ymhen {0} wythnos", "one" : "ymhen wythnos", "two" : "ymhen pythefnos" }, "current" : "yr wythnos hon", "past" : "{0} wythnos yn ôl", "previous" : "wythnos ddiwethaf" }, "hour" : { "future" : { "one" : "ymhen awr", "other" : "ymhen {0} awr" }, "current" : "yr awr hon", "past" : "{0} awr yn ôl" }, "minute" : { "current" : "y funud hon", "past" : "{0} munud yn ôl", "future" : "ymhen {0} munud" }, "second" : { "future" : "ymhen {0} eiliad", "past" : "{0} eiliad yn ôl", "current" : "nawr" }, "quarter" : { "future" : "ymhen {0} chwarter", "current" : "chwarter hwn", "previous" : "chwarter olaf", "next" : "chwarter nesaf", "past" : { "many" : "{0} chwarter yn ôl", "other" : "{0} o chwarteri yn ôl", "one" : "{0} chwarter yn ôl", "few" : "{0} chwarter yn ôl", "two" : "{0} chwarter yn ôl" } }, "day" : { "future" : { "one" : "ymhen diwrnod", "two" : "ymhen deuddydd", "other" : "ymhen {0} diwrnod" }, "previous" : "ddoe", "current" : "heddiw", "next" : "yfory", "past" : { "two" : "{0} ddiwrnod yn ôl", "other" : "{0} diwrnod yn ôl" } } }, "short" : { "year" : { "current" : "eleni", "past" : { "other" : "{0} o flynyddoedd yn ôl", "many" : "{0} blynedd yn ôl", "one" : "blwyddyn yn ôl", "two" : "{0} flynedd yn ôl", "few" : "{0} blynedd yn ôl" }, "future" : { "few" : "ymhen {0} blynedd", "one" : "ymhen blwyddyn", "two" : "ymhen {0} flynedd", "many" : "ymhen {0} blynedd", "other" : "ymhen {0} mlynedd" }, "next" : "blwyddyn nesaf", "previous" : "llynedd" }, "minute" : { "future" : { "one" : "ymhen {0} mun.", "other" : "ymhen {0} munud", "two" : "ymhen {0} fun." }, "past" : { "two" : "{0} fun. yn ôl", "other" : "{0} munud yn ôl" }, "current" : "y funud hon" }, "day" : { "previous" : "ddoe", "current" : "heddiw", "next" : "yfory", "past" : { "two" : "{0} ddiwrnod yn ôl", "other" : "{0} diwrnod yn ôl" }, "future" : { "two" : "ymhen deuddydd", "one" : "ymhen diwrnod", "other" : "ymhen {0} diwrnod" } }, "second" : { "past" : "{0} eiliad yn ôl", "current" : "nawr", "future" : "ymhen {0} eiliad" }, "now" : "nawr", "month" : { "future" : { "one" : "ymhen mis", "two" : "ymhen deufis", "other" : "ymhen {0} mis" }, "current" : "y mis hwn", "next" : "mis nesaf", "previous" : "mis diwethaf", "past" : { "two" : "deufis yn ôl", "other" : "{0} mis yn ôl" } }, "week" : { "past" : { "two" : "pythefnos yn ôl", "other" : "{0} wythnos yn ôl" }, "previous" : "wythnos ddiwethaf", "future" : { "one" : "ymhen wythnos", "other" : "ymhen {0} wythnos", "two" : "ymhen pythefnos" }, "next" : "wythnos nesaf", "current" : "yr wythnos hon" }, "quarter" : { "next" : "chwarter nesaf", "current" : "chwarter hwn", "previous" : "chwarter olaf", "past" : { "many" : "{0} chwarter yn ôl", "one" : "{0} chwarter yn ôl", "few" : "{0} chwarter yn ôl", "other" : "{0} o chwarteri yn ôl", "two" : "{0} chwarter yn ôl" }, "future" : "ymhen {0} chwarter" }, "hour" : { "current" : "yr awr hon", "future" : { "one" : "ymhen awr", "other" : "ymhen {0} awr" }, "past" : { "other" : "{0} awr yn ôl", "one" : "awr yn ôl" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/da.json ================================================ { "narrow" : { "hour" : { "current" : "i den kommende time", "future" : { "one" : "om {0} time", "other" : "om {0} timer" }, "past" : { "other" : "for {0} timer siden", "one" : "for {0} time siden" } }, "week" : { "previous" : "sidste uge", "current" : "denne uge", "next" : "næste uge", "past" : { "one" : "for {0} uge siden", "other" : "for {0} uger siden" }, "future" : { "one" : "om {0} uge", "other" : "om {0} uger" } }, "second" : { "current" : "nu", "future" : "om {0} sek.", "past" : "for {0} sek. siden" }, "month" : { "previous" : "sidste md.", "next" : "næste md.", "future" : { "other" : "om {0} mdr.", "one" : "om {0} md." }, "current" : "denne md.", "past" : { "other" : "for {0} mdr. siden", "one" : "for {0} md. siden" } }, "minute" : { "past" : "for {0} min. siden", "future" : "om {0} min.", "current" : "i det kommende minut" }, "now" : "nu", "year" : { "future" : "om {0} år", "previous" : "sidste år", "current" : "i år", "next" : "næste år", "past" : "for {0} år siden" }, "quarter" : { "next" : "næste kvt.", "future" : "om {0} kvt.", "previous" : "sidste kvt.", "current" : "dette kvt.", "past" : "for {0} kvt. siden" }, "day" : { "previous" : "i går", "current" : "i dag", "next" : "i morgen", "past" : { "one" : "for {0} dag siden", "other" : "for {0} dage siden" }, "future" : { "one" : "om {0} dag", "other" : "om {0} dage" } } }, "long" : { "day" : { "previous" : "i går", "next" : "i morgen", "future" : { "other" : "om {0} dage", "one" : "om {0} dag" }, "current" : "i dag", "past" : { "one" : "for {0} dag siden", "other" : "for {0} dage siden" } }, "second" : { "past" : { "other" : "for {0} sekunder siden", "one" : "for {0} sekund siden" }, "current" : "nu", "future" : { "one" : "om {0} sekund", "other" : "om {0} sekunder" } }, "minute" : { "past" : { "one" : "for {0} minut siden", "other" : "for {0} minutter siden" }, "current" : "i det kommende minut", "future" : { "one" : "om {0} minut", "other" : "om {0} minutter" } }, "week" : { "current" : "denne uge", "past" : { "one" : "for {0} uge siden", "other" : "for {0} uger siden" }, "future" : { "other" : "om {0} uger", "one" : "om {0} uge" }, "previous" : "sidste uge", "next" : "næste uge" }, "now" : "nu", "year" : { "current" : "i år", "next" : "næste år", "past" : "for {0} år siden", "previous" : "sidste år", "future" : "om {0} år" }, "month" : { "previous" : "sidste måned", "past" : { "one" : "for {0} måned siden", "other" : "for {0} måneder siden" }, "future" : { "one" : "om {0} måned", "other" : "om {0} måneder" }, "current" : "denne måned", "next" : "næste måned" }, "hour" : { "future" : { "one" : "om {0} time", "other" : "om {0} timer" }, "past" : { "other" : "for {0} timer siden", "one" : "for {0} time siden" }, "current" : "i den kommende time" }, "quarter" : { "current" : "dette kvartal", "future" : { "other" : "om {0} kvartaler", "one" : "om {0} kvartal" }, "next" : "næste kvartal", "previous" : "sidste kvartal", "past" : { "other" : "for {0} kvartaler siden", "one" : "for {0} kvartal siden" } } }, "short" : { "hour" : { "future" : { "one" : "om {0} time", "other" : "om {0} timer" }, "past" : { "other" : "for {0} timer siden", "one" : "for {0} time siden" }, "current" : "i den kommende time" }, "now" : "nu", "quarter" : { "current" : "dette kvt.", "future" : "om {0} kvt.", "past" : "for {0} kvt. siden", "next" : "næste kvt.", "previous" : "sidste kvt." }, "day" : { "current" : "i dag", "past" : { "one" : "for {0} dag siden", "other" : "for {0} dage siden" }, "future" : { "one" : "om {0} dag", "other" : "om {0} dage" }, "next" : "i morgen", "previous" : "i går" }, "week" : { "current" : "denne uge", "future" : { "one" : "om {0} uge", "other" : "om {0} uger" }, "previous" : "sidste uge", "next" : "næste uge", "past" : { "one" : "for {0} uge siden", "other" : "for {0} uger siden" } }, "minute" : { "past" : "for {0} min. siden", "current" : "i det kommende minut", "future" : "om {0} min." }, "second" : { "future" : "om {0} sek.", "past" : "for {0} sek. siden", "current" : "nu" }, "month" : { "future" : { "one" : "om {0} md.", "other" : "om {0} mdr." }, "previous" : "sidste md.", "next" : "næste md.", "current" : "denne md.", "past" : { "other" : "for {0} mdr. siden", "one" : "for {0} md. siden" } }, "year" : { "next" : "næste år", "previous" : "sidste år", "current" : "i år", "future" : "om {0} år", "past" : "for {0} år siden" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/de.json ================================================ { "narrow" : { "year" : { "past" : { "other" : "vor {0} Jahren", "one" : "vor {0} Jahr" }, "current" : "dieses Jahr", "future" : { "one" : "in {0} Jahr", "other" : "in {0} Jahren" }, "previous" : "letztes Jahr", "next" : "nächstes Jahr" }, "minute" : { "future" : "in {0} m", "current" : "in dieser Minute", "past" : "vor {0} m" }, "day" : { "current" : "heute", "past" : { "one" : "vor {0} Tag", "other" : "vor {0} Tagen" }, "previous" : "gestern", "next" : "morgen", "future" : { "one" : "in {0} Tag", "other" : "in {0} Tagen" } }, "now" : "jetzt", "week" : { "current" : "diese Woche", "future" : "in {0} Wo.", "next" : "nächste Woche", "past" : "vor {0} Wo.", "previous" : "letzte Woche" }, "second" : { "current" : "jetzt", "past" : "vor {0} s", "future" : "in {0} s" }, "hour" : { "current" : "in dieser Stunde", "future" : "in {0} Std.", "past" : "vor {0} Std." }, "quarter" : { "current" : "dieses Quartal", "previous" : "letztes Quartal", "next" : "nächstes Quartal", "future" : "in {0} Q", "past" : "vor {0} Q" }, "month" : { "current" : "diesen Monat", "future" : { "one" : "in {0} Monat", "other" : "in {0} Monaten" }, "previous" : "letzten Monat", "past" : { "other" : "vor {0} Monaten", "one" : "vor {0} Monat" }, "next" : "nächsten Monat" } }, "long" : { "month" : { "previous" : "letzten Monat", "next" : "nächsten Monat", "past" : { "other" : "vor {0} Monaten", "one" : "vor {0} Monat" }, "current" : "diesen Monat", "future" : { "one" : "in {0} Monat", "other" : "in {0} Monaten" } }, "second" : { "past" : { "one" : "vor {0} Sekunde", "other" : "vor {0} Sekunden" }, "future" : { "other" : "in {0} Sekunden", "one" : "in {0} Sekunde" }, "current" : "jetzt" }, "hour" : { "past" : { "other" : "vor {0} Stunden", "one" : "vor {0} Stunde" }, "current" : "in dieser Stunde", "future" : { "one" : "in {0} Stunde", "other" : "in {0} Stunden" } }, "quarter" : { "previous" : "letztes Quartal", "future" : { "one" : "in {0} Quartal", "other" : "in {0} Quartalen" }, "next" : "nächstes Quartal", "current" : "dieses Quartal", "past" : { "one" : "vor {0} Quartal", "other" : "vor {0} Quartalen" } }, "now" : "jetzt", "minute" : { "current" : "in dieser Minute", "future" : { "other" : "in {0} Minuten", "one" : "in {0} Minute" }, "past" : { "other" : "vor {0} Minuten", "one" : "vor {0} Minute" } }, "day" : { "past" : { "one" : "vor {0} Tag", "other" : "vor {0} Tagen" }, "previous" : "gestern", "current" : "heute", "future" : { "one" : "in {0} Tag", "other" : "in {0} Tagen" }, "next" : "morgen" }, "year" : { "past" : { "other" : "vor {0} Jahren", "one" : "vor {0} Jahr" }, "next" : "nächstes Jahr", "current" : "dieses Jahr", "previous" : "letztes Jahr", "future" : { "one" : "in {0} Jahr", "other" : "in {0} Jahren" } }, "week" : { "current" : "diese Woche", "past" : { "other" : "vor {0} Wochen", "one" : "vor {0} Woche" }, "previous" : "letzte Woche", "future" : { "one" : "in {0} Woche", "other" : "in {0} Wochen" }, "next" : "nächste Woche" } }, "short" : { "minute" : { "past" : "vor {0} Min.", "future" : "in {0} Min.", "current" : "in dieser Minute" }, "week" : { "past" : { "one" : "vor {0} Woche", "other" : "vor {0} Wochen" }, "future" : { "one" : "in {0} Woche", "other" : "in {0} Wochen" }, "next" : "nächste Woche", "current" : "diese Woche", "previous" : "letzte Woche" }, "day" : { "future" : { "one" : "in {0} Tag", "other" : "in {0} Tagen" }, "current" : "heute", "previous" : "gestern", "next" : "morgen", "past" : { "one" : "vor {0} Tag", "other" : "vor {0} Tagen" } }, "second" : { "past" : "vor {0} Sek.", "future" : "in {0} Sek.", "current" : "jetzt" }, "now" : "jetzt", "year" : { "past" : { "other" : "vor {0} Jahren", "one" : "vor {0} Jahr" }, "future" : { "one" : "in {0} Jahr", "other" : "in {0} Jahren" }, "previous" : "letztes Jahr", "current" : "dieses Jahr", "next" : "nächstes Jahr" }, "month" : { "past" : { "other" : "vor {0} Monaten", "one" : "vor {0} Monat" }, "future" : { "other" : "in {0} Monaten", "one" : "in {0} Monat" }, "next" : "nächsten Monat", "previous" : "letzten Monat", "current" : "diesen Monat" }, "quarter" : { "previous" : "letztes Quartal", "future" : "in {0} Quart.", "next" : "nächstes Quartal", "past" : "vor {0} Quart.", "current" : "dieses Quartal" }, "hour" : { "current" : "in dieser Stunde", "past" : "vor {0} Std.", "future" : "in {0} Std." } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/dsb.json ================================================ { "narrow" : { "second" : { "future" : "za {0} s", "current" : "now", "past" : "pśed {0} s" }, "year" : { "next" : "znowa", "past" : "pśed {0} l.", "future" : "za {0} l.", "previous" : "łoni", "current" : "lětosa" }, "month" : { "past" : "pśed {0} mjas.", "next" : "pśiducy mjasec", "future" : "za {0} mjas.", "previous" : "slědny mjasec", "current" : "ten mjasec" }, "minute" : { "future" : "za {0} m", "current" : "this minute", "past" : "pśed {0} m" }, "quarter" : { "previous" : "last quarter", "current" : "this quarter", "next" : "next quarter", "past" : "pśed {0} kw.", "future" : "za {0} kw." }, "hour" : { "future" : "za {0} g", "current" : "this hour", "past" : "pśed {0} g" }, "now" : "now", "week" : { "next" : "pśiducy tyźeń", "previous" : "slědny tyźeń", "past" : "pśed {0} tyź.", "future" : "za {0} tyź.", "current" : "ten tyźeń" }, "day" : { "previous" : "cora", "future" : "za {0} ź", "past" : "pśed {0} d", "current" : "źinsa", "next" : "witśe" } }, "long" : { "week" : { "past" : { "two" : "pśed {0} tyźenjoma", "other" : "pśed {0} tyźenjami", "one" : "pśed {0} tyźenjom" }, "future" : { "other" : "za {0} tyźenjow", "two" : "za {0} tyźenja", "few" : "za {0} tyźenje", "one" : "za {0} tyźeń" }, "previous" : "slědny tyźeń", "current" : "ten tyźeń", "next" : "pśiducy tyźeń" }, "now" : "now", "quarter" : { "previous" : "last quarter", "past" : { "one" : "pśed {0} kwartalom", "two" : "pśed {0} kwartaloma", "other" : "pśed {0} kwartalami" }, "future" : { "one" : "za {0} kwartal", "two" : "za {0} kwartala", "few" : "za {0} kwartale", "other" : "za {0} kwartalow" }, "current" : "this quarter", "next" : "next quarter" }, "month" : { "past" : { "one" : "pśed {0} mjasecom", "other" : "pśed {0} mjasecami", "two" : "pśed {0} mjasecoma" }, "current" : "ten mjasec", "next" : "pśiducy mjasec", "future" : { "one" : "za {0} mjasec", "few" : "za {0} mjasecy", "two" : "za {0} mjaseca", "other" : "za {0} mjasecow" }, "previous" : "slědny mjasec" }, "year" : { "previous" : "łoni", "future" : { "one" : "za {0} lěto", "few" : "za {0} lěta", "other" : "za {0} lět", "two" : "za {0} lěśe" }, "next" : "znowa", "current" : "lětosa", "past" : { "two" : "pśed {0} lětoma", "one" : "pśed {0} lětom", "other" : "pśed {0} lětami" } }, "minute" : { "current" : "this minute", "future" : { "one" : "za {0} minutu", "few" : "za {0} minuty", "two" : "za {0} minuśe", "other" : "za {0} minutow" }, "past" : { "two" : "pśed {0} minutoma", "one" : "pśed {0} minutu", "other" : "pśed {0} minutami" } }, "hour" : { "future" : { "two" : "za {0} góźinje", "few" : "za {0} góźiny", "one" : "za {0} góźinu", "other" : "za {0} góźin" }, "past" : { "one" : "pśed {0} góźinu", "two" : "pśed {0} góźinoma", "other" : "pśed {0} góźinami" }, "current" : "this hour" }, "day" : { "previous" : "cora", "current" : "źinsa", "next" : "witśe", "future" : { "two" : "za {0} dnja", "few" : "za {0} dny", "one" : "za {0} źeń", "other" : "za {0} dnjow" }, "past" : { "other" : "pśed {0} dnjami", "two" : "pśed {0} dnjoma", "one" : "pśed {0} dnjom" } }, "second" : { "future" : { "two" : "za {0} sekunźe", "other" : "za {0} sekundow", "few" : "za {0} sekundy", "one" : "za {0} sekundu" }, "current" : "now", "past" : { "one" : "pśed {0} sekundu", "two" : "pśed {0} sekundoma", "other" : "pśed {0} sekundami" } } }, "short" : { "minute" : { "past" : "pśed {0} min.", "current" : "this minute", "future" : "za {0} min." }, "month" : { "current" : "ten mjasec", "past" : "pśed {0} mjas.", "future" : "za {0} mjas.", "next" : "pśiducy mjasec", "previous" : "slědny mjasec" }, "week" : { "current" : "ten tyźeń", "past" : "pśed {0} tyź.", "future" : "za {0} tyź.", "next" : "pśiducy tyźeń", "previous" : "slědny tyźeń" }, "hour" : { "past" : "pśed {0} góź.", "future" : "za {0} góź.", "current" : "this hour" }, "day" : { "next" : "witśe", "current" : "źinsa", "previous" : "cora", "past" : "pśed {0} dnj.", "future" : { "few" : "za {0} dny", "other" : "za {0} dnj.", "one" : "za {0} źeń" } }, "second" : { "current" : "now", "past" : "pśed {0} sek.", "future" : "za {0} sek." }, "now" : "now", "year" : { "future" : "za {0} l.", "previous" : "łoni", "next" : "znowa", "current" : "lětosa", "past" : "pśed {0} l." }, "quarter" : { "current" : "this quarter", "future" : "za {0} kwart.", "previous" : "last quarter", "next" : "next quarter", "past" : "pśed {0} kwart." } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/dz.json ================================================ { "narrow" : { "quarter" : { "current" : "this quarter", "past" : "-{0} Q", "next" : "next quarter", "future" : "+{0} Q", "previous" : "last quarter" }, "day" : { "next" : "ནངས་པ་", "previous" : "ཁ་ཙ་", "future" : "ཉིནམ་ {0} ནང་", "past" : "ཉིནམ་ {0} ཧེ་མ་", "current" : "ད་རིས་" }, "year" : { "past" : "ལོ་འཁོར་ {0} ཧེ་མ་", "previous" : "last year", "future" : "ལོ་འཁོར་ {0} ནང་", "next" : "next year", "current" : "this year" }, "minute" : { "current" : "this minute", "past" : "སྐར་མ་ {0} ཧེ་མ་", "future" : "སྐར་མ་ {0} ནང་" }, "now" : "now", "week" : { "current" : "this week", "previous" : "last week", "past" : "བངུན་ཕྲག་ {0} ཧེ་མ་", "next" : "next week", "future" : "བངུན་ཕྲག་ {0} ནང་" }, "second" : { "current" : "now", "future" : "སྐར་ཆ་ {0} ནང་", "past" : "སྐར་ཆ་ {0} ཧེ་མ་" }, "month" : { "future" : "ཟླཝ་ {0} ནང་", "current" : "this month", "past" : "ཟླཝ་ {0} ཧེ་མ་", "previous" : "last month", "next" : "next month" }, "hour" : { "past" : "ཆུ་ཚོད་ {0} ཧེ་མ་", "future" : "ཆུ་ཚོད་ {0} ནང་", "current" : "this hour" } }, "long" : { "year" : { "next" : "next year", "previous" : "last year", "past" : "ལོ་འཁོར་ {0} ཧེ་མ་", "future" : "ལོ་འཁོར་ {0} ནང་", "current" : "this year" }, "now" : "now", "quarter" : { "next" : "next quarter", "past" : "-{0} Q", "future" : "+{0} Q", "current" : "this quarter", "previous" : "last quarter" }, "month" : { "past" : "ཟླཝ་ {0} ཧེ་མ་", "next" : "next month", "previous" : "last month", "future" : "ཟླཝ་ {0} ནང་", "current" : "this month" }, "second" : { "past" : "སྐར་ཆ་ {0} ཧེ་མ་", "future" : "སྐར་ཆ་ {0} ནང་", "current" : "now" }, "week" : { "past" : "བངུན་ཕྲག་ {0} ཧེ་མ་", "previous" : "last week", "future" : "བངུན་ཕྲག་ {0} ནང་", "next" : "next week", "current" : "this week" }, "day" : { "next" : "ནངས་པ་", "future" : "ཉིནམ་ {0} ནང་", "previous" : "ཁ་ཙ་", "current" : "ད་རིས་", "past" : "ཉིནམ་ {0} ཧེ་མ་" }, "minute" : { "past" : "སྐར་མ་ {0} ཧེ་མ་", "future" : "སྐར་མ་ {0} ནང་", "current" : "this minute" }, "hour" : { "current" : "this hour", "past" : "ཆུ་ཚོད་ {0} ཧེ་མ་", "future" : "ཆུ་ཚོད་ {0} ནང་" } }, "short" : { "minute" : { "current" : "this minute", "past" : "སྐར་མ་ {0} ཧེ་མ་", "future" : "སྐར་མ་ {0} ནང་" }, "week" : { "future" : "བངུན་ཕྲག་ {0} ནང་", "previous" : "last week", "next" : "next week", "current" : "this week", "past" : "བངུན་ཕྲག་ {0} ཧེ་མ་" }, "year" : { "current" : "this year", "previous" : "last year", "future" : "ལོ་འཁོར་ {0} ནང་", "past" : "ལོ་འཁོར་ {0} ཧེ་མ་", "next" : "next year" }, "month" : { "next" : "next month", "past" : "ཟླཝ་ {0} ཧེ་མ་", "future" : "ཟླཝ་ {0} ནང་", "previous" : "last month", "current" : "this month" }, "quarter" : { "next" : "next quarter", "past" : "-{0} Q", "previous" : "last quarter", "current" : "this quarter", "future" : "+{0} Q" }, "day" : { "current" : "ད་རིས་", "previous" : "ཁ་ཙ་", "past" : "ཉིནམ་ {0} ཧེ་མ་", "next" : "ནངས་པ་", "future" : "ཉིནམ་ {0} ནང་" }, "hour" : { "future" : "ཆུ་ཚོད་ {0} ནང་", "current" : "this hour", "past" : "ཆུ་ཚོད་ {0} ཧེ་མ་" }, "second" : { "current" : "now", "past" : "སྐར་ཆ་ {0} ཧེ་མ་", "future" : "སྐར་ཆ་ {0} ནང་" }, "now" : "now" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ee.json ================================================ { "narrow" : { "day" : { "current" : "egbe", "next" : "etsɔ si gbɔna", "future" : { "one" : "le ŋkeke {0} me", "other" : "le ŋkeke {0} wo me" }, "past" : { "one" : "ŋkeke {0} si va yi", "other" : "ŋkeke {0} si wo va yi" }, "previous" : "etsɔ si va yi" }, "week" : { "next" : "kɔsiɖa si gbɔ na", "previous" : "kɔsiɖa si va yi", "future" : { "other" : "le kɔsiɖa {0} wo me", "one" : "le kɔsiɖa {0} me" }, "current" : "kɔsiɖa sia", "past" : { "one" : "kɔsiɖa {0} si va yi", "other" : "kɔsiɖa {0} si wo va yi" } }, "year" : { "current" : "ƒe sia", "past" : "ƒe {0} si va yi me", "next" : "ƒe si gbɔ na", "previous" : "ƒe si va yi", "future" : "le ƒe {0} si gbɔna me" }, "hour" : { "past" : { "other" : "gaƒoƒo {0} si wo va yi", "one" : "gaƒoƒo {0} si va yi" }, "future" : { "other" : "le gaƒoƒo {0} wo me", "one" : "le gaƒoƒo {0} me" }, "current" : "this hour" }, "now" : "fifi", "second" : { "past" : { "one" : "sekend {0} si va yi", "other" : "sekend {0} si wo va yi" }, "current" : "fifi", "future" : { "other" : "le sekend {0} wo me", "one" : "le sekend {0} me" } }, "minute" : { "future" : { "other" : "le aɖabaƒoƒo {0} wo me", "one" : "le aɖabaƒoƒo {0} me" }, "current" : "this minute", "past" : { "one" : "aɖabaƒoƒo {0} si va yi", "other" : "aɖabaƒoƒo {0} si wo va yi" } }, "quarter" : { "current" : "this quarter", "future" : { "one" : "le kɔta {0} si gbɔna me", "other" : "le kɔta {0} si gbɔ na me" }, "next" : "next quarter", "previous" : "last quarter", "past" : "kɔta {0} si va yi me" }, "month" : { "next" : "ɣleti si gbɔ na", "past" : { "other" : "ɣleti {0} si wo va yi", "one" : "ɣleti {0} si va yi" }, "future" : { "one" : "le ɣleti {0} me", "other" : "le ɣleti {0} wo me" }, "previous" : "ɣleti si va yi", "current" : "ɣleti sia" } }, "long" : { "second" : { "past" : { "other" : "sekend {0} si wo va yi", "one" : "sekend {0} si va yi" }, "future" : { "one" : "le sekend {0} me", "other" : "le sekend {0} wo me" }, "current" : "fifi" }, "week" : { "current" : "kɔsiɖa sia", "next" : "kɔsiɖa si gbɔ na", "previous" : "kɔsiɖa si va yi", "past" : { "one" : "kɔsiɖa {0} si va yi", "other" : "kɔsiɖa {0} si wo va yi" }, "future" : { "one" : "le kɔsiɖa {0} me", "other" : "le kɔsiɖa {0} wo me" } }, "day" : { "past" : { "other" : "ŋkeke {0} si wo va yi", "one" : "ŋkeke {0} si va yi" }, "previous" : "etsɔ si va yi", "next" : "etsɔ si gbɔna", "future" : { "one" : "le ŋkeke {0} me", "other" : "le ŋkeke {0} wo me" }, "current" : "egbe" }, "now" : "fifi", "hour" : { "future" : { "other" : "le gaƒoƒo {0} wo me", "one" : "le gaƒoƒo {0} me" }, "past" : { "one" : "gaƒoƒo {0} si va yi", "other" : "gaƒoƒo {0} si wo va yi" }, "current" : "this hour" }, "month" : { "future" : { "one" : "le ɣleti {0} me", "other" : "le ɣleti {0} wo me" }, "past" : { "other" : "ɣleti {0} si wo va yi", "one" : "ɣleti {0} si va yi" }, "current" : "ɣleti sia", "next" : "ɣleti si gbɔ na", "previous" : "ɣleti si va yi" }, "minute" : { "past" : { "other" : "aɖabaƒoƒo {0} si wo va yi", "one" : "aɖabaƒoƒo {0} si va yi" }, "future" : { "other" : "le aɖabaƒoƒo {0} wo me", "one" : "le aɖabaƒoƒo {0} me" }, "current" : "this minute" }, "year" : { "next" : "ƒe si gbɔ na", "previous" : "ƒe si va yi", "past" : { "one" : "ƒe {0} si va yi", "other" : "ƒe {0} si wo va yi" }, "future" : "le ƒe {0} me", "current" : "ƒe sia" }, "quarter" : { "future" : "le kɔta {0} si gbɔ na me", "current" : "this quarter", "past" : "kɔta {0} si va yi me", "next" : "next quarter", "previous" : "last quarter" } }, "short" : { "week" : { "future" : { "one" : "le kɔsiɖa {0} me", "other" : "le kɔsiɖa {0} wo me" }, "next" : "kɔsiɖa si gbɔ na", "current" : "kɔsiɖa sia", "past" : { "one" : "kɔsiɖa {0} si va yi", "other" : "kɔsiɖa {0} si wo va yi" }, "previous" : "kɔsiɖa si va yi" }, "minute" : { "current" : "this minute", "future" : { "one" : "le aɖabaƒoƒo {0} me", "other" : "le aɖabaƒoƒo {0} wo me" }, "past" : { "one" : "aɖabaƒoƒo {0} si va yi", "other" : "aɖabaƒoƒo {0} si wo va yi" } }, "day" : { "next" : "etsɔ si gbɔna", "current" : "egbe", "previous" : "etsɔ si va yi", "future" : { "one" : "le ŋkeke {0} me", "other" : "le ŋkeke {0} wo me" }, "past" : { "other" : "ŋkeke {0} si wo va yi", "one" : "ŋkeke {0} si va yi" } }, "now" : "fifi", "second" : { "past" : { "one" : "sekend {0} si va yi", "other" : "sekend {0} si wo va yi" }, "future" : { "other" : "le sekend {0} wo me", "one" : "le sekend {0} me" }, "current" : "fifi" }, "hour" : { "current" : "this hour", "past" : { "one" : "gaƒoƒo {0} si va yi", "other" : "gaƒoƒo {0} si wo va yi" }, "future" : { "one" : "le gaƒoƒo {0} me", "other" : "le gaƒoƒo {0} wo me" } }, "month" : { "current" : "ɣleti sia", "past" : { "one" : "ɣleti {0} si va yi", "other" : "ɣleti {0} si wo va yi" }, "previous" : "ɣleti si va yi", "next" : "ɣleti si gbɔ na", "future" : { "other" : "le ɣleti {0} wo me", "one" : "le ɣleti {0} me" } }, "year" : { "next" : "ƒe si gbɔ na", "future" : "le ƒe {0} me", "current" : "ƒe sia", "past" : "le ƒe {0} si va yi me", "previous" : "ƒe si va yi" }, "quarter" : { "future" : "le kɔta {0} si gbɔ na me", "next" : "next quarter", "previous" : "last quarter", "current" : "this quarter", "past" : "kɔta {0} si va yi me" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/el.json ================================================ { "narrow" : { "now" : "τώρα", "hour" : { "future" : "σε {0} ώ.", "past" : "{0} ώ. πριν", "current" : "τρέχουσα ώρα" }, "minute" : { "past" : "{0} λ. πριν", "current" : "τρέχον λεπτό", "future" : "σε {0} λ." }, "second" : { "future" : "σε {0} δ.", "current" : "τώρα", "past" : "{0} δ. πριν" }, "year" : { "next" : "επόμενο έτος", "future" : { "one" : "σε {0} έτος", "other" : "σε {0} έτη" }, "previous" : "πέρσι", "current" : "φέτος", "past" : { "one" : "{0} έτος πριν", "other" : "{0} έτη πριν" } }, "week" : { "past" : "{0} εβδ. πριν", "next" : "επόμενη εβδομάδα", "future" : "σε {0} εβδ.", "previous" : "προηγούμενη εβδομάδα", "current" : "τρέχουσα εβδομάδα" }, "month" : { "previous" : "προηγούμενος μήνας", "current" : "τρέχων μήνας", "next" : "επόμενος μήνας", "past" : "{0} μ. πριν", "future" : "σε {0} μ." }, "quarter" : { "next" : "επόμ. τρίμ.", "past" : "{0} τρίμ. πριν", "future" : "σε {0} τρίμ.", "previous" : "προηγ. τρίμ.", "current" : "τρέχον τρίμ." }, "day" : { "next" : "αύριο", "previous" : "χθες", "past" : "{0} ημ. πριν", "future" : "σε {0} ημ.", "current" : "σήμερα" } }, "long" : { "week" : { "future" : { "other" : "σε {0} εβδομάδες", "one" : "σε {0} εβδομάδα" }, "past" : { "other" : "πριν από {0} εβδομάδες", "one" : "πριν από {0} εβδομάδα" }, "next" : "επόμενη εβδομάδα", "current" : "τρέχουσα εβδομάδα", "previous" : "προηγούμενη εβδομάδα" }, "minute" : { "current" : "τρέχον λεπτό", "future" : { "other" : "σε {0} λεπτά", "one" : "σε {0} λεπτό" }, "past" : { "one" : "πριν από {0} λεπτό", "other" : "πριν από {0} λεπτά" } }, "month" : { "current" : "τρέχων μήνας", "past" : { "other" : "πριν από {0} μήνες", "one" : "πριν από {0} μήνα" }, "previous" : "προηγούμενος μήνας", "future" : { "one" : "σε {0} μήνα", "other" : "σε {0} μήνες" }, "next" : "επόμενος μήνας" }, "hour" : { "future" : { "one" : "σε {0} ώρα", "other" : "σε {0} ώρες" }, "past" : { "one" : "πριν από {0} ώρα", "other" : "πριν από {0} ώρες" }, "current" : "τρέχουσα ώρα" }, "year" : { "previous" : "πέρσι", "past" : { "one" : "πριν από {0} έτος", "other" : "πριν από {0} έτη" }, "next" : "επόμενο έτος", "future" : { "one" : "σε {0} έτος", "other" : "σε {0} έτη" }, "current" : "φέτος" }, "day" : { "current" : "σήμερα", "previous" : "χθες", "next" : "αύριο", "future" : { "other" : "σε {0} ημέρες", "one" : "σε {0} ημέρα" }, "past" : { "other" : "πριν από {0} ημέρες", "one" : "πριν από {0} ημέρα" } }, "second" : { "current" : "τώρα", "future" : { "other" : "σε {0} δευτερόλεπτα", "one" : "σε {0} δευτερόλεπτο" }, "past" : { "one" : "πριν από {0} δευτερόλεπτο", "other" : "πριν από {0} δευτερόλεπτα" } }, "quarter" : { "previous" : "προηγούμενο τρίμηνο", "current" : "τρέχον τρίμηνο", "next" : "επόμενο τρίμηνο", "past" : { "one" : "πριν από {0} τρίμηνο", "other" : "πριν από {0} τρίμηνα" }, "future" : { "one" : "σε {0} τρίμηνο", "other" : "σε {0} τρίμηνα" } }, "now" : "τώρα" }, "short" : { "hour" : { "future" : "σε {0} ώ.", "current" : "τρέχουσα ώρα", "past" : "πριν από {0} ώ." }, "now" : "τώρα", "quarter" : { "current" : "τρέχον τρίμ.", "future" : "σε {0} τρίμ.", "past" : "πριν από {0} τρίμ.", "next" : "επόμ. τρίμ.", "previous" : "προηγ. τρίμ." }, "day" : { "current" : "σήμερα", "past" : "πριν από {0} ημ.", "future" : "σε {0} ημ.", "next" : "αύριο", "previous" : "χθες" }, "week" : { "current" : "τρέχουσα εβδομάδα", "past" : "πριν από {0} εβδ.", "future" : "σε {0} εβδ.", "next" : "επόμενη εβδομάδα", "previous" : "προηγούμενη εβδομάδα" }, "minute" : { "future" : "σε {0} λεπ.", "current" : "τρέχον λεπτό", "past" : "πριν από {0} λεπ." }, "second" : { "future" : "σε {0} δευτ.", "current" : "τώρα", "past" : "πριν από {0} δευτ." }, "month" : { "current" : "τρέχων μήνας", "future" : { "one" : "σε {0} μήνα", "other" : "σε {0} μήνες" }, "previous" : "προηγούμενος μήνας", "next" : "επόμενος μήνας", "past" : { "one" : "πριν από {0} μήνα", "other" : "πριν από {0} μήνες" } }, "year" : { "future" : { "one" : "σε {0} έτος", "other" : "σε {0} έτη" }, "previous" : "πέρσι", "next" : "επόμενο έτος", "current" : "φέτος", "past" : { "other" : "πριν από {0} έτη", "one" : "πριν από {0} έτος" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/en.json ================================================ { "long" : { "quarter" : { "next" : "next quarter", "previous" : "last quarter", "current" : "this quarter", "future" : { "one" : "in {0} quarter", "other" : "in {0} quarters" }, "past" : { "one" : "{0} quarter ago", "other" : "{0} quarters ago" } }, "month" : { "next" : "next month", "past" : { "one" : "{0} month ago", "other" : "{0} months ago" }, "future" : { "one" : "in {0} month", "other" : "in {0} months" }, "previous" : "last month", "current" : "this month" }, "hour" : { "past" : { "other" : "{0} hours ago", "one" : "{0} hour ago" }, "future" : { "one" : "in {0} hour", "other" : "in {0} hours" }, "current" : "this hour" }, "week" : { "previous" : "last week", "current" : "this week", "future" : { "one" : "in {0} week", "other" : "in {0} weeks" }, "next" : "next week", "past" : { "one" : "{0} week ago", "other" : "{0} weeks ago" } }, "minute" : { "current" : "this minute", "past" : { "one" : "{0} minute ago", "other" : "{0} minutes ago" }, "future" : { "one" : "in {0} minute", "other" : "in {0} minutes" } }, "now" : "now", "year" : { "next" : "next year", "future" : { "one" : "in {0} year", "other" : "in {0} years" }, "past" : { "other" : "{0} years ago", "one" : "{0} year ago" }, "current" : "this year", "previous" : "last year" }, "second" : { "current" : "now", "past" : { "other" : "{0} seconds ago", "one" : "{0} second ago" }, "future" : { "one" : "in {0} second", "other" : "in {0} seconds" } }, "day" : { "next" : "tomorrow", "past" : { "other" : "{0} days ago", "one" : "{0} day ago" }, "future" : { "other" : "in {0} days", "one" : "in {0} day" }, "previous" : "yesterday", "current" : "today" } }, "short" : { "month" : { "future" : "in {0} mo.", "previous" : "last mo.", "current" : "this mo.", "next" : "next mo.", "past" : "{0} mo. ago" }, "day" : { "past" : { "one" : "{0} day ago", "other" : "{0} days ago" }, "previous" : "yesterday", "current" : "today", "next" : "tomorrow", "future" : { "other" : "in {0} days", "one" : "in {0} day" } }, "hour" : { "current" : "this hour", "future" : "in {0} hr.", "past" : "{0} hr. ago" }, "second" : { "past" : "{0} sec. ago", "future" : "in {0} sec.", "current" : "now" }, "year" : { "current" : "this yr.", "previous" : "last yr.", "future" : "in {0} yr.", "past" : "{0} yr. ago", "next" : "next yr." }, "week" : { "previous" : "last wk.", "current" : "this wk.", "future" : "in {0} wk.", "past" : "{0} wk. ago", "next" : "next wk." }, "minute" : { "current" : "this minute", "future" : "in {0} min.", "past" : "{0} min. ago" }, "now" : "now", "quarter" : { "next" : "next qtr.", "previous" : "last qtr.", "current" : "this qtr.", "past" : { "other" : "{0} qtrs. ago", "one" : "{0} qtr. ago" }, "future" : { "one" : "in {0} qtr.", "other" : "in {0} qtrs." } } }, "narrow" : { "hour" : { "future" : "in {0} hr.", "current" : "this hour", "past" : "{0} hr. ago" }, "week" : { "next" : "next wk.", "previous" : "last wk.", "past" : "{0} wk. ago", "current" : "this wk.", "future" : "in {0} wk." }, "minute" : { "current" : "this minute", "past" : "{0} min. ago", "future" : "in {0} min." }, "second" : { "current" : "now", "past" : "{0} sec. ago", "future" : "in {0} sec." }, "now" : "now", "month" : { "past" : "{0} mo. ago", "current" : "this mo.", "next" : "next mo.", "previous" : "last mo.", "future" : "in {0} mo." }, "day" : { "current" : "today", "future" : { "one" : "in {0} day", "other" : "in {0} days" }, "previous" : "yesterday", "next" : "tomorrow", "past" : { "one" : "{0} day ago", "other" : "{0} days ago" } }, "year" : { "previous" : "last yr.", "current" : "this yr.", "past" : "{0} yr. ago", "next" : "next yr.", "future" : "in {0} yr." }, "quarter" : { "previous" : "last qtr.", "next" : "next qtr.", "past" : { "one" : "{0} qtr. ago", "other" : "{0} qtrs. ago" }, "future" : { "one" : "in {0} qtr.", "other" : "in {0} qtrs." }, "current" : "this qtr." } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/es.json ================================================ { "narrow" : { "quarter" : { "previous" : "el trimestre pasado", "current" : "este trimestre", "next" : "el próximo trimestre", "past" : "hace {0} trim.", "future" : "dentro de {0} trim." }, "month" : { "past" : "hace {0} m", "next" : "el próximo mes", "future" : "dentro de {0} m", "previous" : "el mes pasado", "current" : "este mes" }, "now" : "ahora", "hour" : { "future" : "dentro de {0} h", "past" : "hace {0} h", "current" : "esta hora" }, "minute" : { "past" : "hace {0} min", "current" : "este minuto", "future" : "dentro de {0} min" }, "day" : { "previous" : "ayer", "future" : { "other" : "dentro de {0} días", "one" : "dentro de {0} día" }, "past" : { "one" : "hace {0} día", "other" : "hace {0} días" }, "current" : "hoy", "next" : "mañana" }, "year" : { "next" : "el próximo año", "past" : "hace {0} a", "future" : "dentro de {0} a", "previous" : "el año pasado", "current" : "este año" }, "week" : { "next" : "la próxima semana", "previous" : "la semana pasada", "past" : "hace {0} sem.", "future" : "dentro de {0} sem.", "current" : "esta semana" }, "second" : { "future" : "dentro de {0} s", "past" : "hace {0} s", "current" : "ahora" } }, "short" : { "minute" : { "current" : "este minuto", "past" : "hace {0} min", "future" : "dentro de {0} min" }, "month" : { "current" : "este mes", "past" : "hace {0} m", "future" : "dentro de {0} m", "next" : "el próximo mes", "previous" : "el mes pasado" }, "week" : { "current" : "esta semana", "past" : "hace {0} sem.", "future" : "dentro de {0} sem.", "next" : "la próxima semana", "previous" : "la semana pasada" }, "hour" : { "past" : "hace {0} h", "current" : "esta hora", "future" : "dentro de {0} h" }, "day" : { "next" : "mañana", "current" : "hoy", "previous" : "ayer", "past" : { "other" : "hace {0} días", "one" : "hace {0} día" }, "future" : { "one" : "dentro de {0} día", "other" : "dentro de {0} días" } }, "second" : { "future" : "dentro de {0} s", "current" : "ahora", "past" : "hace {0} s" }, "now" : "ahora", "year" : { "future" : "dentro de {0} a", "previous" : "el año pasado", "next" : "el próximo año", "current" : "este año", "past" : "hace {0} a" }, "quarter" : { "current" : "este trimestre", "future" : "dentro de {0} trim.", "previous" : "el trimestre pasado", "next" : "el próximo trimestre", "past" : "hace {0} trim." } }, "long" : { "week" : { "current" : "esta semana", "previous" : "la semana pasada", "next" : "la próxima semana", "future" : { "one" : "dentro de {0} semana", "other" : "dentro de {0} semanas" }, "past" : { "other" : "hace {0} semanas", "one" : "hace {0} semana" } }, "month" : { "future" : { "one" : "dentro de {0} mes", "other" : "dentro de {0} meses" }, "past" : { "one" : "hace {0} mes", "other" : "hace {0} meses" }, "next" : "el próximo mes", "current" : "este mes", "previous" : "el mes pasado" }, "minute" : { "past" : { "one" : "hace {0} minuto", "other" : "hace {0} minutos" }, "future" : { "one" : "dentro de {0} minuto", "other" : "dentro de {0} minutos" }, "current" : "este minuto" }, "now" : "ahora", "year" : { "previous" : "el año pasado", "current" : "este año", "next" : "el próximo año", "past" : { "other" : "hace {0} años", "one" : "hace {0} año" }, "future" : { "other" : "dentro de {0} años", "one" : "dentro de {0} año" } }, "day" : { "current" : "hoy", "future" : { "other" : "dentro de {0} días", "one" : "dentro de {0} día" }, "past" : { "other" : "hace {0} días", "one" : "hace {0} día" }, "next" : "mañana", "previous" : "ayer" }, "quarter" : { "current" : "este trimestre", "past" : { "one" : "hace {0} trimestre", "other" : "hace {0} trimestres" }, "previous" : "el trimestre pasado", "future" : { "one" : "dentro de {0} trimestre", "other" : "dentro de {0} trimestres" }, "next" : "el próximo trimestre" }, "hour" : { "current" : "esta hora", "future" : { "other" : "dentro de {0} horas", "one" : "dentro de {0} hora" }, "past" : { "other" : "hace {0} horas", "one" : "hace {0} hora" } }, "second" : { "future" : { "other" : "dentro de {0} segundos", "one" : "dentro de {0} segundo" }, "current" : "ahora", "past" : { "one" : "hace {0} segundo", "other" : "hace {0} segundos" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/es_AR.json ================================================ { "narrow" : { "day" : { "next" : "mañana", "future" : "dentro de {0} días", "previous" : "ayer", "current" : "hoy", "past" : "hace {0} días" }, "quarter" : { "next" : "el próximo trimestre", "past" : "hace {0} trim.", "future" : "dentro de {0} trim.", "previous" : "el trimestre pasado", "current" : "este trimestre" }, "hour" : { "future" : "dentro de {0} h", "current" : "esta hora", "past" : "hace {0} h" }, "year" : { "next" : "el próximo año", "past" : "hace {0} a", "current" : "este año", "previous" : "el año pasado", "future" : "dentro de {0} a" }, "now" : "ahora", "month" : { "current" : "este mes", "future" : "dentro de {0} m", "past" : "hace {0} m", "next" : "el próximo mes", "previous" : "el mes pasado" }, "week" : { "previous" : "la semana pasada", "current" : "esta semana", "past" : "hace {0} sem.", "future" : "dentro de {0} sem.", "next" : "la próxima semana" }, "minute" : { "past" : "hace {0} min", "current" : "este minuto", "future" : "dentro de {0} min" }, "second" : { "current" : "ahora", "past" : "hace {0} seg.", "future" : "dentro de {0} seg." } }, "long" : { "month" : { "next" : "el próximo mes", "previous" : "el mes pasado", "future" : { "one" : "dentro de {0} mes", "other" : "dentro de {0} meses" }, "current" : "este mes", "past" : { "other" : "hace {0} meses", "one" : "hace {0} mes" } }, "week" : { "future" : { "one" : "dentro de {0} semana", "other" : "dentro de {0} semanas" }, "previous" : "la semana pasada", "next" : "la próxima semana", "current" : "esta semana", "past" : { "one" : "hace {0} semana", "other" : "hace {0} semanas" } }, "second" : { "current" : "ahora", "past" : { "other" : "hace {0} segundos", "one" : "hace {0} segundo" }, "future" : { "one" : "dentro de {0} segundo", "other" : "dentro de {0} segundos" } }, "year" : { "future" : { "one" : "dentro de {0} año", "other" : "dentro de {0} años" }, "previous" : "el año pasado", "next" : "el próximo año", "current" : "este año", "past" : { "one" : "hace {0} año", "other" : "hace {0} años" } }, "day" : { "next" : "mañana", "current" : "hoy", "previous" : "ayer", "future" : { "other" : "dentro de {0} días", "one" : "dentro de {0} día" }, "past" : { "other" : "hace {0} días", "one" : "hace {0} día" } }, "now" : "ahora", "minute" : { "future" : { "one" : "dentro de {0} minuto", "other" : "dentro de {0} minutos" }, "past" : { "other" : "hace {0} minutos", "one" : "hace {0} minuto" }, "current" : "este minuto" }, "quarter" : { "next" : "el próximo trimestre", "future" : { "one" : "dentro de {0} trimestre", "other" : "dentro de {0} trimestres" }, "previous" : "el trimestre pasado", "current" : "este trimestre", "past" : { "one" : "hace {0} trimestre", "other" : "hace {0} trimestres" } }, "hour" : { "future" : { "other" : "dentro de {0} horas", "one" : "dentro de {0} hora" }, "past" : { "other" : "hace {0} horas", "one" : "hace {0} hora" }, "current" : "esta hora" } }, "short" : { "quarter" : { "future" : "dentro de {0} trim.", "next" : "el próximo trimestre", "previous" : "el trimestre pasado", "current" : "este trimestre", "past" : "hace {0} trim." }, "minute" : { "current" : "este minuto", "past" : "hace {0} min", "future" : "dentro de {0} min" }, "year" : { "next" : "el próximo año", "future" : "dentro de {0} a", "current" : "este año", "past" : "hace {0} a", "previous" : "el año pasado" }, "second" : { "past" : "hace {0} seg.", "current" : "ahora", "future" : "dentro de {0} seg." }, "hour" : { "current" : "esta hora", "past" : "hace {0} h", "future" : "dentro de {0} h" }, "now" : "ahora", "month" : { "previous" : "el mes pasado", "next" : "el próximo mes", "past" : "hace {0} m", "current" : "este mes", "future" : "dentro de {0} m" }, "week" : { "previous" : "la semana pasada", "current" : "esta semana", "next" : "la próxima semana", "past" : "hace {0} sem.", "future" : "dentro de {0} sem." }, "day" : { "next" : "mañana", "past" : "hace {0} días", "future" : "dentro de {0} días", "previous" : "ayer", "current" : "hoy" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/es_MX.json ================================================ { "narrow" : { "minute" : { "future" : "dentro de {0} min", "past" : "hace {0} min", "current" : "este minuto" }, "year" : { "past" : "hace {0} a", "current" : "este año", "next" : "el próximo año", "previous" : "el año pasado", "future" : "dentro de {0} a" }, "now" : "ahora", "second" : { "past" : "hace {0} seg.", "future" : "dentro de {0} seg.", "current" : "ahora" }, "day" : { "previous" : "ayer", "current" : "hoy", "next" : "mañana", "past" : { "one" : "hace {0} día", "other" : "hace {0} días" }, "future" : { "other" : "dentro de {0} días", "one" : "dentro de {0} día" } }, "month" : { "past" : "hace {0} m", "current" : "este mes", "next" : "el próximo mes", "future" : "dentro de {0} m", "previous" : "el mes pasado" }, "week" : { "next" : "la próxima semana", "current" : "esta semana", "past" : "hace {0} sem.", "future" : "dentro de {0} sem.", "previous" : "la semana pasada" }, "quarter" : { "current" : "este trimestre", "future" : "dentro de {0} trim.", "next" : "el próximo trimestre", "past" : "hace {0} trim.", "previous" : "el trimestre pasado" }, "hour" : { "future" : "dentro de {0} h", "current" : "esta hora", "past" : "hace {0} h" } }, "short" : { "second" : { "past" : "hace {0} seg.", "future" : "dentro de {0} seg.", "current" : "ahora" }, "now" : "ahora", "hour" : { "current" : "esta hora", "past" : "hace {0} h", "future" : "dentro de {0} h" }, "month" : { "past" : "hace {0} m", "current" : "este mes", "next" : "el próximo mes", "future" : "dentro de {0} m", "previous" : "el mes pasado" }, "day" : { "future" : { "other" : "dentro de {0} días", "one" : "dentro de {0} día" }, "current" : "hoy", "previous" : "ayer", "next" : "mañana", "past" : { "one" : "hace {0} día", "other" : "hace {0} días" } }, "minute" : { "future" : "dentro de {0} min", "current" : "este minuto", "past" : "hace {0} min" }, "week" : { "next" : "la próxima semana", "past" : "hace {0} sem.", "previous" : "la semana pasada", "current" : "esta semana", "future" : "dentro de {0} sem." }, "quarter" : { "current" : "este trimestre", "future" : "dentro de {0} trim.", "next" : "el próximo trimestre", "previous" : "el trimestre pasado", "past" : "hace {0} trim." }, "year" : { "next" : "el próximo año", "current" : "este año", "future" : "dentro de {0} a", "past" : "hace {0} a", "previous" : "el año pasado" } }, "long" : { "week" : { "current" : "esta semana", "previous" : "la semana pasada", "next" : "la próxima semana", "past" : { "one" : "hace {0} semana", "other" : "hace {0} semanas" }, "future" : { "one" : "dentro de {0} semana", "other" : "dentro de {0} semanas" } }, "hour" : { "current" : "esta hora", "future" : { "other" : "dentro de {0} horas", "one" : "dentro de {0} hora" }, "past" : { "other" : "hace {0} horas", "one" : "hace {0} hora" } }, "minute" : { "future" : { "one" : "dentro de {0} minuto", "other" : "dentro de {0} minutos" }, "current" : "este minuto", "past" : { "one" : "hace {0} minuto", "other" : "hace {0} minutos" } }, "day" : { "previous" : "ayer", "current" : "hoy", "future" : { "other" : "dentro de {0} días", "one" : "dentro de {0} día" }, "next" : "mañana", "past" : { "other" : "hace {0} días", "one" : "hace {0} día" } }, "second" : { "current" : "ahora", "future" : { "other" : "dentro de {0} segundos", "one" : "dentro de {0} segundo" }, "past" : { "one" : "hace {0} segundo", "other" : "hace {0} segundos" } }, "now" : "ahora", "month" : { "next" : "el próximo mes", "future" : { "other" : "dentro de {0} meses", "one" : "dentro de {0} mes" }, "previous" : "el mes pasado", "current" : "este mes", "past" : { "other" : "hace {0} meses", "one" : "hace {0} mes" } }, "quarter" : { "previous" : "el trimestre pasado", "future" : { "one" : "dentro de {0} trimestre", "other" : "dentro de {0} trimestres" }, "current" : "este trimestre", "past" : { "one" : "hace {0} trimestre", "other" : "hace {0} trimestres" }, "next" : "el próximo trimestre" }, "year" : { "next" : "el próximo año", "past" : { "one" : "hace {0} año", "other" : "hace {0} años" }, "future" : { "other" : "dentro de {0} años", "one" : "dentro de {0} año" }, "current" : "este año", "previous" : "el año pasado" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/es_PY.json ================================================ { "narrow" : { "minute" : { "current" : "este minuto", "past" : "-{0} min", "future" : "+{0} min" }, "year" : { "previous" : "el año pasado", "past" : "-{0} a", "future" : "en {0} a", "next" : "el próximo año", "current" : "este año" }, "day" : { "next" : "mañana", "previous" : "ayer", "past" : { "one" : "hace {0} día", "other" : "hace {0} días" }, "future" : { "other" : "+{0} días", "one" : "+{0} día" }, "current" : "hoy" }, "month" : { "current" : "este mes", "past" : "-{0} m", "next" : "el próximo mes", "previous" : "el mes pasado", "future" : "+{0} m" }, "hour" : { "current" : "esta hora", "past" : "hace {0} h", "future" : "dentro de {0} h" }, "now" : "ahora", "quarter" : { "past" : "-{0} T", "current" : "este trimestre", "future" : "+{0} T", "previous" : "el trimestre pasado", "next" : "el próximo trimestre" }, "second" : { "future" : "+{0} s", "current" : "ahora", "past" : "hace {0} s" }, "week" : { "past" : "hace {0} sem.", "previous" : "la semana pasada", "current" : "esta semana", "next" : "la semana próxima", "future" : "dentro de {0} sem." } }, "long" : { "year" : { "next" : "el año próximo", "future" : { "one" : "dentro de {0} año", "other" : "dentro de {0} años" }, "previous" : "el año pasado", "current" : "este año", "past" : { "one" : "hace {0} año", "other" : "hace {0} años" } }, "now" : "ahora", "month" : { "future" : { "one" : "en {0} mes", "other" : "en {0} meses" }, "previous" : "el mes pasado", "next" : "el mes próximo", "current" : "este mes", "past" : { "other" : "hace {0} meses", "one" : "hace {0} mes" } }, "quarter" : { "next" : "el próximo trimestre", "previous" : "el trimestre pasado", "future" : { "other" : "dentro de {0} trimetres", "one" : "dentro de {0} trimetre" }, "current" : "este trimestre", "past" : { "other" : "hace {0} trimestres", "one" : "hace {0} trimestre" } }, "day" : { "previous" : "ayer", "future" : { "other" : "dentro de {0} días", "one" : "dentro de {0} día" }, "current" : "hoy", "next" : "mañana", "past" : { "one" : "hace {0} día", "other" : "hace {0} días" } }, "week" : { "next" : "la semana próxima", "current" : "esta semana", "previous" : "la semana pasada", "future" : { "other" : "dentro de {0} semanas", "one" : "dentro de {0} semana" }, "past" : { "one" : "hace {0} semana", "other" : "hace {0} semanas" } }, "hour" : { "current" : "esta hora", "past" : { "one" : "hace {0} hora", "other" : "hace {0} horas" }, "future" : { "one" : "dentro de {0} hora", "other" : "dentro de {0} horas" } }, "minute" : { "future" : { "other" : "dentro de {0} minutos", "one" : "dentro de {0} minuto" }, "current" : "este minuto", "past" : { "one" : "hace {0} minuto", "other" : "hace {0} minutos" } }, "second" : { "current" : "ahora", "future" : { "other" : "dentro de {0} segundos", "one" : "dentro de {0} segundo" }, "past" : { "one" : "hace {0} segundo", "other" : "hace {0} segundos" } } }, "short" : { "minute" : { "current" : "este minuto", "past" : "hace {0} min", "future" : "en {0} min" }, "second" : { "current" : "ahora", "past" : "hace {0} s", "future" : "en {0} s" }, "hour" : { "future" : { "one" : "en {0} h", "other" : "en {0} n" }, "current" : "esta hora", "past" : "hace {0} h" }, "year" : { "next" : "el próximo año", "future" : "en {0} a", "current" : "este año", "past" : "hace {0} a", "previous" : "el año pasado" }, "now" : "ahora", "quarter" : { "previous" : "el trimestre pasado", "current" : "este trimestre", "past" : "hace {0} trim.", "next" : "el próximo trimestre", "future" : { "other" : "en {0} trim", "one" : "en {0} trim." } }, "month" : { "past" : "hace {0} m", "future" : "en {0} m", "next" : "el próximo mes", "previous" : "el mes pasado", "current" : "este mes" }, "week" : { "future" : "en {0} sem.", "next" : "la semana próxima", "current" : "esta semana", "past" : "hace {0} sem.", "previous" : "la semana pasada" }, "day" : { "previous" : "ayer", "current" : "hoy", "next" : "mañana", "past" : { "one" : "hace {0} día", "other" : "hace {0} días" }, "future" : { "one" : "en {0} día", "other" : "en {0} días" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/es_US.json ================================================ { "narrow" : { "second" : { "future" : "dentro de {0} s", "past" : "hace {0} s", "current" : "ahora" }, "month" : { "past" : "hace {0} m", "future" : "dentro de {0} m", "current" : "este mes", "previous" : "el mes pasado", "next" : "el próximo mes" }, "hour" : { "future" : "dentro de {0} h", "past" : "hace {0} h", "current" : "esta hora" }, "week" : { "next" : "la semana próxima", "current" : "esta semana", "past" : "hace {0} sem.", "future" : "dentro de {0} sem.", "previous" : "la semana pasada" }, "day" : { "future" : { "other" : "dentro de {0} días", "one" : "dentro de {0} día" }, "previous" : "ayer", "next" : "mañana", "past" : { "one" : "hace {0} día", "other" : "hace {0} días" }, "current" : "hoy" }, "minute" : { "past" : "hace {0} min", "current" : "este minuto", "future" : "dentro de {0} min" }, "year" : { "next" : "el próximo año", "future" : "dentro de {0} a", "previous" : "el año pasado", "current" : "este año", "past" : "hace {0} a" }, "now" : "ahora", "quarter" : { "previous" : "el trimestre pasado", "next" : "el próximo trimestre", "future" : "dentro de {0} trim.", "current" : "este trimestre", "past" : "hace {0} trim." } }, "long" : { "minute" : { "past" : { "one" : "hace {0} minuto", "other" : "hace {0} minutos" }, "future" : { "one" : "dentro de {0} minuto", "other" : "dentro de {0} minutos" }, "current" : "este minuto" }, "month" : { "past" : { "other" : "hace {0} meses", "one" : "hace {0} mes" }, "next" : "el mes próximo", "previous" : "el mes pasado", "current" : "este mes", "future" : { "one" : "dentro de {0} mes", "other" : "dentro de {0} meses" } }, "hour" : { "future" : { "other" : "dentro de {0} horas", "one" : "dentro de {0} hora" }, "current" : "esta hora", "past" : { "one" : "hace {0} hora", "other" : "hace {0} horas" } }, "second" : { "past" : { "other" : "hace {0} segundos", "one" : "hace {0} segundo" }, "future" : { "one" : "dentro de {0} segundo", "other" : "dentro de {0} segundos" }, "current" : "ahora" }, "now" : "ahora", "year" : { "future" : { "one" : "dentro de {0} año", "other" : "dentro de {0} años" }, "previous" : "el año pasado", "current" : "este año", "next" : "el año próximo", "past" : { "one" : "hace {0} año", "other" : "hace {0} años" } }, "week" : { "future" : { "other" : "dentro de {0} semanas", "one" : "dentro de {0} semana" }, "current" : "esta semana", "previous" : "la semana pasada", "next" : "la semana próxima", "past" : { "one" : "hace {0} semana", "other" : "hace {0} semanas" } }, "quarter" : { "past" : { "one" : "hace {0} trimestre", "other" : "hace {0} trimestres" }, "future" : { "other" : "dentro de {0} trimestres", "one" : "dentro de {0} trimestre" }, "next" : "el próximo trimestre", "current" : "este trimestre", "previous" : "el trimestre pasado" }, "day" : { "current" : "hoy", "past" : { "other" : "hace {0} días", "one" : "hace {0} día" }, "future" : { "one" : "dentro de {0} día", "other" : "dentro de {0} días" }, "next" : "mañana", "previous" : "ayer" } }, "short" : { "hour" : { "past" : "hace {0} h", "current" : "esta hora", "future" : "dentro de {0} h" }, "now" : "ahora", "quarter" : { "current" : "este trimestre", "future" : "dentro de {0} trim.", "past" : "hace {0} trim.", "next" : "el próximo trimestre", "previous" : "el trimestre pasado" }, "day" : { "current" : "hoy", "past" : { "other" : "hace {0} días", "one" : "hace {0} día" }, "future" : { "one" : "dentro de {0} día", "other" : "dentro de {0} días" }, "next" : "mañana", "previous" : "ayer" }, "week" : { "current" : "esta semana", "past" : "hace {0} sem.", "future" : "dentro de {0} sem.", "next" : "la semana próxima", "previous" : "la semana pasada" }, "minute" : { "current" : "este minuto", "past" : "hace {0} min", "future" : "dentro de {0} min" }, "second" : { "future" : "dentro de {0} s", "current" : "ahora", "past" : "hace {0} s" }, "month" : { "current" : "este mes", "future" : "dentro de {0} m", "previous" : "el mes pasado", "next" : "el próximo mes", "past" : "hace {0} m" }, "year" : { "future" : "dentro de {0} a", "previous" : "el año pasado", "next" : "el próximo año", "current" : "este año", "past" : "hace {0} a" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/et.json ================================================ { "narrow" : { "year" : { "future" : "{0} a pärast", "previous" : "eelmine aasta", "next" : "järgmine aasta", "current" : "käesolev aasta", "past" : "{0} a eest" }, "week" : { "past" : "{0} näd eest", "previous" : "eelmine nädal", "future" : "{0} näd pärast", "current" : "käesolev nädal", "next" : "järgmine nädal" }, "hour" : { "past" : "{0} t eest", "future" : "{0} t pärast", "current" : "praegusel tunnil" }, "quarter" : { "current" : "käesolev kv", "previous" : "eelmine kv", "future" : "{0} kv pärast", "past" : "{0} kv eest", "next" : "järgmine kv" }, "second" : { "future" : "{0} s pärast", "current" : "nüüd", "past" : "{0} s eest" }, "month" : { "current" : "käesolev kuu", "future" : "{0} k pärast", "past" : "{0} k eest", "next" : "järgmine kuu", "previous" : "eelmine kuu" }, "day" : { "previous" : "eile", "future" : "{0} p pärast", "next" : "homme", "current" : "täna", "past" : "{0} p eest" }, "minute" : { "future" : "{0} min pärast", "current" : "praegusel minutil", "past" : "{0} min eest" }, "now" : "nüüd" }, "long" : { "quarter" : { "next" : "järgmine kvartal", "current" : "käesolev kvartal", "previous" : "eelmine kvartal", "past" : "{0} kvartali eest", "future" : "{0} kvartali pärast" }, "now" : "nüüd", "year" : { "previous" : "eelmine aasta", "past" : "{0} aasta eest", "future" : "{0} aasta pärast", "next" : "järgmine aasta", "current" : "käesolev aasta" }, "month" : { "previous" : "eelmine kuu", "current" : "käesolev kuu", "future" : "{0} kuu pärast", "past" : "{0} kuu eest", "next" : "järgmine kuu" }, "day" : { "previous" : "eile", "future" : "{0} päeva pärast", "next" : "homme", "past" : "{0} päeva eest", "current" : "täna" }, "second" : { "past" : "{0} sekundi eest", "future" : "{0} sekundi pärast", "current" : "nüüd" }, "week" : { "past" : "{0} nädala eest", "current" : "käesolev nädal", "next" : "järgmine nädal", "previous" : "eelmine nädal", "future" : "{0} nädala pärast" }, "hour" : { "future" : "{0} tunni pärast", "current" : "praegusel tunnil", "past" : "{0} tunni eest" }, "minute" : { "current" : "praegusel minutil", "past" : "{0} minuti eest", "future" : "{0} minuti pärast" } }, "short" : { "second" : { "current" : "nüüd", "past" : "{0} sek eest", "future" : "{0} sek pärast" }, "now" : "nüüd", "month" : { "future" : "{0} kuu pärast", "current" : "käesolev kuu", "past" : "{0} kuu eest", "previous" : "eelmine kuu", "next" : "järgmine kuu" }, "day" : { "previous" : "eile", "current" : "täna", "next" : "homme", "future" : "{0} p pärast", "past" : "{0} p eest" }, "minute" : { "current" : "praegusel minutil", "future" : "{0} min pärast", "past" : "{0} min eest" }, "quarter" : { "past" : "{0} kv eest", "previous" : "eelmine kv", "current" : "käesolev kv", "future" : "{0} kv pärast", "next" : "järgmine kv" }, "hour" : { "current" : "praegusel tunnil", "future" : "{0} t pärast", "past" : "{0} t eest" }, "week" : { "future" : "{0} näd pärast", "previous" : "eelmine nädal", "next" : "järgmine nädal", "past" : "{0} näd eest", "current" : "käesolev nädal" }, "year" : { "next" : "järgmine aasta", "current" : "käesolev aasta", "past" : "{0} a eest", "future" : "{0} a pärast", "previous" : "eelmine aasta" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/eu.json ================================================ { "narrow" : { "year" : { "current" : "aurten", "past" : "Duela {0} urte", "future" : "{0} urte barru", "next" : "hurrengo urtea", "previous" : "aurreko urtea" }, "minute" : { "future" : "{0} minutu barru", "current" : "minutu honetan", "past" : "Duela {0} minutu" }, "day" : { "past" : "Duela {0} egun", "future" : "{0} egun barru", "current" : "gaur", "previous" : "atzo", "next" : "bihar" }, "second" : { "future" : "{0} segundo barru", "current" : "orain", "past" : "Duela {0} segundo" }, "now" : "orain", "month" : { "next" : "hurrengo hilabetean", "future" : "{0} hilabete barru", "previous" : "aurreko hilabetean", "current" : "hilabete honetan", "past" : "Duela {0} hilabete" }, "week" : { "previous" : "aurreko astean", "next" : "hurrengo astean", "future" : "{0} aste barru", "current" : "aste honetan", "past" : "Duela {0} aste" }, "quarter" : { "current" : "hiruhileko hau", "past" : "Duela {0} hiruhileko", "future" : "{0} hiruhileko barru", "next" : "hurrengo hiruhilekoa", "previous" : "aurreko hiruhilekoa" }, "hour" : { "current" : "ordu honetan", "past" : "Duela {0} ordu", "future" : "{0} ordu barru" } }, "short" : { "hour" : { "future" : "{0} ordu barru", "current" : "ordu honetan", "past" : "Duela {0} ordu" }, "now" : "orain", "quarter" : { "future" : "{0} hiruhileko barru", "previous" : "aurreko hiruhilekoa", "next" : "hurrengo hiruhilekoa", "past" : "Duela {0} hiruhileko", "current" : "hiruhileko hau" }, "day" : { "current" : "gaur", "future" : "{0} egun barru", "past" : "Duela {0} egun", "next" : "bihar", "previous" : "atzo" }, "week" : { "future" : "{0} aste barru", "previous" : "aurreko astean", "next" : "hurrengo astean", "current" : "aste honetan", "past" : "Duela {0} aste" }, "minute" : { "past" : "Duela {0} minutu", "future" : "{0} minutu barru", "current" : "minutu honetan" }, "second" : { "past" : "Duela {0} segundo", "current" : "orain", "future" : "{0} segundo barru" }, "month" : { "current" : "hilabete honetan", "previous" : "aurreko hilabetean", "next" : "hurrengo hilabetean", "past" : "Duela {0} hilabete", "future" : "{0} hilabete barru" }, "year" : { "current" : "aurten", "future" : "{0} urte barru", "previous" : "aurreko urtea", "next" : "hurrengo urtea", "past" : "Duela {0} urte" } }, "long" : { "quarter" : { "previous" : "aurreko hiruhilekoa", "current" : "hiruhileko hau", "future" : "{0} hiruhileko barru", "next" : "hurrengo hiruhilekoa", "past" : "Duela {0} hiruhileko" }, "hour" : { "future" : "{0} ordu barru", "current" : "ordu honetan", "past" : "Duela {0} ordu" }, "now" : "orain", "week" : { "current" : "aste honetan", "next" : "hurrengo astean", "previous" : "aurreko astean", "future" : "{0} aste barru", "past" : "Duela {0} aste" }, "second" : { "past" : "Duela {0} segundo", "current" : "orain", "future" : "{0} segundo barru" }, "year" : { "current" : "aurten", "previous" : "iaz", "past" : "Duela {0} urte", "future" : "{0} urte barru", "next" : "hurrengo urtean" }, "day" : { "future" : "{0} egun barru", "previous" : "atzo", "past" : "Duela {0} egun", "next" : "bihar", "current" : "gaur" }, "minute" : { "future" : "{0} minutu barru", "past" : "Duela {0} minutu", "current" : "minutu honetan" }, "month" : { "future" : "{0} hilabete barru", "next" : "hurrengo hilabetean", "past" : "Duela {0} hilabete", "previous" : "aurreko hilabetean", "current" : "hilabete honetan" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/fa.json ================================================ { "narrow" : { "day" : { "next" : "فردا", "future" : "{0} روز بعد", "previous" : "دیروز", "current" : "امروز", "past" : "{0} روز پیش" }, "quarter" : { "next" : "سه‌ماههٔ آینده", "past" : "{0} سه‌ماههٔ پیش", "future" : "{0} سه‌ماههٔ بعد", "previous" : "سه‌ماههٔ گذشته", "current" : "سه‌ماههٔ کنونی" }, "hour" : { "future" : "{0} ساعت بعد", "current" : "همین ساعت", "past" : "{0} ساعت پیش" }, "year" : { "next" : "سال آینده", "past" : "{0} سال پیش", "current" : "امسال", "previous" : "سال گذشته", "future" : "{0} سال بعد" }, "now" : "اکنون", "month" : { "current" : "این ماه", "future" : "{0} ماه بعد", "past" : "{0} ماه پیش", "next" : "ماه آینده", "previous" : "ماه پیش" }, "week" : { "previous" : "هفتهٔ گذشته", "current" : "این هفته", "past" : "{0} هفته پیش", "future" : "{0} هفته بعد", "next" : "هفتهٔ آینده" }, "minute" : { "current" : "همین دقیقه", "future" : "{0} دقیقه بعد", "past" : "{0} دقیقه پیش" }, "second" : { "past" : "{0} ثانیه پیش", "current" : "اکنون", "future" : "{0} ثانیه بعد" } }, "long" : { "day" : { "previous" : "دیروز", "current" : "امروز", "next" : "فردا", "past" : "{0} روز پیش", "future" : "{0} روز بعد" }, "week" : { "current" : "این هفته", "future" : "{0} هفته بعد", "past" : "{0} هفته پیش", "previous" : "هفتهٔ گذشته", "next" : "هفتهٔ آینده" }, "minute" : { "past" : "{0} دقیقه پیش", "current" : "همین دقیقه", "future" : "{0} دقیقه بعد" }, "month" : { "future" : "{0} ماه بعد", "next" : "ماه آینده", "previous" : "ماه گذشته", "current" : "این ماه", "past" : "{0} ماه پیش" }, "hour" : { "past" : "{0} ساعت پیش", "current" : "همین ساعت", "future" : "{0} ساعت بعد" }, "year" : { "past" : "{0} سال پیش", "future" : "{0} سال بعد", "previous" : "سال گذشته", "next" : "سال آینده", "current" : "امسال" }, "second" : { "current" : "اکنون", "past" : "{0} ثانیه پیش", "future" : "{0} ثانیه بعد" }, "now" : "اکنون", "quarter" : { "past" : "{0} سه‌ماههٔ پیش", "current" : "سه‌ماههٔ کنونی", "previous" : "سه‌ماههٔ گذشته", "future" : "{0} سه‌ماههٔ بعد", "next" : "سه‌ماههٔ آینده" } }, "short" : { "quarter" : { "future" : "{0} سه‌ماههٔ بعد", "next" : "سه‌ماههٔ آینده", "previous" : "سه‌ماههٔ گذشته", "current" : "سه‌ماههٔ کنونی", "past" : "{0} سه‌ماههٔ پیش" }, "minute" : { "current" : "همین دقیقه", "past" : "{0} دقیقه پیش", "future" : "{0} دقیقه بعد" }, "year" : { "next" : "سال آینده", "future" : "{0} سال بعد", "current" : "امسال", "past" : "{0} سال پیش", "previous" : "سال گذشته" }, "second" : { "future" : "{0} ثانیه بعد", "current" : "اکنون", "past" : "{0} ثانیه پیش" }, "hour" : { "past" : "{0} ساعت پیش", "future" : "{0} ساعت بعد", "current" : "همین ساعت" }, "now" : "اکنون", "month" : { "previous" : "ماه پیش", "next" : "ماه آینده", "past" : "{0} ماه پیش", "current" : "این ماه", "future" : "{0} ماه بعد" }, "week" : { "previous" : "هفتهٔ گذشته", "current" : "این هفته", "next" : "هفتهٔ آینده", "past" : "{0} هفته پیش", "future" : "{0} هفته بعد" }, "day" : { "next" : "فردا", "past" : "{0} روز پیش", "future" : "{0} روز بعد", "previous" : "دیروز", "current" : "امروز" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/fi.json ================================================ { "long" : { "minute" : { "past" : { "other" : "{0} minuuttia sitten", "one" : "{0} minuutti sitten" }, "future" : "{0} minuutin päästä", "current" : "tämän minuutin aikana" }, "year" : { "future" : "{0} vuoden päästä", "current" : "tänä vuonna", "previous" : "viime vuonna", "next" : "ensi vuonna", "past" : { "one" : "{0} vuosi sitten", "other" : "{0} vuotta sitten" } }, "quarter" : { "past" : { "one" : "{0} neljännesvuosi sitten", "other" : "{0} neljännesvuotta sitten" }, "next" : "ensi neljännesvuonna", "previous" : "viime neljännesvuonna", "future" : "{0} neljännesvuoden päästä", "current" : "tänä neljännesvuonna" }, "week" : { "future" : "{0} viikon päästä", "past" : { "other" : "{0} viikkoa sitten", "one" : "{0} viikko sitten" }, "previous" : "viime viikolla", "current" : "tällä viikolla", "next" : "ensi viikolla" }, "hour" : { "past" : { "other" : "{0} tuntia sitten", "one" : "{0} tunti sitten" }, "future" : "{0} tunnin päästä", "current" : "tämän tunnin aikana" }, "month" : { "previous" : "viime kuussa", "next" : "ensi kuussa", "future" : "{0} kuukauden päästä", "past" : { "one" : "{0} kuukausi sitten", "other" : "{0} kuukautta sitten" }, "current" : "tässä kuussa" }, "second" : { "current" : "nyt", "past" : { "one" : "{0} sekunti sitten", "other" : "{0} sekuntia sitten" }, "future" : "{0} sekunnin päästä" }, "now" : "nyt", "day" : { "next" : "huomenna", "future" : "{0} päivän päästä", "previous" : "eilen", "current" : "tänään", "past" : { "other" : "{0} päivää sitten", "one" : "{0} päivä sitten" } } }, "short" : { "month" : { "future" : "{0} kk päästä", "previous" : "viime kk", "current" : "tässä kk", "next" : "ensi kk", "past" : "{0} kk sitten" }, "week" : { "previous" : "viime vk", "current" : "tällä vk", "future" : "{0} vk päästä", "past" : "{0} vk sitten", "next" : "ensi vk" }, "hour" : { "future" : "{0} t päästä", "current" : "tunnin sisällä", "past" : "{0} t sitten" }, "quarter" : { "next" : "ensi neljänneksenä", "previous" : "viime neljänneksenä", "current" : "tänä neljänneksenä", "past" : { "other" : "{0} neljännestä sitten", "one" : "{0} neljännes sitten" }, "future" : "{0} neljänneksen päästä" }, "minute" : { "past" : "{0} min sitten", "future" : "{0} min päästä", "current" : "minuutin sisällä" }, "now" : "nyt", "day" : { "past" : "{0} pv sitten", "previous" : "eilen", "current" : "tänään", "next" : "huom.", "future" : "{0} pv päästä" }, "year" : { "current" : "tänä v", "previous" : "viime v", "future" : "{0} v päästä", "past" : "{0} v sitten", "next" : "ensi v" }, "second" : { "current" : "nyt", "past" : "{0} s sitten", "future" : "{0} s päästä" } }, "narrow" : { "day" : { "next" : "huom.", "current" : "tänään", "future" : "{0} pv päästä", "past" : "{0} pv sitten", "previous" : "eilen" }, "week" : { "current" : "tällä vk", "next" : "ensi vk", "previous" : "viime vk", "past" : "{0} vk sitten", "future" : "{0} vk päästä" }, "quarter" : { "past" : "{0} nelj. sitten", "future" : "{0} nelj. päästä", "previous" : "viime nelj.", "current" : "tänä nelj.", "next" : "ensi nelj." }, "month" : { "previous" : "viime kk", "current" : "tässä kk", "next" : "ensi kk", "past" : "{0} kk sitten", "future" : "{0} kk päästä" }, "hour" : { "future" : "{0} t päästä", "past" : "{0} t sitten", "current" : "tunnin sisällä" }, "year" : { "current" : "tänä v", "past" : "{0} v sitten", "future" : "{0} v päästä", "next" : "ensi v", "previous" : "viime v" }, "now" : "nyt", "minute" : { "past" : "{0} min sitten", "current" : "minuutin sisällä", "future" : "{0} min päästä" }, "second" : { "past" : "{0} s sitten", "future" : "{0} s päästä", "current" : "nyt" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/fil.json ================================================ { "narrow" : { "hour" : { "current" : "ngayong oras", "past" : { "one" : "{0} oras nakalipas", "other" : "{0} (na) oras nakalipas" }, "future" : { "other" : "sa {0} (na) oras", "one" : "sa {0} oras" } }, "week" : { "previous" : "nakaraang linggo", "past" : { "one" : "{0} linggo ang nakalipas", "other" : "{0} (na) linggo ang nakalipas" }, "future" : { "other" : "sa {0} (na) linggo", "one" : "sa {0} linggo" }, "next" : "susunod na linggo", "current" : "ngayong linggo" }, "minute" : { "future" : { "one" : "sa {0} min.", "other" : "sa {0} (na) min." }, "past" : { "one" : "{0} min. ang nakalipas", "other" : "{0} (na) min. ang nakalipas" }, "current" : "sa minutong ito" }, "quarter" : { "future" : { "other" : "sa {0} (na) quarter", "one" : "sa {0} quarter" }, "next" : "susunod na quarter", "current" : "ngayong quarter", "previous" : "nakaraang quarter", "past" : { "other" : "{0} (na) quarter ang nakalipas", "one" : "{0} quarter ang nakalipas" } }, "year" : { "current" : "ngayong taon", "past" : { "one" : "{0} taon ang nakalipas", "other" : "{0} (na) taon ang nakalipas" }, "future" : { "one" : "sa {0} taon", "other" : "sa {0} (na) taon" }, "next" : "susunod na taon", "previous" : "nakaraang taon" }, "month" : { "previous" : "nakaraang buwan", "current" : "ngayong buwan", "next" : "susunod na buwan", "past" : { "one" : "{0} buwan ang nakalipas", "other" : "{0} (na) buwan ang nakalipas" }, "future" : { "other" : "sa {0} (na) buwan", "one" : "sa {0} buwan" } }, "day" : { "next" : "bukas", "previous" : "kahapon", "past" : { "other" : "{0} (na) araw ang nakalipas", "one" : "{0} araw ang nakalipas" }, "current" : "ngayong araw", "future" : { "one" : "sa {0} araw", "other" : "sa {0} (na) araw" } }, "now" : "ngayon", "second" : { "current" : "ngayon", "past" : { "one" : "{0} seg. nakalipas", "other" : "{0} (na) seg. nakalipas" }, "future" : { "one" : "sa {0} seg.", "other" : "sa {0} (na) seg." } } }, "long" : { "week" : { "current" : "sa linggong ito", "next" : "susunod na linggo", "past" : { "one" : "{0} linggo ang nakalipas", "other" : "{0} (na) linggo ang nakalipas" }, "previous" : "nakalipas na linggo", "future" : { "one" : "sa {0} linggo", "other" : "sa {0} (na) linggo" } }, "quarter" : { "current" : "ngayong quarter", "past" : { "one" : "{0} quarter ang nakalipas", "other" : "{0} (na) quarter ang nakalipas" }, "previous" : "nakaraang quarter", "future" : { "one" : "sa {0} quarter", "other" : "sa {0} (na) quarter" }, "next" : "susunod na quarter" }, "second" : { "future" : { "one" : "sa {0} segundo", "other" : "sa {0} (na) segundo" }, "past" : { "other" : "{0} (na) segundo ang nakalipas", "one" : "{0} segundo ang nakalipas" }, "current" : "ngayon" }, "day" : { "current" : "ngayong araw", "future" : { "one" : "sa {0} araw", "other" : "sa {0} (na) araw" }, "previous" : "kahapon", "past" : { "one" : "{0} araw ang nakalipas", "other" : "{0} (na) araw ang nakalipas" }, "next" : "bukas" }, "minute" : { "current" : "sa minutong ito", "past" : { "other" : "{0} (na) minuto ang nakalipas", "one" : "{0} minuto ang nakalipas" }, "future" : { "other" : "sa {0} (na) minuto", "one" : "sa {0} minuto" } }, "month" : { "next" : "susunod na buwan", "previous" : "nakaraang buwan", "past" : { "one" : "{0} buwan ang nakalipas", "other" : "{0} (na) buwan ang nakalipas" }, "future" : { "other" : "sa {0} (na) buwan", "one" : "sa {0} buwan" }, "current" : "ngayong buwan" }, "now" : "ngayon", "year" : { "previous" : "nakaraang taon", "past" : { "one" : "{0} taon ang nakalipas", "other" : "{0} (na) taon ang nakalipas" }, "current" : "ngayong taon", "next" : "susunod na taon", "future" : { "one" : "sa {0} taon", "other" : "sa {0} (na) taon" } }, "hour" : { "past" : { "other" : "{0} (na) oras ang nakalipas", "one" : "{0} oras ang nakalipas" }, "current" : "ngayong oras", "future" : { "other" : "sa {0} (na) oras", "one" : "sa {0} oras" } } }, "short" : { "minute" : { "current" : "sa minutong ito", "future" : { "other" : "sa {0} (na) min.", "one" : "sa {0} min." }, "past" : { "other" : "{0} (na) min. ang nakalipas", "one" : "{0} min. ang nakalipas" } }, "second" : { "past" : { "other" : "{0} (na) seg. nakalipas", "one" : "{0} seg. ang nakalipas" }, "current" : "ngayon", "future" : { "one" : "sa {0} seg.", "other" : "sa {0} (na) seg." } }, "year" : { "next" : "susunod na taon", "current" : "ngayong taon", "past" : { "one" : "{0} taon ang nakalipas", "other" : "{0} (na) taon ang nakalipas" }, "previous" : "nakaraang taon", "future" : { "one" : "sa {0} taon", "other" : "sa {0} (na) taon" } }, "month" : { "past" : { "other" : "{0} (na) buwan ang nakalipas", "one" : "{0} buwan ang nakalipas" }, "next" : "susunod na buwan", "future" : { "one" : "sa {0} buwan", "other" : "sa {0} (na) buwan" }, "previous" : "nakaraang buwan", "current" : "ngayong buwan" }, "day" : { "previous" : "kahapon", "past" : "{0} (na) araw ang nakalipas", "current" : "ngayong araw", "next" : "bukas", "future" : "sa {0} (na) araw" }, "now" : "ngayon", "hour" : { "current" : "ngayong oras", "future" : { "one" : "sa {0} oras", "other" : "sa {0} (na) oras" }, "past" : { "other" : "{0} (na) oras ang nakalipas", "one" : "{0} oras ang nakalipas" } }, "quarter" : { "next" : "susunod na quarter", "previous" : "nakaraang quarter", "past" : { "one" : "{0} quarter ang nakalipas", "other" : "{0} (na) quarter ang nakalipas" }, "future" : "sa {0} (na) quarter", "current" : "ngayong quarter" }, "week" : { "next" : "susunod na linggo", "previous" : "nakaraang linggo", "past" : { "one" : "{0} linggo ang nakalipas", "other" : "{0} (na) linggo ang nakalipas" }, "current" : "ngayong linggo", "future" : { "one" : "sa {0} linggo", "other" : "sa {0} (na) linggo" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/fo.json ================================================ { "narrow" : { "year" : { "future" : "um {0} ár", "previous" : "í fjør", "next" : "næsta ár", "current" : "í ár", "past" : "{0} ár síðan" }, "week" : { "past" : "{0} v. síðan", "previous" : "seinastu viku", "future" : "um {0} v.", "current" : "hesu viku", "next" : "næstu viku" }, "hour" : { "current" : "hendan tíman", "future" : "um {0} t.", "past" : "{0} t. síðan" }, "quarter" : { "current" : "hendan ársfjórðingin", "previous" : "seinasta ársfjórðing", "future" : "um {0} ársfj.", "past" : "{0} ársfj. síðan", "next" : "næsta ársfjórðing" }, "second" : { "past" : "{0} s. síðan", "future" : "um {0} s.", "current" : "nú" }, "month" : { "current" : "henda mánaðin", "future" : "um {0} mnð.", "past" : "{0} mnð. síðan", "next" : "næsta mánað", "previous" : "seinasta mánað" }, "day" : { "previous" : "í gjár", "future" : "um {0} d.", "next" : "í morgin", "current" : "í dag", "past" : "{0} d. síðan" }, "minute" : { "past" : "{0} m. síðan", "future" : "um {0} m.", "current" : "hendan minuttin" }, "now" : "nú" }, "long" : { "month" : { "previous" : "seinasta mánað", "past" : { "one" : "{0} mánað síðan", "other" : "{0} mánaðir síðan" }, "next" : "næsta mánað", "current" : "henda mánaðin", "future" : { "other" : "um {0} mánaðir", "one" : "um {0} mánað" } }, "minute" : { "current" : "hendan minuttin", "future" : { "one" : "um {0} minutt", "other" : "um {0} minuttir" }, "past" : { "one" : "{0} minutt síðan", "other" : "{0} minuttir síðan" } }, "quarter" : { "next" : "næsta ársfjórðing", "past" : { "one" : "{0} ársfjórðing síðan", "other" : "{0} ársfjórðingar síðan" }, "future" : { "one" : "um {0} ársfjórðing", "other" : "um {0} ársfjórðingar" }, "current" : "hendan ársfjórðingin", "previous" : "seinasta ársfjórðing" }, "week" : { "next" : "næstu viku", "previous" : "seinastu viku", "future" : { "other" : "um {0} vikur", "one" : "um {0} viku" }, "past" : { "one" : "{0} vika síðan", "other" : "{0} vikur síðan" }, "current" : "hesu viku" }, "day" : { "previous" : "í gjár", "past" : { "one" : "{0} dagur síðan", "other" : "{0} dagar síðan" }, "future" : { "other" : "um {0} dagar", "one" : "um {0} dag" }, "current" : "í dag", "next" : "í morgin" }, "hour" : { "future" : { "one" : "um {0} tíma", "other" : "um {0} tímar" }, "current" : "hendan tíman", "past" : { "one" : "{0} tími síðan", "other" : "{0} tímar síðan" } }, "year" : { "current" : "í ár", "future" : "um {0} ár", "next" : "næsta ár", "past" : "{0} ár síðan", "previous" : "í fjør" }, "second" : { "future" : "um {0} sekund", "current" : "nú", "past" : "{0} sekund síðan" }, "now" : "nú" }, "short" : { "second" : { "current" : "nú", "past" : "{0} sek. síðan", "future" : "um {0} sek." }, "now" : "nú", "month" : { "future" : "um {0} mnð.", "current" : "henda mánaðin", "past" : "{0} mnð. síðan", "previous" : "seinasta mánað", "next" : "næsta mánað" }, "day" : { "previous" : "í gjár", "current" : "í dag", "next" : "í morgin", "future" : "um {0} da.", "past" : "{0} da. síðan" }, "minute" : { "past" : "{0} min. síðan", "future" : "um {0} min.", "current" : "hendan minuttin" }, "quarter" : { "past" : "{0} ársfj. síðan", "previous" : "seinasta ársfjórðing", "current" : "hendan ársfjórðingin", "future" : "um {0} ársfj.", "next" : "næsta ársfjórðing" }, "hour" : { "future" : "um {0} t.", "current" : "hendan tíman", "past" : "{0} t. síðan" }, "week" : { "future" : "um {0} vi.", "previous" : "seinastu viku", "next" : "næstu viku", "past" : "{0} vi. síðan", "current" : "hesu viku" }, "year" : { "next" : "næsta ár", "current" : "í ár", "past" : "{0} ár síðan", "future" : "um {0} ár", "previous" : "í fjør" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/fr.json ================================================ { "narrow" : { "day" : { "next" : "demain", "future" : "+{0} j", "previous" : "hier", "current" : "aujourd’hui", "past" : "-{0} j" }, "quarter" : { "next" : "le trimestre prochain", "past" : "-{0} trim.", "future" : "+{0} trim.", "previous" : "le trimestre dernier", "current" : "ce trimestre" }, "hour" : { "past" : "-{0} h", "future" : "+{0} h", "current" : "cette heure-ci" }, "year" : { "next" : "l’année prochaine", "past" : "-{0} a", "current" : "cette année", "previous" : "l’année dernière", "future" : "+{0} a" }, "now" : "maintenant", "month" : { "current" : "ce mois-ci", "future" : "+{0} m.", "past" : "-{0} m.", "next" : "le mois prochain", "previous" : "le mois dernier" }, "week" : { "previous" : "la semaine dernière", "current" : "cette semaine", "past" : "-{0} sem.", "future" : "+{0} sem.", "next" : "la semaine prochaine" }, "minute" : { "current" : "cette minute-ci", "past" : "-{0} min", "future" : "+{0} min" }, "second" : { "current" : "maintenant", "past" : "-{0} s", "future" : "+{0} s" } }, "long" : { "quarter" : { "next" : "le trimestre prochain", "future" : { "other" : "dans {0} trimestres", "one" : "dans {0} trimestre" }, "previous" : "le trimestre dernier", "current" : "ce trimestre", "past" : { "other" : "il y a {0} trimestres", "one" : "il y a {0} trimestre" } }, "hour" : { "future" : { "other" : "dans {0} heures", "one" : "dans {0} heure" }, "past" : { "one" : "il y a {0} heure", "other" : "il y a {0} heures" }, "current" : "cette heure-ci" }, "minute" : { "past" : { "one" : "il y a {0} minute", "other" : "il y a {0} minutes" }, "future" : { "one" : "dans {0} minute", "other" : "dans {0} minutes" }, "current" : "cette minute-ci" }, "now" : "maintenant", "month" : { "next" : "le mois prochain", "previous" : "le mois dernier", "future" : "dans {0} mois", "current" : "ce mois-ci", "past" : "il y a {0} mois" }, "day" : { "next" : "demain", "future" : { "one" : "dans {0} jour", "other" : "dans {0} jours" }, "past" : { "one" : "il y a {0} jour", "other" : "il y a {0} jours" }, "current" : "aujourd’hui", "previous" : "hier" }, "year" : { "future" : { "one" : "dans {0} an", "other" : "dans {0} ans" }, "previous" : "l’année dernière", "next" : "l’année prochaine", "current" : "cette année", "past" : { "other" : "il y a {0} ans", "one" : "il y a {0} an" } }, "week" : { "next" : "la semaine prochaine", "past" : { "one" : "il y a {0} semaine", "other" : "il y a {0} semaines" }, "previous" : "la semaine dernière", "current" : "cette semaine", "future" : { "one" : "dans {0} semaine", "other" : "dans {0} semaines" } }, "second" : { "future" : { "one" : "dans {0} seconde", "other" : "dans {0} secondes" }, "current" : "maintenant", "past" : { "other" : "il y a {0} secondes", "one" : "il y a {0} seconde" } } }, "short" : { "quarter" : { "future" : "dans {0} trim.", "next" : "le trimestre prochain", "previous" : "le trimestre dernier", "current" : "ce trimestre", "past" : "il y a {0} trim." }, "minute" : { "future" : "dans {0} min", "current" : "cette minute-ci", "past" : "il y a {0} min" }, "year" : { "next" : "l’année prochaine", "future" : "dans {0} a", "current" : "cette année", "past" : "il y a {0} a", "previous" : "l’année dernière" }, "second" : { "current" : "maintenant", "past" : "il y a {0} s", "future" : "dans {0} s" }, "hour" : { "current" : "cette heure-ci", "past" : "il y a {0} h", "future" : "dans {0} h" }, "now" : "maintenant", "month" : { "previous" : "le mois dernier", "next" : "le mois prochain", "past" : "il y a {0} m.", "current" : "ce mois-ci", "future" : "dans {0} m." }, "week" : { "previous" : "la semaine dernière", "current" : "cette semaine", "next" : "la semaine prochaine", "past" : "il y a {0} sem.", "future" : "dans {0} sem." }, "day" : { "next" : "demain", "past" : "il y a {0} j", "future" : "dans {0} j", "previous" : "hier", "current" : "aujourd’hui" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/fr_CA.json ================================================ { "long" : { "quarter" : { "past" : { "one" : "il y a {0} trimestre", "other" : "il y a {0} trimestres" }, "next" : "le trimestre prochain", "previous" : "le trimestre dernier", "current" : "ce trimestre-ci", "future" : { "one" : "dans {0} trimestre", "other" : "dans {0} trimestres" } }, "day" : { "future" : { "other" : "dans {0} jours", "one" : "dans {0} jour" }, "previous" : "hier", "past" : { "other" : "il y a {0} jours", "one" : "il y a {0} jour" }, "next" : "demain", "current" : "aujourd’hui" }, "year" : { "previous" : "l’année dernière", "current" : "cette année", "future" : { "one" : "Dans {0} an", "other" : "Dans {0} ans" }, "next" : "l’année prochaine", "past" : { "other" : "Il y a {0} ans", "one" : "Il y a {0} an" } }, "hour" : { "future" : { "one" : "dans {0} heure", "other" : "dans {0} heures" }, "current" : "cette heure-ci", "past" : { "other" : "il y a {0} heures", "one" : "il y a {0} heure" } }, "second" : { "past" : { "one" : "il y a {0} seconde", "other" : "il y a {0} secondes" }, "current" : "maintenant", "future" : { "other" : "dans {0} secondes", "one" : "dans {0} seconde" } }, "minute" : { "past" : { "one" : "il y a {0} minute", "other" : "il y a {0} minutes" }, "current" : "cette minute-ci", "future" : { "one" : "dans {0} minute", "other" : "dans {0} minutes" } }, "month" : { "current" : "ce mois-ci", "next" : "le mois prochain", "previous" : "le mois dernier", "past" : "il y a {0} mois", "future" : "dans {0} mois" }, "now" : "maintenant", "week" : { "past" : { "one" : "il y a {0} semaine", "other" : "il y a {0} semaines" }, "current" : "cette semaine", "next" : "la semaine prochaine", "previous" : "la semaine dernière", "future" : { "one" : "dans {0} semaine", "other" : "dans {0} semaines" } } }, "narrow" : { "week" : { "current" : "cette semaine", "next" : "la semaine prochaine", "past" : "-{0} sem.", "previous" : "la semaine dernière", "future" : "+{0} sem." }, "minute" : { "current" : "cette minute-ci", "past" : "-{0} min", "future" : "+{0} min" }, "month" : { "current" : "ce mois-ci", "previous" : "le mois dernier", "past" : "-{0} m.", "next" : "le mois prochain", "future" : "+{0} m." }, "now" : "maintenant", "year" : { "current" : "cette année", "next" : "l’année prochaine", "past" : "-{0} a", "future" : "+{0} a", "previous" : "l’année dernière" }, "hour" : { "current" : "cette heure-ci", "future" : "+{0} h", "past" : "-{0} h" }, "quarter" : { "previous" : "le trimestre dernier", "current" : "ce trimestre-ci", "past" : "-{0} trim.", "future" : "+{0} trim.", "next" : "le trimestre prochain" }, "second" : { "past" : "-{0} s", "future" : { "one" : "+ {0} s", "other" : "+{0} s" }, "current" : "maintenant" }, "day" : { "past" : "-{0} j", "current" : "aujourd’hui", "future" : "+{0} j", "previous" : "hier", "next" : "demain" } }, "short" : { "month" : { "previous" : "le mois dernier", "current" : "ce mois-ci", "next" : "le mois prochain", "past" : "il y a {0} m.", "future" : "dans {0} m." }, "now" : "maintenant", "day" : { "next" : "demain", "current" : "aujourd’hui", "previous" : "hier", "past" : "il y a {0} j", "future" : "dans {0} j" }, "year" : { "current" : "cette année", "future" : "dans {0} a", "previous" : "l’année dernière", "next" : "l’année prochaine", "past" : "il y a {0} a" }, "hour" : { "future" : "dans {0} h", "current" : "cette heure-ci", "past" : "il y a {0} h" }, "minute" : { "past" : "il y a {0} min", "future" : "dans {0} min", "current" : "cette minute-ci" }, "second" : { "past" : "il y a {0} s", "current" : "maintenant", "future" : "dans {0} s" }, "quarter" : { "future" : "dans {0} trim.", "previous" : "le trimestre dernier", "next" : "le trimestre prochain", "past" : "il y a {0} trim.", "current" : "ce trimestre-ci" }, "week" : { "next" : "la semaine prochaine", "past" : "il y a {0} sem.", "current" : "cette semaine", "previous" : "la semaine dernière", "future" : "dans {0} sem." } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/fur.json ================================================ { "short" : { "day" : { "next" : "doman", "past" : { "one" : "{0} zornade indaûr", "other" : "{0} zornadis indaûr" }, "current" : "vuê", "previous" : "îr", "future" : { "one" : "ca di {0} zornade", "other" : "ca di {0} zornadis" } }, "month" : { "past" : "{0} mês indaûr", "future" : "ca di {0} mês", "next" : "next month", "current" : "this month", "previous" : "last month" }, "quarter" : { "current" : "this quarter", "past" : "-{0} Q", "next" : "next quarter", "previous" : "last quarter", "future" : "+{0} Q" }, "hour" : { "current" : "this hour", "future" : { "one" : "ca di {0} ore", "other" : "ca di {0} oris" }, "past" : { "other" : "{0} oris indaûr", "one" : "{0} ore indaûr" } }, "year" : { "next" : "next year", "previous" : "last year", "past" : { "other" : "{0} agns indaûr", "one" : "{0} an indaûr" }, "current" : "this year", "future" : { "one" : "ca di {0} an", "other" : "ca di {0} agns" } }, "minute" : { "current" : "this minute", "future" : { "one" : "ca di {0} minût", "other" : "ca di {0} minûts" }, "past" : { "other" : "{0} minûts indaûr", "one" : "{0} minût indaûr" } }, "now" : "now", "week" : { "next" : "next week", "previous" : "last week", "current" : "this week", "past" : { "one" : "{0} setemane indaûr", "other" : "{0} setemanis indaûr" }, "future" : { "other" : "ca di {0} setemanis", "one" : "ca di {0} setemane" } }, "second" : { "current" : "now", "future" : { "one" : "ca di {0} secont", "other" : "ca di {0} seconts" }, "past" : { "one" : "{0} secont indaûr", "other" : "{0} seconts indaûr" } } }, "long" : { "hour" : { "past" : { "one" : "{0} ore indaûr", "other" : "{0} oris indaûr" }, "future" : { "one" : "ca di {0} ore", "other" : "ca di {0} oris" }, "current" : "this hour" }, "now" : "now", "quarter" : { "future" : "+{0} Q", "previous" : "last quarter", "next" : "next quarter", "current" : "this quarter", "past" : "-{0} Q" }, "day" : { "current" : "vuê", "past" : { "one" : "{0} zornade indaûr", "other" : "{0} zornadis indaûr" }, "future" : { "one" : "ca di {0} zornade", "other" : "ca di {0} zornadis" }, "next" : "doman", "previous" : "îr" }, "week" : { "current" : "this week", "future" : { "one" : "ca di {0} setemane", "other" : "ca di {0} setemanis" }, "previous" : "last week", "next" : "next week", "past" : { "other" : "{0} setemanis indaûr", "one" : "{0} setemane indaûr" } }, "minute" : { "future" : { "one" : "ca di {0} minût", "other" : "ca di {0} minûts" }, "past" : { "other" : "{0} minûts indaûr", "one" : "{0} minût indaûr" }, "current" : "this minute" }, "second" : { "current" : "now", "future" : { "one" : "ca di {0} secont", "other" : "ca di {0} seconts" }, "past" : { "one" : "{0} secont indaûr", "other" : "{0} seconts indaûr" } }, "month" : { "current" : "this month", "future" : "ca di {0} mês", "past" : "{0} mês indaûr", "next" : "next month", "previous" : "last month" }, "year" : { "current" : "this year", "past" : { "one" : "{0} an indaûr", "other" : "{0} agns indaûr" }, "next" : "next year", "previous" : "last year", "future" : { "one" : "ca di {0} an", "other" : "ca di {0} agns" } } }, "narrow" : { "week" : { "previous" : "last week", "past" : { "other" : "{0} setemanis indaûr", "one" : "{0} setemane indaûr" }, "current" : "this week", "future" : { "other" : "ca di {0} setemanis", "one" : "ca di {0} setemane" }, "next" : "next week" }, "hour" : { "future" : { "other" : "ca di {0} oris", "one" : "ca di {0} ore" }, "current" : "this hour", "past" : { "one" : "{0} ore indaûr", "other" : "{0} oris indaûr" } }, "now" : "now", "second" : { "past" : { "other" : "{0} seconts indaûr", "one" : "{0} secont indaûr" }, "future" : { "other" : "ca di {0} seconts", "one" : "ca di {0} secont" }, "current" : "now" }, "month" : { "previous" : "last month", "next" : "next month", "current" : "this month", "past" : "{0} mês indaûr", "future" : "ca di {0} mês" }, "day" : { "previous" : "îr", "current" : "vuê", "next" : "doman", "past" : { "other" : "{0} zornadis indaûr", "one" : "{0} zornade indaûr" }, "future" : { "other" : "ca di {0} zornadis", "one" : "ca di {0} zornade" } }, "year" : { "future" : { "one" : "ca di {0} an", "other" : "ca di {0} agns" }, "current" : "this year", "past" : { "other" : "{0} agns indaûr", "one" : "{0} an indaûr" }, "previous" : "last year", "next" : "next year" }, "quarter" : { "past" : "-{0} Q", "next" : "next quarter", "previous" : "last quarter", "current" : "this quarter", "future" : "+{0} Q" }, "minute" : { "current" : "this minute", "past" : { "other" : "{0} minûts indaûr", "one" : "{0} minût indaûr" }, "future" : { "other" : "ca di {0} minûts", "one" : "ca di {0} minût" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/fy.json ================================================ { "narrow" : { "week" : { "current" : "dizze wike", "next" : "folgjende wike", "future" : { "other" : "Oer {0} wiken", "one" : "Oer {0} wike" }, "past" : { "one" : "{0} wike lyn", "other" : "{0} wiken lyn" }, "previous" : "foarige wike" }, "month" : { "past" : { "one" : "{0} moanne lyn", "other" : "{0} moannen lyn" }, "current" : "dizze moanne", "future" : { "one" : "Oer {0} moanne", "other" : "Oer {0} moannen" }, "next" : "folgjende moanne", "previous" : "foarige moanne" }, "quarter" : { "next" : "next quarter", "previous" : "last quarter", "current" : "this quarter", "past" : "-{0} Q", "future" : "+{0} Q" }, "year" : { "current" : "dit jier", "next" : "folgjend jier", "previous" : "foarich jier", "past" : "{0} jier lyn", "future" : "Oer {0} jier" }, "hour" : { "past" : "{0} oere lyn", "future" : "Oer {0} oere", "current" : "this hour" }, "now" : "nu", "day" : { "current" : "vandaag", "past" : { "other" : "{0} deien lyn", "one" : "{0} dei lyn" }, "previous" : "gisteren", "next" : "morgen", "future" : { "one" : "Oer {0} dei", "other" : "Oer {0} deien" } }, "minute" : { "current" : "this minute", "past" : { "other" : "{0} minuten lyn", "one" : "{0} minút lyn" }, "future" : { "other" : "Oer {0} minuten", "one" : "Oer {0} minút" } }, "second" : { "future" : { "one" : "Oer {0} sekonde", "other" : "Oer {0} sekonden" }, "current" : "nu", "past" : { "one" : "{0} sekonde lyn", "other" : "{0} sekonden lyn" } } }, "long" : { "quarter" : { "next" : "next quarter", "current" : "this quarter", "past" : "-{0} Q", "previous" : "last quarter", "future" : "+{0} Q" }, "now" : "nu", "day" : { "previous" : "gisteren", "past" : { "one" : "{0} dei lyn", "other" : "{0} deien lyn" }, "current" : "vandaag", "next" : "morgen", "future" : { "one" : "Oer {0} dei", "other" : "Oer {0} deien" } }, "month" : { "next" : "folgjende moanne", "previous" : "foarige moanne", "past" : { "other" : "{0} moannen lyn", "one" : "{0} moanne lyn" }, "future" : { "other" : "Oer {0} moannen", "one" : "Oer {0} moanne" }, "current" : "dizze moanne" }, "second" : { "past" : { "one" : "{0} sekonde lyn", "other" : "{0} sekonden lyn" }, "future" : { "one" : "Oer {0} sekonde", "other" : "Oer {0} sekonden" }, "current" : "nu" }, "year" : { "previous" : "foarich jier", "next" : "folgjend jier", "future" : "Oer {0} jier", "past" : "{0} jier lyn", "current" : "dit jier" }, "week" : { "future" : { "one" : "Oer {0} wike", "other" : "Oer {0} wiken" }, "current" : "dizze wike", "past" : { "other" : "{0} wiken lyn", "one" : "{0} wike lyn" }, "next" : "folgjende wike", "previous" : "foarige wike" }, "hour" : { "past" : "{0} oere lyn", "future" : "Oer {0} oere", "current" : "this hour" }, "minute" : { "future" : { "one" : "Oer {0} minút", "other" : "Oer {0} minuten" }, "past" : { "one" : "{0} minút lyn", "other" : "{0} minuten lyn" }, "current" : "this minute" } }, "short" : { "year" : { "next" : "folgjend jier", "future" : "Oer {0} jier", "current" : "dit jier", "past" : "{0} jier lyn", "previous" : "foarich jier" }, "minute" : { "current" : "this minute", "past" : { "one" : "{0} minút lyn", "other" : "{0} minuten lyn" }, "future" : { "one" : "Oer {0} minút", "other" : "Oer {0} minuten" } }, "week" : { "future" : { "other" : "Oer {0} wiken", "one" : "Oer {0} wike" }, "next" : "folgjende wike", "current" : "dizze wike", "past" : { "one" : "{0} wike lyn", "other" : "{0} wiken lyn" }, "previous" : "foarige wike" }, "day" : { "next" : "morgen", "current" : "vandaag", "previous" : "gisteren", "future" : { "one" : "Oer {0} dei", "other" : "Oer {0} deien" }, "past" : { "one" : "{0} dei lyn", "other" : "{0} deien lyn" } }, "quarter" : { "future" : "+{0} Q", "next" : "next quarter", "previous" : "last quarter", "current" : "this quarter", "past" : "-{0} Q" }, "month" : { "current" : "dizze moanne", "past" : { "other" : "{0} moannen lyn", "one" : "{0} moanne lyn" }, "previous" : "foarige moanne", "next" : "folgjende moanne", "future" : { "other" : "Oer {0} moannen", "one" : "Oer {0} moanne" } }, "now" : "nu", "second" : { "current" : "nu", "future" : { "other" : "Oer {0} sekonden", "one" : "Oer {0} sekonde" }, "past" : { "one" : "{0} sekonde lyn", "other" : "{0} sekonden lyn" } }, "hour" : { "past" : "{0} oere lyn", "current" : "this hour", "future" : "Oer {0} oere" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ga.json ================================================ { "long" : { "month" : { "next" : "an mhí seo chugainn", "current" : "an mhí seo", "future" : { "one" : "i gceann {0} mhí", "two" : "i gceann {0} mhí", "other" : "i gceann {0} mí", "few" : "i gceann {0} mhí" }, "previous" : "an mhí seo caite", "past" : { "one" : "{0} mhí ó shin", "other" : "{0} mí ó shin", "two" : "{0} mhí ó shin", "few" : "{0} mhí ó shin" } }, "second" : { "current" : "anois", "past" : { "few" : "{0} shoicind ó shin", "other" : "{0} soicind ó shin", "two" : "{0} shoicind ó shin" }, "future" : { "other" : "i gceann {0} soicind", "two" : "i gceann {0} shoicind", "few" : "i gceann {0} shoicind" } }, "day" : { "past" : "{0} lá ó shin", "future" : "i gceann {0} lá", "next" : "amárach", "previous" : "inné", "current" : "inniu" }, "minute" : { "future" : "i gceann {0} nóiméad", "current" : "an nóiméad seo", "past" : "{0} nóiméad ó shin" }, "hour" : { "current" : "an uair seo", "past" : { "many" : "{0} n-uaire an chloig ó shin", "few" : "{0} huaire an chloig ó shin", "other" : "{0} uair an chloig ó shin" }, "future" : { "few" : "i gceann {0} huaire an chloig", "many" : "i gceann {0} n-uaire an chloig", "other" : "i gceann {0} uair an chloig" } }, "quarter" : { "next" : "an ráithe seo chugainn", "current" : "an ráithe seo", "future" : "i gceann {0} ráithe", "past" : "{0} ráithe ó shin", "previous" : "an ráithe seo caite" }, "now" : "anois", "year" : { "next" : "an bhliain seo chugainn", "past" : { "two" : "{0} bhliain ó shin", "many" : "{0} mbliana ó shin", "other" : "{0} bliain ó shin", "one" : "{0} bhliain ó shin", "few" : "{0} bliana ó shin" }, "future" : { "one" : "i gceann {0} bhliain", "two" : "i gceann {0} bhliain", "few" : "i gceann {0} bliana", "many" : "i gceann {0} mbliana", "other" : "i gceann {0} bliain" }, "previous" : "anuraidh", "current" : "an bhliain seo" }, "week" : { "previous" : "an tseachtain seo caite", "next" : "an tseachtain seo chugainn", "past" : { "other" : "{0} seachtain ó shin", "many" : "{0} seachtaine ó shin", "few" : "{0} seachtaine ó shin", "two" : "{0} sheachtain ó shin" }, "current" : "an tseachtain seo", "future" : { "other" : "i gceann {0} seachtain", "many" : "i gceann {0} seachtaine", "few" : "i gceann {0} seachtaine", "two" : "i gceann {0} sheachtain" } } }, "short" : { "minute" : { "past" : "{0} nóim. ó shin", "future" : "i gceann {0} nóim.", "current" : "an nóiméad seo" }, "hour" : { "current" : "an uair seo", "future" : { "few" : "i gceann {0} huaire", "many" : "i gceann {0} n-uaire", "other" : "i gceann {0} uair" }, "past" : { "other" : "{0} uair ó shin", "few" : "{0} huaire ó shin", "many" : "{0} n-uaire ó shin" } }, "day" : { "past" : "{0} lá ó shin", "previous" : "inné", "current" : "inniu", "future" : "i gceann {0} lá", "next" : "amárach" }, "second" : { "future" : { "few" : "i gceann {0} shoic.", "two" : "i gceann {0} shoic.", "other" : "i gceann {0} soic." }, "past" : { "few" : "{0} shoic. ó shin", "two" : "{0} shoic. ó shin", "other" : "{0} soic. ó shin" }, "current" : "anois" }, "quarter" : { "future" : "i gceann {0} ráithe", "next" : "an ráithe seo chugainn", "current" : "an ráithe seo", "past" : "{0} ráithe ó shin", "previous" : "an ráithe seo caite" }, "year" : { "current" : "an bhl. seo", "past" : { "other" : "{0} bl. ó shin", "two" : "{0} bhl. ó shin", "many" : "{0} mbl. ó shin", "one" : "{0} bhl. ó shin" }, "previous" : "anuraidh", "next" : "an bhl. seo chugainn", "future" : { "two" : "i gceann {0} bhl.", "many" : "i gceann {0} mbl.", "other" : "i gceann {0} bl." } }, "week" : { "future" : { "two" : "i gceann {0} shcht.", "other" : "i gceann {0} scht." }, "past" : "{0} scht. ó shin", "next" : "an tscht. seo chugainn", "current" : "an tscht. seo", "previous" : "an tscht. seo caite" }, "month" : { "future" : { "few" : "i gceann {0} mhí", "other" : "i gceann {0} mí", "one" : "i gceann {0} mhí", "two" : "i gceann {0} mhí" }, "past" : { "two" : "{0} mhí ó shin", "one" : "{0} mhí ó shin", "other" : "{0} mí ó shin", "few" : "{0} mhí ó shin" }, "current" : "an mhí seo", "previous" : "an mhí seo caite", "next" : "an mhí seo chugainn" }, "now" : "anois" }, "narrow" : { "now" : "anois", "day" : { "future" : "+{0} lá", "current" : "inniu", "next" : "amárach", "previous" : "inné", "past" : "-{0} lá" }, "year" : { "next" : "an bhl. seo chugainn", "future" : { "two" : "+{0} bhl.", "many" : "+{0} mbl.", "one" : "+{0} bhl.", "other" : "+{0} bl." }, "past" : { "other" : "-{0} bl.", "two" : "-{0} bhl.", "one" : "-{0} bhl.", "many" : "-{0} mbl." }, "current" : "an bhl. seo", "previous" : "anuraidh" }, "minute" : { "current" : "an nóiméad seo", "past" : "-{0} n", "future" : "+{0} n" }, "week" : { "previous" : "an tscht. seo caite", "current" : "an tscht. seo", "past" : "-{0} scht.", "future" : "+{0} scht.", "next" : "an tscht. seo chugainn" }, "hour" : { "current" : "an uair seo", "future" : "+{0} u", "past" : "-{0} u" }, "second" : { "current" : "anois", "future" : "+{0} s", "past" : "-{0} s" }, "month" : { "next" : "an mhí seo chugainn", "past" : { "one" : "-{0} mhí", "other" : "-{0} mí", "few" : "-{0} mhí", "two" : "-{0} mhí" }, "previous" : "an mhí seo caite", "future" : { "two" : "+{0} mhí", "one" : "+{0} mhí", "few" : "+{0} mhí", "other" : "+{0} mí" }, "current" : "an mhí seo" }, "quarter" : { "next" : "an ráithe seo chugainn", "future" : "+{0} R", "past" : "-{0} R", "previous" : "an ráithe seo caite", "current" : "an ráithe seo" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/gd.json ================================================ { "narrow" : { "week" : { "next" : "ath-shn.", "previous" : "sn. ch.", "current" : "an t-sn. seo", "future" : "+{0} sn.", "past" : "-{0} sn." }, "quarter" : { "future" : "+{0} c.", "previous" : "c. ch.", "current" : "an c. seo", "next" : "ath-ch.", "past" : "-{0} c." }, "minute" : { "past" : "-{0} m", "current" : "sa mhion.", "future" : "+{0} m" }, "year" : { "previous" : "an-uir.", "current" : "am bl.", "past" : { "two" : "-{0} bhl.", "other" : "-{0} bl.", "one" : "-{0} bhl." }, "next" : "an ath-bhl.", "future" : { "two" : "+{0} bhl.", "one" : "+{0} bhl.", "other" : "+{0} bl." } }, "second" : { "future" : "+{0} d", "past" : "-{0} d", "current" : "an-dràsta" }, "month" : { "future" : { "two" : "+{0} mhì.", "other" : "+{0} mì.", "one" : "+{0} mhì." }, "current" : "am mì. seo", "past" : { "two" : "-{0} mhì.", "one" : "-{0} mhì.", "other" : "-{0} mì." }, "previous" : "mì. ch.", "next" : "ath-mhì." }, "now" : "an-dràsta", "hour" : { "future" : "+{0} u.", "current" : "san uair", "past" : "-{0} u." }, "day" : { "current" : "an-diugh", "previous" : "an-dè", "next" : "a-màireach", "past" : "-{0} là", "future" : "+{0} là" } }, "long" : { "hour" : { "future" : { "few" : "an ceann {0} uairean a thìde", "other" : "an ceann {0} uair a thìde" }, "current" : "am broinn uair a thìde", "past" : { "other" : "{0} uair a thìde air ais", "few" : "{0} uairean a thìde air ais" } }, "year" : { "previous" : "an-uiridh", "current" : "am bliadhna", "next" : "an ath-bhliadhna", "past" : { "few" : "{0} bhliadhnaichean air ais", "two" : "{0} bhliadhna air ais", "other" : "{0} bliadhna air ais", "one" : "{0} bhliadhna air ais" }, "future" : { "one" : "an ceann {0} bhliadhna", "few" : "an ceann {0} bliadhnaichean", "other" : "an ceann {0} bliadhna", "two" : "an ceann {0} bhliadhna" } }, "day" : { "previous" : "an-dè", "current" : "an-diugh", "next" : "a-màireach", "future" : { "other" : "an ceann {0} latha", "few" : "an ceann {0} làithean" }, "past" : { "few" : "{0} làithean air ais", "other" : "{0} latha air ais" } }, "minute" : { "future" : { "one" : "an ceann {0} mhionaid", "two" : "an ceann {0} mhionaid", "other" : "an ceann {0} mionaid", "few" : "an ceann {0} mionaidean" }, "current" : "am broinn mionaid", "past" : { "two" : "{0} mhionaid air ais", "one" : "{0} mhionaid air ais", "few" : "{0} mionaidean air ais", "other" : "{0} mionaid air ais" } }, "second" : { "future" : { "few" : "an ceann {0} diogan", "other" : "an ceann {0} diog", "two" : "an ceann {0} dhiog" }, "current" : "an-dràsta", "past" : { "two" : "{0} dhiog air ais", "few" : "{0} diogan air ais", "other" : "{0} diog air ais" } }, "now" : "an-dràsta", "week" : { "previous" : "an t-seachdain seo chaidh", "next" : "an ath-sheachdain", "future" : { "two" : "an ceann {0} sheachdain", "few" : "an ceann {0} seachdainean", "other" : "an ceann {0} seachdain" }, "current" : "an t-seachdain seo", "past" : { "few" : "{0} seachdainean air ais", "two" : "{0} sheachdain air ais", "other" : "{0} seachdain air ais" } }, "quarter" : { "past" : { "two" : "o chionn {0} chairteil", "few" : "o chionn {0} cairtealan", "other" : "o chionn {0} cairteil", "one" : "o chionn {0} chairteil" }, "future" : { "other" : "an ceann {0} cairteil", "two" : "an ceann {0} chairteil", "few" : "an ceann {0} cairtealan", "one" : "an ceann {0} chairteil" }, "previous" : "an cairteal seo chaidh", "current" : "an cairteal seo", "next" : "an ath-chairteal" }, "month" : { "past" : { "two" : "{0} mhìos air ais", "few" : "{0} mìosan air ais", "one" : "{0} mhìos air ais", "other" : "{0} mìos air ais" }, "previous" : "am mìos seo chaidh", "future" : { "one" : "an ceann {0} mhìosa", "other" : "an ceann {0} mìosa", "two" : "an ceann {0} mhìosa", "few" : "an ceann {0} mìosan" }, "current" : "am mìos seo", "next" : "an ath-mhìos" } }, "short" : { "hour" : { "future" : { "few" : "an {0} uair.", "other" : "an {0} uair" }, "past" : { "other" : "o {0} uair", "few" : "o {0} uair." }, "current" : "am broinn uair" }, "quarter" : { "current" : "an cairt. seo", "future" : { "one" : "an {0} chairt.", "other" : "an {0} cairt.", "two" : "an {0} chairt." }, "previous" : "an cairt. sa chaidh", "next" : "an ath-chairt.", "past" : { "other" : "o {0} cairt.", "one" : "o {0} chairt.", "two" : "o {0} chairt." } }, "week" : { "next" : "an ath-sheachd.", "current" : "an t-seachd. seo", "previous" : "seachd. sa chaidh", "past" : { "two" : "o {0} sheachd.", "other" : "o {0} seachd.", "one" : "o {0} sheachd." }, "future" : { "two" : "an {0} sheachd.", "one" : "an {0} sheachd.", "other" : "an {0} seachd." } }, "year" : { "current" : "am bliadhna", "future" : { "one" : "an {0} bhlia.", "two" : "an {0} bhlia.", "other" : "an {0} blia." }, "past" : { "two" : "o {0} bhlia.", "other" : "o {0} blia.", "one" : "o {0} bhlia." }, "next" : "an ath-bhliadhna", "previous" : "an-uiridh" }, "minute" : { "current" : "am broinn mion.", "future" : { "two" : "an {0} mhion.", "one" : "an {0} mhion.", "other" : "an {0} mion." }, "past" : { "one" : "o {0} mhion.", "two" : "o {0} mhion.", "other" : "o {0} mion." } }, "now" : "an-dràsta", "month" : { "current" : "am mìos seo", "past" : { "two" : "o {0} mhìos.", "one" : "o {0} mhìos.", "other" : "o {0} mìos." }, "future" : { "two" : "an {0} mhìos.", "one" : "an {0} mhìos.", "other" : "an {0} mìos." }, "next" : "an ath-mhìos", "previous" : "am mìos sa chaidh" }, "day" : { "future" : { "other" : "an {0} là", "few" : "an {0} là." }, "current" : "an-diugh", "next" : "a-màireach", "previous" : "an-dè", "past" : { "few" : "o {0} là.", "other" : "o {0} là" } }, "second" : { "past" : { "other" : "o {0} diog", "two" : "o {0} dhiog", "few" : "o {0} diog." }, "future" : { "few" : "an {0} diog.", "two" : "an {0} dhiog", "other" : "an {0} diog" }, "current" : "an-dràsta" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/gl.json ================================================ { "narrow" : { "second" : { "future" : "en {0} s", "current" : "agora", "past" : "hai {0} s" }, "year" : { "next" : "seguinte ano", "past" : "hai {0} a.", "future" : "en {0} a.", "previous" : "ano pasado", "current" : "este ano" }, "month" : { "past" : "hai {0} m.", "next" : "m. seguinte", "future" : "en {0} m.", "previous" : "m. pasado", "current" : "este m." }, "minute" : { "past" : "hai {0} min", "current" : "este minuto", "future" : "en {0} min" }, "quarter" : { "previous" : "trim. pasado", "current" : "este trim.", "next" : "trim. seguinte", "past" : "hai {0} trim.", "future" : "en {0} trim." }, "hour" : { "future" : "en {0} h", "past" : "hai {0} h", "current" : "esta hora" }, "now" : "agora", "week" : { "next" : "sem. seguinte", "previous" : "sem. pasada", "past" : "hai {0} sem.", "future" : "en {0} sem.", "current" : "esta sem." }, "day" : { "previous" : "onte", "future" : "en {0} d.", "past" : "hai {0} d.", "current" : "hoxe", "next" : "mañá" } }, "short" : { "minute" : { "future" : "dentro de {0} min", "past" : "hai {0} min", "current" : "este minuto" }, "month" : { "current" : "este m.", "past" : { "one" : "hai {0} mes", "other" : "hai {0} mes." }, "future" : { "one" : "dentro de {0} mes", "other" : "dentro de {0} mes." }, "next" : "m. seguinte", "previous" : "m. pasado" }, "week" : { "future" : "dentro de {0} sem.", "previous" : "sem. pasada", "current" : "esta sem.", "next" : "sem. seguinte", "past" : "hai {0} sem." }, "hour" : { "future" : "dentro de {0} h", "current" : "esta hora", "past" : "hai {0} h" }, "day" : { "next" : "mañá", "current" : "hoxe", "previous" : "onte", "past" : { "one" : "hai {0} día", "other" : "hai {0} días" }, "future" : { "one" : "dentro de {0} día", "other" : "dentro de {0} días" } }, "second" : { "past" : "hai {0} s", "current" : "agora", "future" : "dentro de {0} s" }, "now" : "agora", "year" : { "future" : { "one" : "dentro de {0} ano", "other" : "dentro de {0} anos" }, "previous" : "ano pasado", "next" : "seguinte ano", "current" : "este ano", "past" : { "other" : "hai {0} anos", "one" : "hai {0} ano" } }, "quarter" : { "current" : "este trim.", "future" : "dentro de {0} trim.", "previous" : "trim. pasado", "next" : "trim. seguinte", "past" : "hai {0} trim." } }, "long" : { "day" : { "previous" : "onte", "past" : { "other" : "hai {0} días", "one" : "hai {0} día" }, "future" : { "other" : "dentro de {0} días", "one" : "dentro de {0} día" }, "next" : "mañá", "current" : "hoxe" }, "hour" : { "current" : "esta hora", "future" : { "one" : "dentro de {0} hora", "other" : "dentro de {0} horas" }, "past" : { "one" : "hai {0} hora", "other" : "hai {0} horas" } }, "month" : { "next" : "o próximo mes", "current" : "este mes", "past" : { "other" : "hai {0} meses", "one" : "hai {0} mes" }, "previous" : "o mes pasado", "future" : { "one" : "dentro de {0} mes", "other" : "dentro de {0} meses" } }, "quarter" : { "next" : "o próximo trimestre", "past" : { "one" : "hai {0} trimestre", "other" : "hai {0} trimestres" }, "previous" : "o trimestre pasado", "current" : "este trimestre", "future" : { "one" : "dentro de {0} trimestre", "other" : "dentro de {0} trimestres" } }, "year" : { "next" : "o próximo ano", "current" : "este ano", "future" : { "one" : "dentro de {0} ano", "other" : "dentro de {0} anos" }, "previous" : "o ano pasado", "past" : { "one" : "hai {0} ano", "other" : "hai {0} anos" } }, "minute" : { "past" : { "one" : "hai {0} minuto", "other" : "hai {0} minutos" }, "future" : { "other" : "dentro de {0} minutos", "one" : "dentro de {0} minuto" }, "current" : "este minuto" }, "second" : { "future" : { "other" : "dentro de {0} segundos", "one" : "dentro de {0} segundo" }, "current" : "agora", "past" : { "one" : "hai {0} segundo", "other" : "hai {0} segundos" } }, "week" : { "future" : { "other" : "dentro de {0} semanas", "one" : "dentro de {0} semana" }, "previous" : "a semana pasada", "current" : "esta semana", "next" : "a próxima semana", "past" : { "other" : "hai {0} semanas", "one" : "hai {0} semana" } }, "now" : "agora" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/gu.json ================================================ { "narrow" : { "day" : { "next" : "આવતીકાલે", "future" : "{0} દિવસમાં", "previous" : "ગઈકાલે", "current" : "આજે", "past" : "{0} દિવસ પહેલાં" }, "quarter" : { "next" : "પછીનું ત્રિમાસિક", "past" : "{0} ત્રિમાસિક પહેલાં", "future" : "{0} ત્રિમાસિકમાં", "previous" : "છેલ્લું ત્રિમાસિક", "current" : "આ ત્રિમાસિક" }, "hour" : { "current" : "આ કલાક", "past" : "{0} કલાક પહેલાં", "future" : "{0} કલાકમાં" }, "year" : { "next" : "આવતા વર્ષે", "past" : "{0} વર્ષ પહેલાં", "current" : "આ વર્ષે", "previous" : "ગયા વર્ષે", "future" : "{0} વર્ષમાં" }, "now" : "હમણાં", "month" : { "current" : "આ મહિને", "future" : "{0} મહિનામાં", "past" : "{0} મહિના પહેલાં", "next" : "આવતા મહિને", "previous" : "ગયા મહિને" }, "week" : { "previous" : "ગયા અઠવાડિયે", "current" : "આ અઠવાડિયે", "past" : "{0} અઠ. પહેલાં", "future" : "{0} અઠ. માં", "next" : "આવતા અઠવાડિયે" }, "minute" : { "current" : "આ મિનિટ", "past" : "{0} મિનિટ પહેલાં", "future" : "{0} મિનિટમાં" }, "second" : { "current" : "હમણાં", "past" : "{0} સેકંડ પહેલાં", "future" : "{0} સેકંડમાં" } }, "long" : { "day" : { "previous" : "ગઈકાલે", "current" : "આજે", "next" : "આવતીકાલે", "past" : "{0} દિવસ પહેલાં", "future" : "{0} દિવસમાં" }, "week" : { "current" : "આ અઠવાડિયે", "future" : "{0} અઠવાડિયામાં", "past" : "{0} અઠવાડિયા પહેલાં", "previous" : "ગયા અઠવાડિયે", "next" : "આવતા અઠવાડિયે" }, "minute" : { "current" : "આ મિનિટ", "past" : "{0} મિનિટ પહેલાં", "future" : "{0} મિનિટમાં" }, "month" : { "future" : "{0} મહિનામાં", "next" : "આવતા મહિને", "previous" : "ગયા મહિને", "current" : "આ મહિને", "past" : "{0} મહિના પહેલાં" }, "hour" : { "current" : "આ કલાક", "past" : "{0} કલાક પહેલાં", "future" : "{0} કલાકમાં" }, "year" : { "past" : "{0} વર્ષ પહેલાં", "future" : "{0} વર્ષમાં", "previous" : "ગયા વર્ષે", "next" : "આવતા વર્ષે", "current" : "આ વર્ષે" }, "second" : { "past" : "{0} સેકંડ પહેલાં", "future" : "{0} સેકંડમાં", "current" : "હમણાં" }, "now" : "હમણાં", "quarter" : { "past" : "{0} ત્રિમાસિક પહેલાં", "current" : "આ ત્રિમાસિક", "previous" : "છેલ્લું ત્રિમાસિક", "future" : "{0} ત્રિમાસિકમાં", "next" : "પછીનું ત્રિમાસિક" } }, "short" : { "quarter" : { "future" : "{0} ત્રિમાસિકમાં", "next" : "પછીનું ત્રિમાસિક", "previous" : "છેલ્લું ત્રિમાસિક", "current" : "આ ત્રિમાસિક", "past" : "{0} ત્રિમાસિક પહેલાં" }, "minute" : { "past" : "{0} મિનિટ પહેલાં", "future" : "{0} મિનિટમાં", "current" : "આ મિનિટ" }, "year" : { "next" : "આવતા વર્ષે", "future" : "{0} વર્ષમાં", "current" : "આ વર્ષે", "past" : "{0} વર્ષ પહેલાં", "previous" : "ગયા વર્ષે" }, "second" : { "past" : "{0} સેકંડ પહેલાં", "current" : "હમણાં", "future" : "{0} સેકંડમાં" }, "hour" : { "past" : "{0} કલાક પહેલાં", "current" : "આ કલાક", "future" : "{0} કલાકમાં" }, "now" : "હમણાં", "month" : { "previous" : "ગયા મહિને", "next" : "આવતા મહિને", "past" : "{0} મહિના પહેલાં", "current" : "આ મહિને", "future" : "{0} મહિનામાં" }, "week" : { "previous" : "ગયા અઠવાડિયે", "current" : "આ અઠવાડિયે", "next" : "આવતા અઠવાડિયે", "past" : "{0} અઠ. પહેલાં", "future" : "{0} અઠ. માં" }, "day" : { "next" : "આવતીકાલે", "past" : "{0} દિવસ પહેલાં", "future" : "{0} દિવસમાં", "previous" : "ગઈકાલે", "current" : "આજે" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/he.json ================================================ { "long" : { "second" : { "past" : { "one" : "לפני שנייה", "two" : "לפני שתי שניות", "other" : "לפני {0} שניות" }, "current" : "עכשיו", "future" : { "two" : "בעוד שתי שניות", "other" : "בעוד {0} שניות", "one" : "בעוד שנייה" } }, "hour" : { "current" : "בשעה זו", "past" : { "one" : "לפני שעה", "two" : "לפני שעתיים", "other" : "לפני {0} שעות" }, "future" : { "one" : "בעוד שעה", "two" : "בעוד שעתיים", "other" : "בעוד {0} שעות" } }, "quarter" : { "next" : "הרבעון הבא", "current" : "רבעון זה", "future" : { "one" : "ברבעון הבא", "two" : "בעוד שני רבעונים", "other" : "בעוד {0} רבעונים" }, "previous" : "הרבעון הקודם", "past" : { "one" : "ברבעון הקודם", "two" : "לפני שני רבעונים", "other" : "לפני {0} רבעונים" } }, "year" : { "future" : { "one" : "בעוד שנה", "many" : "בעוד {0} שנה", "other" : "בעוד {0} שנים", "two" : "בעוד שנתיים" }, "next" : "השנה הבאה", "past" : { "other" : "לפני {0} שנים", "two" : "לפני שנתיים", "many" : "לפני {0} שנה", "one" : "לפני שנה" }, "previous" : "השנה שעברה", "current" : "השנה" }, "day" : { "future" : { "other" : "בעוד {0} ימים", "two" : "בעוד יומיים", "one" : "בעוד יום {0}" }, "previous" : "אתמול", "current" : "היום", "next" : "מחר", "past" : { "other" : "לפני {0} ימים", "one" : "לפני יום {0}", "two" : "לפני יומיים" } }, "minute" : { "future" : { "other" : "בעוד {0} דקות", "two" : "בעוד שתי דקות", "one" : "בעוד דקה" }, "past" : { "two" : "לפני שתי דקות", "one" : "לפני דקה", "other" : "לפני {0} דקות" }, "current" : "בדקה זו" }, "month" : { "next" : "החודש הבא", "past" : { "two" : "לפני חודשיים", "other" : "לפני {0} חודשים", "one" : "לפני חודש" }, "previous" : "החודש שעבר", "current" : "החודש", "future" : { "two" : "בעוד חודשיים", "one" : "בעוד חודש", "other" : "בעוד {0} חודשים" } }, "week" : { "next" : "השבוע הבא", "current" : "השבוע", "past" : { "two" : "לפני שבועיים", "one" : "לפני שבוע", "other" : "לפני {0} שבועות" }, "previous" : "השבוע שעבר", "future" : { "two" : "בעוד שבועיים", "other" : "בעוד {0} שבועות", "one" : "בעוד שבוע" } }, "now" : "עכשיו" }, "short" : { "week" : { "previous" : "השבוע שעבר", "next" : "השבוע הבא", "current" : "השבוע", "past" : { "other" : "לפני {0} שב׳", "two" : "לפני שבועיים", "one" : "לפני שב׳" }, "future" : { "other" : "בעוד {0} שב׳", "one" : "בעוד שב׳", "two" : "בעוד שבועיים" } }, "month" : { "next" : "החודש הבא", "current" : "החודש", "previous" : "החודש שעבר", "future" : { "two" : "בעוד חודשיים", "one" : "בעוד חודש", "other" : "בעוד {0} חודשים" }, "past" : { "two" : "לפני חודשיים", "one" : "לפני חודש", "other" : "לפני {0} חודשים" } }, "minute" : { "current" : "בדקה זו", "past" : { "other" : "לפני {0} דק׳", "one" : "לפני דקה" }, "future" : { "two" : "בעוד שתי דק׳", "other" : "בעוד {0} דק׳", "one" : "בעוד דקה" } }, "second" : { "past" : { "two" : "לפני שתי שנ׳", "one" : "לפני שנ׳", "other" : "לפני {0} שנ׳" }, "future" : { "one" : "בעוד שנ׳", "two" : "בעוד שתי שנ׳", "other" : "בעוד {0} שנ׳" }, "current" : "עכשיו" }, "now" : "עכשיו", "day" : { "next" : "מחר", "previous" : "אתמול", "current" : "היום", "past" : { "other" : "לפני {0} ימים", "one" : "אתמול", "two" : "לפני יומיים" }, "future" : { "one" : "מחר", "two" : "בעוד יומיים", "other" : "בעוד {0} ימים" } }, "year" : { "current" : "השנה", "past" : { "two" : "לפני שנתיים", "other" : "לפני {0} שנים", "one" : "לפני שנה", "many" : "לפני {0} שנה" }, "previous" : "השנה שעברה", "next" : "השנה הבאה", "future" : { "many" : "בעוד {0} שנה", "one" : "בעוד שנה", "two" : "בעוד שנתיים", "other" : "בעוד {0} שנים" } }, "quarter" : { "future" : { "one" : "ברבע׳ הבא", "two" : "בעוד שני רבע׳", "other" : "בעוד {0} רבע׳" }, "next" : "הרבעון הבא", "current" : "רבעון זה", "past" : { "one" : "ברבע׳ הקודם", "two" : "לפני שני רבע׳", "other" : "לפני {0} רבע׳" }, "previous" : "הרבעון הקודם" }, "hour" : { "current" : "בשעה זו", "past" : { "other" : "לפני {0} שע׳", "two" : "לפני שעתיים", "one" : "לפני שעה" }, "future" : { "other" : "בעוד {0} שע׳", "one" : "בעוד שעה", "two" : "בעוד שעתיים" } } }, "narrow" : { "year" : { "current" : "השנה", "past" : { "one" : "לפני שנה", "two" : "לפני שנתיים", "other" : "לפני {0} שנים", "many" : "לפני {0} שנה" }, "future" : { "one" : "בעוד שנה", "two" : "בעוד שנתיים", "many" : "בעוד {0} שנה", "other" : "בעוד {0} שנים" }, "next" : "השנה הבאה", "previous" : "השנה שעברה" }, "minute" : { "current" : "בדקה זו", "future" : { "other" : "בעוד {0} דק׳", "two" : "בעוד שתי דק׳", "one" : "בעוד דקה" }, "past" : { "two" : "לפני שתי דק׳", "other" : "לפני {0} דק׳", "one" : "לפני דקה" } }, "day" : { "previous" : "אתמול", "current" : "היום", "next" : "מחר", "past" : { "two" : "לפני יומיים", "other" : "לפני {0} ימים", "one" : "אתמול" }, "future" : { "two" : "בעוד יומיים", "one" : "מחר", "other" : "בעוד {0} ימים" } }, "second" : { "future" : { "two" : "בעוד שתי שנ׳", "one" : "בעוד שנ׳", "other" : "בעוד {0} שנ׳" }, "past" : { "one" : "לפני שנ׳", "two" : "לפני שתי שנ׳", "other" : "לפני {0} שנ׳" }, "current" : "עכשיו" }, "now" : "עכשיו", "month" : { "future" : { "one" : "בעוד חו׳", "other" : "בעוד {0} חו׳", "two" : "בעוד חודשיים" }, "current" : "החודש", "next" : "החודש הבא", "previous" : "החודש שעבר", "past" : { "other" : "לפני {0} חו׳", "one" : "לפני חו׳", "two" : "לפני חודשיים" } }, "week" : { "past" : { "two" : "לפני שבועיים", "one" : "לפני שבוע", "other" : "לפני {0} שב׳" }, "previous" : "השבוע שעבר", "future" : { "two" : "בעוד שבועיים", "one" : "בעוד שב׳", "other" : "בעוד {0} שב׳" }, "next" : "השבוע הבא", "current" : "השבוע" }, "quarter" : { "next" : "הרבעון הבא", "current" : "רבעון זה", "previous" : "הרבעון הקודם", "past" : { "two" : "לפני שני רבע׳", "other" : "לפני {0} רבע׳", "one" : "ברבע׳ הקודם" }, "future" : { "one" : "ברבע׳ הבא", "two" : "בעוד שני רבע׳", "other" : "בעוד {0} רבע׳" } }, "hour" : { "past" : { "one" : "לפני שעה", "two" : "לפני שעתיים", "other" : "לפני {0} שע׳" }, "future" : { "one" : "בעוד שעה", "other" : "בעוד {0} שע׳", "two" : "בעוד שעתיים" }, "current" : "בשעה זו" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/hi.json ================================================ { "long" : { "year" : { "future" : "{0} वर्ष में", "past" : "{0} वर्ष पहले", "next" : "अगला वर्ष", "previous" : "पिछला वर्ष", "current" : "इस वर्ष" }, "week" : { "current" : "इस सप्ताह", "next" : "अगला सप्ताह", "future" : "{0} सप्ताह में", "previous" : "पिछला सप्ताह", "past" : "{0} सप्ताह पहले" }, "quarter" : { "next" : "अगली तिमाही", "past" : "{0} तिमाही पहले", "current" : "इस तिमाही", "future" : { "other" : "{0} तिमाहियों में", "one" : "{0} तिमाही में" }, "previous" : "अंतिम तिमाही" }, "minute" : { "future" : "{0} मिनट में", "past" : "{0} मिनट पहले", "current" : "यह मिनट" }, "month" : { "current" : "इस माह", "next" : "अगला माह", "previous" : "पिछला माह", "future" : "{0} माह में", "past" : "{0} माह पहले" }, "second" : { "future" : "{0} सेकंड में", "current" : "अब", "past" : "{0} सेकंड पहले" }, "day" : { "next" : "कल", "past" : "{0} दिन पहले", "previous" : "कल", "current" : "आज", "future" : "{0} दिन में" }, "now" : "अब", "hour" : { "future" : "{0} घंटे में", "current" : "यह घंटा", "past" : "{0} घंटे पहले" } }, "narrow" : { "now" : "अब", "minute" : { "future" : "{0} मि॰ में", "current" : "यह मिनट", "past" : "{0} मि॰ पहले" }, "year" : { "previous" : "पिछला वर्ष", "current" : "इस वर्ष", "next" : "अगला वर्ष", "past" : "{0} वर्ष पहले", "future" : "{0} वर्ष में" }, "second" : { "current" : "अब", "future" : "{0} से॰ में", "past" : "{0} से॰ पहले" }, "hour" : { "past" : "{0} घं॰ पहले", "future" : "{0} घं॰ में", "current" : "यह घंटा" }, "month" : { "current" : "इस माह", "previous" : "पिछला माह", "past" : "{0} माह पहले", "next" : "अगला माह", "future" : "{0} माह में" }, "quarter" : { "previous" : "अंतिम तिमाही", "current" : "इस तिमाही", "past" : "{0} ति॰ पहले", "future" : "{0} ति॰ में", "next" : "अगली तिमाही" }, "week" : { "future" : "{0} सप्ताह में", "current" : "इस सप्ताह", "past" : "{0} सप्ताह पहले", "previous" : "पिछला सप्ताह", "next" : "अगला सप्ताह" }, "day" : { "current" : "आज", "future" : "{0} दिन में", "next" : "कल", "past" : "{0} दिन पहले", "previous" : "कल" } }, "short" : { "month" : { "future" : "{0} माह में", "previous" : "पिछला माह", "next" : "अगला माह", "past" : "{0} माह पहले", "current" : "इस माह" }, "now" : "अब", "day" : { "next" : "कल", "current" : "आज", "previous" : "कल", "past" : "{0} दिन पहले", "future" : "{0} दिन में" }, "year" : { "current" : "इस वर्ष", "future" : "{0} वर्ष में", "previous" : "पिछला वर्ष", "next" : "अगला वर्ष", "past" : "{0} वर्ष पहले" }, "hour" : { "future" : "{0} घं॰ में", "past" : "{0} घं॰ पहले", "current" : "यह घंटा" }, "minute" : { "current" : "यह मिनट", "past" : "{0} मि॰ पहले", "future" : "{0} मि॰ में" }, "second" : { "past" : "{0} से॰ पहले", "future" : "{0} से॰ में", "current" : "अब" }, "quarter" : { "current" : "इस तिमाही", "next" : "अगली तिमाही", "past" : { "other" : "{0} तिमाहियों पहले", "one" : "{0} तिमाही पहले" }, "future" : { "other" : "{0} तिमाहियों में", "one" : "{0} तिमाही में" }, "previous" : "अंतिम तिमाही" }, "week" : { "next" : "अगला सप्ताह", "past" : "{0} सप्ताह पहले", "current" : "इस सप्ताह", "previous" : "पिछला सप्ताह", "future" : "{0} सप्ताह में" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/hr.json ================================================ { "narrow" : { "year" : { "future" : "za {0} g.", "previous" : "prošle g.", "current" : "ove g.", "next" : "sljedeće g.", "past" : "prije {0} g." }, "day" : { "current" : "danas", "past" : "prije {0} d", "next" : "sutra", "previous" : "jučer", "future" : "za {0} d" }, "quarter" : { "future" : "za {0} kv.", "previous" : "prošli kv.", "current" : "ovaj kv.", "next" : "sljedeći kv.", "past" : "prije {0} kv." }, "week" : { "next" : "sljedeći tj.", "past" : "prije {0} tj.", "current" : "ovaj tj.", "future" : "za {0} tj.", "previous" : "prošli tj." }, "hour" : { "current" : "ovaj sat", "past" : "prije {0} h", "future" : "za {0} h" }, "month" : { "past" : "prije {0} mj.", "previous" : "prošli mj.", "current" : "ovaj mj.", "future" : "za {0} mj.", "next" : "sljedeći mj." }, "minute" : { "current" : "ova minuta", "past" : "prije {0} min", "future" : "za {0} min" }, "second" : { "past" : "prije {0} s", "future" : "za {0} s", "current" : "sad" }, "now" : "sad" }, "short" : { "hour" : { "current" : "ovaj sat", "past" : "prije {0} h", "future" : "za {0} h" }, "now" : "sad", "quarter" : { "current" : "ovaj kv.", "future" : "za {0} kv.", "past" : "prije {0} kv.", "next" : "sljedeći kv.", "previous" : "prošli kv." }, "day" : { "current" : "danas", "past" : { "other" : "prije {0} dana", "one" : "prije {0} dan" }, "future" : { "other" : "za {0} dana", "one" : "za {0} dan" }, "next" : "sutra", "previous" : "jučer" }, "week" : { "current" : "ovaj tj.", "future" : "za {0} tj.", "previous" : "prošli tj.", "next" : "sljedeći tj.", "past" : "prije {0} tj." }, "minute" : { "future" : "za {0} min", "past" : "prije {0} min", "current" : "ova minuta" }, "second" : { "future" : "za {0} s", "current" : "sad", "past" : "prije {0} s" }, "month" : { "future" : "za {0} mj.", "previous" : "prošli mj.", "next" : "sljedeći mj.", "current" : "ovaj mj.", "past" : "prije {0} mj." }, "year" : { "next" : "sljedeće god.", "previous" : "prošle god.", "current" : "ove god.", "future" : "za {0} g.", "past" : "prije {0} g." } }, "long" : { "second" : { "future" : { "few" : "za {0} sekunde", "one" : "za {0} sekundu", "other" : "za {0} sekundi" }, "current" : "sad", "past" : { "one" : "prije {0} sekundu", "other" : "prije {0} sekundi", "few" : "prije {0} sekunde" } }, "year" : { "previous" : "prošle godine", "next" : "sljedeće godine", "past" : { "few" : "prije {0} godine", "one" : "prije {0} godinu", "other" : "prije {0} godina" }, "current" : "ove godine", "future" : { "other" : "za {0} godina", "few" : "za {0} godine", "one" : "za {0} godinu" } }, "day" : { "previous" : "jučer", "future" : { "one" : "za {0} dan", "other" : "za {0} dana" }, "past" : { "other" : "prije {0} dana", "one" : "prije {0} dan" }, "current" : "danas", "next" : "sutra" }, "now" : "sad", "month" : { "past" : { "few" : "prije {0} mjeseca", "one" : "prije {0} mjesec", "other" : "prije {0} mjeseci" }, "next" : "sljedeći mjesec", "previous" : "prošli mjesec", "future" : { "other" : "za {0} mjeseci", "one" : "za {0} mjesec", "few" : "za {0} mjeseca" }, "current" : "ovaj mjesec" }, "week" : { "past" : { "one" : "prije {0} tjedan", "few" : "prije {0} tjedna", "other" : "prije {0} tjedana" }, "future" : { "one" : "za {0} tjedan", "few" : "za {0} tjedna", "other" : "za {0} tjedana" }, "current" : "ovaj tjedan", "previous" : "prošli tjedan", "next" : "sljedeći tjedan" }, "quarter" : { "past" : { "one" : "prije {0} kvartal", "other" : "prije {0} kvartala" }, "current" : "ovaj kvartal", "future" : { "one" : "za {0} kvartal", "other" : "za {0} kvartala" }, "previous" : "prošli kvartal", "next" : "sljedeći kvartal" }, "hour" : { "past" : { "one" : "prije {0} sat", "few" : "prije {0} sata", "other" : "prije {0} sati" }, "future" : { "other" : "za {0} sati", "few" : "za {0} sata", "one" : "za {0} sat" }, "current" : "ovaj sat" }, "minute" : { "current" : "ova minuta", "future" : { "few" : "za {0} minute", "other" : "za {0} minuta", "one" : "za {0} minutu" }, "past" : { "few" : "prije {0} minute", "other" : "prije {0} minuta", "one" : "prije {0} minutu" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/hsb.json ================================================ { "narrow" : { "week" : { "next" : "přichodny tydźeń", "past" : "před {0} tydź.", "future" : "za {0} tydź.", "previous" : "zašły tydźeń", "current" : "tutón tydźeń" }, "quarter" : { "next" : "next quarter", "past" : "před {0} kw.", "previous" : "last quarter", "current" : "this quarter", "future" : "za {0} kw." }, "minute" : { "past" : "před {0} m", "future" : "za {0} m", "current" : "this minute" }, "day" : { "previous" : "wčera", "next" : "jutře", "current" : "dźensa", "future" : "za {0} d", "past" : "před {0} d" }, "year" : { "current" : "lětsa", "previous" : "loni", "next" : "klětu", "past" : "před {0} l.", "future" : "za {0} l." }, "second" : { "future" : "za {0} s", "current" : "now", "past" : "před {0} s" }, "month" : { "past" : "před {0} měs.", "future" : "za {0} měs.", "next" : "přichodny měsac", "current" : "tutón měsac", "previous" : "zašły měsac" }, "now" : "now", "hour" : { "future" : "za {0} h", "current" : "this hour", "past" : "před {0} h" } }, "short" : { "year" : { "next" : "klětu", "future" : "za {0} l.", "current" : "lětsa", "past" : "před {0} l.", "previous" : "loni" }, "quarter" : { "future" : "za {0} kwart.", "next" : "next quarter", "previous" : "last quarter", "current" : "this quarter", "past" : "před {0} kwart." }, "second" : { "current" : "now", "past" : "před {0} sek.", "future" : "za {0} sek." }, "minute" : { "past" : "před {0} min.", "future" : "za {0} min.", "current" : "this minute" }, "day" : { "past" : "před {0} dnj.", "next" : "jutře", "future" : { "one" : "za {0} dźeń", "few" : "za {0} dny", "other" : "za {0} dnj." }, "previous" : "wčera", "current" : "dźensa" }, "month" : { "previous" : "zašły měsac", "next" : "přichodny měsac", "past" : "před {0} měs.", "current" : "tutón měsac", "future" : "za {0} měs." }, "hour" : { "current" : "this hour", "past" : "před {0} hodź.", "future" : "za {0} hodź." }, "now" : "now", "week" : { "previous" : "zašły tydźeń", "current" : "tutón tydźeń", "next" : "přichodny tydźeń", "past" : "před {0} tydź.", "future" : "za {0} tydź." } }, "long" : { "quarter" : { "previous" : "last quarter", "current" : "this quarter", "next" : "next quarter", "past" : { "two" : "před {0} kwartalomaj", "one" : "před {0} kwartalom", "other" : "před {0} kwartalemi" }, "future" : { "one" : "za {0} kwartal", "few" : "za {0} kwartale", "other" : "za {0} kwartalow", "two" : "za {0} kwartalej" } }, "hour" : { "future" : { "two" : "za {0} hodźinje", "few" : "za {0} hodźiny", "one" : "za {0} hodźinu", "other" : "za {0} hodźin" }, "current" : "this hour", "past" : { "two" : "před {0} hodźinomaj", "other" : "před {0} hodźinami", "one" : "před {0} hodźinu" } }, "day" : { "previous" : "wčera", "current" : "dźensa", "next" : "jutře", "past" : { "one" : "před {0} dnjom", "two" : "před {0} dnjomaj", "other" : "před {0} dnjemi" }, "future" : { "one" : "za {0} dźeń", "two" : "za {0} dnjej", "other" : "za {0} dnjow", "few" : "za {0} dny" } }, "month" : { "next" : "přichodny měsac", "previous" : "zašły měsac", "current" : "tutón měsac", "future" : { "one" : "za {0} měsac", "other" : "za {0} měsacow", "two" : "za {0} měsacaj", "few" : "za {0} měsacy" }, "past" : { "one" : "před {0} měsacom", "two" : "před {0} měsacomaj", "other" : "před {0} měsacami" } }, "second" : { "future" : { "other" : "za {0} sekundow", "two" : "za {0} sekundźe", "one" : "za {0} sekundu", "few" : "za {0} sekundy" }, "current" : "now", "past" : { "other" : "před {0} sekundami", "one" : "před {0} sekundu", "two" : "před {0} sekundomaj" } }, "now" : "now", "week" : { "current" : "tutón tydźeń", "previous" : "zašły tydźeń", "next" : "přichodny tydźeń", "past" : { "two" : "před {0} tydźenjomaj", "one" : "před {0} tydźenjom", "other" : "před {0} tydźenjemi" }, "future" : { "two" : "za {0} tydźenjej", "one" : "za {0} tydźeń", "few" : "za {0} tydźenje", "other" : "za {0} tydźenjow" } }, "minute" : { "past" : { "other" : "před {0} minutami", "one" : "před {0} minutu", "two" : "před {0} minutomaj" }, "future" : { "one" : "za {0} minutu", "few" : "za {0} minuty", "two" : "za {0} minuće", "other" : "za {0} minutow" }, "current" : "this minute" }, "year" : { "next" : "klětu", "previous" : "loni", "past" : { "one" : "před {0} lětom", "two" : "před {0} lětomaj", "other" : "před {0} lětami" }, "current" : "lětsa", "future" : { "other" : "za {0} lět", "two" : "za {0} lěće", "few" : "za {0} lěta", "one" : "za {0} lěto" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/hu.json ================================================ { "narrow" : { "day" : { "next" : "holnap", "future" : "{0} nap múlva", "previous" : "tegnap", "current" : "ma", "past" : "{0} napja" }, "quarter" : { "next" : "következő negyedév", "past" : "{0} negyedévvel ezelőtt", "future" : "{0} n.év múlva", "previous" : "előző negyedév", "current" : "ez a negyedév" }, "hour" : { "future" : "{0} óra múlva", "current" : "ebben az órában", "past" : "{0} órával ezelőtt" }, "year" : { "next" : "következő év", "past" : "{0} évvel ezelőtt", "current" : "ez az év", "previous" : "előző év", "future" : "{0} év múlva" }, "now" : "most", "month" : { "current" : "ez a hónap", "future" : "{0} hónap múlva", "past" : "{0} hónappal ezelőtt", "next" : "következő hónap", "previous" : "előző hónap" }, "week" : { "previous" : "előző hét", "current" : "ez a hét", "past" : "{0} héttel ezelőtt", "future" : "{0} hét múlva", "next" : "következő hét" }, "minute" : { "future" : "{0} perc múlva", "current" : "ebben a percben", "past" : "{0} perccel ezelőtt" }, "second" : { "current" : "most", "past" : "{0} másodperccel ezelőtt", "future" : "{0} másodperc múlva" } }, "long" : { "day" : { "previous" : "tegnap", "current" : "ma", "next" : "holnap", "past" : "{0} nappal ezelőtt", "future" : "{0} nap múlva" }, "week" : { "current" : "ez a hét", "future" : "{0} hét múlva", "past" : "{0} héttel ezelőtt", "previous" : "előző hét", "next" : "következő hét" }, "minute" : { "current" : "ebben a percben", "past" : "{0} perccel ezelőtt", "future" : "{0} perc múlva" }, "month" : { "future" : "{0} hónap múlva", "next" : "következő hónap", "previous" : "előző hónap", "current" : "ez a hónap", "past" : "{0} hónappal ezelőtt" }, "hour" : { "current" : "ebben az órában", "past" : "{0} órával ezelőtt", "future" : "{0} óra múlva" }, "year" : { "past" : "{0} évvel ezelőtt", "future" : "{0} év múlva", "previous" : "előző év", "next" : "következő év", "current" : "ez az év" }, "second" : { "past" : "{0} másodperccel ezelőtt", "future" : "{0} másodperc múlva", "current" : "most" }, "now" : "most", "quarter" : { "past" : "{0} negyedévvel ezelőtt", "current" : "ez a negyedév", "previous" : "előző negyedév", "future" : "{0} negyedév múlva", "next" : "következő negyedév" } }, "short" : { "quarter" : { "future" : "{0} negyedév múlva", "next" : "következő negyedév", "previous" : "előző negyedév", "current" : "ez a negyedév", "past" : "{0} negyedévvel ezelőtt" }, "minute" : { "future" : "{0} perc múlva", "current" : "ebben a percben", "past" : "{0} perccel ezelőtt" }, "year" : { "next" : "következő év", "future" : "{0} év múlva", "current" : "ez az év", "past" : "{0} évvel ezelőtt", "previous" : "előző év" }, "second" : { "future" : "{0} másodperc múlva", "current" : "most", "past" : "{0} másodperccel ezelőtt" }, "hour" : { "past" : "{0} órával ezelőtt", "current" : "ebben az órában", "future" : "{0} óra múlva" }, "now" : "most", "month" : { "previous" : "előző hónap", "next" : "következő hónap", "past" : "{0} hónappal ezelőtt", "current" : "ez a hónap", "future" : "{0} hónap múlva" }, "week" : { "previous" : "előző hét", "current" : "ez a hét", "next" : "következő hét", "past" : "{0} héttel ezelőtt", "future" : "{0} hét múlva" }, "day" : { "next" : "holnap", "past" : "{0} napja", "future" : "{0} nap múlva", "previous" : "tegnap", "current" : "ma" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/hy.json ================================================ { "narrow" : { "day" : { "next" : "վաղը", "future" : "{0} օրից", "previous" : "երեկ", "current" : "այսօր", "past" : "{0} օր առաջ" }, "quarter" : { "next" : "հաջորդ եռամսյակ", "past" : "{0} եռմս առաջ", "future" : "{0} եռմս-ից", "previous" : "նախորդ եռամսյակ", "current" : "այս եռամսյակ" }, "hour" : { "past" : "{0} ժ առաջ", "current" : "այս ժամին", "future" : "{0} ժ-ից" }, "year" : { "next" : "հաջորդ տարի", "past" : "{0} տ առաջ", "current" : "այս տարի", "previous" : "նախորդ տարի", "future" : "{0} տարուց" }, "now" : "հիմա", "month" : { "current" : "այս ամիս", "future" : "{0} ամսից", "past" : "{0} ամիս առաջ", "next" : "հաջորդ ամիս", "previous" : "անցյալ ամիս" }, "week" : { "previous" : "նախորդ շաբաթ", "current" : "այս շաբաթ", "past" : "{0} շաբ առաջ", "future" : "{0} շաբ անց", "next" : "հաջորդ շաբաթ" }, "minute" : { "current" : "այս րոպեին", "future" : "{0} ր-ից", "past" : "{0} ր առաջ" }, "second" : { "current" : "հիմա", "past" : "{0} վ առաջ", "future" : "{0} վ-ից" } }, "long" : { "day" : { "previous" : "երեկ", "current" : "այսօր", "next" : "վաղը", "past" : "{0} օր առաջ", "future" : "{0} օրից" }, "week" : { "current" : "այս շաբաթ", "future" : "{0} շաբաթից", "past" : "{0} շաբաթ առաջ", "previous" : "նախորդ շաբաթ", "next" : "հաջորդ շաբաթ" }, "minute" : { "current" : "այս րոպեին", "past" : "{0} րոպե առաջ", "future" : "{0} րոպեից" }, "month" : { "future" : "{0} ամսից", "next" : "հաջորդ ամիս", "previous" : "նախորդ ամիս", "current" : "այս ամիս", "past" : "{0} ամիս առաջ" }, "hour" : { "past" : "{0} ժամ առաջ", "current" : "այս ժամին", "future" : "{0} ժամից" }, "year" : { "past" : "{0} տարի առաջ", "future" : "{0} տարուց", "previous" : "նախորդ տարի", "next" : "հաջորդ տարի", "current" : "այս տարի" }, "second" : { "future" : "{0} վայրկյանից", "current" : "հիմա", "past" : "{0} վայրկյան առաջ" }, "now" : "հիմա", "quarter" : { "past" : "{0} եռամսյակ առաջ", "current" : "այս եռամսյակ", "previous" : "նախորդ եռամսյակ", "future" : "{0} եռամսյակից", "next" : "հաջորդ եռամսյակ" } }, "short" : { "quarter" : { "future" : "{0} եռմս-ից", "next" : "հաջորդ եռամսյակ", "previous" : "նախորդ եռամսյակ", "current" : "այս եռամսյակ", "past" : "{0} եռմս առաջ" }, "minute" : { "past" : "{0} ր առաջ", "current" : "այս րոպեին", "future" : "{0} ր-ից" }, "year" : { "next" : "հաջորդ տարի", "future" : "{0} տարուց", "current" : "այս տարի", "past" : "{0} տ առաջ", "previous" : "նախորդ տարի" }, "second" : { "current" : "հիմա", "past" : "{0} վրկ առաջ", "future" : "{0} վրկ-ից" }, "hour" : { "current" : "այս ժամին", "past" : "{0} ժ առաջ", "future" : "{0} ժ-ից" }, "now" : "հիմա", "month" : { "previous" : "անցյալ ամիս", "next" : "հաջորդ ամիս", "past" : "{0} ամիս առաջ", "current" : "այս ամիս", "future" : "{0} ամսից" }, "week" : { "previous" : "նախորդ շաբաթ", "current" : "այս շաբաթ", "next" : "հաջորդ շաբաթ", "past" : "{0} շաբ առաջ", "future" : "{0} շաբ-ից" }, "day" : { "next" : "վաղը", "past" : "{0} օր առաջ", "future" : "{0} օրից", "previous" : "երեկ", "current" : "այսօր" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/id.json ================================================ { "narrow" : { "day" : { "next" : "besok", "future" : "dalam {0} h", "previous" : "kemarin", "current" : "hari ini", "past" : "{0} h lalu" }, "quarter" : { "next" : "kuartal berikutnya", "past" : "{0} krtl. lalu", "future" : "dlm {0} krtl.", "previous" : "Kuartal lalu", "current" : "kuartal ini" }, "hour" : { "future" : "dalam {0} jam", "current" : "jam ini", "past" : "{0} jam lalu" }, "year" : { "next" : "tahun depan", "past" : "{0} thn lalu", "current" : "tahun ini", "previous" : "tahun lalu", "future" : "dlm {0} thn" }, "now" : "sekarang", "month" : { "current" : "bulan ini", "future" : "dlm {0} bln", "past" : "{0} bln lalu", "next" : "bulan berikutnya", "previous" : "bulan lalu" }, "week" : { "previous" : "minggu lalu", "current" : "minggu ini", "past" : "{0} mgg lalu", "future" : "dlm {0} mgg", "next" : "minggu depan" }, "minute" : { "future" : "dlm {0} mnt", "current" : "menit ini", "past" : "{0} mnt lalu" }, "second" : { "current" : "sekarang", "future" : "dlm {0} dtk", "past" : "{0} dtk lalu" } }, "long" : { "day" : { "previous" : "kemarin", "current" : "hari ini", "next" : "besok", "past" : "{0} hari yang lalu", "future" : "dalam {0} hari" }, "week" : { "current" : "minggu ini", "future" : "dalam {0} minggu", "past" : "{0} minggu yang lalu", "previous" : "minggu lalu", "next" : "minggu depan" }, "minute" : { "past" : "{0} menit yang lalu", "current" : "menit ini", "future" : "dalam {0} menit" }, "month" : { "future" : "dalam {0} bulan", "next" : "bulan berikutnya", "previous" : "bulan lalu", "current" : "bulan ini", "past" : "{0} bulan yang lalu" }, "hour" : { "past" : "{0} jam yang lalu", "future" : "dalam {0} jam", "current" : "jam ini" }, "year" : { "past" : "{0} tahun yang lalu", "future" : "dalam {0} tahun", "previous" : "tahun lalu", "next" : "tahun depan", "current" : "tahun ini" }, "second" : { "past" : "{0} detik yang lalu", "current" : "sekarang", "future" : "dalam {0} detik" }, "now" : "sekarang", "quarter" : { "past" : "{0} kuartal yang lalu", "current" : "kuartal ini", "previous" : "Kuartal lalu", "future" : "dalam {0} kuartal", "next" : "kuartal berikutnya" } }, "short" : { "quarter" : { "future" : "dlm {0} krtl.", "next" : "kuartal berikutnya", "previous" : "Kuartal lalu", "current" : "kuartal ini", "past" : "{0} krtl. lalu" }, "minute" : { "past" : "{0} mnt lalu", "current" : "menit ini", "future" : "dlm {0} mnt" }, "year" : { "next" : "tahun depan", "future" : "dlm {0} thn", "current" : "tahun ini", "past" : "{0} thn lalu", "previous" : "tahun lalu" }, "second" : { "current" : "sekarang", "past" : "{0} dtk lalu", "future" : "dlm {0} dtk" }, "hour" : { "current" : "jam ini", "past" : "{0} jam lalu", "future" : "dalam {0} jam" }, "now" : "sekarang", "month" : { "previous" : "bulan lalu", "next" : "bulan berikutnya", "past" : "{0} bln lalu", "current" : "bulan ini", "future" : "dlm {0} bln" }, "week" : { "previous" : "minggu lalu", "current" : "minggu ini", "next" : "minggu depan", "past" : "{0} mgg lalu", "future" : "dlm {0} mgg" }, "day" : { "next" : "besok", "past" : "{0} h lalu", "future" : "dalam {0} h", "previous" : "kemarin", "current" : "hari ini" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/is.json ================================================ { "short" : { "now" : "núna", "month" : { "future" : "eftir {0} mán.", "next" : "í næsta mán.", "current" : "í þessum mán.", "past" : "fyrir {0} mán.", "previous" : "í síðasta mán." }, "second" : { "past" : "fyrir {0} sek.", "future" : "eftir {0} sek.", "current" : "núna" }, "year" : { "next" : "á næsta ári", "current" : "á þessu ári", "past" : { "other" : "fyrir {0} árum", "one" : "fyrir {0} ári" }, "previous" : "á síðasta ári", "future" : "eftir {0} ár" }, "week" : { "previous" : "í síðustu viku", "current" : "í þessari viku", "past" : { "other" : "fyrir {0} vikum", "one" : "fyrir {0} viku" }, "future" : "eftir {0} vikur", "next" : "í næstu viku" }, "day" : { "previous" : "í gær", "current" : "í dag", "past" : { "one" : "fyrir {0} degi", "other" : "fyrir {0} dögum" }, "next" : "á morgun", "future" : { "one" : "eftir {0} dag", "other" : "eftir {0} daga" } }, "hour" : { "past" : "fyrir {0} klst.", "future" : "eftir {0} klst.", "current" : "þessa stundina" }, "quarter" : { "next" : "næsti ársfj.", "previous" : "síðasti ársfj.", "past" : "fyrir {0} ársfj.", "future" : "eftir {0} ársfj.", "current" : "þessi ársfj." }, "minute" : { "future" : "eftir {0} mín.", "current" : "á þessari mínútu", "past" : "fyrir {0} mín." } }, "long" : { "minute" : { "past" : { "one" : "fyrir {0} mínútu", "other" : "fyrir {0} mínútum" }, "future" : { "one" : "eftir {0} mínútu", "other" : "eftir {0} mínútur" }, "current" : "á þessari mínútu" }, "year" : { "future" : "eftir {0} ár", "current" : "á þessu ári", "previous" : "á síðasta ári", "next" : "á næsta ári", "past" : { "one" : "fyrir {0} ári", "other" : "fyrir {0} árum" } }, "quarter" : { "past" : { "one" : "fyrir {0} ársfjórðungi", "other" : "fyrir {0} ársfjórðungum" }, "next" : "næsti ársfjórðungur", "previous" : "síðasti ársfjórðungur", "future" : { "one" : "eftir {0} ársfjórðung", "other" : "eftir {0} ársfjórðunga" }, "current" : "þessi ársfjórðungur" }, "week" : { "future" : { "one" : "eftir {0} viku", "other" : "eftir {0} vikur" }, "past" : { "other" : "fyrir {0} vikum", "one" : "fyrir {0} viku" }, "previous" : "í síðustu viku", "current" : "í þessari viku", "next" : "í næstu viku" }, "hour" : { "past" : { "one" : "fyrir {0} klukkustund", "other" : "fyrir {0} klukkustundum" }, "future" : { "other" : "eftir {0} klukkustundir", "one" : "eftir {0} klukkustund" }, "current" : "þessa stundina" }, "month" : { "previous" : "í síðasta mánuði", "next" : "í næsta mánuði", "future" : { "one" : "eftir {0} mánuð", "other" : "eftir {0} mánuði" }, "past" : { "one" : "fyrir {0} mánuði", "other" : "fyrir {0} mánuðum" }, "current" : "í þessum mánuði" }, "second" : { "current" : "núna", "past" : { "one" : "fyrir {0} sekúndu", "other" : "fyrir {0} sekúndum" }, "future" : { "other" : "eftir {0} sekúndur", "one" : "eftir {0} sekúndu" } }, "now" : "núna", "day" : { "next" : "á morgun", "future" : { "other" : "eftir {0} daga", "one" : "eftir {0} dag" }, "previous" : "í gær", "current" : "í dag", "past" : { "one" : "fyrir {0} degi", "other" : "fyrir {0} dögum" } } }, "narrow" : { "day" : { "previous" : "í gær", "future" : { "other" : "+{0} daga", "one" : "+{0} dag" }, "past" : { "one" : "-{0} degi", "other" : "-{0} dögum" }, "current" : "í dag", "next" : "á morgun" }, "week" : { "past" : { "other" : "-{0} vikur", "one" : "-{0} viku" }, "future" : { "one" : "+{0} viku", "other" : "+{0} vikur" }, "current" : "í þessari viku", "previous" : "í síðustu viku", "next" : "í næstu viku" }, "quarter" : { "previous" : "síðasti ársfj.", "future" : "eftir {0} ársfj.", "next" : "næsti ársfj.", "past" : "fyrir {0} ársfj.", "current" : "þessi ársfj." }, "month" : { "future" : "eftir {0} mán.", "current" : "í þessum mán.", "previous" : "í síðasta mán.", "past" : "fyrir {0} mán.", "next" : "í næsta mán." }, "hour" : { "past" : "-{0} klst.", "future" : "+{0} klst.", "current" : "þessa stundina" }, "year" : { "past" : "fyrir {0} árum", "previous" : "á síðasta ári", "next" : "á næsta ári", "future" : "eftir {0} ár", "current" : "á þessu ári" }, "now" : "núna", "minute" : { "past" : "-{0} mín.", "future" : "+{0} mín.", "current" : "á þessari mínútu" }, "second" : { "current" : "núna", "past" : "-{0} sek.", "future" : "+{0} sek." } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/it.json ================================================ { "long" : { "day" : { "current" : "oggi", "next" : "domani", "past" : { "one" : "{0} giorno fa", "other" : "{0} giorni fa" }, "future" : { "one" : "tra {0} giorno", "other" : "tra {0} giorni" }, "previous" : "ieri" }, "minute" : { "future" : { "one" : "tra {0} minuto", "other" : "tra {0} minuti" }, "current" : "questo minuto", "past" : { "other" : "{0} minuti fa", "one" : "{0} minuto fa" } }, "second" : { "future" : { "one" : "tra {0} secondo", "other" : "tra {0} secondi" }, "current" : "ora", "past" : { "one" : "{0} secondo fa", "other" : "{0} secondi fa" } }, "week" : { "future" : { "other" : "tra {0} settimane", "one" : "tra {0} settimana" }, "previous" : "settimana scorsa", "current" : "questa settimana", "next" : "settimana prossima", "past" : { "one" : "{0} settimana fa", "other" : "{0} settimane fa" } }, "now" : "ora", "month" : { "past" : { "other" : "{0} mesi fa", "one" : "{0} mese fa" }, "current" : "questo mese", "future" : { "other" : "tra {0} mesi", "one" : "tra {0} mese" }, "previous" : "mese scorso", "next" : "mese prossimo" }, "quarter" : { "future" : { "other" : "tra {0} trimestri", "one" : "tra {0} trimestre" }, "previous" : "trimestre scorso", "next" : "trimestre prossimo", "current" : "questo trimestre", "past" : { "other" : "{0} trimestri fa", "one" : "{0} trimestre fa" } }, "hour" : { "past" : { "other" : "{0} ore fa", "one" : "{0} ora fa" }, "current" : "quest’ora", "future" : { "one" : "tra {0} ora", "other" : "tra {0} ore" } }, "year" : { "current" : "quest’anno", "next" : "anno prossimo", "previous" : "anno scorso", "future" : { "one" : "tra {0} anno", "other" : "tra {0} anni" }, "past" : { "one" : "{0} anno fa", "other" : "{0} anni fa" } } }, "narrow" : { "second" : { "future" : "tra {0} s", "current" : "ora", "past" : "{0}s fa" }, "month" : { "past" : { "other" : "{0} mesi fa", "one" : "{0} mese fa" }, "future" : { "one" : "tra {0} mese", "other" : "tra {0} mesi" }, "next" : "mese prossimo", "current" : "questo mese", "previous" : "mese scorso" }, "quarter" : { "current" : "questo trimestre", "next" : "trimestre prossimo", "past" : "{0} trim. fa", "future" : "tra {0} trim.", "previous" : "trimestre scorso" }, "now" : "ora", "week" : { "past" : "{0} sett. fa", "current" : "questa settimana", "next" : "settimana prossima", "future" : "tra {0} sett.", "previous" : "settimana scorsa" }, "year" : { "previous" : "anno scorso", "current" : "quest’anno", "past" : { "one" : "{0} anno fa", "other" : "{0} anni fa" }, "future" : { "one" : "tra {0} anno", "other" : "tra {0} anni" }, "next" : "anno prossimo" }, "day" : { "current" : "oggi", "future" : { "one" : "tra {0}g", "other" : "tra {0} gg" }, "past" : { "other" : "{0} gg fa", "one" : "{0}g fa" }, "next" : "domani", "previous" : "ieri" }, "hour" : { "future" : "tra {0} h", "current" : "quest’ora", "past" : "{0}h fa" }, "minute" : { "future" : "tra {0} min", "current" : "questo minuto", "past" : "{0} min fa" } }, "short" : { "month" : { "next" : "mese prossimo", "past" : { "one" : "{0} mese fa", "other" : "{0} mesi fa" }, "current" : "questo mese", "previous" : "mese scorso", "future" : { "one" : "tra {0} mese", "other" : "tra {0} mesi" } }, "now" : "ora", "day" : { "next" : "domani", "current" : "oggi", "previous" : "ieri", "past" : { "other" : "{0}gg fa", "one" : "{0}g fa" }, "future" : { "one" : "tra {0} g", "other" : "tra {0} gg" } }, "year" : { "previous" : "anno scorso", "current" : "quest’anno", "next" : "anno prossimo", "past" : { "other" : "{0} anni fa", "one" : "{0} anno fa" }, "future" : { "one" : "tra {0} anno", "other" : "tra {0} anni" } }, "hour" : { "past" : "{0}h fa", "future" : "tra {0}h", "current" : "quest’ora" }, "minute" : { "future" : "tra {0} min", "current" : "questo minuto", "past" : "{0} min fa" }, "second" : { "past" : { "one" : "{0}s fa", "other" : "{0} sec. fa" }, "future" : { "one" : "tra {0}s", "other" : "tra {0} sec." }, "current" : "ora" }, "quarter" : { "current" : "questo trimestre", "future" : "tra {0} trim.", "previous" : "trimestre scorso", "next" : "trimestre prossimo", "past" : "{0} trim. fa" }, "week" : { "future" : "tra {0} sett.", "previous" : "settimana scorsa", "next" : "settimana prossima", "past" : "{0} sett. fa", "current" : "questa settimana" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ja.json ================================================ { "long" : { "hour" : { "future" : "{0} 時間後", "past" : "{0} 時間前", "current" : "1 時間以内" }, "second" : { "future" : "{0} 秒後", "past" : "{0} 秒前", "current" : "今" }, "week" : { "next" : "翌週", "current" : "今週", "previous" : "先週", "future" : "{0} 週間後", "past" : "{0} 週間前" }, "minute" : { "current" : "1 分以内", "past" : "{0} 分前", "future" : "{0} 分後" }, "year" : { "current" : "今年", "past" : "{0} 年前", "future" : "{0} 年後", "previous" : "昨年", "next" : "翌年" }, "quarter" : { "next" : "翌四半期", "current" : "今四半期", "future" : "{0} 四半期後", "previous" : "前四半期", "past" : "{0} 四半期前" }, "month" : { "previous" : "先月", "future" : "{0} か月後", "current" : "今月", "next" : "翌月", "past" : "{0} か月前" }, "day" : { "next" : "明日", "past" : "{0} 日前", "current" : "今日", "future" : "{0} 日後", "previous" : "昨日" }, "now" : "今" }, "narrow" : { "week" : { "current" : "今週", "next" : "翌週", "past" : "{0}週間前", "previous" : "先週", "future" : "{0}週間後" }, "minute" : { "current" : "1 分以内", "past" : "{0}分前", "future" : "{0}分後" }, "month" : { "current" : "今月", "previous" : "先月", "past" : "{0}か月前", "next" : "翌月", "future" : "{0}か月後" }, "now" : "今", "year" : { "current" : "今年", "next" : "翌年", "past" : "{0}年前", "future" : "{0}年後", "previous" : "昨年" }, "hour" : { "future" : "{0}時間後", "current" : "1 時間以内", "past" : "{0}時間前" }, "quarter" : { "previous" : "前四半期", "current" : "今四半期", "past" : "{0}四半期前", "future" : "{0}四半期後", "next" : "翌四半期" }, "second" : { "past" : "{0}秒前", "current" : "今", "future" : "{0}秒後" }, "day" : { "past" : "{0}日前", "current" : "今日", "future" : "{0}日後", "previous" : "昨日", "next" : "明日" } }, "short" : { "month" : { "previous" : "先月", "current" : "今月", "next" : "翌月", "past" : "{0} か月前", "future" : "{0} か月後" }, "now" : "今", "day" : { "next" : "明日", "current" : "今日", "previous" : "昨日", "past" : "{0} 日前", "future" : "{0} 日後" }, "year" : { "current" : "今年", "future" : "{0} 年後", "previous" : "昨年", "next" : "翌年", "past" : "{0} 年前" }, "hour" : { "current" : "1 時間以内", "past" : "{0} 時間前", "future" : "{0} 時間後" }, "minute" : { "past" : "{0} 分前", "future" : "{0} 分後", "current" : "1 分以内" }, "second" : { "current" : "今", "past" : "{0} 秒前", "future" : "{0} 秒後" }, "quarter" : { "future" : "{0} 四半期後", "previous" : "前四半期", "next" : "翌四半期", "past" : "{0} 四半期前", "current" : "今四半期" }, "week" : { "next" : "翌週", "past" : "{0} 週間前", "current" : "今週", "previous" : "先週", "future" : "{0} 週間後" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/jgo.json ================================================ { "narrow" : { "now" : "now", "week" : { "next" : "next week", "current" : "this week", "past" : "Ɛ́ gɛ́ mɔ {0} ŋgap-mbi", "future" : "Nǔu ŋgap-mbi {0}", "previous" : "last week" }, "quarter" : { "previous" : "last quarter", "next" : "next quarter", "future" : "+{0} Q", "current" : "this quarter", "past" : "-{0} Q" }, "day" : { "future" : "Nǔu lɛ́Ꞌ {0}", "previous" : "yesterday", "next" : "tomorrow", "past" : "Ɛ́ gɛ́ mɔ́ lɛ́Ꞌ {0}", "current" : "lɔꞋɔ" }, "hour" : { "current" : "this hour", "past" : "ɛ́ gɛ mɔ́ {0} háwa", "future" : "nǔu háwa {0}" }, "second" : { "future" : "+{0} s", "current" : "now", "past" : "-{0} s" }, "month" : { "past" : "ɛ́ gɛ́ mɔ́ pɛsaŋ {0}", "future" : "Nǔu {0} saŋ", "current" : "this month", "previous" : "last month", "next" : "next month" }, "minute" : { "future" : "nǔu {0} minút", "current" : "this minute", "past" : "ɛ́ gɛ́ mɔ́ minút {0}" }, "year" : { "next" : "next year", "future" : "Nǔu ŋguꞋ {0}", "previous" : "last year", "current" : "this year", "past" : "Ɛ́gɛ́ mɔ́ ŋguꞋ {0}" } }, "long" : { "day" : { "future" : "Nǔu lɛ́Ꞌ {0}", "past" : "Ɛ́ gɛ́ mɔ́ lɛ́Ꞌ {0}", "current" : "lɔꞋɔ", "next" : "tomorrow", "previous" : "yesterday" }, "second" : { "past" : "-{0} s", "current" : "now", "future" : "+{0} s" }, "quarter" : { "current" : "this quarter", "next" : "next quarter", "previous" : "last quarter", "future" : "+{0} Q", "past" : "-{0} Q" }, "month" : { "future" : "Nǔu {0} saŋ", "previous" : "last month", "past" : "ɛ́ gɛ́ mɔ́ pɛsaŋ {0}", "next" : "next month", "current" : "this month" }, "week" : { "future" : "Nǔu ŋgap-mbi {0}", "past" : "Ɛ́ gɛ́ mɔ {0} ŋgap-mbi", "current" : "this week", "previous" : "last week", "next" : "next week" }, "hour" : { "future" : "nǔu háwa {0}", "current" : "this hour", "past" : "ɛ́ gɛ mɔ́ {0} háwa" }, "minute" : { "future" : "nǔu {0} minút", "past" : "ɛ́ gɛ́ mɔ́ minút {0}", "current" : "this minute" }, "now" : "now", "year" : { "future" : "Nǔu ŋguꞋ {0}", "next" : "next year", "past" : "Ɛ́gɛ́ mɔ́ ŋguꞋ {0}", "previous" : "last year", "current" : "this year" } }, "short" : { "hour" : { "future" : "nǔu háwa {0}", "current" : "this hour", "past" : "ɛ́ gɛ mɔ́ {0} háwa" }, "now" : "now", "quarter" : { "current" : "this quarter", "future" : "+{0} Q", "past" : "-{0} Q", "next" : "next quarter", "previous" : "last quarter" }, "day" : { "current" : "lɔꞋɔ", "past" : "Ɛ́ gɛ́ mɔ́ lɛ́Ꞌ {0}", "future" : "Nǔu lɛ́Ꞌ {0}", "next" : "tomorrow", "previous" : "yesterday" }, "week" : { "current" : "this week", "past" : "Ɛ́ gɛ́ mɔ {0} ŋgap-mbi", "future" : "Nǔu ŋgap-mbi {0}", "next" : "next week", "previous" : "last week" }, "minute" : { "past" : "ɛ́ gɛ́ mɔ́ minút {0}", "future" : "nǔu {0} minút", "current" : "this minute" }, "second" : { "past" : "-{0} s", "current" : "now", "future" : "+{0} s" }, "month" : { "current" : "this month", "future" : "Nǔu {0} saŋ", "previous" : "last month", "next" : "next month", "past" : "ɛ́ gɛ́ mɔ́ pɛsaŋ {0}" }, "year" : { "future" : "Nǔu ŋguꞋ {0}", "previous" : "last year", "next" : "next year", "current" : "this year", "past" : "Ɛ́gɛ́ mɔ́ ŋguꞋ {0}" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ka.json ================================================ { "narrow" : { "quarter" : { "current" : "ამ კვარტალში", "past" : "{0} კვარტ. წინ", "next" : "შემდეგ კვარტალში", "future" : "{0} კვარტალში", "previous" : "გასულ კვარტალში" }, "day" : { "next" : "ხვალ", "previous" : "გუშინ", "future" : "{0} დღეში", "past" : "{0} დღის წინ", "current" : "დღეს" }, "year" : { "past" : "{0} წლის წინ", "previous" : "გასულ წელს", "future" : "{0} წელში", "next" : "მომავალ წელს", "current" : "ამ წელს" }, "minute" : { "current" : "ამ წუთში", "past" : "{0} წთ წინ", "future" : "{0} წუთში" }, "now" : "ახლა", "week" : { "current" : "ამ კვირაში", "previous" : "გასულ კვირაში", "past" : "{0} კვირის წინ", "next" : "მომავალ კვირაში", "future" : "{0} კვირაში" }, "second" : { "current" : "ახლა", "future" : "{0} წამში", "past" : "{0} წმ წინ" }, "month" : { "future" : "{0} თვეში", "current" : "ამ თვეში", "past" : "{0} თვის წინ", "previous" : "გასულ თვეს", "next" : "მომავალ თვეს" }, "hour" : { "past" : "{0} სთ წინ", "future" : "{0} საათში", "current" : "ამ საათში" } }, "long" : { "year" : { "next" : "მომავალ წელს", "previous" : "გასულ წელს", "past" : "{0} წლის წინ", "future" : "{0} წელიწადში", "current" : "ამ წელს" }, "now" : "ახლა", "quarter" : { "next" : "შემდეგ კვარტალში", "past" : "{0} კვარტალის წინ", "future" : "{0} კვარტალში", "current" : "ამ კვარტალში", "previous" : "გასულ კვარტალში" }, "month" : { "past" : "{0} თვის წინ", "next" : "მომავალ თვეს", "previous" : "გასულ თვეს", "future" : "{0} თვეში", "current" : "ამ თვეში" }, "second" : { "current" : "ახლა", "future" : "{0} წამში", "past" : "{0} წამის წინ" }, "week" : { "past" : "{0} კვირის წინ", "previous" : "გასულ კვირაში", "future" : "{0} კვირაში", "next" : "მომავალ კვირაში", "current" : "ამ კვირაში" }, "day" : { "next" : "ხვალ", "future" : "{0} დღეში", "previous" : "გუშინ", "current" : "დღეს", "past" : "{0} დღის წინ" }, "minute" : { "current" : "ამ წუთში", "future" : "{0} წუთში", "past" : "{0} წუთის წინ" }, "hour" : { "past" : "{0} საათის წინ", "future" : "{0} საათში", "current" : "ამ საათში" } }, "short" : { "minute" : { "past" : "{0} წთ წინ", "future" : "{0} წუთში", "current" : "ამ წუთში" }, "week" : { "future" : "{0} კვირაში", "previous" : "გასულ კვირაში", "next" : "მომავალ კვირაში", "current" : "ამ კვირაში", "past" : "{0} კვ. წინ" }, "year" : { "current" : "ამ წელს", "previous" : "გასულ წელს", "future" : "{0} წელში", "past" : "{0} წლის წინ", "next" : "მომავალ წელს" }, "month" : { "next" : "მომავალ თვეს", "past" : "{0} თვის წინ", "future" : "{0} თვეში", "previous" : "გასულ თვეს", "current" : "ამ თვეში" }, "quarter" : { "next" : "შემდეგ კვარტალში", "past" : "{0} კვარტ. წინ", "previous" : "გასულ კვარტალში", "current" : "ამ კვარტალში", "future" : "{0} კვარტალში" }, "day" : { "current" : "დღეს", "previous" : "გუშინ", "past" : "{0} დღის წინ", "next" : "ხვალ", "future" : "{0} დღეში" }, "hour" : { "past" : "{0} სთ წინ", "future" : "{0} საათში", "current" : "ამ საათში" }, "second" : { "past" : "{0} წმ წინ", "future" : "{0} წამში", "current" : "ახლა" }, "now" : "ახლა" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/kea.json ================================================ { "narrow" : { "day" : { "next" : "manha", "future" : "di li {0} dia", "previous" : "onti", "current" : "oji", "past" : "a ten {0} dia" }, "quarter" : { "next" : "next quarter", "past" : "a ten {0} trim.", "future" : "di li {0} trim.", "previous" : "last quarter", "current" : "this quarter" }, "hour" : { "current" : "this hour", "past" : "a ten {0} ora", "future" : "di li {0} ora" }, "year" : { "next" : "prósimu anu", "past" : "a ten {0} anu", "current" : "es anu li", "previous" : "anu pasadu", "future" : "di li {0} anu" }, "now" : "now", "month" : { "current" : "es mes li", "future" : "di li {0} mes", "past" : "a ten {0} mes", "next" : "prósimu mes", "previous" : "mes pasadu" }, "week" : { "previous" : "simana pasadu", "current" : "es simana li", "past" : "a ten {0} sim.", "future" : "di li {0} sim.", "next" : "prósimu simana" }, "minute" : { "past" : "a ten {0} m", "future" : "di li {0} m", "current" : "this minute" }, "second" : { "current" : "now", "past" : "a ten {0} s", "future" : "di li {0} s" } }, "long" : { "day" : { "previous" : "onti", "current" : "oji", "next" : "manha", "past" : "a ten {0} dia", "future" : "di li {0} dia" }, "week" : { "current" : "es simana li", "future" : "di li {0} simana", "past" : "a ten {0} simana", "previous" : "simana pasadu", "next" : "prósimu simana" }, "minute" : { "current" : "this minute", "past" : "a ten {0} minutu", "future" : "di li {0} minutu" }, "month" : { "future" : "di li {0} mes", "next" : "prósimu mes", "previous" : "mes pasadu", "current" : "es mes li", "past" : "a ten {0} mes" }, "hour" : { "current" : "this hour", "past" : "a ten {0} ora", "future" : "di li {0} ora" }, "year" : { "past" : "a ten {0} anu", "future" : "di li {0} anu", "previous" : "anu pasadu", "next" : "prósimu anu", "current" : "es anu li" }, "second" : { "future" : "di li {0} sigundu", "current" : "now", "past" : "a ten {0} sigundu" }, "now" : "now", "quarter" : { "past" : "a ten {0} trimestri", "current" : "this quarter", "previous" : "last quarter", "future" : "di li {0} trimestri", "next" : "next quarter" } }, "short" : { "quarter" : { "future" : "di li {0} trim.", "next" : "next quarter", "previous" : "last quarter", "current" : "this quarter", "past" : "a ten {0} trim." }, "minute" : { "past" : "a ten {0} min", "current" : "this minute", "future" : "di li {0} min" }, "year" : { "next" : "prósimu anu", "future" : "di li {0} anu", "current" : "es anu li", "past" : "a ten {0} anu", "previous" : "anu pasadu" }, "second" : { "future" : "di li {0} sig", "current" : "now", "past" : "a ten {0} sig" }, "hour" : { "future" : "di li {0} ora", "current" : "this hour", "past" : "a ten {0} ora" }, "now" : "now", "month" : { "previous" : "mes pasadu", "next" : "prósimu mes", "past" : "a ten {0} mes", "current" : "es mes li", "future" : "di li {0} mes" }, "week" : { "previous" : "simana pasadu", "current" : "es simana li", "next" : "prósimu simana", "past" : "a ten {0} sim.", "future" : "di li {0} sim." }, "day" : { "next" : "manha", "past" : "a ten {0} dia", "future" : "di li {0} dia", "previous" : "onti", "current" : "oji" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/kk.json ================================================ { "narrow" : { "day" : { "next" : "ертең", "future" : "{0} күннен кейін", "previous" : "кеше", "current" : "бүгін", "past" : "{0} күн бұрын" }, "quarter" : { "next" : "келесі тоқсан", "past" : "{0} тқс. бұрын", "future" : "{0} тқс. кейін", "previous" : "өткен тоқсан", "current" : "осы тоқсан" }, "hour" : { "current" : "осы сағат", "past" : "{0} сағ. бұрын", "future" : "{0} сағ. кейін" }, "year" : { "next" : "келесі жыл", "past" : "{0} ж. бұрын", "current" : "биылғы жыл", "previous" : "былтырғы жыл", "future" : "{0} ж. кейін" }, "now" : "қазір", "month" : { "current" : "осы ай", "future" : "{0} айдан кейін", "past" : "{0} ай бұрын", "next" : "келесі ай", "previous" : "өткен ай" }, "week" : { "previous" : "өткен апта", "current" : "осы апта", "past" : "{0} ап. бұрын", "future" : "{0} ап. кейін", "next" : "келесі апта" }, "minute" : { "past" : "{0} мин. бұрын", "current" : "осы минут", "future" : "{0} мин. кейін" }, "second" : { "future" : "{0} сек. кейін", "current" : "қазір", "past" : "{0} сек. бұрын" } }, "long" : { "day" : { "previous" : "кеше", "current" : "бүгін", "next" : "ертең", "past" : "{0} күн бұрын", "future" : "{0} күннен кейін" }, "week" : { "current" : "осы апта", "future" : "{0} аптадан кейін", "past" : "{0} апта бұрын", "previous" : "өткен апта", "next" : "келесі апта" }, "minute" : { "current" : "осы минут", "past" : "{0} минут бұрын", "future" : "{0} минуттан кейін" }, "month" : { "future" : "{0} айдан кейін", "next" : "келесі ай", "previous" : "өткен ай", "current" : "осы ай", "past" : "{0} ай бұрын" }, "hour" : { "current" : "осы сағат", "past" : "{0} сағат бұрын", "future" : "{0} сағаттан кейін" }, "year" : { "past" : "{0} жыл бұрын", "future" : "{0} жылдан кейін", "previous" : "былтырғы жыл", "next" : "келесі жыл", "current" : "биылғы жыл" }, "second" : { "current" : "қазір", "past" : "{0} секунд бұрын", "future" : "{0} секундтан кейін" }, "now" : "қазір", "quarter" : { "past" : "{0} тоқсан бұрын", "current" : "осы тоқсан", "previous" : "өткен тоқсан", "future" : "{0} тоқсаннан кейін", "next" : "келесі тоқсан" } }, "short" : { "quarter" : { "future" : "{0} тқс. кейін", "next" : "келесі тоқсан", "previous" : "өткен тоқсан", "current" : "осы тоқсан", "past" : "{0} тқс. бұрын" }, "minute" : { "past" : "{0} мин. бұрын", "future" : "{0} мин. кейін", "current" : "осы минут" }, "year" : { "next" : "келесі жыл", "future" : "{0} ж. кейін", "current" : "биылғы жыл", "past" : "{0} ж. бұрын", "previous" : "былтырғы жыл" }, "second" : { "current" : "қазір", "past" : "{0} сек. бұрын", "future" : "{0} сек. кейін" }, "hour" : { "future" : "{0} сағ. кейін", "current" : "осы сағат", "past" : "{0} сағ. бұрын" }, "now" : "қазір", "month" : { "previous" : "өткен ай", "next" : "келесі ай", "past" : "{0} ай бұрын", "current" : "осы ай", "future" : "{0} айдан кейін" }, "week" : { "previous" : "өткен апта", "current" : "осы апта", "next" : "келесі апта", "past" : "{0} ап. бұрын", "future" : "{0} ап. кейін" }, "day" : { "next" : "ертең", "past" : "{0} күн бұрын", "future" : "{0} күннен кейін", "previous" : "кеше", "current" : "бүгін" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/kl.json ================================================ { "narrow" : { "quarter" : { "past" : "-{0} Q", "future" : "+{0} Q", "current" : "this quarter", "previous" : "last quarter", "next" : "next quarter" }, "year" : { "previous" : "last year", "next" : "next year", "future" : "om {0} ukioq", "current" : "this year", "past" : "for {0} ukioq siden" }, "week" : { "future" : "om {0} sapaatip-akunnera", "previous" : "last week", "next" : "next week", "past" : "for {0} sapaatip-akunnera siden", "current" : "this week" }, "day" : { "current" : "today", "past" : "for {0} ulloq unnuarlu siden", "next" : "tomorrow", "future" : "om {0} ulloq unnuarlu", "previous" : "yesterday" }, "hour" : { "current" : "this hour", "past" : "for {0} nalunaaquttap-akunnera siden", "future" : "om {0} nalunaaquttap-akunnera" }, "minute" : { "future" : "om {0} minutsi", "current" : "this minute", "past" : "for {0} minutsi siden" }, "month" : { "next" : "next month", "current" : "this month", "past" : "for {0} qaammat siden", "future" : "om {0} qaammat", "previous" : "last month" }, "now" : "now", "second" : { "future" : "om {0} sekundi", "current" : "now", "past" : "for {0} sekundi siden" } }, "short" : { "minute" : { "past" : "for {0} minutsi siden", "future" : "om {0} minutsi", "current" : "this minute" }, "week" : { "current" : "this week", "past" : "for {0} sapaatip-akunnera siden", "future" : "om {0} sapaatip-akunnera", "next" : "next week", "previous" : "last week" }, "year" : { "current" : "this year", "future" : "om {0} ukioq", "past" : "for {0} ukioq siden", "next" : "next year", "previous" : "last year" }, "day" : { "next" : "tomorrow", "current" : "today", "previous" : "yesterday", "past" : "for {0} ulloq unnuarlu siden", "future" : "om {0} ulloq unnuarlu" }, "hour" : { "future" : "om {0} nalunaaquttap-akunnera", "current" : "this hour", "past" : "for {0} nalunaaquttap-akunnera siden" }, "quarter" : { "current" : "this quarter", "future" : "+{0} Q", "previous" : "last quarter", "next" : "next quarter", "past" : "-{0} Q" }, "second" : { "past" : "for {0} sekundi siden", "current" : "now", "future" : "om {0} sekundi" }, "month" : { "current" : "this month", "past" : "for {0} qaammat siden", "future" : "om {0} qaammat", "next" : "next month", "previous" : "last month" }, "now" : "now" }, "long" : { "hour" : { "future" : "om {0} nalunaaquttap-akunnera", "current" : "this hour", "past" : "for {0} nalunaaquttap-akunnera siden" }, "day" : { "next" : "tomorrow", "past" : "for {0} ulloq unnuarlu siden", "previous" : "yesterday", "future" : "om {0} ulloq unnuarlu", "current" : "today" }, "second" : { "past" : "for {0} sekundi siden", "current" : "now", "future" : "om {0} sekundi" }, "week" : { "future" : "om {0} sapaatip-akunnera", "past" : "for {0} sapaatip-akunnera siden", "current" : "this week", "next" : "next week", "previous" : "last week" }, "minute" : { "future" : "om {0} minutsi", "past" : "for {0} minutsi siden", "current" : "this minute" }, "month" : { "future" : "om {0} qaammat", "past" : "for {0} qaammat siden", "current" : "this month", "previous" : "last month", "next" : "next month" }, "now" : "now", "year" : { "current" : "this year", "next" : "next year", "previous" : "last year", "future" : "om {0} ukioq", "past" : "for {0} ukioq siden" }, "quarter" : { "future" : "+{0} Q", "previous" : "last quarter", "past" : "-{0} Q", "next" : "next quarter", "current" : "this quarter" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/km.json ================================================ { "narrow" : { "day" : { "next" : "ថ្ងៃស្អែក", "future" : "{0} ថ្ងៃទៀត", "previous" : "ម្សិលមិញ", "current" : "ថ្ងៃ​នេះ", "past" : "{0} ថ្ងៃ​​មុន" }, "quarter" : { "next" : "ត្រីមាស​ក្រោយ", "past" : "{0} ត្រីមាស​មុន", "future" : "{0} ត្រីមាសទៀត", "previous" : "ត្រីមាស​មុន", "current" : "ត្រីមាស​នេះ" }, "hour" : { "current" : "ម៉ោងនេះ", "future" : "{0} ម៉ោងទៀត", "past" : "{0} ម៉ោង​មុន" }, "year" : { "next" : "ឆ្នាំ​ក្រោយ", "past" : "{0} ឆ្នាំ​មុន", "current" : "ឆ្នាំ​នេះ", "previous" : "ឆ្នាំ​មុន", "future" : "{0} ឆ្នាំទៀត" }, "now" : "ឥឡូវ", "month" : { "current" : "ខែ​នេះ", "future" : "{0} ខែទៀត", "past" : "{0} ខែមុន", "next" : "ខែ​ក្រោយ", "previous" : "ខែ​មុន" }, "week" : { "previous" : "សប្ដាហ៍​មុន", "current" : "សប្ដាហ៍​នេះ", "past" : "{0} សប្ដាហ៍​មុន", "future" : "{0} សប្ដាហ៍ទៀត", "next" : "សប្ដាហ៍​ក្រោយ" }, "minute" : { "past" : "{0} នាទី​​មុន", "future" : "{0} នាទីទៀត", "current" : "នាទីនេះ" }, "second" : { "current" : "ឥឡូវ", "past" : "{0} វិនាទី​មុន", "future" : "{0} វិនាទីទៀត" } }, "long" : { "day" : { "previous" : "ម្សិលមិញ", "current" : "ថ្ងៃ​នេះ", "next" : "ថ្ងៃ​ស្អែក", "past" : "{0} ថ្ងៃ​មុន", "future" : "{0} ថ្ងៃទៀត" }, "week" : { "current" : "សប្ដាហ៍​នេះ", "future" : "{0} សប្ដាហ៍ទៀត", "past" : "{0} សប្ដាហ៍​មុន", "previous" : "សប្ដាហ៍​មុន", "next" : "សប្ដាហ៍​ក្រោយ" }, "minute" : { "past" : "{0} នាទី​មុន", "future" : "{0} នាទីទៀត", "current" : "នាទីនេះ" }, "month" : { "future" : "{0} ខែទៀត", "next" : "ខែ​ក្រោយ", "previous" : "ខែ​មុន", "current" : "ខែ​នេះ", "past" : "{0} ខែមុន" }, "hour" : { "future" : "ក្នុង​រយៈ​ពេល {0} ម៉ោង", "current" : "ម៉ោងនេះ", "past" : "{0} ម៉ោង​មុន" }, "year" : { "past" : "{0} ឆ្នាំ​មុន", "future" : "{0} ឆ្នាំទៀត", "previous" : "ឆ្នាំ​មុន", "next" : "ឆ្នាំ​ក្រោយ", "current" : "ឆ្នាំ​នេះ" }, "second" : { "future" : "{0} វិនាទីទៀត", "current" : "ឥឡូវ", "past" : "{0} វិនាទី​មុន" }, "now" : "ឥឡូវ", "quarter" : { "past" : "{0} ត្រីមាស​មុន", "current" : "ត្រីមាស​នេះ", "previous" : "ត្រីមាស​មុន", "future" : "{0} ត្រីមាសទៀត", "next" : "ត្រីមាស​ក្រោយ" } }, "short" : { "quarter" : { "future" : "{0} ត្រីមាសទៀត", "next" : "ត្រីមាស​ក្រោយ", "previous" : "ត្រីមាស​មុន", "current" : "ត្រីមាស​នេះ", "past" : "{0} ត្រីមាស​មុន" }, "minute" : { "current" : "នាទីនេះ", "past" : "{0} នាទី​​មុន", "future" : "{0} នាទីទៀត" }, "year" : { "next" : "ឆ្នាំ​ក្រោយ", "future" : "{0} ឆ្នាំទៀត", "current" : "ឆ្នាំ​នេះ", "past" : "{0} ឆ្នាំ​មុន", "previous" : "ឆ្នាំ​មុន" }, "second" : { "current" : "ឥឡូវ", "past" : "{0} វិនាទី​មុន", "future" : "{0} វិនាទីទៀត" }, "hour" : { "past" : "{0} ម៉ោង​មុន", "current" : "ម៉ោងនេះ", "future" : "{0} ម៉ោងទៀត" }, "now" : "ឥឡូវ", "month" : { "previous" : "ខែ​មុន", "next" : "ខែ​ក្រោយ", "past" : "{0} ខែមុន", "current" : "ខែ​នេះ", "future" : "{0} ខែទៀត" }, "week" : { "previous" : "សប្ដាហ៍​មុន", "current" : "សប្ដាហ៍​នេះ", "next" : "សប្ដាហ៍​ក្រោយ", "past" : "{0} សប្ដាហ៍​មុន", "future" : "{0} សប្ដាហ៍ទៀត" }, "day" : { "next" : "ថ្ងៃស្អែក", "past" : "{0} ថ្ងៃ​​មុន", "future" : "{0} ថ្ងៃទៀត", "previous" : "ម្សិលមិញ", "current" : "ថ្ងៃ​នេះ" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/kn.json ================================================ { "narrow" : { "now" : "ಈಗ", "minute" : { "current" : "ಈ ನಿಮಿಷ", "future" : { "one" : "{0} ನಿಮಿಷದಲ್ಲಿ", "other" : "{0} ನಿಮಿಷಗಳಲ್ಲಿ" }, "past" : { "other" : "{0} ನಿಮಿಷಗಳ ಹಿಂದೆ", "one" : "{0} ನಿಮಿಷದ ಹಿಂದೆ" } }, "week" : { "future" : { "one" : "{0} ವಾರದಲ್ಲಿ", "other" : "{0} ವಾರಗಳಲ್ಲಿ" }, "current" : "ಈ ವಾರ", "previous" : "ಕಳೆದ ವಾರ", "next" : "ಮುಂದಿನ ವಾರ", "past" : { "one" : "{0} ವಾರದ ಹಿಂದೆ", "other" : "{0} ವಾರಗಳ ಹಿಂದೆ" } }, "hour" : { "current" : "ಈ ಗಂಟೆ", "future" : { "one" : "{0} ಗಂಟೆಯಲ್ಲಿ", "other" : "{0} ಗಂಟೆಗಳಲ್ಲಿ" }, "past" : { "other" : "{0} ಗಂಟೆಗಳ ಹಿಂದೆ", "one" : "{0} ಗಂಟೆ ಹಿಂದೆ" } }, "month" : { "current" : "ಈ ತಿಂಗಳು", "future" : { "other" : "{0} ತಿಂಗಳುಗಳಲ್ಲಿ", "one" : "{0} ತಿಂಗಳಲ್ಲಿ" }, "previous" : "ಕಳೆದ ತಿಂಗಳು", "next" : "ಮುಂದಿನ ತಿಂಗಳು", "past" : { "one" : "{0} ತಿಂಗಳ ಹಿಂದೆ", "other" : "{0} ತಿಂಗಳುಗಳ ಹಿಂದೆ" } }, "second" : { "future" : { "one" : "{0} ಸೆಕೆಂಡ್‌ನಲ್ಲಿ", "other" : "{0} ಸೆಕೆಂಡ್‌ಗಳಲ್ಲಿ" }, "past" : { "other" : "{0} ಸೆಕೆಂಡುಗಳ ಹಿಂದೆ", "one" : "{0} ಸೆಕೆಂಡ್ ಹಿಂದೆ" }, "current" : "ಈಗ" }, "year" : { "past" : { "one" : "{0} ವರ್ಷದ ಹಿಂದೆ", "other" : "{0} ವರ್ಷಗಳ ಹಿಂದೆ" }, "future" : { "other" : "{0} ವರ್ಷಗಳಲ್ಲಿ", "one" : "{0} ವರ್ಷದಲ್ಲಿ" }, "current" : "ಈ ವರ್ಷ", "next" : "ಮುಂದಿನ ವರ್ಷ", "previous" : "ಕಳೆದ ವರ್ಷ" }, "day" : { "next" : "ನಾಳೆ", "future" : { "one" : "{0} ದಿನದಲ್ಲಿ", "other" : "{0} ದಿನಗಳಲ್ಲಿ" }, "past" : { "one" : "{0} ದಿನದ ಹಿಂದೆ", "other" : "{0} ದಿನಗಳ ಹಿಂದೆ" }, "current" : "ಇಂದು", "previous" : "ನಿನ್ನೆ" }, "quarter" : { "future" : "{0} ತ್ರೈಮಾಸಿಕಗಳಲ್ಲಿ", "current" : "ಈ ತ್ರೈಮಾಸಿಕ", "next" : "ಮುಂದಿನ ತ್ರೈಮಾಸಿಕ", "past" : { "one" : "{0} ತ್ರೈ.ಮಾ. ಹಿಂದೆ", "other" : "{0} ತ್ರೈಮಾಸಿಕಗಳ ಹಿಂದೆ" }, "previous" : "ಕಳೆದ ತ್ರೈಮಾಸಿಕ" } }, "long" : { "second" : { "past" : { "one" : "{0} ಸೆಕೆಂಡ್ ಹಿಂದೆ", "other" : "{0} ಸೆಕೆಂಡುಗಳ ಹಿಂದೆ" }, "future" : { "one" : "{0} ಸೆಕೆಂಡ್‌ನಲ್ಲಿ", "other" : "{0} ಸೆಕೆಂಡ್‌ಗಳಲ್ಲಿ" }, "current" : "ಈಗ" }, "year" : { "future" : { "other" : "{0} ವರ್ಷಗಳಲ್ಲಿ", "one" : "{0} ವರ್ಷದಲ್ಲಿ" }, "past" : { "one" : "{0} ವರ್ಷದ ಹಿಂದೆ", "other" : "{0} ವರ್ಷಗಳ ಹಿಂದೆ" }, "next" : "ಮುಂದಿನ ವರ್ಷ", "current" : "ಈ ವರ್ಷ", "previous" : "ಹಿಂದಿನ ವರ್ಷ" }, "day" : { "current" : "ಇಂದು", "next" : "ನಾಳೆ", "previous" : "ನಿನ್ನೆ", "past" : { "one" : "{0} ದಿನದ ಹಿಂದೆ", "other" : "{0} ದಿನಗಳ ಹಿಂದೆ" }, "future" : { "one" : "{0} ದಿನದಲ್ಲಿ", "other" : "{0} ದಿನಗಳಲ್ಲಿ" } }, "minute" : { "past" : { "one" : "{0} ನಿಮಿಷದ ಹಿಂದೆ", "other" : "{0} ನಿಮಿಷಗಳ ಹಿಂದೆ" }, "future" : { "one" : "{0} ನಿಮಿಷದಲ್ಲಿ", "other" : "{0} ನಿಮಿಷಗಳಲ್ಲಿ" }, "current" : "ಈ ನಿಮಿಷ" }, "quarter" : { "next" : "ಮುಂದಿನ ತ್ರೈಮಾಸಿಕ", "previous" : "ಹಿಂದಿನ ತ್ರೈಮಾಸಿಕ", "future" : { "one" : "{0} ತ್ರೈಮಾಸಿಕದಲ್ಲಿ", "other" : "{0} ತ್ರೈಮಾಸಿಕಗಳಲ್ಲಿ" }, "past" : { "other" : "{0} ತ್ರೈಮಾಸಿಕಗಳ ಹಿಂದೆ", "one" : "{0} ತ್ರೈಮಾಸಿಕದ ಹಿಂದೆ" }, "current" : "ಈ ತ್ರೈಮಾಸಿಕ" }, "month" : { "current" : "ಈ ತಿಂಗಳು", "next" : "ಮುಂದಿನ ತಿಂಗಳು", "previous" : "ಕಳೆದ ತಿಂಗಳು", "past" : { "other" : "{0} ತಿಂಗಳುಗಳ ಹಿಂದೆ", "one" : "{0} ತಿಂಗಳ ಹಿಂದೆ" }, "future" : { "other" : "{0} ತಿಂಗಳುಗಳಲ್ಲಿ", "one" : "{0} ತಿಂಗಳಲ್ಲಿ" } }, "hour" : { "current" : "ಈ ಗಂಟೆ", "past" : { "other" : "{0} ಗಂಟೆಗಳ ಹಿಂದೆ", "one" : "{0} ಗಂಟೆ ಹಿಂದೆ" }, "future" : { "one" : "{0} ಗಂಟೆಯಲ್ಲಿ", "other" : "{0} ಗಂಟೆಗಳಲ್ಲಿ" } }, "week" : { "next" : "ಮುಂದಿನ ವಾರ", "future" : { "one" : "{0} ವಾರದಲ್ಲಿ", "other" : "{0} ವಾರಗಳಲ್ಲಿ" }, "current" : "ಈ ವಾರ", "previous" : "ಕಳೆದ ವಾರ", "past" : { "other" : "{0} ವಾರಗಳ ಹಿಂದೆ", "one" : "{0} ವಾರದ ಹಿಂದೆ" } }, "now" : "ಈಗ" }, "short" : { "month" : { "previous" : "ಕಳೆದ ತಿಂಗಳು", "current" : "ಈ ತಿಂಗಳು", "next" : "ಮುಂದಿನ ತಿಂಗಳು", "past" : { "one" : "{0} ತಿಂಗಳು ಹಿಂದೆ", "other" : "{0} ತಿಂಗಳುಗಳ ಹಿಂದೆ" }, "future" : { "other" : "{0} ತಿಂಗಳುಗಳಲ್ಲಿ", "one" : "{0} ತಿಂಗಳಲ್ಲಿ" } }, "hour" : { "current" : "ಈ ಗಂಟೆ", "past" : { "other" : "{0} ಗಂಟೆಗಳ ಹಿಂದೆ", "one" : "{0} ಗಂಟೆ ಹಿಂದೆ" }, "future" : { "one" : "{0} ಗಂಟೆಯಲ್ಲಿ", "other" : "{0} ಗಂಟೆಗಳಲ್ಲಿ" } }, "now" : "ಈಗ", "quarter" : { "future" : { "one" : "{0} ತ್ರೈ.ಮಾ.ದಲ್ಲಿ", "other" : "{0} ತ್ರೈಮಾಸಿಕಗಳಲ್ಲಿ" }, "previous" : "ಕಳೆದ ತ್ರೈಮಾಸಿಕ", "current" : "ಈ ತ್ರೈಮಾಸಿಕ", "next" : "ಮುಂದಿನ ತ್ರೈಮಾಸಿಕ", "past" : { "one" : "{0} ತ್ರೈ.ಮಾ. ಹಿಂದೆ", "other" : "{0} ತ್ರೈಮಾಸಿಕಗಳ ಹಿಂದೆ" } }, "week" : { "previous" : "ಕಳೆದ ವಾರ", "next" : "ಮುಂದಿನ ವಾರ", "future" : { "one" : "{0} ವಾರದಲ್ಲಿ", "other" : "{0} ವಾರಗಳಲ್ಲಿ" }, "current" : "ಈ ವಾರ", "past" : { "one" : "{0} ವಾರದ ಹಿಂದೆ", "other" : "{0} ವಾರಗಳ ಹಿಂದೆ" } }, "day" : { "current" : "ಇಂದು", "previous" : "ನಿನ್ನೆ", "next" : "ನಾಳೆ", "past" : { "other" : "{0} ದಿನಗಳ ಹಿಂದೆ", "one" : "{0} ದಿನದ ಹಿಂದೆ" }, "future" : { "other" : "{0} ದಿನಗಳಲ್ಲಿ", "one" : "{0} ದಿನದಲ್ಲಿ" } }, "minute" : { "future" : { "other" : "{0} ನಿಮಿಷಗಳಲ್ಲಿ", "one" : "{0} ನಿಮಿಷದಲ್ಲಿ" }, "past" : { "other" : "{0} ನಿಮಿಷಗಳ ಹಿಂದೆ", "one" : "{0} ನಿಮಿಷದ ಹಿಂದೆ" }, "current" : "ಈ ನಿಮಿಷ" }, "second" : { "current" : "ಈಗ", "past" : { "one" : "{0} ಸೆಕೆಂಡ್ ಹಿಂದೆ", "other" : "{0} ಸೆಕೆಂಡುಗಳ ಹಿಂದೆ" }, "future" : { "one" : "{0} ಸೆಕೆಂಡ್‌ನಲ್ಲಿ", "other" : "{0} ಸೆಕೆಂಡ್‌ಗಳಲ್ಲಿ" } }, "year" : { "previous" : "ಕಳೆದ ವರ್ಷ", "next" : "ಮುಂದಿನ ವರ್ಷ", "current" : "ಈ ವರ್ಷ", "future" : { "other" : "{0} ವರ್ಷಗಳಲ್ಲಿ", "one" : "{0} ವರ್ಷದಲ್ಲಿ" }, "past" : { "one" : "{0} ವರ್ಷದ ಹಿಂದೆ", "other" : "{0} ವರ್ಷಗಳ ಹಿಂದೆ" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ko.json ================================================ { "narrow" : { "now" : "지금", "week" : { "next" : "다음 주", "current" : "이번 주", "past" : "{0}주 전", "future" : "{0}주 후", "previous" : "지난주" }, "quarter" : { "previous" : "지난 분기", "next" : "다음 분기", "future" : "{0}분기 후", "current" : "이번 분기", "past" : "{0}분기 전" }, "day" : { "future" : "{0}일 후", "previous" : "어제", "next" : "내일", "past" : "{0}일 전", "current" : "오늘" }, "hour" : { "current" : "현재 시간", "past" : "{0}시간 전", "future" : "{0}시간 후" }, "second" : { "future" : "{0}초 후", "current" : "지금", "past" : "{0}초 전" }, "month" : { "past" : "{0}개월 전", "future" : "{0}개월 후", "current" : "이번 달", "previous" : "지난달", "next" : "다음 달" }, "minute" : { "future" : "{0}분 후", "current" : "현재 분", "past" : "{0}분 전" }, "year" : { "next" : "내년", "future" : "{0}년 후", "previous" : "작년", "current" : "올해", "past" : "{0}년 전" } }, "long" : { "day" : { "future" : "{0}일 후", "past" : "{0}일 전", "current" : "오늘", "next" : "내일", "previous" : "어제" }, "second" : { "past" : "{0}초 전", "current" : "지금", "future" : "{0}초 후" }, "quarter" : { "current" : "이번 분기", "next" : "다음 분기", "previous" : "지난 분기", "future" : "{0}분기 후", "past" : "{0}분기 전" }, "month" : { "future" : "{0}개월 후", "previous" : "지난달", "past" : "{0}개월 전", "next" : "다음 달", "current" : "이번 달" }, "week" : { "future" : "{0}주 후", "past" : "{0}주 전", "current" : "이번 주", "previous" : "지난주", "next" : "다음 주" }, "hour" : { "future" : "{0}시간 후", "current" : "현재 시간", "past" : "{0}시간 전" }, "minute" : { "future" : "{0}분 후", "past" : "{0}분 전", "current" : "현재 분" }, "now" : "지금", "year" : { "future" : "{0}년 후", "next" : "내년", "past" : "{0}년 전", "previous" : "작년", "current" : "올해" } }, "short" : { "hour" : { "future" : "{0}시간 후", "current" : "현재 시간", "past" : "{0}시간 전" }, "now" : "지금", "quarter" : { "current" : "이번 분기", "future" : "{0}분기 후", "past" : "{0}분기 전", "next" : "다음 분기", "previous" : "지난 분기" }, "day" : { "current" : "오늘", "past" : "{0}일 전", "future" : "{0}일 후", "next" : "내일", "previous" : "어제" }, "week" : { "current" : "이번 주", "past" : "{0}주 전", "future" : "{0}주 후", "next" : "다음 주", "previous" : "지난주" }, "minute" : { "past" : "{0}분 전", "future" : "{0}분 후", "current" : "현재 분" }, "second" : { "past" : "{0}초 전", "current" : "지금", "future" : "{0}초 후" }, "month" : { "current" : "이번 달", "future" : "{0}개월 후", "previous" : "지난달", "next" : "다음 달", "past" : "{0}개월 전" }, "year" : { "future" : "{0}년 후", "previous" : "작년", "next" : "내년", "current" : "올해", "past" : "{0}년 전" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/kok.json ================================================ { "narrow" : { "quarter" : { "past" : "{0} त्रैमासीकां आदीं", "future" : "{0} त्रैमासीकांत", "current" : "हो त्रैमासीक", "previous" : "फाटलो त्रैमासीक", "next" : "फुडलो त्रैमासीक" }, "year" : { "previous" : "फाटलें वर्स", "next" : "फुडलें वर्स", "future" : "{0} वर्सांनीं", "current" : "हें वर्स", "past" : "{0} वर्स आदीं" }, "week" : { "future" : "{0} सप्तकांनीं", "previous" : "निमाणो सप्तक", "next" : "फुडलो सप्तक", "past" : "{0} सप्त. आदीं", "current" : "हो सप्तक" }, "day" : { "current" : "आयज", "past" : "{0} दीस आदीं", "next" : "फाल्यां", "future" : "{0} दिसानीं", "previous" : "काल" }, "hour" : { "current" : "हें वर", "past" : "{0} वरा आदीं", "future" : "{0} वरांनीं" }, "minute" : { "future" : "{0} मिन्टां", "current" : "हें मिनीट", "past" : "{0} मिन्टां आदीं" }, "month" : { "next" : "फुडलो म्हयनो", "current" : "हो म्हयनो", "past" : "{0} म्हयन्यां आदीं", "future" : "{0} म्हयन्यानीं", "previous" : "फाटलो म्हयनो" }, "now" : "आतां", "second" : { "future" : "{0} सेकंदानीं", "current" : "आतां", "past" : "{0} से. आदीं" } }, "short" : { "minute" : { "past" : "{0} मिन्टां आदीं", "future" : "{0} मिन्टां", "current" : "हें मिनीट" }, "week" : { "current" : "हो सप्तक", "past" : "{0} सप्तकां आदीं", "future" : "{0} सप्त.", "next" : "फुडलो सप्तक", "previous" : "निमाणो सप्तक" }, "year" : { "future" : "{0} वर्सांनीं", "previous" : "फाटलें वर्स", "next" : "फुडलें वर्स", "current" : "हें वर्स", "past" : "{0} वर्स आदीं" }, "day" : { "next" : "फाल्यां", "current" : "आयज", "previous" : "काल", "past" : "{0} दीस आदीं", "future" : "{0} दिसानीं" }, "hour" : { "future" : "{0} वरांनीं", "current" : "हें वर", "past" : "{0} वरा आदीं" }, "quarter" : { "current" : "हो त्रैमासीक", "future" : "{0} त्रैमासीकांत", "previous" : "फाटलो त्रैमासीक", "next" : "फुडलो त्रैमासीक", "past" : "{0} त्रैमासीकां आदीं" }, "second" : { "past" : "{0} से. आदीं", "current" : "आतां", "future" : "{0} सेकंदानीं" }, "month" : { "current" : "हो म्हयनो", "past" : "{0} म्हयन्यां आदीं", "future" : "{0} म्हयन्यानीं", "next" : "फुडलो म्हयनो", "previous" : "फाटलो म्हयनो" }, "now" : "आतां" }, "long" : { "hour" : { "future" : "{0} वरांनीं", "current" : "हें वर", "past" : "{0} वरा आदीं" }, "day" : { "next" : "फाल्यां", "past" : "{0} दीस आदीं", "previous" : "काल", "future" : "{0} दिसानीं", "current" : "आयज" }, "second" : { "past" : "{0} सेकंद आदीं", "current" : "आतां", "future" : "{0} सेकंदानीं" }, "week" : { "future" : "{0} सप्तकांनीं", "past" : "{0} सप्तकां आदीं", "current" : "हो सप्तक", "next" : "फुडलो सप्तक", "previous" : "निमाणो सप्तक" }, "minute" : { "future" : "{0} मिन्टां", "past" : "{0} मिन्टां आदीं", "current" : "हें मिनीट" }, "month" : { "future" : "{0} म्हयन्यानीं", "past" : "{0} म्हयन्यां आदीं", "current" : "हो म्हयनो", "previous" : "फाटलो म्हयनो", "next" : "फुडलो म्हयनो" }, "now" : "आतां", "year" : { "current" : "हें वर्स", "next" : "फुडलें वर्स", "previous" : "फाटलें वर्स", "future" : "{0} वर्सांनीं", "past" : "{0} वर्सां आदीं" }, "quarter" : { "future" : "{0} त्रैमासीकांत", "previous" : "फाटलो त्रैमासीक", "past" : "{0} त्रैमासीकां आदीं", "next" : "फुडलो त्रैमासीक", "current" : "हो त्रैमासीक" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ksh.json ================================================ { "narrow" : { "second" : { "future" : "+{0} s", "past" : "-{0} s", "current" : "now" }, "year" : { "previous" : "läz Johr", "next" : "näx Johr", "future" : { "zero" : "en keinem Johr", "other" : "en {0} Johre", "one" : "en {0} Johr" }, "current" : "diß Johr", "past" : { "other" : "vör {0} Johre", "zero" : "vör keijnem Johr", "one" : "vör {0} Johr" } }, "month" : { "past" : "-{0} m", "next" : "nächste Mohnd", "future" : "+{0} m", "previous" : "lätzde Mohnd", "current" : "diese Mohnd" }, "minute" : { "past" : "-{0} min", "current" : "this minute", "future" : "+{0} min" }, "quarter" : { "previous" : "last quarter", "current" : "this quarter", "next" : "next quarter", "past" : "-{0} Q", "future" : "+{0} Q" }, "hour" : { "future" : "+{0} h", "past" : "-{0} h", "current" : "this hour" }, "now" : "now", "week" : { "next" : "nächste Woche", "previous" : "läz Woch", "past" : "-{0} w", "future" : "+{0} w", "current" : "di Woch" }, "day" : { "previous" : "jestere", "future" : "+{0} d", "past" : "-{0} d", "current" : "hück", "next" : "morje" } }, "short" : { "minute" : { "current" : "this minute", "past" : "-{0} min", "future" : "+{0} min" }, "week" : { "current" : "di Woch", "past" : "-{0} w", "future" : "+{0} w", "next" : "nächste Woche", "previous" : "läz Woch" }, "year" : { "future" : { "one" : "en {0} Johr", "zero" : "en keinem Johr", "other" : "en {0} Johre" }, "previous" : "läz Johr", "next" : "näx Johr", "current" : "diß Johr", "past" : { "zero" : "vör keijnem Johr", "other" : "vör {0} Johre", "one" : "vör {0} Johr" } }, "day" : { "next" : "morje", "current" : "hück", "previous" : "jestere", "past" : "-{0} d", "future" : "+{0} d" }, "hour" : { "past" : "-{0} h", "current" : "this hour", "future" : "+{0} h" }, "quarter" : { "current" : "this quarter", "future" : "+{0} Q", "previous" : "last quarter", "next" : "next quarter", "past" : "-{0} Q" }, "second" : { "future" : "+{0} s", "current" : "now", "past" : "-{0} s" }, "month" : { "current" : "diese Mohnd", "past" : "-{0} m", "future" : "+{0} m", "next" : "nächste Mohnd", "previous" : "lätzde Mohnd" }, "now" : "now" }, "long" : { "minute" : { "past" : "-{0} min", "future" : "+{0} min", "current" : "this minute" }, "now" : "now", "week" : { "previous" : "läz Woch", "past" : "-{0} w", "future" : "+{0} w", "current" : "di Woch", "next" : "nächste Woche" }, "quarter" : { "next" : "next quarter", "past" : "-{0} Q", "previous" : "last quarter", "current" : "this quarter", "future" : "+{0} Q" }, "hour" : { "future" : "+{0} h", "current" : "this hour", "past" : "-{0} h" }, "day" : { "current" : "hück", "future" : "+{0} d", "next" : "morje", "previous" : "jestere", "past" : "-{0} d" }, "month" : { "next" : "nächste Mohnd", "past" : "-{0} m", "future" : "+{0} m", "previous" : "lätzde Mohnd", "current" : "diese Mohnd" }, "second" : { "current" : "now", "future" : "+{0} s", "past" : "-{0} s" }, "year" : { "next" : "näx Johr", "current" : "diß Johr", "future" : { "other" : "en {0} Johre", "zero" : "en keinem Johr", "one" : "en {0} Johr" }, "previous" : "läz Johr", "past" : { "zero" : "vör keijnem Johr", "other" : "vör {0} Johre", "one" : "vör {0} Johr" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ky.json ================================================ { "narrow" : { "quarter" : { "next" : "кийинки чейр.", "past" : "{0} чейр. мурун", "future" : "{0} чейр. кийин", "previous" : "акыркы чейр.", "current" : "бул чейр." }, "day" : { "next" : "эртеӊ", "future" : "{0} күн. кийин", "previous" : "кечээ", "current" : "бүгүн", "past" : "{0} күн мурун" }, "year" : { "next" : "эмдиги жылы", "past" : "{0} жыл мурун", "current" : "быйыл", "previous" : "былтыр", "future" : "{0} жыл. кийин" }, "week" : { "previous" : "өткөн апт.", "current" : "ушул апт.", "past" : "{0} апт. мурун", "future" : "{0} апт. кийин", "next" : "келерки апт." }, "second" : { "future" : "{0} сек. кийн", "current" : "азыр", "past" : "{0} сек. мурн" }, "minute" : { "current" : "ушул мүнөттө", "past" : "{0} мүн. мурн", "future" : "{0} мүн. кийн" }, "hour" : { "past" : "{0} с. мурн", "future" : "{0} с. кийн", "current" : "ушул саатта" }, "now" : "азыр", "month" : { "current" : "бул айда", "future" : "{0} айд. кийн", "past" : "{0} ай мурн", "next" : "эмдиги айда", "previous" : "өткөн айда" } }, "long" : { "now" : "азыр", "minute" : { "current" : "ушул мүнөттө", "past" : "{0} мүнөт мурун", "future" : "{0} мүнөттөн кийин" }, "quarter" : { "future" : "{0} чейректен кийин", "current" : "бул чейрек", "next" : "кийинки чейрек", "past" : "{0} чейрек мурун", "previous" : "акыркы чейрек" }, "week" : { "past" : "{0} апта мурун", "future" : "{0} аптадан кийин", "previous" : "өткөн аптада", "next" : "келерки аптада", "current" : "ушул аптада" }, "day" : { "next" : "эртеӊ", "previous" : "кечээ", "future" : "{0} күндөн кийин", "current" : "бүгүн", "past" : "{0} күн мурун" }, "hour" : { "future" : "{0} сааттан кийин", "current" : "ушул саатта", "past" : "{0} саат мурун" }, "month" : { "current" : "бул айда", "past" : "{0} ай мурун", "previous" : "өткөн айда", "future" : "{0} айдан кийин", "next" : "эмдиги айда" }, "second" : { "future" : "{0} секунддан кийин", "current" : "азыр", "past" : "{0} секунд мурун" }, "year" : { "previous" : "былтыр", "next" : "эмдиги жылы", "past" : "{0} жыл мурун", "current" : "быйыл", "future" : "{0} жылдан кийин" } }, "short" : { "quarter" : { "future" : "{0} чейректен кийин", "next" : "кийинки чейр.", "previous" : "акыркы чейр.", "current" : "бул чейр.", "past" : "{0} чейр. мурун" }, "minute" : { "current" : "ушул мүнөттө", "past" : "{0} мүн. мурун", "future" : "{0} мүн. кийин" }, "year" : { "next" : "эмдиги жылы", "future" : "{0} жыл. кийин", "current" : "быйыл", "past" : "{0} жыл мурун", "previous" : "былтыр" }, "second" : { "future" : "{0} сек. кийин", "current" : "азыр", "past" : "{0} сек. мурун" }, "hour" : { "current" : "ушул саатта", "past" : "{0} саат. мурун", "future" : "{0} саат. кийин" }, "now" : "азыр", "month" : { "previous" : "өткөн айда", "next" : "эмдиги айда", "past" : "{0} ай мурун", "current" : "бул айда", "future" : "{0} айд. кийин" }, "week" : { "previous" : "өткөн апт.", "current" : "ушул апт.", "next" : "келерки апт.", "past" : "{0} апт. мурун", "future" : "{0} апт. кийин" }, "day" : { "next" : "эртеӊ", "past" : "{0} күн мурун", "future" : "{0} күн. кийин", "previous" : "кечээ", "current" : "бүгүн" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/lb.json ================================================ { "narrow" : { "month" : { "past" : "-{0} M.", "next" : "nächste Mount", "previous" : "leschte Mount", "current" : "dëse Mount", "future" : "+{0} M." }, "second" : { "current" : "now", "past" : "-{0} Sek.", "future" : "+{0} Sek." }, "day" : { "current" : "haut", "past" : "-{0} D.", "previous" : "gëschter", "next" : "muer", "future" : "+{0} D." }, "now" : "now", "quarter" : { "previous" : "last quarter", "next" : "next quarter", "current" : "this quarter", "future" : "+{0} Q.", "past" : "-{0} Q." }, "week" : { "current" : "dës Woch", "future" : "+{0} W.", "next" : "nächst Woch", "past" : "-{0} W.", "previous" : "lescht Woch" }, "minute" : { "future" : "+{0} Min.", "past" : "-{0} Min.", "current" : "this minute" }, "hour" : { "current" : "this hour", "past" : "-{0} St.", "future" : "+{0} St." }, "year" : { "previous" : "lescht Joer", "next" : "nächst Joer", "current" : "dëst Joer", "future" : "+{0} J.", "past" : "-{0} J." } }, "long" : { "quarter" : { "next" : "next quarter", "current" : "this quarter", "past" : { "one" : "virun {0} Quartal", "other" : "viru(n) {0} Quartaler" }, "previous" : "last quarter", "future" : { "other" : "a(n) {0} Quartaler", "one" : "an {0} Quartal" } }, "hour" : { "future" : { "one" : "an {0} Stonn", "other" : "a(n) {0} Stonnen" }, "past" : { "one" : "virun {0} Stonn", "other" : "viru(n) {0} Stonnen" }, "current" : "this hour" }, "month" : { "next" : "nächste Mount", "previous" : "leschte Mount", "current" : "dëse Mount", "future" : { "one" : "an {0} Mount", "other" : "a(n) {0} Méint" }, "past" : { "other" : "viru(n) {0} Méint", "one" : "virun {0} Mount" } }, "now" : "now", "week" : { "current" : "dës Woch", "next" : "nächst Woch", "past" : { "one" : "virun {0} Woch", "other" : "viru(n) {0} Wochen" }, "previous" : "lescht Woch", "future" : { "other" : "a(n) {0} Wochen", "one" : "an {0} Woch" } }, "minute" : { "current" : "this minute", "future" : { "one" : "an {0} Minutt", "other" : "a(n) {0} Minutten" }, "past" : { "one" : "virun {0} Minutt", "other" : "viru(n) {0} Minutten" } }, "second" : { "future" : { "one" : "an {0} Sekonn", "other" : "a(n) {0} Sekonnen" }, "current" : "now", "past" : { "other" : "viru(n) {0} Sekonnen", "one" : "virun {0} Sekonn" } }, "day" : { "current" : "haut", "future" : { "one" : "an {0} Dag", "other" : "a(n) {0} Deeg" }, "previous" : "gëschter", "next" : "muer", "past" : { "other" : "viru(n) {0} Deeg", "one" : "virun {0} Dag" } }, "year" : { "past" : { "one" : "virun {0} Joer", "other" : "viru(n) {0} Joer" }, "next" : "nächst Joer", "previous" : "lescht Joer", "current" : "dëst Joer", "future" : { "one" : "an {0} Joer", "other" : "a(n) {0} Joer" } } }, "short" : { "hour" : { "current" : "this hour", "past" : { "other" : "viru(n) {0} St.", "one" : "virun {0} St." }, "future" : { "one" : "an {0} St.", "other" : "a(n) {0} St." } }, "second" : { "future" : { "one" : "an {0} Sek.", "other" : "a(n) {0} Sek." }, "past" : { "one" : "virun {0} Sek.", "other" : "viru(n) {0} Sek." }, "current" : "now" }, "now" : "now", "year" : { "past" : { "one" : "virun {0} J.", "other" : "viru(n) {0} J." }, "future" : { "one" : "an {0} J.", "other" : "a(n) {0} J." }, "previous" : "lescht Joer", "current" : "dëst Joer", "next" : "nächst Joer" }, "quarter" : { "previous" : "last quarter", "future" : { "other" : "a(n) {0} Q.", "one" : "an {0} Q." }, "next" : "next quarter", "past" : { "other" : "viru(n) {0} Q.", "one" : "virun {0} Q." }, "current" : "this quarter" }, "week" : { "previous" : "lescht Woch", "current" : "dës Woch", "next" : "nächst Woch", "past" : { "one" : "virun {0} W.", "other" : "viru(n) {0} W." }, "future" : { "other" : "a(n) {0} W.", "one" : "an {0} W." } }, "day" : { "next" : "muer", "past" : { "other" : "viru(n) {0} D.", "one" : "virun {0} D." }, "previous" : "gëschter", "future" : { "one" : "an {0} D.", "other" : "a(n) {0} D." }, "current" : "haut" }, "minute" : { "past" : { "other" : "viru(n) {0} Min.", "one" : "virun {0} Min." }, "future" : { "one" : "an {0} Min.", "other" : "a(n) {0} Min." }, "current" : "this minute" }, "month" : { "next" : "nächste Mount", "past" : { "one" : "virun {0} M.", "other" : "viru(n) {0} M." }, "future" : { "one" : "an {0} M.", "other" : "a(n) {0} M." }, "previous" : "leschte Mount", "current" : "dëse Mount" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/lkt.json ================================================ { "narrow" : { "year" : { "future" : "Letáŋhaŋ ómakȟa {0} kiŋháŋ", "previous" : "Ómakȟa kʼuŋ héhaŋ", "next" : "Tȟokáta ómakȟa kiŋháŋ", "current" : "Lé ómakȟa kiŋ", "past" : "Hékta ómakȟa {0} kʼuŋ héhaŋ" }, "week" : { "past" : "Hékta okó {0} kʼuŋ héhaŋ", "previous" : "Okó kʼuŋ héhaŋ", "future" : "Letáŋhaŋ okó {0} kiŋháŋ", "current" : "Lé okó kiŋ", "next" : "Tȟokáta okó kiŋháŋ" }, "hour" : { "current" : "this hour", "past" : "Hékta owápȟe {0} kʼuŋ héhaŋ", "future" : "Letáŋhaŋ owápȟe {0} kiŋháŋ" }, "quarter" : { "current" : "this quarter", "previous" : "last quarter", "future" : "+{0} Q", "past" : "-{0} Q", "next" : "next quarter" }, "second" : { "past" : "Hékta okpí {0} k’uŋ héhaŋ", "future" : "Letáŋhaŋ okpí {0} kiŋháŋ", "current" : "now" }, "month" : { "current" : "Lé wí kiŋ", "future" : "Letáŋhaŋ wíyawapi {0} kiŋháŋ", "past" : "Hékta wíyawapi {0} kʼuŋ héhaŋ", "next" : "Tȟokáta wí kiŋháŋ", "previous" : "Wí kʼuŋ héhaŋ" }, "day" : { "previous" : "Ȟtálehaŋ", "future" : "Letáŋhaŋ {0}-čháŋ kiŋháŋ", "next" : "Híŋhaŋni kiŋháŋ", "current" : "Lé aŋpétu kiŋ", "past" : "Hékta {0}-čháŋ k’uŋ héhaŋ" }, "minute" : { "future" : "Letáŋhaŋ oȟ’áŋkȟo {0} kiŋháŋ", "current" : "this minute", "past" : "Hékta oȟ’áŋkȟo {0} k’uŋ héhaŋ" }, "now" : "now" }, "long" : { "quarter" : { "next" : "next quarter", "current" : "this quarter", "previous" : "last quarter", "past" : "-{0} Q", "future" : "+{0} Q" }, "now" : "now", "year" : { "previous" : "Ómakȟa kʼuŋ héhaŋ", "past" : "Hékta ómakȟa {0} kʼuŋ héhaŋ", "future" : "Letáŋhaŋ ómakȟa {0} kiŋháŋ", "next" : "Tȟokáta ómakȟa kiŋháŋ", "current" : "Lé ómakȟa kiŋ" }, "month" : { "previous" : "Wí kʼuŋ héhaŋ", "current" : "Lé wí kiŋ", "future" : "Letáŋhaŋ wíyawapi {0} kiŋháŋ", "past" : "Hékta wíyawapi {0} kʼuŋ héhaŋ", "next" : "Tȟokáta wí kiŋháŋ" }, "day" : { "previous" : "Ȟtálehaŋ", "future" : "Letáŋhaŋ {0}-čháŋ kiŋháŋ", "next" : "Híŋhaŋni kiŋháŋ", "past" : "Hékta {0}-čháŋ k’uŋ héhaŋ", "current" : "Lé aŋpétu kiŋ" }, "second" : { "past" : "Hékta okpí {0} k’uŋ héhaŋ", "future" : "Letáŋhaŋ okpí {0} kiŋháŋ", "current" : "now" }, "week" : { "past" : "Hékta okó {0} kʼuŋ héhaŋ", "current" : "Lé okó kiŋ", "next" : "Tȟokáta okó kiŋháŋ", "previous" : "Okó kʼuŋ héhaŋ", "future" : "Letáŋhaŋ okó {0} kiŋháŋ" }, "hour" : { "future" : "Letáŋhaŋ owápȟe {0} kiŋháŋ", "current" : "this hour", "past" : "Hékta owápȟe {0} kʼuŋ héhaŋ" }, "minute" : { "future" : "Letáŋhaŋ oȟ’áŋkȟo {0} kiŋháŋ", "current" : "this minute", "past" : "Hékta oȟ’áŋkȟo {0} k’uŋ héhaŋ" } }, "short" : { "second" : { "current" : "now", "past" : "Hékta okpí {0} k’uŋ héhaŋ", "future" : "Letáŋhaŋ okpí {0} kiŋháŋ" }, "now" : "now", "month" : { "future" : "Letáŋhaŋ wíyawapi {0} kiŋháŋ", "current" : "Lé wí kiŋ", "past" : "Hékta wíyawapi {0} kʼuŋ héhaŋ", "previous" : "Wí kʼuŋ héhaŋ", "next" : "Tȟokáta wí kiŋháŋ" }, "day" : { "previous" : "Ȟtálehaŋ", "current" : "Lé aŋpétu kiŋ", "next" : "Híŋhaŋni kiŋháŋ", "future" : "Letáŋhaŋ {0}-čháŋ kiŋháŋ", "past" : "Hékta {0}-čháŋ k’uŋ héhaŋ" }, "minute" : { "current" : "this minute", "future" : "Letáŋhaŋ oȟ’áŋkȟo {0} kiŋháŋ", "past" : "Hékta oȟ’áŋkȟo {0} k’uŋ héhaŋ" }, "quarter" : { "past" : "-{0} Q", "previous" : "last quarter", "current" : "this quarter", "future" : "+{0} Q", "next" : "next quarter" }, "hour" : { "current" : "this hour", "future" : "Letáŋhaŋ owápȟe {0} kiŋháŋ", "past" : "Hékta owápȟe {0} kʼuŋ héhaŋ" }, "week" : { "future" : "Letáŋhaŋ okó {0} kiŋháŋ", "previous" : "Okó kʼuŋ héhaŋ", "next" : "Tȟokáta okó kiŋháŋ", "past" : "Hékta okó {0} kʼuŋ héhaŋ", "current" : "Lé okó kiŋ" }, "year" : { "next" : "Tȟokáta ómakȟa kiŋháŋ", "current" : "Lé ómakȟa kiŋ", "past" : "Hékta ómakȟa {0} kʼuŋ héhaŋ", "future" : "Letáŋhaŋ ómakȟa {0} kiŋháŋ", "previous" : "Ómakȟa kʼuŋ héhaŋ" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/lo.json ================================================ { "narrow" : { "quarter" : { "past" : "{0} ຕມ. ກ່ອນ", "future" : "ໃນ {0} ຕມ.", "current" : "ໄຕຣມາດນີ້", "previous" : "ໄຕຣມາດກ່ອນໜ້າ", "next" : "ໄຕຣມາດໜ້າ" }, "year" : { "previous" : "ປີກາຍ", "next" : "ປີໜ້າ", "future" : "ໃນອີກ {0} ປີ", "current" : "ປີນີ້", "past" : "{0} ປີກ່ອນ" }, "week" : { "future" : "ໃນອີກ {0} ອທ.", "previous" : "ອາທິດແລ້ວ", "next" : "ອາທິດໜ້າ", "past" : "{0} ອທ. ກ່ອນ", "current" : "ອາທິດນີ້" }, "day" : { "current" : "ມື້ນີ້", "past" : "{0} ມື້ກ່ອນ", "next" : "ມື້ອື່ນ", "future" : "ໃນອີກ {0} ມື້", "previous" : "ມື້ວານ" }, "hour" : { "current" : "ຊົ່ວໂມງນີ້", "past" : "{0} ຊມ. ກ່ອນ", "future" : "ໃນອີກ {0} ຊມ." }, "minute" : { "future" : "ໃນ {0} ນທ.", "current" : "ນາທີນີ້", "past" : "{0} ນທ. ກ່ອນ" }, "month" : { "next" : "ເດືອນໜ້າ", "current" : "ເດືອນນີ້", "past" : "{0} ດ. ກ່ອນ", "future" : "ໃນອີກ {0} ດ.", "previous" : "ເດືອນແລ້ວ" }, "now" : "ຕອນນີ້", "second" : { "future" : "ໃນ {0} ວິ.", "current" : "ຕອນນີ້", "past" : "{0} ວິ. ກ່ອນ" } }, "short" : { "minute" : { "past" : "{0} ນທ. ກ່ອນ", "future" : "ໃນ {0} ນທ.", "current" : "ນາທີນີ້" }, "week" : { "current" : "ອາທິດນີ້", "past" : "{0} ອທ. ກ່ອນ", "future" : "ໃນອີກ {0} ອທ.", "next" : "ອາທິດໜ້າ", "previous" : "ອາທິດແລ້ວ" }, "year" : { "future" : "ໃນອີກ {0} ປີ", "previous" : "ປີກາຍ", "next" : "ປີໜ້າ", "current" : "ປີນີ້", "past" : "{0} ປີກ່ອນ" }, "day" : { "next" : "ມື້ອື່ນ", "current" : "ມື້ນີ້", "previous" : "ມື້ວານ", "past" : "{0} ມື້ກ່ອນ", "future" : "ໃນອີກ {0} ມື້" }, "hour" : { "future" : "ໃນອີກ {0} ຊມ.", "current" : "ຊົ່ວໂມງນີ້", "past" : "{0} ຊມ. ກ່ອນ" }, "quarter" : { "current" : "ໄຕຣມາດນີ້", "future" : "ໃນ {0} ຕມ.", "previous" : "ໄຕຣມາດກ່ອນໜ້າ", "next" : "ໄຕຣມາດໜ້າ", "past" : "{0} ຕມ. ກ່ອນ" }, "second" : { "past" : "{0} ວິ. ກ່ອນ", "current" : "ຕອນນີ້", "future" : "ໃນ {0} ວິ." }, "month" : { "current" : "ເດືອນນີ້", "past" : "{0} ດ. ກ່ອນ", "future" : "ໃນອີກ {0} ດ.", "next" : "ເດືອນໜ້າ", "previous" : "ເດືອນແລ້ວ" }, "now" : "ຕອນນີ້" }, "long" : { "hour" : { "future" : "ໃນອີກ {0} ຊົ່ວໂມງ", "current" : "ຊົ່ວໂມງນີ້", "past" : "{0} ຊົ່ວໂມງກ່ອນ" }, "day" : { "next" : "ມື້ອື່ນ", "past" : "{0} ມື້ກ່ອນ", "previous" : "ມື້ວານ", "future" : "ໃນອີກ {0} ມື້", "current" : "ມື້ນີ້" }, "second" : { "past" : "{0} ວິນາທີກ່ອນ", "current" : "ຕອນນີ້", "future" : "ໃນອີກ {0} ວິນາທີ" }, "week" : { "future" : "ໃນອີກ {0} ອາທິດ", "past" : "{0} ອາທິດກ່ອນ", "current" : "ອາທິດນີ້", "next" : "ອາທິດໜ້າ", "previous" : "ອາທິດແລ້ວ" }, "minute" : { "future" : "{0} ໃນອີກ 0 ນາທີ", "past" : "{0} ນາທີກ່ອນ", "current" : "ນາທີນີ້" }, "month" : { "future" : "ໃນອີກ {0} ເດືອນ", "past" : "{0} ເດືອນກ່ອນ", "current" : "ເດືອນນີ້", "previous" : "ເດືອນແລ້ວ", "next" : "ເດືອນໜ້າ" }, "now" : "ຕອນນີ້", "year" : { "current" : "ປີນີ້", "next" : "ປີໜ້າ", "previous" : "ປີກາຍ", "future" : "ໃນອີກ {0} ປີ", "past" : "{0} ປີກ່ອນ" }, "quarter" : { "future" : "ໃນອີກ {0} ໄຕຣມາດ", "previous" : "ໄຕຣມາດກ່ອນໜ້າ", "past" : "{0} ໄຕຣມາດກ່ອນ", "next" : "ໄຕຣມາດໜ້າ", "current" : "ໄຕຣມາດນີ້" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/lt.json ================================================ { "narrow" : { "quarter" : { "past" : "prieš {0} ketv.", "future" : "po {0} ketv.", "current" : "šis ketvirtis", "previous" : "praėjęs ketvirtis", "next" : "kitas ketvirtis" }, "year" : { "previous" : "praėjusiais metais", "next" : "kitais metais", "future" : "po {0} m.", "current" : "šiais metais", "past" : "prieš {0} m." }, "week" : { "future" : "po {0} sav.", "previous" : "praėjusią savaitę", "next" : "kitą savaitę", "past" : "prieš {0} sav.", "current" : "šią savaitę" }, "day" : { "current" : "šiandien", "past" : "prieš {0} d.", "next" : "rytoj", "future" : "po {0} d.", "previous" : "vakar" }, "hour" : { "current" : "šią valandą", "past" : "prieš {0} val.", "future" : "po {0} val." }, "minute" : { "future" : "po {0} min.", "current" : "šią minutę", "past" : "prieš {0} min." }, "month" : { "next" : "kitą mėnesį", "current" : "šį mėnesį", "past" : "prieš {0} mėn.", "future" : "po {0} mėn.", "previous" : "praėjusį mėnesį" }, "now" : "dabar", "second" : { "future" : "po {0} s", "current" : "dabar", "past" : "prieš {0} s" } }, "short" : { "minute" : { "past" : "prieš {0} min.", "future" : "po {0} min.", "current" : "šią minutę" }, "week" : { "current" : "šią savaitę", "past" : "prieš {0} sav.", "future" : "po {0} sav.", "next" : "kitą savaitę", "previous" : "praėjusią savaitę" }, "year" : { "future" : "po {0} m.", "previous" : "praėjusiais metais", "next" : "kitais metais", "current" : "šiais metais", "past" : "prieš {0} m." }, "day" : { "next" : "rytoj", "current" : "šiandien", "previous" : "vakar", "past" : "prieš {0} d.", "future" : "po {0} d." }, "hour" : { "future" : "po {0} val.", "current" : "šią valandą", "past" : "prieš {0} val." }, "quarter" : { "current" : "šis ketvirtis", "future" : "po {0} ketv.", "previous" : "praėjęs ketvirtis", "next" : "kitas ketvirtis", "past" : "prieš {0} ketv." }, "second" : { "past" : "prieš {0} sek.", "current" : "dabar", "future" : "po {0} sek." }, "month" : { "current" : "šį mėnesį", "past" : "prieš {0} mėn.", "future" : "po {0} mėn.", "next" : "kitą mėnesį", "previous" : "praėjusį mėnesį" }, "now" : "dabar" }, "long" : { "quarter" : { "next" : "kitas ketvirtis", "current" : "šis ketvirtis", "future" : { "many" : "po {0} ketvirčio", "one" : "po {0} ketvirčio", "other" : "po {0} ketvirčių" }, "previous" : "praėjęs ketvirtis", "past" : { "few" : "prieš {0} ketvirčius", "one" : "prieš {0} ketvirtį", "many" : "prieš {0} ketvirčio", "other" : "prieš {0} ketvirčių" } }, "day" : { "next" : "rytoj", "past" : { "few" : "prieš {0} dienas", "one" : "prieš {0} dieną", "many" : "prieš {0} dienos", "other" : "prieš {0} dienų" }, "previous" : "vakar", "current" : "šiandien", "future" : { "other" : "po {0} dienų", "many" : "po {0} dienos", "one" : "po {0} dienos" } }, "week" : { "current" : "šią savaitę", "next" : "kitą savaitę", "past" : { "one" : "prieš {0} savaitę", "few" : "prieš {0} savaites", "other" : "prieš {0} savaičių", "many" : "prieš {0} savaitės" }, "future" : { "one" : "po {0} savaitės", "other" : "po {0} savaičių", "many" : "po {0} savaitės" }, "previous" : "praėjusią savaitę" }, "month" : { "current" : "šį mėnesį", "future" : { "one" : "po {0} mėnesio", "many" : "po {0} mėnesio", "other" : "po {0} mėnesių" }, "next" : "kitą mėnesį", "previous" : "praėjusį mėnesį", "past" : { "one" : "prieš {0} mėnesį", "many" : "prieš {0} mėnesio", "other" : "prieš {0} mėnesių", "few" : "prieš {0} mėnesius" } }, "second" : { "future" : { "one" : "po {0} sekundės", "many" : "po {0} sekundės", "other" : "po {0} sekundžių" }, "current" : "dabar", "past" : { "many" : "prieš {0} sekundės", "one" : "prieš {0} sekundę", "few" : "prieš {0} sekundes", "other" : "prieš {0} sekundžių" } }, "year" : { "current" : "šiais metais", "next" : "kitais metais", "previous" : "praėjusiais metais", "future" : "po {0} metų", "past" : { "other" : "prieš {0} metų", "one" : "prieš {0} metus", "few" : "prieš {0} metus" } }, "hour" : { "past" : { "one" : "prieš {0} valandą", "other" : "prieš {0} valandų", "few" : "prieš {0} valandas", "many" : "prieš {0} valandos" }, "future" : { "many" : "po {0} valandos", "other" : "po {0} valandų", "one" : "po {0} valandos" }, "current" : "šią valandą" }, "now" : "dabar", "minute" : { "current" : "šią minutę", "future" : { "one" : "po {0} minutės", "other" : "po {0} minučių", "many" : "po {0} minutės" }, "past" : { "other" : "prieš {0} minučių", "many" : "prieš {0} minutės", "one" : "prieš {0} minutę", "few" : "prieš {0} minutes" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/lv.json ================================================ { "short" : { "now" : "tagad", "minute" : { "past" : "pirms {0} min.", "future" : "pēc {0} min.", "current" : "šajā minūtē" }, "year" : { "current" : "šajā gadā", "future" : "pēc {0} g.", "previous" : "pagājušajā gadā", "next" : "nākamajā gadā", "past" : "pirms {0} g." }, "second" : { "current" : "tagad", "future" : "pēc {0} sek.", "past" : "pirms {0} sek." }, "hour" : { "future" : "pēc {0} st.", "past" : "pirms {0} st.", "current" : "šajā stundā" }, "month" : { "current" : "šajā mēnesī", "next" : "nākamajā mēnesī", "past" : "pirms {0} mēn.", "future" : "pēc {0} mēn.", "previous" : "pagājušajā mēnesī" }, "quarter" : { "future" : "pēc {0} cet.", "previous" : "pēdējais ceturksnis", "next" : "nākamais ceturksnis", "past" : "pirms {0} cet.", "current" : "šis ceturksnis" }, "week" : { "next" : "nākamajā nedēļā", "past" : "pirms {0} ned.", "current" : "šajā nedēļā", "previous" : "pagājušajā nedēļā", "future" : "pēc {0} ned." }, "day" : { "next" : "rīt", "current" : "šodien", "previous" : "vakar", "past" : { "one" : "pirms {0} d.", "other" : "pirms {0} d." }, "future" : { "one" : "pēc {0} d.", "other" : "pēc {0} d." } } }, "long" : { "quarter" : { "current" : "šis ceturksnis", "previous" : "pēdējais ceturksnis", "next" : "nākamais ceturksnis", "past" : { "one" : "pirms {0} ceturkšņa", "other" : "pirms {0} ceturkšņiem" }, "future" : { "one" : "pēc {0} ceturkšņa", "other" : "pēc {0} ceturkšņiem" } }, "minute" : { "current" : "šajā minūtē", "past" : { "one" : "pirms {0} minūtes", "other" : "pirms {0} minūtēm" }, "future" : { "one" : "pēc {0} minūtes", "other" : "pēc {0} minūtēm" } }, "now" : "tagad", "second" : { "past" : { "other" : "pirms {0} sekundēm", "one" : "pirms {0} sekundes" }, "future" : { "one" : "pēc {0} sekundes", "other" : "pēc {0} sekundēm" }, "current" : "tagad" }, "hour" : { "future" : { "other" : "pēc {0} stundām", "one" : "pēc {0} stundas" }, "current" : "šajā stundā", "past" : { "other" : "pirms {0} stundām", "one" : "pirms {0} stundas" } }, "week" : { "next" : "nākamajā nedēļā", "past" : { "other" : "pirms {0} nedēļām", "one" : "pirms {0} nedēļas" }, "current" : "šajā nedēļā", "previous" : "pagājušajā nedēļā", "future" : { "other" : "pēc {0} nedēļām", "one" : "pēc {0} nedēļas" } }, "day" : { "future" : { "other" : "pēc {0} dienām", "one" : "pēc {0} dienas" }, "past" : { "one" : "pirms {0} dienas", "other" : "pirms {0} dienām" }, "previous" : "vakar", "current" : "šodien", "next" : "rīt" }, "month" : { "past" : { "other" : "pirms {0} mēnešiem", "one" : "pirms {0} mēneša" }, "previous" : "pagājušajā mēnesī", "next" : "nākamajā mēnesī", "future" : { "other" : "pēc {0} mēnešiem", "one" : "pēc {0} mēneša" }, "current" : "šajā mēnesī" }, "year" : { "current" : "šajā gadā", "previous" : "pagājušajā gadā", "future" : { "one" : "pēc {0} gada", "other" : "pēc {0} gadiem" }, "next" : "nākamajā gadā", "past" : { "other" : "pirms {0} gadiem", "one" : "pirms {0} gada" } } }, "narrow" : { "week" : { "past" : "pirms {0} ned.", "future" : "pēc {0} ned.", "next" : "nākamajā nedēļā", "current" : "šajā nedēļā", "previous" : "pagājušajā nedēļā" }, "minute" : { "past" : "pirms {0} min", "future" : { "other" : "pēc {0} min", "one" : "pēc {0} min" }, "current" : "šajā minūtē" }, "month" : { "next" : "nākamajā mēnesī", "past" : "pirms {0} mēn.", "current" : "šajā mēnesī", "future" : "pēc {0} mēn.", "previous" : "pagājušajā mēnesī" }, "now" : "tagad", "year" : { "previous" : "pagājušajā gadā", "current" : "šajā gadā", "past" : "pirms {0} g.", "future" : "pēc {0} g.", "next" : "nākamajā gadā" }, "day" : { "current" : "šodien", "past" : { "one" : "pirms {0} d.", "other" : "pirms {0} d." }, "future" : { "one" : "pēc {0} d.", "other" : "pēc {0} d." }, "previous" : "vakar", "next" : "rīt" }, "second" : { "past" : { "other" : "pirms {0} s", "one" : "pirms {0} s" }, "current" : "tagad", "future" : "pēc {0} s" }, "quarter" : { "current" : "šis ceturksnis", "previous" : "pēdējais ceturksnis", "past" : "pirms {0} cet.", "next" : "nākamais ceturksnis", "future" : "pēc {0} cet." }, "hour" : { "current" : "šajā stundā", "past" : "pirms {0} h", "future" : "pēc {0} h" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/mk.json ================================================ { "short" : { "month" : { "current" : "овој месец", "next" : "следниот месец", "past" : { "one" : "пред {0} месец", "other" : "пред {0} месеци" }, "future" : { "one" : "за {0} месец", "other" : "за {0} месеци" }, "previous" : "минатиот месец" }, "now" : "сега", "day" : { "next" : "утре", "current" : "денес", "previous" : "вчера", "past" : { "one" : "пред {0} ден", "other" : "пред {0} дена" }, "future" : { "one" : "за {0} ден", "other" : "за {0} дена" } }, "year" : { "current" : "оваа година", "future" : "за {0} год.", "previous" : "минатата година", "next" : "следната година", "past" : "пред {0} год." }, "hour" : { "past" : { "other" : "пред {0} часа", "one" : "пред {0} час" }, "future" : { "one" : "за {0} час", "other" : "за {0} часа" }, "current" : "часов" }, "minute" : { "future" : "за {0} мин.", "past" : "пред {0} мин.", "current" : "оваа минута" }, "second" : { "current" : "сега", "future" : "за {0} сек.", "past" : "пред {0} сек." }, "quarter" : { "future" : "за {0} тромес.", "previous" : "последното тромесечје", "next" : "следното тромесечје", "past" : "пред {0} тромес.", "current" : "ова тромесечје" }, "week" : { "next" : "следната седмица", "past" : { "one" : "пред {0} седмица", "other" : "пред {0} седмици" }, "current" : "оваа седмица", "previous" : "минатата седмица", "future" : { "other" : "за {0} седмици", "one" : "за {0} седмица" } } }, "narrow" : { "second" : { "current" : "сега", "past" : "пред {0} сек.", "future" : "за {0} сек." }, "now" : "сега", "quarter" : { "previous" : "последното тромесечје", "current" : "ова тромесечје", "past" : "пред {0} тромес.", "future" : "за {0} тромес.", "next" : "следното тромесечје" }, "minute" : { "future" : "за {0} мин.", "current" : "оваа минута", "past" : "пред {0} мин." }, "day" : { "next" : "утре", "current" : "денес", "past" : { "other" : "пред {0} дена", "one" : "пред {0} ден" }, "future" : { "other" : "за {0} дена", "one" : "за {0} ден" }, "previous" : "вчера" }, "week" : { "current" : "оваа седмица", "next" : "следната седмица", "past" : { "other" : "пред {0} седмици", "one" : "пред {0} седмица" }, "previous" : "минатата седмица", "future" : { "one" : "за {0} седмица", "other" : "за {0} седмици" } }, "month" : { "current" : "овој месец", "next" : "следниот месец", "past" : { "other" : "пред {0} месеци", "one" : "пред {0} месец" }, "previous" : "минатиот месец", "future" : { "one" : "за {0} месец", "other" : "за {0} месеци" } }, "year" : { "previous" : "минатата година", "current" : "оваа година", "next" : "следната година", "past" : "пред {0} год.", "future" : "за {0} год." }, "hour" : { "current" : "часов", "past" : { "one" : "пред {0} час", "other" : "пред {0} часа" }, "future" : { "one" : "за {0} час", "other" : "за {0} часа" } } }, "long" : { "now" : "сега", "minute" : { "current" : "оваа минута", "future" : { "one" : "за {0} минута", "other" : "за {0} минути" }, "past" : { "other" : "пред {0} минути", "one" : "пред {0} минута" } }, "year" : { "future" : { "other" : "за {0} години", "one" : "за {0} година" }, "previous" : "минатата година", "next" : "следната година", "past" : { "other" : "пред {0} години", "one" : "пред {0} година" }, "current" : "оваа година" }, "hour" : { "current" : "часов", "past" : { "one" : "пред {0} час", "other" : "пред {0} часа" }, "future" : { "one" : "за {0} час", "other" : "за {0} часа" } }, "second" : { "current" : "сега", "past" : { "one" : "пред {0} секунда", "other" : "пред {0} секунди" }, "future" : { "one" : "за {0} секунда", "other" : "за {0} секунди" } }, "day" : { "previous" : "вчера", "current" : "денес", "future" : { "other" : "за {0} дена", "one" : "за {0} ден" }, "past" : { "one" : "пред {0} ден", "other" : "пред {0} дена" }, "next" : "утре" }, "month" : { "previous" : "минатиот месец", "current" : "овој месец", "next" : "следниот месец", "future" : { "other" : "за {0} месеци", "one" : "за {0} месец" }, "past" : { "one" : "пред {0} месец", "other" : "пред {0} месеци" } }, "quarter" : { "current" : "ова тромесечје", "previous" : "последното тромесечје", "past" : { "one" : "пред {0} тромесечје", "other" : "пред {0} тромесечја" }, "future" : { "other" : "за {0} тромесечја", "one" : "за {0} тромесечје" }, "next" : "следното тромесечје" }, "week" : { "future" : { "one" : "за {0} седмица", "other" : "за {0} седмици" }, "current" : "оваа седмица", "next" : "следната седмица", "previous" : "минатата седмица", "past" : { "one" : "пред {0} седмица", "other" : "пред {0} седмици" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ml.json ================================================ { "narrow" : { "day" : { "next" : "നാളെ", "future" : "{0} ദിവസത്തിൽ", "previous" : "ഇന്നലെ", "current" : "ഇന്ന്", "past" : "{0} ദിവസം മുമ്പ്" }, "quarter" : { "next" : "അടുത്ത പാദം", "past" : "{0} പാദം മുമ്പ്", "future" : "{0} പാദത്തിൽ", "previous" : "കഴിഞ്ഞ പാദം", "current" : "ഈ പാദം" }, "hour" : { "future" : "{0} മണിക്കൂറിൽ", "current" : "ഈ മണിക്കൂറിൽ", "past" : "{0} മണിക്കൂർ മുമ്പ്" }, "year" : { "next" : "അടുത്തവർഷം", "past" : "{0} വർഷം മുമ്പ്", "current" : "ഈ വർ‌ഷം", "previous" : "കഴിഞ്ഞ വർഷം", "future" : "{0} വർഷത്തിൽ" }, "now" : "ഇപ്പോൾ", "month" : { "current" : "ഈ മാസം", "future" : "{0} മാസത്തിൽ", "past" : "{0} മാസം മുമ്പ്", "next" : "അടുത്ത മാസം", "previous" : "കഴിഞ്ഞ മാസം" }, "week" : { "previous" : "കഴിഞ്ഞ ആഴ്‌ച", "current" : "ഈ ആഴ്ച", "past" : "{0} ആഴ്ച മുമ്പ്", "future" : "{0} ആഴ്ചയിൽ", "next" : "അടുത്ത ആഴ്ച" }, "minute" : { "current" : "ഈ മിനിറ്റിൽ", "past" : "{0} മിനിറ്റ് മുമ്പ്", "future" : "{0} മിനിറ്റിൽ" }, "second" : { "current" : "ഇപ്പോൾ", "past" : "{0} സെക്കൻഡ് മുമ്പ്", "future" : "{0} സെക്കൻഡിൽ" } }, "long" : { "day" : { "previous" : "ഇന്നലെ", "current" : "ഇന്ന്", "next" : "നാളെ", "past" : "{0} ദിവസം മുമ്പ്", "future" : "{0} ദിവസത്തിൽ" }, "week" : { "current" : "ഈ ആഴ്ച", "future" : "{0} ആഴ്ചയിൽ", "past" : "{0} ആഴ്ച മുമ്പ്", "previous" : "കഴിഞ്ഞ ആഴ്‌ച", "next" : "അടുത്ത ആഴ്ച" }, "minute" : { "past" : "{0} മിനിറ്റ് മുമ്പ്", "future" : "{0} മിനിറ്റിൽ", "current" : "ഈ മിനിറ്റിൽ" }, "month" : { "future" : "{0} മാസത്തിൽ", "next" : "അടുത്ത മാസം", "previous" : "കഴിഞ്ഞ മാസം", "current" : "ഈ മാസം", "past" : "{0} മാസം മുമ്പ്" }, "hour" : { "future" : "{0} മണിക്കൂറിൽ", "current" : "ഈ മണിക്കൂറിൽ", "past" : "{0} മണിക്കൂർ മുമ്പ്" }, "year" : { "past" : "{0} വർഷം മുമ്പ്", "future" : "{0} വർഷത്തിൽ", "previous" : "കഴിഞ്ഞ വർഷം", "next" : "അടുത്തവർഷം", "current" : "ഈ വർ‌ഷം" }, "second" : { "past" : "{0} സെക്കൻഡ് മുമ്പ്", "future" : "{0} സെക്കൻഡിൽ", "current" : "ഇപ്പോൾ" }, "now" : "ഇപ്പോൾ", "quarter" : { "past" : "{0} പാദം മുമ്പ്", "current" : "ഈ പാദം", "previous" : "കഴിഞ്ഞ പാദം", "future" : "{0} പാദത്തിൽ", "next" : "അടുത്ത പാദം" } }, "short" : { "quarter" : { "future" : "{0} പാദത്തിൽ", "next" : "അടുത്ത പാദം", "previous" : "കഴിഞ്ഞ പാദം", "current" : "ഈ പാദം", "past" : "{0} പാദം മുമ്പ്" }, "minute" : { "future" : "{0} മിനിറ്റിൽ", "current" : "ഈ മിനിറ്റിൽ", "past" : "{0} മിനിറ്റ് മുമ്പ്" }, "year" : { "next" : "അടുത്തവർഷം", "future" : "{0} വർഷത്തിൽ", "current" : "ഈ വർ‌ഷം", "past" : "{0} വർഷം മുമ്പ്", "previous" : "കഴിഞ്ഞ വർഷം" }, "second" : { "future" : "{0} സെക്കൻഡിൽ", "current" : "ഇപ്പോൾ", "past" : "{0} സെക്കൻഡ് മുമ്പ്" }, "hour" : { "past" : "{0} മണിക്കൂർ മുമ്പ്", "current" : "ഈ മണിക്കൂറിൽ", "future" : "{0} മണിക്കൂറിൽ" }, "now" : "ഇപ്പോൾ", "month" : { "previous" : "കഴിഞ്ഞ മാസം", "next" : "അടുത്ത മാസം", "past" : "{0} മാസം മുമ്പ്", "current" : "ഈ മാസം", "future" : "{0} മാസത്തിൽ" }, "week" : { "previous" : "കഴിഞ്ഞ ആഴ്‌ച", "current" : "ഈ ആഴ്ച", "next" : "അടുത്ത ആഴ്ച", "past" : "{0} ആഴ്ച മുമ്പ്", "future" : "{0} ആഴ്ചയിൽ" }, "day" : { "next" : "നാളെ", "past" : "{0} ദിവസം മുമ്പ്", "future" : "{0} ദിവസത്തിൽ", "previous" : "ഇന്നലെ", "current" : "ഇന്ന്" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/mn.json ================================================ { "narrow" : { "day" : { "next" : "маргааш", "future" : "{0} өдөрт", "previous" : "өчигдөр", "current" : "өнөөдөр", "past" : "{0} өдрийн өмнө" }, "quarter" : { "next" : "дараагийн улирал", "past" : "{0} улирлын өмнө", "future" : "{0} улиралд", "previous" : "өнгөрсөн улирал", "current" : "энэ улирал" }, "hour" : { "future" : "{0} цагт", "current" : "энэ цаг", "past" : "{0} ц. өмнө" }, "year" : { "next" : "ирэх жил", "past" : "-{0} жил.н өмнө", "current" : "энэ жил", "previous" : "өнгөрсөн жил", "future" : "+{0} жилд" }, "now" : "одоо", "month" : { "current" : "энэ сар", "future" : "+{0} сард", "past" : "{0} сарын өмнө", "next" : "ирэх сар", "previous" : "өнгөрсөн сар" }, "week" : { "previous" : "өнгөрсөн долоо хоног", "current" : "энэ долоо хоног", "past" : "{0} 7 хоногийн өмнө", "future" : "{0} 7 хоногт", "next" : "ирэх долоо хоног" }, "minute" : { "current" : "энэ минут", "past" : "{0} мин. өмнө", "future" : "{0} мин. дотор" }, "second" : { "current" : "одоо", "past" : "{0} сек. өмнө", "future" : "{0} сек. дотор" } }, "long" : { "day" : { "previous" : "өчигдөр", "current" : "өнөөдөр", "next" : "маргааш", "past" : "{0} өдрийн өмнө", "future" : "{0} өдөрт" }, "week" : { "current" : "энэ долоо хоног", "future" : "{0} 7 хоногт", "past" : "{0} 7 хоногийн өмнө", "previous" : "өнгөрсөн долоо хоног", "next" : "ирэх долоо хоног" }, "minute" : { "current" : "энэ минут", "past" : "{0} минутын өмнө", "future" : "{0} минутын дотор" }, "month" : { "future" : "{0} сард", "next" : "ирэх сар", "previous" : "өнгөрсөн сар", "current" : "энэ сар", "past" : "{0} сарын өмнө" }, "hour" : { "current" : "энэ цаг", "past" : "{0} цагийн өмнө", "future" : "{0} цагт" }, "year" : { "past" : "{0} жилийн өмнө", "future" : "{0} жилийн дотор", "previous" : "өнгөрсөн жил", "next" : "ирэх жил", "current" : "энэ жил" }, "second" : { "past" : "{0} секундын өмнө", "future" : "{0} секундын дотор", "current" : "одоо" }, "now" : "одоо", "quarter" : { "past" : "{0} улирлын өмнө", "current" : "энэ улирал", "previous" : "өнгөрсөн улирал", "future" : "{0} улиралд", "next" : "дараагийн улирал" } }, "short" : { "quarter" : { "future" : "{0} улиралд", "next" : "дараагийн улирал", "previous" : "өнгөрсөн улирал", "current" : "энэ улирал", "past" : "{0} улирлын өмнө" }, "minute" : { "current" : "энэ минут", "past" : "{0} мин. өмнө", "future" : "{0} мин. дотор" }, "year" : { "next" : "ирэх жил", "future" : "{0} жилийн дотор", "current" : "энэ жил", "past" : "{0} жилийн өмнө", "previous" : "өнгөрсөн жил" }, "second" : { "future" : "{0} сек. дотор", "current" : "одоо", "past" : "{0} сек. өмнө" }, "hour" : { "current" : "энэ цаг", "past" : "{0} ц. өмнө", "future" : "{0} цагт" }, "now" : "одоо", "month" : { "previous" : "өнгөрсөн сар", "next" : "ирэх сар", "past" : "{0} сарын өмнө", "current" : "энэ сар", "future" : "{0} сард" }, "week" : { "previous" : "өнгөрсөн долоо хоног", "current" : "энэ долоо хоног", "next" : "ирэх долоо хоног", "past" : "{0} 7 хоногийн өмнө", "future" : "{0} 7 хоногт" }, "day" : { "next" : "маргааш", "past" : "{0} өдрийн өмнө", "future" : "{0} өдөрт", "previous" : "өчигдөр", "current" : "өнөөдөр" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/mr.json ================================================ { "narrow" : { "hour" : { "future" : { "other" : "येत्या {0} तासांमध्ये", "one" : "येत्या {0} तासामध्ये" }, "past" : { "one" : "{0} तासापूर्वी", "other" : "{0} तासांपूर्वी" }, "current" : "तासात" }, "now" : "आत्ता", "quarter" : { "current" : "ही तिमाही", "next" : "पुढील तिमाही", "previous" : "मागील तिमाही", "past" : { "one" : "{0} तिमाहीपूर्वी", "other" : "{0} तिमाहींपूर्वी" }, "future" : { "one" : "{0} तिमाहीमध्ये", "other" : "{0} तिमाहींमध्ये" } }, "year" : { "next" : "पुढील वर्ष", "previous" : "मागील वर्ष", "past" : { "one" : "{0} वर्षापूर्वी", "other" : "{0} वर्षांपूर्वी" }, "current" : "हे वर्ष", "future" : { "one" : "येत्या {0} वर्षामध्ये", "other" : "येत्या {0} वर्षांमध्ये" } }, "week" : { "next" : "पुढील आठवडा", "previous" : "मागील आठवडा", "future" : { "other" : "येत्या {0} आठवड्यांमध्ये", "one" : "येत्या {0} आठवड्यामध्ये" }, "past" : { "other" : "{0} आठवड्यांपूर्वी", "one" : "{0} आठवड्यापूर्वी" }, "current" : "हा आठवडा" }, "month" : { "previous" : "मागील महिना", "next" : "पुढील महिना", "future" : { "one" : "{0} महिन्यामध्ये", "other" : "{0} महिन्यांमध्ये" }, "past" : { "other" : "{0} महिन्यांपूर्वी", "one" : "{0} महिन्यापूर्वी" }, "current" : "हा महिना" }, "day" : { "future" : { "one" : "{0} दिवसामध्ये", "other" : "{0} दिवसांमध्ये" }, "previous" : "काल", "current" : "आज", "next" : "उद्या", "past" : { "one" : "{0} दिवसापूर्वी", "other" : "{0} दिवसांपूर्वी" } }, "minute" : { "future" : "{0} मिनि. मध्ये", "current" : "या मिनिटात", "past" : "{0} मिनि. पूर्वी" }, "second" : { "past" : "{0} से. पूर्वी", "current" : "आत्ता", "future" : { "one" : "{0} से. मध्ये", "other" : "येत्या {0} से. मध्ये" } } }, "long" : { "second" : { "past" : { "other" : "{0} सेकंदांपूर्वी", "one" : "{0} सेकंदापूर्वी" }, "future" : { "other" : "{0} सेकंदांमध्ये", "one" : "{0} सेकंदामध्ये" }, "current" : "आत्ता" }, "year" : { "previous" : "मागील वर्ष", "current" : "हे वर्ष", "next" : "पुढील वर्ष", "past" : { "one" : "{0} वर्षापूर्वी", "other" : "{0} वर्षांपूर्वी" }, "future" : { "one" : "येत्या {0} वर्षामध्ये", "other" : "येत्या {0} वर्षांमध्ये" } }, "quarter" : { "current" : "ही तिमाही", "future" : { "one" : "{0} तिमाहीमध्ये", "other" : "{0} तिमाहींमध्ये" }, "past" : { "other" : "{0} तिमाहींपूर्वी", "one" : "{0} तिमाहीपूर्वी" }, "previous" : "मागील तिमाही", "next" : "पुढील तिमाही" }, "minute" : { "current" : "या मिनिटात", "past" : { "other" : "{0} मिनिटांपूर्वी", "one" : "{0} मिनिटापूर्वी" }, "future" : { "other" : "{0} मिनिटांमध्ये", "one" : "{0} मिनिटामध्ये" } }, "now" : "आत्ता", "month" : { "future" : { "other" : "येत्या {0} महिन्यांमध्ये", "one" : "येत्या {0} महिन्यामध्ये" }, "past" : { "one" : "{0} महिन्यापूर्वी", "other" : "{0} महिन्यांपूर्वी" }, "current" : "हा महिना", "next" : "पुढील महिना", "previous" : "मागील महिना" }, "week" : { "current" : "हा आठवडा", "next" : "पुढील आठवडा", "previous" : "मागील आठवडा", "past" : { "one" : "{0} आठवड्यापूर्वी", "other" : "{0} आठवड्यांपूर्वी" }, "future" : { "one" : "{0} आठवड्यामध्ये", "other" : "{0} आठवड्यांमध्ये" } }, "hour" : { "current" : "तासात", "past" : { "one" : "{0} तासापूर्वी", "other" : "{0} तासांपूर्वी" }, "future" : { "other" : "{0} तासांमध्ये", "one" : "{0} तासामध्ये" } }, "day" : { "past" : { "one" : "{0} दिवसापूर्वी", "other" : "{0} दिवसांपूर्वी" }, "previous" : "काल", "next" : "उद्या", "future" : { "other" : "येत्या {0} दिवसांमध्ये", "one" : "येत्या {0} दिवसामध्ये" }, "current" : "आज" } }, "short" : { "second" : { "past" : "{0} से. पूर्वी", "current" : "आत्ता", "future" : "{0} से. मध्ये" }, "day" : { "next" : "उद्या", "past" : { "one" : "{0} दिवसापूर्वी", "other" : "{0} दिवसांपूर्वी" }, "previous" : "काल", "future" : { "other" : "येत्या {0} दिवसांमध्ये", "one" : "{0} दिवसामध्ये" }, "current" : "आज" }, "year" : { "past" : { "one" : "{0} वर्षापूर्वी", "other" : "{0} वर्षांपूर्वी" }, "future" : { "one" : "{0} वर्षामध्ये", "other" : "{0} वर्षांमध्ये" }, "previous" : "मागील वर्ष", "current" : "हे वर्ष", "next" : "पुढील वर्ष" }, "week" : { "previous" : "मागील आठवडा", "current" : "हा आठवडा", "next" : "पुढील आठवडा", "past" : { "one" : "{0} आठवड्यापूर्वी", "other" : "{0} आठवड्यांपूर्वी" }, "future" : { "one" : "येत्या {0} आठवड्यामध्ये", "other" : "येत्या {0} आठवड्यांमध्ये" } }, "now" : "आत्ता", "minute" : { "past" : "{0} मिनि. पूर्वी", "future" : "{0} मिनि. मध्ये", "current" : "या मिनिटात" }, "quarter" : { "previous" : "मागील तिमाही", "future" : { "other" : "येत्या {0} तिमाहींमध्ये", "one" : "येत्या {0} तिमाहीमध्ये" }, "next" : "पुढील तिमाही", "past" : { "other" : "{0} तिमाहींपूर्वी", "one" : "{0} तिमाहीपूर्वी" }, "current" : "ही तिमाही" }, "month" : { "next" : "पुढील महिना", "past" : { "one" : "{0} महिन्यापूर्वी", "other" : "{0} महिन्यांपूर्वी" }, "future" : "{0} महिन्यामध्ये", "previous" : "मागील महिना", "current" : "हा महिना" }, "hour" : { "current" : "तासात", "past" : { "one" : "{0} तासापूर्वी", "other" : "{0} तासांपूर्वी" }, "future" : { "one" : "{0} तासामध्ये", "other" : "{0} तासांमध्ये" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ms.json ================================================ { "narrow" : { "day" : { "next" : "esok", "future" : "dlm {0} hari", "previous" : "semlm", "current" : "hari ini", "past" : "{0} hari lalu" }, "quarter" : { "next" : "suku seterusnya", "past" : "{0} suku thn lalu", "future" : "dlm {0} suku thn", "previous" : "suku lepas", "current" : "suku ini" }, "hour" : { "future" : "dlm {0} jam", "current" : "jam ini", "past" : "{0} jam lalu" }, "year" : { "next" : "thn depan", "past" : "{0} thn lalu", "current" : "thn ini", "previous" : "thn lepas", "future" : "dalam {0} thn" }, "now" : "sekarang", "month" : { "current" : "bln ini", "future" : "dlm {0} bln", "past" : "{0} bulan lalu", "next" : "bln depan", "previous" : "bln lalu" }, "week" : { "previous" : "mng lepas", "current" : "mng ini", "past" : "{0} mgu lalu", "future" : "dlm {0} mgu", "next" : "mng depan" }, "minute" : { "current" : "pada minit ini", "past" : "{0} min lalu", "future" : "dlm {0} min" }, "second" : { "past" : "{0} saat lalu", "current" : "sekarang", "future" : "dlm {0} saat" } }, "long" : { "day" : { "previous" : "semalam", "current" : "hari ini", "next" : "esok", "past" : "{0} hari lalu", "future" : "dalam {0} hari" }, "week" : { "current" : "minggu ini", "future" : "dalam {0} minggu", "past" : "{0} minggu lalu", "previous" : "minggu lalu", "next" : "minggu depan" }, "minute" : { "current" : "pada minit ini", "past" : "{0} minit lalu", "future" : "dalam {0} minit" }, "month" : { "future" : "dalam {0} bulan", "next" : "bulan depan", "previous" : "bulan lalu", "current" : "bulan ini", "past" : "{0} bulan lalu" }, "hour" : { "current" : "jam ini", "future" : "dalam {0} jam", "past" : "{0} jam lalu" }, "year" : { "past" : "{0} tahun lalu", "future" : "dalam {0} tahun", "previous" : "tahun lalu", "next" : "tahun depan", "current" : "tahun ini" }, "second" : { "past" : "{0} saat lalu", "current" : "sekarang", "future" : "dalam {0} saat" }, "now" : "sekarang", "quarter" : { "past" : "{0} suku tahun lalu", "current" : "suku tahun ini", "previous" : "suku tahun lalu", "future" : "dalam {0} suku tahun", "next" : "suku tahun seterusnya" } }, "short" : { "quarter" : { "future" : "dlm {0} suku thn", "next" : "suku seterusnya", "previous" : "suku lepas", "current" : "suku ini", "past" : "{0} suku thn lalu" }, "minute" : { "current" : "pada minit ini", "past" : "{0} min lalu", "future" : "dlm {0} min" }, "year" : { "next" : "thn depan", "future" : "dalam {0} thn", "current" : "thn ini", "past" : "{0} thn lalu", "previous" : "thn lepas" }, "second" : { "past" : "{0} saat lalu", "current" : "sekarang", "future" : "dlm {0} saat" }, "hour" : { "current" : "jam ini", "past" : "{0} jam lalu", "future" : "dlm {0} jam" }, "now" : "sekarang", "month" : { "previous" : "bln lalu", "next" : "bln depan", "past" : "{0} bln lalu", "current" : "bln ini", "future" : "dlm {0} bln" }, "week" : { "previous" : "mng lepas", "current" : "mng ini", "next" : "mng depan", "past" : "{0} mgu lalu", "future" : "dlm {0} mgu" }, "day" : { "next" : "esok", "past" : "{0} hari lalu", "future" : "dlm {0} hari", "previous" : "semlm", "current" : "hari ini" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/mt.json ================================================ { "narrow" : { "now" : "now", "hour" : { "future" : "+{0} h", "current" : "this hour", "past" : "-{0} h" }, "minute" : { "future" : "+{0} min", "current" : "this minute", "past" : "-{0} min" }, "second" : { "future" : "+{0} s", "past" : "-{0} s", "current" : "now" }, "year" : { "next" : "Is-sena d-dieħla", "future" : "+{0} y", "previous" : "Is-sena li għaddiet", "current" : "din is-sena", "past" : { "one" : "{0} sena ilu", "other" : "{0} snin ilu" } }, "week" : { "past" : "-{0} w", "next" : "Il-ġimgħa d-dieħla", "future" : "+{0} w", "previous" : "Il-ġimgħa li għaddiet", "current" : "Din il-ġimgħa" }, "month" : { "previous" : "Ix-xahar li għadda", "current" : "Dan ix-xahar", "next" : "Ix-xahar id-dieħel", "past" : "-{0} m", "future" : "+{0} m" }, "quarter" : { "next" : "next quarter", "past" : "-{0} Q", "future" : "+{0} Q", "previous" : "last quarter", "current" : "this quarter" }, "day" : { "next" : "Għada", "previous" : "Ilbieraħ", "past" : "-{0} d", "future" : "+{0} d", "current" : "Illum" } }, "long" : { "quarter" : { "previous" : "last quarter", "current" : "this quarter", "next" : "next quarter", "past" : "-{0} Q", "future" : "+{0} Q" }, "day" : { "previous" : "Ilbieraħ", "past" : "-{0} d", "future" : "+{0} d", "current" : "Illum", "next" : "Għada" }, "week" : { "next" : "Il-ġimgħa d-dieħla", "past" : "-{0} w", "future" : "+{0} w", "previous" : "Il-ġimgħa li għaddiet", "current" : "Din il-ġimgħa" }, "hour" : { "future" : "+{0} h", "past" : "-{0} h", "current" : "this hour" }, "second" : { "future" : "+{0} s", "current" : "now", "past" : "-{0} s" }, "year" : { "previous" : "Is-sena li għaddiet", "past" : { "one" : "{0} sena ilu", "other" : "{0} snin ilu" }, "next" : "Is-sena d-dieħla", "future" : "+{0} y", "current" : "din is-sena" }, "month" : { "next" : "Ix-xahar id-dieħel", "past" : "-{0} m", "previous" : "Ix-xahar li għadda", "current" : "Dan ix-xahar", "future" : "+{0} m" }, "minute" : { "past" : "-{0} min", "current" : "this minute", "future" : "+{0} min" }, "now" : "now" }, "short" : { "hour" : { "past" : "-{0} h", "future" : "+{0} h", "current" : "this hour" }, "now" : "now", "quarter" : { "current" : "this quarter", "future" : "+{0} Q", "past" : "-{0} Q", "next" : "next quarter", "previous" : "last quarter" }, "day" : { "current" : "Illum", "past" : "-{0} d", "future" : "+{0} d", "next" : "Għada", "previous" : "Ilbieraħ" }, "week" : { "current" : "Din il-ġimgħa", "past" : "-{0} w", "future" : "+{0} w", "next" : "Il-ġimgħa d-dieħla", "previous" : "Il-ġimgħa li għaddiet" }, "minute" : { "past" : "-{0} min", "current" : "this minute", "future" : "+{0} min" }, "second" : { "current" : "now", "past" : "-{0} s", "future" : "+{0} s" }, "month" : { "current" : "Dan ix-xahar", "future" : "+{0} m", "previous" : "Ix-xahar li għadda", "next" : "Ix-xahar id-dieħel", "past" : "-{0} m" }, "year" : { "future" : "+{0} y", "previous" : "Is-sena li għaddiet", "next" : "Is-sena d-dieħla", "current" : "din is-sena", "past" : { "other" : "{0} snin ilu", "one" : "{0} sena ilu" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/my.json ================================================ { "narrow" : { "quarter" : { "past" : "ပြီးခဲ့သည့် သုံးလပတ်ကာလ {0} ခုအတွင်း", "future" : "သုံးလပတ်ကာလ {0} ခုအတွင်း", "current" : "ယခုသုံးလပတ်", "previous" : "ပြီးခဲ့သောသုံးလပတ်", "next" : "နောက်လာမည့်သုံးလပတ်" }, "year" : { "previous" : "ယမန်နှစ်", "next" : "လာမည့်နှစ်", "future" : "{0} နှစ်အတွင်း", "current" : "ယခုနှစ်", "past" : "ပြီးခဲ့သည့် {0} နှစ်" }, "week" : { "future" : "{0} ပတ်အတွင်း", "previous" : "ပြီးခဲ့သည့် သီတင်းပတ်", "next" : "လာမည့် သီတင်းပတ်", "past" : "ပြီးခဲ့သည့် {0} ပတ်", "current" : "ယခု သီတင်းပတ်" }, "day" : { "current" : "ယနေ့", "past" : "ပြီးခဲ့သည့် {0} ရက်", "next" : "မနက်ဖြန်", "future" : "{0} ရက်အတွင်း", "previous" : "မနေ့က" }, "hour" : { "current" : "ဤအချိန်", "past" : "ပြီးခဲ့သည့် {0} နာရီ", "future" : "{0} နာရီအတွင်း" }, "minute" : { "future" : "{0} မိနစ်အတွင်း", "current" : "ဤမိနစ်", "past" : "ပြီးခဲ့သည့် {0} မိနစ်" }, "month" : { "next" : "လာမည့်လ", "current" : "ယခုလ", "past" : "ပြီးခဲ့သည့် {0} လ", "future" : "{0} လအတွင်း", "previous" : "ပြီးခဲ့သည့်လ" }, "now" : "ယခု", "second" : { "future" : "{0} စက္ကန့်အတွင်း", "current" : "ယခု", "past" : "ပြီးခဲ့သည့် {0} စက္ကန့်" } }, "short" : { "minute" : { "past" : "ပြီးခဲ့သည့် {0} မိနစ်", "future" : "{0} မိနစ်အတွင်း", "current" : "ဤမိနစ်" }, "week" : { "current" : "ယခု သီတင်းပတ်", "past" : "ပြီးခဲ့သည့် {0} ပတ်", "future" : "{0} ပတ်အတွင်း", "next" : "လာမည့် သီတင်းပတ်", "previous" : "ပြီးခဲ့သည့် သီတင်းပတ်" }, "year" : { "current" : "ယခုနှစ်", "future" : "{0} နှစ်အတွင်း", "past" : "ပြီးခဲ့သည့် {0} နှစ်", "next" : "လာမည့်နှစ်", "previous" : "ယမန်နှစ်" }, "day" : { "next" : "မနက်ဖြန်", "current" : "ယနေ့", "previous" : "မနေ့က", "past" : "ပြီးခဲ့သည့် {0} ရက်", "future" : "{0} ရက်အတွင်း" }, "hour" : { "future" : "{0} နာရီအတွင်း", "current" : "ဤအချိန်", "past" : "ပြီးခဲ့သည့် {0} နာရီ" }, "quarter" : { "current" : "ယခုသုံးလပတ်", "future" : "သုံးလပတ်ကာလ {0} ခုအတွင်း", "previous" : "ပြီးခဲ့သောသုံးလပတ်", "next" : "နောက်လာမည့်သုံးလပတ်", "past" : "ပြီးခဲ့သည့် သုံးလပတ်ကာလ {0} ခုအတွင်း" }, "second" : { "past" : "ပြီးခဲ့သည့် {0} စက္ကန့်", "current" : "ယခု", "future" : "{0} စက္ကန့်အတွင်း" }, "month" : { "current" : "ယခုလ", "past" : "ပြီးခဲ့သည့် {0} လ", "future" : "{0} လအတွင်း", "next" : "လာမည့်လ", "previous" : "ပြီးခဲ့သည့်လ" }, "now" : "ယခု" }, "long" : { "hour" : { "future" : "{0} နာရီအတွင်း", "current" : "ဤအချိန်", "past" : "ပြီးခဲ့သည့် {0} နာရီ" }, "day" : { "next" : "မနက်ဖြန်", "past" : "ပြီးခဲ့သည့် {0} ရက်", "previous" : "မနေ့က", "future" : "{0} ရက်အတွင်း", "current" : "ယနေ့" }, "second" : { "past" : "ပြီးခဲ့သည့် {0} စက္ကန့်", "current" : "ယခု", "future" : "{0} စက္ကန့်အတွင်း" }, "week" : { "future" : "{0} ပတ်အတွင်း", "past" : "ပြီးခဲ့သည့် {0} ပတ်", "current" : "ယခု သီတင်းပတ်", "next" : "လာမည့် သီတင်းပတ်", "previous" : "ပြီးခဲ့သည့် သီတင်းပတ်" }, "minute" : { "future" : "{0} မိနစ်အတွင်း", "past" : "ပြီးခဲ့သည့် {0} မိနစ်", "current" : "ဤမိနစ်" }, "month" : { "future" : "{0} လအတွင်း", "past" : "ပြီးခဲ့သည့် {0} လ", "current" : "ယခုလ", "previous" : "ပြီးခဲ့သည့်လ", "next" : "လာမည့်လ" }, "now" : "ယခု", "year" : { "current" : "ယခုနှစ်", "next" : "လာမည့်နှစ်", "previous" : "ယမန်နှစ်", "future" : "{0} နှစ်အတွင်း", "past" : "ပြီးခဲ့သည့် {0} နှစ်" }, "quarter" : { "future" : "သုံးလပတ်ကာလ {0} အတွင်း", "previous" : "ပြီးခဲ့သည့် သုံးလပတ်", "past" : "ပြီးခဲ့သည့် သုံးလပတ်ကာလ {0} ခုအတွင်း", "next" : "လာမည့် သုံးလပတ်", "current" : "ယခု သုံးလပတ်" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/mzn.json ================================================ { "long" : { "hour" : { "future" : "{0} ساعِت دله", "current" : "this hour", "past" : "{0} ساعِت پیش" }, "second" : { "future" : "{0} ثانیه دله", "past" : "{0} ثانیه پیش", "current" : "now" }, "week" : { "next" : "بعدی هفته", "current" : "این هفته", "previous" : "قبلی هفته", "future" : "{0} هفته دله", "past" : "{0} هفته پیش" }, "minute" : { "past" : "{0} دَقه پیش", "future" : "{0} دقیقه دله", "current" : "this minute" }, "year" : { "current" : "امسال", "past" : "{0} سال پیش", "future" : "{0} سال دله", "previous" : "پارسال", "next" : "سال دیگه" }, "quarter" : { "next" : "next quarter", "current" : "this quarter", "future" : "{0} ربع دله", "previous" : "last quarter", "past" : "{0} ربع پیش" }, "month" : { "previous" : "ماه قبل", "future" : "{0} ماه دله", "current" : "این ماه", "next" : "ماه ِبعد", "past" : "{0} ماه پیش" }, "day" : { "next" : "فِردا", "past" : "{0} روز پیش", "current" : "اَمروز", "future" : "{0} روز دله", "previous" : "دیروز" }, "now" : "now" }, "narrow" : { "week" : { "current" : "این هفته", "next" : "بعدی هفته", "past" : "{0} هفته پیش", "previous" : "قبلی هفته", "future" : "{0} هفته دله" }, "minute" : { "future" : "{0} دَقه دله", "past" : "{0} دَقه پیش", "current" : "this minute" }, "month" : { "current" : "این ماه", "previous" : "ماه قبل", "past" : "{0} ماه پیش", "next" : "ماه ِبعد", "future" : "{0} ماه دله" }, "now" : "now", "year" : { "previous" : "پارسال", "current" : "امسال", "next" : "سال دیگه", "past" : "{0} سال پیش", "future" : "{0} سال دله" }, "hour" : { "current" : "this hour", "past" : "{0} ساعت پیش", "future" : "{0} ساعت دله" }, "quarter" : { "previous" : "last quarter", "current" : "this quarter", "past" : "{0} ربع پیش", "future" : "{0} ربع دله", "next" : "next quarter" }, "second" : { "current" : "now", "future" : "{0} ثانیه دله", "past" : "{0} ثانیه پیش" }, "day" : { "past" : "{0} روز پیش", "current" : "اَمروز", "future" : "{0} روز دله", "previous" : "دیروز", "next" : "فِردا" } }, "short" : { "month" : { "current" : "این ماه", "next" : "ماه ِبعد", "past" : "{0} ماه پیش", "future" : "{0} ماه دله", "previous" : "ماه قبل" }, "now" : "now", "day" : { "next" : "فِردا", "current" : "اَمروز", "previous" : "دیروز", "past" : "{0} روز پیش", "future" : "{0} روز دله" }, "year" : { "current" : "امسال", "future" : "{0} سال دله", "previous" : "پارسال", "next" : "سال دیگه", "past" : "{0} سال پیش" }, "hour" : { "past" : "{0} ساعت پیش", "current" : "this hour", "future" : "{0} ساعت دله" }, "minute" : { "past" : "{0} دَقه پیش", "future" : "{0} دَقه دله", "current" : "this minute" }, "second" : { "future" : "{0} ثانیه دله", "current" : "now", "past" : "{0} ثانیه پیش" }, "quarter" : { "future" : "{0} ربع دله", "previous" : "last quarter", "next" : "next quarter", "past" : "{0} ربع پیش", "current" : "this quarter" }, "week" : { "next" : "بعدی هفته", "past" : "{0} هفته پیش", "current" : "این هفته", "previous" : "قبلی هفته", "future" : "{0} هفته دله" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/nb.json ================================================ { "narrow" : { "quarter" : { "past" : "–{0} kv.", "future" : "+{0} kv.", "current" : "dette kv.", "previous" : "forrige kv.", "next" : "neste kv." }, "year" : { "previous" : "i fjor", "next" : "neste år", "future" : "+{0} år", "current" : "i år", "past" : "–{0} år" }, "week" : { "future" : "+{0} u.", "previous" : "forrige uke", "next" : "neste uke", "past" : "-{0} u.", "current" : "denne uken" }, "day" : { "current" : "i dag", "past" : "-{0} d.", "next" : "i morgen", "future" : "+{0} d.", "previous" : "i går" }, "hour" : { "current" : "denne timen", "past" : "-{0} t", "future" : "+{0} t" }, "minute" : { "future" : "+{0} min", "current" : "dette minuttet", "past" : "-{0} min" }, "month" : { "next" : "neste md.", "current" : "denne md.", "past" : "-{0} md.", "future" : "+{0} md.", "previous" : "forrige md." }, "now" : "nå", "second" : { "future" : "+{0} s", "current" : "nå", "past" : "-{0} s" } }, "long" : { "month" : { "next" : "neste måned", "current" : "denne måneden", "future" : { "one" : "om {0} måned", "other" : "om {0} måneder" }, "previous" : "forrige måned", "past" : { "one" : "for {0} måned siden", "other" : "for {0} måneder siden" } }, "hour" : { "current" : "denne timen", "future" : { "one" : "om {0} time", "other" : "om {0} timer" }, "past" : { "one" : "for {0} time siden", "other" : "for {0} timer siden" } }, "second" : { "future" : { "one" : "om {0} sekund", "other" : "om {0} sekunder" }, "current" : "nå", "past" : { "other" : "for {0} sekunder siden", "one" : "for {0} sekund siden" } }, "week" : { "previous" : "forrige uke", "past" : { "other" : "for {0} uker siden", "one" : "for {0} uke siden" }, "future" : { "other" : "om {0} uker", "one" : "om {0} uke" }, "current" : "denne uken", "next" : "neste uke" }, "year" : { "current" : "i år", "next" : "neste år", "previous" : "i fjor", "future" : "om {0} år", "past" : "for {0} år siden" }, "now" : "nå", "quarter" : { "future" : { "one" : "om {0} kvartal", "other" : "om {0} kvartaler" }, "previous" : "forrige kvartal", "past" : { "other" : "for {0} kvartaler siden", "one" : "for {0} kvartal siden" }, "next" : "neste kvartal", "current" : "dette kvartalet" }, "day" : { "previous" : "i går", "current" : "i dag", "next" : "i morgen", "past" : "for {0} døgn siden", "future" : "om {0} døgn" }, "minute" : { "future" : { "one" : "om {0} minutt", "other" : "om {0} minutter" }, "past" : { "one" : "for {0} minutt siden", "other" : "for {0} minutter siden" }, "current" : "dette minuttet" } }, "short" : { "minute" : { "past" : "for {0} min siden", "future" : "om {0} min", "current" : "dette minuttet" }, "week" : { "current" : "denne uken", "past" : "for {0} u. siden", "future" : "om {0} u.", "next" : "neste uke", "previous" : "forrige uke" }, "year" : { "current" : "i år", "future" : "om {0} år", "past" : "for {0} år siden", "next" : "neste år", "previous" : "i fjor" }, "day" : { "next" : "i morgen", "current" : "i dag", "previous" : "i går", "past" : "for {0} d. siden", "future" : "om {0} d." }, "hour" : { "future" : "om {0} t", "current" : "denne timen", "past" : "for {0} t siden" }, "quarter" : { "current" : "dette kv.", "future" : "om {0} kv.", "previous" : "forrige kv.", "next" : "neste kv.", "past" : "for {0} kv. siden" }, "second" : { "past" : "for {0} sek siden", "current" : "nå", "future" : "om {0} sek" }, "month" : { "current" : "denne md.", "past" : "for {0} md. siden", "future" : "om {0} md.", "next" : "neste md.", "previous" : "forrige md." }, "now" : "nå" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ne.json ================================================ { "narrow" : { "quarter" : { "past" : "{0}सत्र अघि", "future" : "{0}सत्रमा", "current" : "यो सत्र", "previous" : "अघिल्लो सत्र", "next" : "अर्को सत्र" }, "year" : { "previous" : "गत वर्ष", "next" : "आगामी वर्ष", "future" : "{0} वर्षमा", "current" : "यो वर्ष", "past" : "{0} वर्ष अघि" }, "week" : { "future" : "{0} हप्तामा", "previous" : "गत हप्ता", "next" : "आउने हप्ता", "past" : "{0} हप्ता पहिले", "current" : "यो हप्ता" }, "day" : { "current" : "आज", "past" : "{0} दिन पहिले", "next" : "भोलि", "future" : "{0} दिनमा", "previous" : "हिजो" }, "hour" : { "current" : "यस घडीमा", "past" : "{0} घण्टा पहिले", "future" : "{0} घण्टामा" }, "minute" : { "future" : "{0} मिनेटमा", "current" : "यही मिनेटमा", "past" : "{0} मिनेट पहिले" }, "month" : { "next" : "अर्को महिना", "current" : "यो महिना", "past" : "{0} महिना पहिले", "future" : "{0} महिनामा", "previous" : "गत महिना" }, "now" : "अहिले", "second" : { "future" : "{0} सेकेन्डमा", "current" : "अहिले", "past" : "{0} सेकेन्ड पहिले" } }, "short" : { "minute" : { "past" : "{0} मिनेट पहिले", "future" : "{0} मिनेटमा", "current" : "यही मिनेटमा" }, "week" : { "current" : "यो हप्ता", "past" : "{0} हप्ता पहिले", "future" : "{0} हप्तामा", "next" : "आउने हप्ता", "previous" : "गत हप्ता" }, "year" : { "current" : "यो वर्ष", "future" : "{0} वर्षमा", "past" : "{0} वर्ष अघि", "next" : "आगामी वर्ष", "previous" : "गत वर्ष" }, "day" : { "next" : "भोलि", "current" : "आज", "previous" : "हिजो", "past" : "{0} दिन पहिले", "future" : "{0} दिनमा" }, "hour" : { "future" : "{0} घण्टामा", "current" : "यस घडीमा", "past" : "{0} घण्टा पहिले" }, "quarter" : { "current" : "यो सत्र", "future" : "{0}सत्रमा", "previous" : "अघिल्लो सत्र", "next" : "अर्को सत्र", "past" : "{0}सत्र अघि" }, "second" : { "past" : "{0} सेकेन्ड पहिले", "current" : "अहिले", "future" : "{0} सेकेन्डमा" }, "month" : { "current" : "यो महिना", "past" : "{0} महिना पहिले", "future" : "{0} महिनामा", "next" : "अर्को महिना", "previous" : "गत महिना" }, "now" : "अहिले" }, "long" : { "week" : { "current" : "यो हप्ता", "past" : "{0} हप्ता पहिले", "next" : "आउने हप्ता", "previous" : "गत हप्ता", "future" : "{0} हप्तामा" }, "second" : { "future" : "{0} सेकेन्डमा", "past" : "{0} सेकेन्ड पहिले", "current" : "अहिले" }, "quarter" : { "future" : { "other" : "{0}सत्रमा", "one" : "+{0} सत्रमा" }, "previous" : "अघिल्लो सत्र", "past" : "{0}सत्र अघि", "next" : "अर्को सत्र", "current" : "यो सत्र" }, "now" : "अहिले", "day" : { "future" : "{0} दिनमा", "previous" : "हिजो", "past" : "{0} दिन पहिले", "next" : "भोलि", "current" : "आज" }, "year" : { "current" : "यो वर्ष", "next" : "आगामी वर्ष", "previous" : "गत वर्ष", "future" : "{0} वर्षमा", "past" : "{0} वर्ष अघि" }, "hour" : { "future" : "{0} घण्टामा", "past" : "{0} घण्टा पहिले", "current" : "यस घडीमा" }, "month" : { "next" : "अर्को महिना", "current" : "यो महिना", "future" : "{0} महिनामा", "previous" : "गत महिना", "past" : "{0} महिना पहिले" }, "minute" : { "past" : "{0} मिनेट पहिले", "current" : "यही मिनेटमा", "future" : "{0} मिनेटमा" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/nl.json ================================================ { "narrow" : { "minute" : { "future" : "over {0} min.", "current" : "binnen een minuut", "past" : "{0} min. geleden" }, "week" : { "current" : "deze week", "previous" : "vorige week", "past" : { "one" : "{0} week geleden", "other" : "{0} weken geleden" }, "future" : { "other" : "over {0} weken", "one" : "over {0} week" }, "next" : "volgende week" }, "quarter" : { "previous" : "vorig kwartaal", "current" : "dit kwartaal", "past" : { "other" : "{0} kwartalen geleden", "one" : "{0} kwartaal geleden" }, "next" : "volgend kwartaal", "future" : { "one" : "over {0} kw.", "other" : "over {0} kwartalen" } }, "year" : { "future" : "over {0} jaar", "previous" : "vorig jaar", "current" : "dit jaar", "next" : "volgend jaar", "past" : "{0} jaar geleden" }, "hour" : { "current" : "binnen een uur", "future" : "over {0} uur", "past" : "{0} uur geleden" }, "day" : { "previous" : "gisteren", "current" : "vandaag", "past" : { "one" : "{0} dag geleden", "other" : "{0} dgn geleden" }, "future" : { "other" : "over {0} dgn", "one" : "over {0} dag" }, "next" : "morgen" }, "second" : { "past" : "{0} sec. geleden", "current" : "nu", "future" : "over {0} sec." }, "now" : "nu", "month" : { "previous" : "vorige maand", "current" : "deze maand", "past" : { "one" : "{0} maand geleden", "other" : "{0} maanden geleden" }, "future" : { "one" : "over {0} maand", "other" : "over {0} maanden" }, "next" : "volgende maand" } }, "long" : { "year" : { "past" : "{0} jaar geleden", "future" : "over {0} jaar", "previous" : "vorig jaar", "next" : "volgend jaar", "current" : "dit jaar" }, "quarter" : { "future" : { "one" : "over {0} kwartaal", "other" : "over {0} kwartalen" }, "current" : "dit kwartaal", "next" : "volgend kwartaal", "past" : { "one" : "{0} kwartaal geleden", "other" : "{0} kwartalen geleden" }, "previous" : "vorig kwartaal" }, "hour" : { "future" : "over {0} uur", "current" : "binnen een uur", "past" : "{0} uur geleden" }, "month" : { "past" : { "one" : "{0} maand geleden", "other" : "{0} maanden geleden" }, "previous" : "vorige maand", "current" : "deze maand", "future" : { "other" : "over {0} maanden", "one" : "over {0} maand" }, "next" : "volgende maand" }, "week" : { "past" : { "one" : "{0} week geleden", "other" : "{0} weken geleden" }, "previous" : "vorige week", "next" : "volgende week", "current" : "deze week", "future" : { "other" : "over {0} weken", "one" : "over {0} week" } }, "day" : { "previous" : "gisteren", "current" : "vandaag", "future" : { "one" : "over {0} dag", "other" : "over {0} dagen" }, "next" : "morgen", "past" : { "other" : "{0} dagen geleden", "one" : "{0} dag geleden" } }, "minute" : { "future" : { "other" : "over {0} minuten", "one" : "over {0} minuut" }, "past" : { "one" : "{0} minuut geleden", "other" : "{0} minuten geleden" }, "current" : "binnen een minuut" }, "now" : "nu", "second" : { "current" : "nu", "future" : { "other" : "over {0} seconden", "one" : "over {0} seconde" }, "past" : { "one" : "{0} seconde geleden", "other" : "{0} seconden geleden" } } }, "short" : { "hour" : { "past" : "{0} uur geleden", "current" : "binnen een uur", "future" : "over {0} uur" }, "quarter" : { "current" : "dit kwartaal", "future" : { "one" : "over {0} kwartaal", "other" : "over {0} kwartalen" }, "previous" : "vorig kwartaal", "next" : "volgend kwartaal", "past" : { "other" : "{0} kwartalen geleden", "one" : "{0} kwartaal geleden" } }, "week" : { "next" : "volgende week", "current" : "deze week", "previous" : "vorige week", "past" : { "one" : "{0} week geleden", "other" : "{0} weken geleden" }, "future" : { "one" : "over {0} week", "other" : "over {0} weken" } }, "year" : { "future" : "over {0} jaar", "previous" : "vorig jaar", "next" : "volgend jaar", "current" : "dit jaar", "past" : "{0} jaar geleden" }, "minute" : { "future" : "over {0} min.", "past" : "{0} min. geleden", "current" : "binnen een minuut" }, "now" : "nu", "month" : { "current" : "deze maand", "past" : { "one" : "{0} maand geleden", "other" : "{0} maanden geleden" }, "future" : { "one" : "over {0} maand", "other" : "over {0} maanden" }, "next" : "volgende maand", "previous" : "vorige maand" }, "day" : { "future" : { "one" : "over {0} dag", "other" : "over {0} dgn" }, "current" : "vandaag", "next" : "morgen", "previous" : "gisteren", "past" : { "other" : "{0} dgn geleden", "one" : "{0} dag geleden" } }, "second" : { "past" : "{0} sec. geleden", "current" : "nu", "future" : "over {0} sec." } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/nn.json ================================================ { "narrow" : { "hour" : { "future" : "+{0} t", "current" : "denne timen", "past" : "–{0} t" }, "quarter" : { "current" : "dette kvartalet", "previous" : "førre kvartal", "future" : "+{0} kv.", "past" : "–{0} kv.", "next" : "neste kvartal" }, "year" : { "future" : "om {0} år", "previous" : "i fjor", "next" : "neste år", "current" : "i år", "past" : "for {0} år sidan" }, "day" : { "previous" : "i går", "future" : "+{0} d.", "next" : "i morgon", "current" : "i dag", "past" : "–{0} d." }, "month" : { "current" : "denne månaden", "future" : "+{0} md.", "past" : "–{0} md.", "next" : "neste månad", "previous" : "førre månad" }, "now" : "no", "second" : { "past" : { "one" : "–{0} s", "other" : "–{0} s" }, "future" : "+{0} s", "current" : "no" }, "minute" : { "current" : "dette minuttet", "past" : "–{0} min", "future" : "+{0} min" }, "week" : { "past" : "–{0} v.", "previous" : "førre veke", "future" : "+{0} v.", "current" : "denne veka", "next" : "neste veke" } }, "long" : { "week" : { "future" : { "other" : "om {0} veker", "one" : "om {0} veke" }, "past" : { "one" : "for {0} veke sidan", "other" : "for {0} veker sidan" }, "next" : "neste veke", "previous" : "førre veke", "current" : "denne veka" }, "day" : { "future" : "om {0} døgn", "previous" : "i går", "current" : "i dag", "next" : "i morgon", "past" : "for {0} døgn sidan" }, "hour" : { "past" : { "one" : "for {0} time sidan", "other" : "for {0} timar sidan" }, "current" : "denne timen", "future" : { "one" : "om {0} time", "other" : "om {0} timar" } }, "minute" : { "future" : "om {0} minutt", "current" : "dette minuttet", "past" : "for {0} minutt sidan" }, "now" : "no", "month" : { "past" : { "other" : "for {0} månadar sidan", "one" : "for {0} månad sidan" }, "current" : "denne månaden", "next" : "neste månad", "previous" : "førre månad", "future" : { "one" : "om {0} månad", "other" : "om {0} månadar" } }, "quarter" : { "next" : "neste kvartal", "past" : "for {0} kvartal sidan", "future" : "om {0} kvartal", "previous" : "førre kvartal", "current" : "dette kvartalet" }, "second" : { "current" : "no", "past" : "for {0} sekund sidan", "future" : { "one" : "om {0} sekund", "other" : "om {0} sekund" } }, "year" : { "future" : "om {0} år", "previous" : "i fjor", "next" : "neste år", "past" : "for {0} år sidan", "current" : "i år" } }, "short" : { "second" : { "current" : "no", "past" : "for {0} sek sidan", "future" : "om {0} sek" }, "now" : "no", "month" : { "future" : "om {0} md.", "current" : "denne månaden", "past" : "for {0} md. sidan", "previous" : "førre månad", "next" : "neste månad" }, "day" : { "previous" : "i går", "current" : "i dag", "next" : "i morgon", "future" : "om {0} d.", "past" : "for {0} d. sidan" }, "minute" : { "past" : "for {0} min sidan", "future" : "om {0} min", "current" : "dette minuttet" }, "quarter" : { "past" : "for {0} kv. sidan", "previous" : "førre kvartal", "current" : "dette kvartalet", "future" : "om {0} kv.", "next" : "neste kvartal" }, "hour" : { "future" : "om {0} t", "current" : "denne timen", "past" : "for {0} t sidan" }, "week" : { "future" : "om {0} v.", "previous" : "førre veke", "next" : "neste veke", "past" : "for {0} v. sidan", "current" : "denne veka" }, "year" : { "next" : "neste år", "current" : "i år", "past" : "for {0} år sidan", "future" : "om {0} år", "previous" : "i fjor" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/or.json ================================================ { "narrow" : { "day" : { "next" : "ଆସନ୍ତାକାଲି", "future" : "{0} ଦିନରେ", "previous" : "ଗତକାଲି", "current" : "ଆଜି", "past" : "{0} ଦିନ ପୂର୍ବେ" }, "quarter" : { "next" : "ଆଗାମୀ ତ୍ରୟମାସ", "past" : "{0} ତ୍ରୟ. ପୂର୍ବେ", "future" : "{0} ତ୍ରୟ. ରେ", "previous" : "ଗତ ତ୍ରୟମାସ", "current" : "ଗତ ତ୍ରୟମାସ" }, "hour" : { "past" : "{0} ଘ. ପୂର୍ବେ", "future" : "{0} ଘ. ରେ", "current" : "ଏହି ଘଣ୍ଟା" }, "year" : { "next" : "ଆଗାମୀ ବର୍ଷ", "past" : "{0} ବ. ପୂର୍ବେ", "current" : "ଏହି ବର୍ଷ", "previous" : "ଗତ ବର୍ଷ", "future" : "{0} ବ. ରେ" }, "now" : "ବର୍ତ୍ତମାନ", "month" : { "current" : "ଏହି ମାସ", "future" : "{0} ମା. ରେ", "past" : "{0} ମା. ପୂର୍ବେ", "next" : "ଆଗାମୀ ମାସ", "previous" : "ଗତ ମାସ" }, "week" : { "previous" : "ଗତ ସପ୍ତାହ", "current" : "ଏହି ସପ୍ତାହ", "past" : "{0} ସପ୍ତା. ପୂର୍ବେ", "future" : "{0} ସପ୍ତା. ରେ", "next" : "ଆଗାମୀ ସପ୍ତାହ" }, "minute" : { "future" : "{0} ମି. ରେ", "current" : "ଏହି ମିନିଟ୍", "past" : "{0} ମି. ପୂର୍ବେ" }, "second" : { "past" : "{0} ସେ. ପୂର୍ବେ", "future" : "{0} ସେ. ରେ", "current" : "ବର୍ତ୍ତମାନ" } }, "long" : { "now" : "ବର୍ତ୍ତମାନ", "hour" : { "current" : "ଏହି ଘଣ୍ଟା", "past" : "{0} ଘଣ୍ଟା ପୂର୍ବେ", "future" : "{0} ଘଣ୍ଟାରେ" }, "quarter" : { "past" : "{0} ତ୍ରୟମାସ ପୂର୍ବେ", "current" : "ଗତ ତ୍ରୟମାସ", "previous" : "ଗତ ତ୍ରୟମାସ", "future" : "{0} ତ୍ରୟମାସରେ", "next" : "ଆଗାମୀ ତ୍ରୟମାସ" }, "second" : { "current" : "ବର୍ତ୍ତମାନ", "past" : "{0} ସେକେଣ୍ଡ ପୂର୍ବେ", "future" : "{0} ସେକେଣ୍ଡରେ" }, "month" : { "future" : "{0} ମାସରେ", "next" : "ଆଗାମୀ ମାସ", "previous" : "ଗତ ମାସ", "current" : "ଏହି ମାସ", "past" : "{0} ମାସ ପୂର୍ବେ" }, "minute" : { "future" : "{0} ମିନିଟ୍‌‌ରେ", "current" : "ଏହି ମିନିଟ୍", "past" : "{0} ମିନିଟ୍ ପୂର୍ବେ" }, "day" : { "next" : "ଆସନ୍ତାକାଲି", "current" : "ଆଜି", "future" : "{0} ଦିନରେ", "previous" : "ଗତକାଲି", "past" : "{0} ଦିନ ପୂର୍ବେ" }, "week" : { "future" : "{0} ସପ୍ତାହରେ", "current" : "ଏହି ସପ୍ତାହ", "next" : "ଆଗାମୀ ସପ୍ତାହ", "past" : { "other" : "{0} ସପ୍ତାହ ପୂର୍ବେ", "one" : "{0} ସପ୍ତାହରେ" }, "previous" : "ଗତ ସପ୍ତାହ" }, "year" : { "past" : "{0} ବର୍ଷ ପୂର୍ବେ", "future" : "{0} ବର୍ଷରେ", "previous" : "ଗତ ବର୍ଷ", "next" : "ଆଗାମୀ ବର୍ଷ", "current" : "ଏହି ବର୍ଷ" } }, "short" : { "quarter" : { "future" : "{0} ତ୍ରୟ. ରେ", "next" : "ଆଗାମୀ ତ୍ରୟମାସ", "previous" : "ଗତ ତ୍ରୟମାସ", "current" : "ଗତ ତ୍ରୟମାସ", "past" : "{0} ତ୍ରୟ. ପୂର୍ବେ" }, "minute" : { "future" : "{0} ମି. ରେ", "current" : "ଏହି ମିନିଟ୍", "past" : "{0} ମି. ପୂର୍ବେ" }, "year" : { "next" : "ଆଗାମୀ ବର୍ଷ", "future" : "{0} ବ. ରେ", "current" : "ଏହି ବର୍ଷ", "past" : "{0} ବ. ପୂର୍ବେ", "previous" : "ଗତ ବର୍ଷ" }, "second" : { "future" : "{0} ସେ. ରେ", "current" : "ବର୍ତ୍ତମାନ", "past" : "{0} ସେ. ପୂର୍ବେ" }, "hour" : { "past" : "{0} ଘ. ପୂର୍ବେ", "current" : "ଏହି ଘଣ୍ଟା", "future" : "{0} ଘ. ରେ" }, "now" : "ବର୍ତ୍ତମାନ", "month" : { "previous" : "ଗତ ମାସ", "next" : "ଆଗାମୀ ମାସ", "past" : "{0} ମା. ପୂର୍ବେ", "current" : "ଏହି ମାସ", "future" : "{0} ମା. ରେ" }, "week" : { "previous" : "ଗତ ସପ୍ତାହ", "current" : "ଏହି ସପ୍ତାହ", "next" : "ଆଗାମୀ ସପ୍ତାହ", "past" : "{0} ସପ୍ତା. ପୂର୍ବେ", "future" : "{0} ସପ୍ତା. ରେ" }, "day" : { "next" : "ଆସନ୍ତାକାଲି", "past" : "{0} ଦିନ ପୂର୍ବେ", "future" : "{0} ଦିନରେ", "previous" : "ଗତକାଲି", "current" : "ଆଜି" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/pa.json ================================================ { "narrow" : { "quarter" : { "past" : "{0} ਤਿਮਾਹੀਆਂ ਪਹਿਲਾਂ", "current" : "ਇਹ ਤਿਮਾਹੀ", "next" : "ਅਗਲੀ ਤਿਮਾਹੀ", "future" : "{0} ਤਿਮਾਹੀਆਂ ਵਿੱਚ", "previous" : "ਪਿਛਲੀ ਤਿਮਾਹੀ" }, "week" : { "next" : "ਅਗਲਾ ਹਫ਼ਤਾ", "future" : { "other" : "{0} ਹਫ਼ਤਿਆਂ ਵਿੱਚ", "one" : "{0} ਹਫ਼ਤੇ ਵਿੱਚ" }, "previous" : "ਪਿਛਲਾ ਹਫ਼ਤਾ", "current" : "ਇਹ ਹਫ਼ਤਾ", "past" : { "one" : "{0} ਹਫ਼ਤਾ ਪਹਿਲਾਂ", "other" : "{0} ਹਫ਼ਤੇ ਪਹਿਲਾਂ" } }, "hour" : { "current" : "ਇਸ ਘੰਟੇ", "past" : { "one" : "{0} ਘੰਟਾ ਪਹਿਲਾਂ", "other" : "{0} ਘੰਟੇ ਪਹਿਲਾਂ" }, "future" : { "other" : "{0} ਘੰਟਿਆਂ ਵਿੱਚ", "one" : "{0} ਘੰਟੇ ਵਿੱਚ" } }, "now" : "ਹੁਣ", "day" : { "past" : "{0} ਦਿਨ ਪਹਿਲਾਂ", "future" : { "one" : "{0} ਦਿਨ ਵਿੱਚ", "other" : "{0} ਦਿਨਾਂ ਵਿੱਚ" }, "previous" : "ਬੀਤਿਆ ਕੱਲ੍ਹ", "current" : "ਅੱਜ", "next" : "ਭਲਕੇ" }, "minute" : { "current" : "ਇਸ ਮਿੰਟ", "past" : "{0} ਮਿੰਟ ਪਹਿਲਾਂ", "future" : { "one" : "{0} ਮਿੰਟ ਵਿੱਚ", "other" : "{0} ਮਿੰਟਾਂ ਵਿੱਚ" } }, "month" : { "current" : "ਇਹ ਮਹੀਨਾ", "future" : { "one" : "{0} ਮਹੀਨੇ ਵਿੱਚ", "other" : "{0} ਮਹੀਨਿਆਂ ਵਿੱਚ" }, "past" : { "other" : "{0} ਮਹੀਨੇ ਪਹਿਲਾਂ", "one" : "{0} ਮਹੀਨਾ ਪਹਿਲਾਂ" }, "next" : "ਅਗਲਾ ਮਹੀਨਾ", "previous" : "ਪਿਛਲਾ ਮਹੀਨਾ" }, "year" : { "past" : "{0} ਸਾਲ ਪਹਿਲਾਂ", "future" : { "one" : "{0} ਸਾਲ ਵਿੱਚ", "other" : "{0} ਸਾਲਾਂ ਵਿੱਚ" }, "next" : "ਅਗਲਾ ਸਾਲ", "current" : "ਇਹ ਸਾਲ", "previous" : "ਪਿਛਲਾ ਸਾਲ" }, "second" : { "future" : { "other" : "{0} ਸਕਿੰਟਾਂ ਵਿੱਚ", "one" : "{0} ਸਕਿੰਟ ਵਿੱਚ" }, "current" : "ਹੁਣ", "past" : "{0} ਸਕਿੰਟ ਪਹਿਲਾਂ" } }, "long" : { "hour" : { "current" : "ਇਸ ਘੰਟੇ", "past" : { "one" : "{0} ਘੰਟਾ ਪਹਿਲਾਂ", "other" : "{0} ਘੰਟੇ ਪਹਿਲਾਂ" }, "future" : { "other" : "{0} ਘੰਟਿਆਂ ਵਿੱਚ", "one" : "{0} ਘੰਟੇ ਵਿੱਚ" } }, "now" : "ਹੁਣ", "quarter" : { "previous" : "ਪਿਛਲੀ ਤਿਮਾਹੀ", "future" : { "other" : "{0} ਤਿਮਾਹੀਆਂ ਵਿੱਚ", "one" : "{0} ਤਿਮਾਹੀ ਵਿੱਚ" }, "next" : "ਅਗਲੀ ਤਿਮਾਹੀ", "past" : { "one" : "{0} ਤਿਮਾਹੀ ਪਹਿਲਾਂ", "other" : "{0} ਤਿਮਾਹੀਆਂ ਪਹਿਲਾਂ" }, "current" : "ਇਸ ਤਿਮਾਹੀ" }, "day" : { "future" : { "other" : "{0} ਦਿਨਾਂ ਵਿੱਚ", "one" : "{0} ਦਿਨ ਵਿੱਚ" }, "current" : "ਅੱਜ", "past" : "{0} ਦਿਨ ਪਹਿਲਾਂ", "previous" : "ਬੀਤਿਆ ਕੱਲ੍ਹ", "next" : "ਭਲਕੇ" }, "week" : { "previous" : "ਪਿਛਲਾ ਹਫ਼ਤਾ", "past" : { "one" : "{0} ਹਫ਼ਤਾ ਪਹਿਲਾਂ", "other" : "{0} ਹਫ਼ਤੇ ਪਹਿਲਾਂ" }, "current" : "ਇਹ ਹਫ਼ਤਾ", "next" : "ਅਗਲਾ ਹਫ਼ਤਾ", "future" : { "one" : "{0} ਹਫ਼ਤੇ ਵਿੱਚ", "other" : "{0} ਹਫ਼ਤਿਆਂ ਵਿੱਚ" } }, "minute" : { "past" : "{0} ਮਿੰਟ ਪਹਿਲਾਂ", "current" : "ਇਸ ਮਿੰਟ", "future" : { "other" : "{0} ਮਿੰਟਾਂ ਵਿੱਚ", "one" : "{0} ਮਿੰਟ ਵਿੱਚ" } }, "second" : { "past" : "{0} ਸਕਿੰਟ ਪਹਿਲਾਂ", "future" : { "other" : "{0} ਸਕਿੰਟਾਂ ਵਿੱਚ", "one" : "{0} ਸਕਿੰਟ ਵਿੱਚ" }, "current" : "ਹੁਣ" }, "month" : { "past" : { "other" : "{0} ਮਹੀਨੇ ਪਹਿਲਾਂ", "one" : "{0} ਮਹੀਨਾ ਪਹਿਲਾਂ" }, "next" : "ਅਗਲਾ ਮਹੀਨਾ", "future" : { "one" : "{0} ਮਹੀਨੇ ਵਿੱਚ", "other" : "{0} ਮਹੀਨਿਆਂ ਵਿੱਚ" }, "previous" : "ਪਿਛਲਾ ਮਹੀਨਾ", "current" : "ਇਹ ਮਹੀਨਾ" }, "year" : { "current" : "ਇਹ ਸਾਲ", "future" : { "other" : "{0} ਸਾਲਾਂ ਵਿੱਚ", "one" : "{0} ਸਾਲ ਵਿੱਚ" }, "previous" : "ਪਿਛਲਾ ਸਾਲ", "next" : "ਅਗਲਾ ਸਾਲ", "past" : "{0} ਸਾਲ ਪਹਿਲਾਂ" } }, "short" : { "month" : { "next" : "ਅਗਲਾ ਮਹੀਨਾ", "current" : "ਇਹ ਮਹੀਨਾ", "previous" : "ਪਿਛਲਾ ਮਹੀਨਾ", "past" : { "one" : "{0} ਮਹੀਨਾ ਪਹਿਲਾਂ", "other" : "{0} ਮਹੀਨੇ ਪਹਿਲਾਂ" }, "future" : { "other" : "{0} ਮਹੀਨਿਆਂ ਵਿੱਚ", "one" : "{0} ਮਹੀਨੇ ਵਿੱਚ" } }, "now" : "ਹੁਣ", "week" : { "previous" : "ਪਿਛਲਾ ਹਫ਼ਤਾ", "current" : "ਇਹ ਹਫ਼ਤਾ", "past" : { "one" : "{0} ਹਫ਼ਤਾ ਪਹਿਲਾਂ", "other" : "{0} ਹਫ਼ਤੇ ਪਹਿਲਾਂ" }, "future" : { "one" : "{0} ਹਫ਼ਤੇ ਵਿੱਚ", "other" : "{0} ਹਫ਼ਤਿਆਂ ਵਿੱਚ" }, "next" : "ਅਗਲਾ ਹਫ਼ਤਾ" }, "day" : { "future" : { "other" : "{0} ਦਿਨਾਂ ਵਿੱਚ", "one" : "{0} ਦਿਨ ਵਿੱਚ" }, "previous" : "ਬੀਤਿਆ ਕੱਲ੍ਹ", "current" : "ਅੱਜ", "next" : "ਭਲਕੇ", "past" : "{0} ਦਿਨ ਪਹਿਲਾਂ" }, "year" : { "previous" : "ਪਿਛਲਾ ਸਾਲ", "current" : "ਇਹ ਸਾਲ", "next" : "ਅਗਲਾ ਸਾਲ", "past" : "{0} ਸਾਲ ਪਹਿਲਾਂ", "future" : { "other" : "{0} ਸਾਲਾਂ ਵਿੱਚ", "one" : "{0} ਸਾਲ ਵਿੱਚ" } }, "hour" : { "past" : { "other" : "{0} ਘੰਟੇ ਪਹਿਲਾਂ", "one" : "{0} ਘੰਟਾ ਪਹਿਲਾਂ" }, "future" : { "one" : "{0} ਘੰਟੇ ਵਿੱਚ", "other" : "{0} ਘੰਟਿਆਂ ਵਿੱਚ" }, "current" : "ਇਸ ਘੰਟੇ" }, "minute" : { "current" : "ਇਸ ਮਿੰਟ", "past" : "{0} ਮਿੰਟ ਪਹਿਲਾਂ", "future" : { "one" : "{0} ਮਿੰਟ ਵਿੱਚ", "other" : "{0} ਮਿੰਟਾਂ ਵਿੱਚ" } }, "second" : { "future" : { "one" : "{0} ਸਕਿੰਟ ਵਿੱਚ", "other" : "{0} ਸਕਿੰਟਾਂ ਵਿੱਚ" }, "past" : "{0} ਸਕਿੰਟ ਪਹਿਲਾਂ", "current" : "ਹੁਣ" }, "quarter" : { "next" : "ਅਗਲੀ ਤਿਮਾਹੀ", "past" : { "one" : "{0} ਤਿਮਾਹੀ ਪਹਿਲਾਂ", "other" : "{0} ਤਿਮਾਹੀਆਂ ਪਹਿਲਾਂ" }, "current" : "ਇਹ ਤਿਮਾਹੀ", "previous" : "ਪਿਛਲੀ ਤਿਮਾਹੀ", "future" : { "one" : "{0} ਤਿਮਾਹੀ ਵਿੱਚ", "other" : "{0} ਤਿਮਾਹੀਆਂ ਵਿੱਚ" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/pl.json ================================================ { "narrow" : { "day" : { "previous" : "wczoraj", "current" : "dzisiaj", "next" : "jutro", "past" : { "many" : "{0} dni temu", "few" : "{0} dni temu", "one" : "{0} dzień temu", "other" : "{0} dnia temu" }, "future" : { "few" : "za {0} dni", "many" : "za {0} dni", "other" : "za {0} dnia", "one" : "za {0} dzień" } }, "hour" : { "future" : "za {0} g.", "past" : "{0} g. temu", "current" : "ta godzina" }, "now" : "teraz", "week" : { "current" : "w tym tygodniu", "previous" : "w zeszłym tygodniu", "past" : { "one" : "{0} tydz. temu", "other" : "{0} tyg. temu" }, "future" : { "other" : "za {0} tyg.", "one" : "za {0} tydz." }, "next" : "w przyszłym tygodniu" }, "month" : { "next" : "w przyszłym miesiącu", "previous" : "w zeszłym miesiącu", "current" : "w tym miesiącu", "future" : "za {0} mies.", "past" : "{0} mies. temu" }, "year" : { "previous" : "w zeszłym roku", "future" : { "few" : "za {0} lata", "one" : "za {0} rok", "many" : "za {0} lat", "other" : "za {0} roku" }, "next" : "w przyszłym roku", "current" : "w tym roku", "past" : { "one" : "{0} rok temu", "other" : "{0} roku temu", "few" : "{0} lata temu", "many" : "{0} lat temu" } }, "quarter" : { "current" : "w tym kwartale", "future" : "za {0} kw.", "previous" : "w zeszłym kwartale", "next" : "w przyszłym kwartale", "past" : "{0} kw. temu" }, "minute" : { "past" : "{0} min temu", "current" : "ta minuta", "future" : "za {0} min" }, "second" : { "future" : "za {0} s", "past" : "{0} s temu", "current" : "teraz" } }, "long" : { "week" : { "future" : { "other" : "za {0} tygodnia", "one" : "za {0} tydzień", "few" : "za {0} tygodnie", "many" : "za {0} tygodni" }, "current" : "w tym tygodniu", "past" : { "few" : "{0} tygodnie temu", "one" : "{0} tydzień temu", "other" : "{0} tygodnia temu", "many" : "{0} tygodni temu" }, "previous" : "w zeszłym tygodniu", "next" : "w przyszłym tygodniu" }, "quarter" : { "current" : "w tym kwartale", "next" : "w przyszłym kwartale", "future" : { "other" : "za {0} kwartału", "many" : "za {0} kwartałów", "few" : "za {0} kwartały", "one" : "za {0} kwartał" }, "previous" : "w zeszłym kwartale", "past" : { "one" : "{0} kwartał temu", "many" : "{0} kwartałów temu", "few" : "{0} kwartały temu", "other" : "{0} kwartału temu" } }, "second" : { "future" : { "one" : "za {0} sekundę", "many" : "za {0} sekund", "other" : "za {0} sekundy" }, "current" : "teraz", "past" : { "one" : "{0} sekundę temu", "many" : "{0} sekund temu", "other" : "{0} sekundy temu" } }, "year" : { "past" : { "many" : "{0} lat temu", "few" : "{0} lata temu", "one" : "{0} rok temu", "other" : "{0} roku temu" }, "previous" : "w zeszłym roku", "future" : { "one" : "za {0} rok", "other" : "za {0} roku", "few" : "za {0} lata", "many" : "za {0} lat" }, "current" : "w tym roku", "next" : "w przyszłym roku" }, "now" : "teraz", "hour" : { "past" : { "other" : "{0} godziny temu", "many" : "{0} godzin temu", "one" : "{0} godzinę temu" }, "future" : { "many" : "za {0} godzin", "other" : "za {0} godziny", "one" : "za {0} godzinę" }, "current" : "ta godzina" }, "day" : { "previous" : "wczoraj", "next" : "jutro", "past" : { "one" : "{0} dzień temu", "many" : "{0} dni temu", "other" : "{0} dnia temu", "few" : "{0} dni temu" }, "current" : "dzisiaj", "future" : { "many" : "za {0} dni", "one" : "za {0} dzień", "few" : "za {0} dni", "other" : "za {0} dnia" } }, "minute" : { "future" : { "one" : "za {0} minutę", "many" : "za {0} minut", "other" : "za {0} minuty" }, "past" : { "one" : "{0} minutę temu", "other" : "{0} minuty temu", "many" : "{0} minut temu" }, "current" : "ta minuta" }, "month" : { "past" : { "few" : "{0} miesiące temu", "one" : "{0} miesiąc temu", "many" : "{0} miesięcy temu", "other" : "{0} miesiąca temu" }, "previous" : "w zeszłym miesiącu", "next" : "w przyszłym miesiącu", "future" : { "few" : "za {0} miesiące", "many" : "za {0} miesięcy", "other" : "za {0} miesiąca", "one" : "za {0} miesiąc" }, "current" : "w tym miesiącu" } }, "short" : { "minute" : { "current" : "ta minuta", "past" : "{0} min temu", "future" : "za {0} min" }, "year" : { "current" : "w tym roku", "past" : { "one" : "{0} rok temu", "other" : "{0} roku temu", "many" : "{0} lat temu", "few" : "{0} lata temu" }, "future" : { "few" : "za {0} lata", "one" : "za {0} rok", "many" : "za {0} lat", "other" : "za {0} roku" }, "next" : "w przyszłym roku", "previous" : "w zeszłym roku" }, "day" : { "past" : { "other" : "{0} dnia temu", "one" : "{0} dzień temu", "many" : "{0} dni temu", "few" : "{0} dni temu" }, "current" : "dzisiaj", "future" : { "few" : "za {0} dni", "other" : "za {0} dnia", "one" : "za {0} dzień", "many" : "za {0} dni" }, "next" : "jutro", "previous" : "wczoraj" }, "hour" : { "past" : "{0} godz. temu", "current" : "ta godzina", "future" : "za {0} godz." }, "quarter" : { "future" : "za {0} kw.", "previous" : "w zeszłym kwartale", "current" : "w tym kwartale", "next" : "w przyszłym kwartale", "past" : "{0} kw. temu" }, "second" : { "future" : "za {0} sek.", "current" : "teraz", "past" : "{0} sek. temu" }, "now" : "teraz", "month" : { "next" : "w przyszłym miesiącu", "current" : "w tym miesiącu", "previous" : "w zeszłym miesiącu", "past" : "{0} mies. temu", "future" : "za {0} mies." }, "week" : { "next" : "w przyszłym tygodniu", "past" : { "other" : "{0} tyg. temu", "one" : "{0} tydz. temu" }, "previous" : "w zeszłym tygodniu", "current" : "w tym tygodniu", "future" : { "one" : "za {0} tydz.", "other" : "za {0} tyg." } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ps.json ================================================ { "short" : { "month" : { "future" : "+{0} m", "previous" : "last month", "current" : "this month", "next" : "next month", "past" : "-{0} m" }, "week" : { "previous" : "last week", "current" : "this week", "future" : "+{0} w", "past" : "-{0} w", "next" : "next week" }, "hour" : { "past" : "-{0} h", "current" : "this hour", "future" : "+{0} h" }, "quarter" : { "future" : "+{0} Q", "previous" : "last quarter", "current" : "this quarter", "next" : "next quarter", "past" : "-{0} Q" }, "minute" : { "future" : "+{0} min", "current" : "this minute", "past" : "-{0} min" }, "now" : "now", "day" : { "past" : "-{0} d", "previous" : "yesterday", "current" : "today", "next" : "tomorrow", "future" : "+{0} d" }, "year" : { "previous" : "پروسږکال", "next" : "بل کال", "current" : "سږکال", "future" : { "other" : "په {0} کالونو کې", "one" : "په {0} کال کې" }, "past" : { "one" : "{0} کال مخکې", "other" : "{0} کاله مخکې" } }, "second" : { "past" : "-{0} s", "future" : "+{0} s", "current" : "now" } }, "narrow" : { "day" : { "future" : "+{0} d", "previous" : "yesterday", "current" : "today", "next" : "tomorrow", "past" : "-{0} d" }, "second" : { "current" : "now", "past" : "-{0} s", "future" : "+{0} s" }, "hour" : { "future" : "+{0} h", "current" : "this hour", "past" : "-{0} h" }, "quarter" : { "previous" : "last quarter", "next" : "next quarter", "past" : "-{0} Q", "future" : "+{0} Q", "current" : "this quarter" }, "minute" : { "past" : "-{0} min", "future" : "+{0} min", "current" : "this minute" }, "year" : { "current" : "سږکال", "past" : { "one" : "{0} کال مخکې", "other" : "{0} کاله مخکې" }, "future" : { "one" : "په {0} کال کې", "other" : "په {0} کالونو کې" }, "next" : "بل کال", "previous" : "پروسږکال" }, "now" : "now", "week" : { "current" : "this week", "past" : "-{0} w", "future" : "+{0} w", "next" : "next week", "previous" : "last week" }, "month" : { "previous" : "last month", "current" : "this month", "next" : "next month", "future" : "+{0} m", "past" : "-{0} m" } }, "long" : { "quarter" : { "next" : "next quarter", "current" : "this quarter", "future" : "+{0} Q", "past" : "-{0} Q", "previous" : "last quarter" }, "week" : { "next" : "next week", "current" : "this week", "past" : "-{0} w", "previous" : "last week", "future" : "+{0} w" }, "year" : { "next" : "بل کال", "past" : { "other" : "{0} کاله مخکې", "one" : "{0} کال مخکې" }, "future" : { "other" : "په {0} کالونو کې", "one" : "په {0} کال کې" }, "current" : "سږکال", "previous" : "پروسږکال" }, "hour" : { "current" : "this hour", "future" : "+{0} h", "past" : "-{0} h" }, "minute" : { "past" : "-{0} min", "future" : "+{0} min", "current" : "this minute" }, "second" : { "current" : "now", "past" : "-{0} s", "future" : "+{0} s" }, "day" : { "previous" : "yesterday", "current" : "today", "next" : "tomorrow", "past" : "-{0} d", "future" : "+{0} d" }, "now" : "now", "month" : { "current" : "this month", "future" : "+{0} m", "next" : "next month", "previous" : "last month", "past" : "-{0} m" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/pt.json ================================================ { "narrow" : { "minute" : { "future" : "+{0} min", "past" : "-{0} min", "current" : "este minuto" }, "second" : { "future" : "+{0} s", "current" : "agora", "past" : "-{0} s" }, "now" : "agora", "day" : { "next" : "amanhã", "previous" : "ontem", "past" : "há {0} dias", "current" : "hoje", "future" : { "other" : "+{0} dias", "one" : "+{0} dia" } }, "month" : { "previous" : "mês passado", "next" : "próximo mês", "current" : "este mês", "future" : { "one" : "+{0} mês", "other" : "+{0} meses" }, "past" : { "one" : "-{0} mês", "other" : "-{0} meses" } }, "quarter" : { "next" : "próximo trim.", "past" : "-{0} trim.", "future" : "+{0} trim.", "previous" : "trim. passado", "current" : "este trim." }, "week" : { "previous" : "semana passada", "next" : "próxima semana", "future" : "+{0} sem.", "current" : "esta semana", "past" : "-{0} sem." }, "hour" : { "current" : "esta hora", "past" : "-{0} h", "future" : "+{0} h" }, "year" : { "past" : { "one" : "-{0} ano", "other" : "-{0} anos" }, "next" : "próximo ano", "previous" : "ano passado", "current" : "este ano", "future" : { "other" : "+{0} anos", "one" : "+{0} ano" } } }, "long" : { "year" : { "previous" : "ano passado", "current" : "este ano", "next" : "próximo ano", "past" : { "one" : "há {0} ano", "other" : "há {0} anos" }, "future" : { "one" : "dentro de {0} ano", "other" : "dentro de {0} anos" } }, "month" : { "previous" : "mês passado", "current" : "este mês", "next" : "próximo mês", "past" : { "other" : "há {0} meses", "one" : "há {0} mês" }, "future" : { "other" : "dentro de {0} meses", "one" : "dentro de {0} mês" } }, "day" : { "previous" : "ontem", "current" : "hoje", "future" : { "one" : "dentro de {0} dia", "other" : "dentro de {0} dias" }, "next" : "amanhã", "past" : { "one" : "há {0} dia", "other" : "há {0} dias" } }, "hour" : { "past" : { "other" : "há {0} horas", "one" : "há {0} hora" }, "current" : "esta hora", "future" : { "other" : "dentro de {0} horas", "one" : "dentro de {0} hora" } }, "now" : "agora", "minute" : { "current" : "este minuto", "past" : { "one" : "há {0} minuto", "other" : "há {0} minutos" }, "future" : { "one" : "dentro de {0} minuto", "other" : "dentro de {0} minutos" } }, "second" : { "past" : { "one" : "há {0} segundo", "other" : "há {0} segundos" }, "current" : "agora", "future" : { "one" : "dentro de {0} segundo", "other" : "dentro de {0} segundos" } }, "quarter" : { "next" : "próximo trimestre", "future" : { "other" : "dentro de {0} trimestres", "one" : "dentro de {0} trimestre" }, "past" : { "one" : "há {0} trimestre", "other" : "há {0} trimestres" }, "current" : "este trimestre", "previous" : "trimestre passado" }, "week" : { "past" : { "other" : "há {0} semanas", "one" : "há {0} semana" }, "current" : "esta semana", "previous" : "semana passada", "next" : "próxima semana", "future" : { "one" : "dentro de {0} semana", "other" : "dentro de {0} semanas" } } }, "short" : { "year" : { "past" : { "one" : "há {0} ano", "other" : "há {0} anos" }, "future" : { "one" : "dentro de {0} ano", "other" : "dentro de {0} anos" }, "previous" : "ano passado", "current" : "este ano", "next" : "próximo ano" }, "day" : { "next" : "amanhã", "current" : "hoje", "previous" : "ontem", "future" : { "other" : "dentro de {0} dias", "one" : "dentro de {0} dia" }, "past" : { "other" : "há {0} dias", "one" : "há {0} dia" } }, "minute" : { "current" : "este minuto", "past" : "há {0} min", "future" : "dentro de {0} min" }, "hour" : { "past" : "há {0} h", "future" : "dentro de {0} h", "current" : "esta hora" }, "quarter" : { "previous" : "trim. passado", "future" : "dentro de {0} trim.", "next" : "próximo trim.", "past" : "há {0} trim.", "current" : "este trim." }, "second" : { "future" : "dentro de {0} s", "current" : "agora", "past" : "há {0} s" }, "now" : "agora", "week" : { "past" : "há {0} sem.", "future" : "dentro de {0} sem.", "next" : "próxima semana", "current" : "esta semana", "previous" : "semana passada" }, "month" : { "past" : { "one" : "há {0} mês", "other" : "há {0} meses" }, "future" : { "other" : "dentro de {0} meses", "one" : "dentro de {0} mês" }, "next" : "próximo mês", "previous" : "mês passado", "current" : "este mês" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ro.json ================================================ { "short" : { "year" : { "previous" : "anul trecut", "next" : "anul viitor", "current" : "anul acesta", "future" : { "other" : "peste {0} de ani", "one" : "peste {0} an", "few" : "peste {0} ani" }, "past" : { "few" : "acum {0} ani", "one" : "acum {0} an", "other" : "acum {0} de ani" } }, "quarter" : { "future" : "peste {0} trim.", "previous" : "trim. trecut", "current" : "trim. acesta", "next" : "trim. viitor", "past" : "acum {0} trim." }, "hour" : { "past" : "acum {0} h", "future" : "peste {0} h", "current" : "ora aceasta" }, "day" : { "past" : { "one" : "acum {0} zi", "few" : "acum {0} zile", "other" : "acum {0} de zile" }, "future" : { "other" : "peste {0} de zile", "few" : "peste {0} zile", "one" : "peste {0} zi" }, "previous" : "ieri", "next" : "mâine", "current" : "azi" }, "minute" : { "current" : "minutul acesta", "past" : "acum {0} min.", "future" : "peste {0} min." }, "month" : { "future" : { "other" : "peste {0} luni", "one" : "peste {0} lună" }, "previous" : "luna trecută", "current" : "luna aceasta", "next" : "luna viitoare", "past" : { "one" : "acum {0} lună", "other" : "acum {0} luni" } }, "week" : { "past" : "acum {0} săpt.", "future" : "peste {0} săpt.", "next" : "săptămâna viitoare", "current" : "săptămâna aceasta", "previous" : "săptămâna trecută" }, "second" : { "future" : "peste {0} sec.", "current" : "acum", "past" : "acum {0} sec." }, "now" : "acum" }, "narrow" : { "month" : { "future" : { "one" : "+{0} lună", "other" : "+{0} luni" }, "next" : "luna viitoare", "current" : "luna aceasta", "previous" : "luna trecută", "past" : { "one" : "-{0} lună", "other" : "-{0} luni" } }, "minute" : { "future" : "+{0} m", "current" : "minutul acesta", "past" : "-{0} m" }, "second" : { "current" : "acum", "future" : "+{0} s", "past" : "-{0} s" }, "now" : "acum", "day" : { "future" : { "other" : "+{0} zile", "one" : "+{0} zi" }, "previous" : "ieri", "current" : "azi", "next" : "mâine", "past" : { "one" : "-{0} zi", "other" : "-{0} zile" } }, "year" : { "previous" : "anul trecut", "past" : { "one" : "-{0} an", "other" : "-{0} ani" }, "future" : { "other" : "+{0} ani", "one" : "+{0} an" }, "current" : "anul acesta", "next" : "anul viitor" }, "quarter" : { "past" : "-{0} trim.", "previous" : "trim. trecut", "current" : "trim. acesta", "next" : "trim. viitor", "future" : "+{0} trim." }, "week" : { "previous" : "săptămâna trecută", "past" : "-{0} săpt.", "future" : "+{0} săpt.", "current" : "săptămâna aceasta", "next" : "săptămâna viitoare" }, "hour" : { "future" : "+{0} h", "past" : "-{0} h", "current" : "ora aceasta" } }, "long" : { "minute" : { "past" : { "one" : "acum {0} minut", "few" : "acum {0} minute", "other" : "acum {0} de minute" }, "current" : "minutul acesta", "future" : { "one" : "peste {0} minut", "other" : "peste {0} de minute", "few" : "peste {0} minute" } }, "second" : { "current" : "acum", "future" : { "few" : "peste {0} secunde", "one" : "peste {0} secundă", "other" : "peste {0} de secunde" }, "past" : { "few" : "acum {0} secunde", "other" : "acum {0} de secunde", "one" : "acum {0} secundă" } }, "month" : { "next" : "luna viitoare", "current" : "luna aceasta", "previous" : "luna trecută", "past" : { "other" : "acum {0} de luni", "one" : "acum {0} lună", "few" : "acum {0} luni" }, "future" : { "one" : "peste {0} lună", "other" : "peste {0} de luni", "few" : "peste {0} luni" } }, "day" : { "current" : "azi", "future" : { "other" : "peste {0} de zile", "few" : "peste {0} zile", "one" : "peste {0} zi" }, "next" : "mâine", "previous" : "ieri", "past" : { "one" : "acum {0} zi", "few" : "acum {0} zile", "other" : "acum {0} de zile" } }, "now" : "acum", "hour" : { "current" : "ora aceasta", "past" : { "few" : "acum {0} ore", "one" : "acum {0} oră", "other" : "acum {0} de ore" }, "future" : { "few" : "peste {0} ore", "other" : "peste {0} de ore", "one" : "peste {0} oră" } }, "quarter" : { "previous" : "trimestrul trecut", "current" : "trimestrul acesta", "past" : { "one" : "acum {0} trimestru", "few" : "acum {0} trimestre", "other" : "acum {0} de trimestre" }, "future" : { "other" : "peste {0} de trimestre", "one" : "peste {0} trimestru", "few" : "peste {0} trimestre" }, "next" : "trimestrul viitor" }, "year" : { "next" : "anul viitor", "past" : { "few" : "acum {0} ani", "other" : "acum {0} de ani", "one" : "acum {0} an" }, "previous" : "anul trecut", "future" : { "few" : "peste {0} ani", "other" : "peste {0} de ani", "one" : "peste {0} an" }, "current" : "anul acesta" }, "week" : { "next" : "săptămâna viitoare", "future" : { "few" : "peste {0} săptămâni", "other" : "peste {0} de săptămâni", "one" : "peste {0} săptămână" }, "previous" : "săptămâna trecută", "current" : "săptămâna aceasta", "past" : { "one" : "acum {0} săptămână", "few" : "acum {0} săptămâni", "other" : "acum {0} de săptămâni" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ru.json ================================================ { "narrow" : { "second" : { "past" : "-{0} с", "current" : "сейчас", "future" : "+{0} с" }, "year" : { "previous" : "в пр. г.", "next" : "в сл. г.", "future" : { "many" : "+{0} л.", "other" : "+{0} г." }, "current" : "в эт. г.", "past" : { "other" : "-{0} г.", "many" : "-{0} л." } }, "month" : { "past" : "-{0} мес.", "next" : "в след. мес.", "future" : "+{0} мес.", "previous" : "в пр. мес.", "current" : "в эт. мес." }, "minute" : { "future" : "+{0} мин.", "past" : "-{0} мин.", "current" : "в эту минуту" }, "quarter" : { "previous" : "посл. кв.", "current" : "тек. кв.", "next" : "след. кв.", "past" : "-{0} кв.", "future" : "+{0} кв." }, "hour" : { "past" : "-{0} ч.", "current" : "в этот час", "future" : "+{0} ч." }, "now" : "сейчас", "week" : { "next" : "на след. неделе", "previous" : "на пр. нед.", "past" : "-{0} нед.", "future" : "+{0} нед.", "current" : "на эт. нед." }, "day" : { "previous" : "вчера", "future" : "+{0} дн.", "past" : "-{0} дн.", "current" : "сегодня", "next" : "завтра" } }, "short" : { "minute" : { "future" : "через {0} мин.", "current" : "в эту минуту", "past" : "{0} мин. назад" }, "week" : { "current" : "на этой нед.", "past" : "{0} нед. назад", "future" : "через {0} нед.", "next" : "на следующей нед.", "previous" : "на прошлой нед." }, "year" : { "current" : "в этом г.", "future" : { "many" : "через {0} л.", "other" : "через {0} г." }, "past" : { "many" : "{0} л. назад", "other" : "{0} г. назад" }, "next" : "в след. г.", "previous" : "в прошлом г." }, "day" : { "next" : "завтра", "current" : "сегодня", "previous" : "вчера", "past" : "{0} дн. назад", "future" : "через {0} дн." }, "hour" : { "past" : { "one" : "{0} ч. назад", "other" : "{0} ч. назад" }, "future" : { "one" : "через {0} ч.", "other" : "через {0} ч." }, "current" : "в этот час" }, "quarter" : { "current" : "текущий кв.", "future" : "через {0} кв.", "previous" : "последний кв.", "next" : "следующий кв.", "past" : "{0} кв. назад" }, "second" : { "future" : "через {0} сек.", "current" : "сейчас", "past" : "{0} сек. назад" }, "month" : { "current" : "в этом мес.", "past" : "{0} мес. назад", "future" : "через {0} мес.", "next" : "в следующем мес.", "previous" : "в прошлом мес." }, "now" : "сейчас" }, "long" : { "quarter" : { "next" : "в следующем квартале", "past" : { "one" : "{0} квартал назад", "many" : "{0} кварталов назад", "other" : "{0} квартала назад" }, "previous" : "в прошлом квартале", "current" : "в текущем квартале", "future" : { "one" : "через {0} квартал", "many" : "через {0} кварталов", "other" : "через {0} квартала" } }, "month" : { "next" : "в следующем месяце", "current" : "в этом месяце", "past" : { "one" : "{0} месяц назад", "many" : "{0} месяцев назад", "other" : "{0} месяца назад" }, "previous" : "в прошлом месяце", "future" : { "other" : "через {0} месяца", "many" : "через {0} месяцев", "one" : "через {0} месяц" } }, "now" : "сейчас", "week" : { "future" : { "other" : "через {0} недели", "one" : "через {0} неделю", "many" : "через {0} недель" }, "previous" : "на прошлой неделе", "current" : "на этой неделе", "next" : "на следующей неделе", "past" : { "many" : "{0} недель назад", "one" : "{0} неделю назад", "other" : "{0} недели назад" } }, "hour" : { "current" : "в этот час", "future" : { "one" : "через {0} час", "many" : "через {0} часов", "other" : "через {0} часа" }, "past" : { "other" : "{0} часа назад", "many" : "{0} часов назад", "one" : "{0} час назад" } }, "minute" : { "future" : { "many" : "через {0} минут", "one" : "через {0} минуту", "other" : "через {0} минуты" }, "current" : "в эту минуту", "past" : { "one" : "{0} минуту назад", "many" : "{0} минут назад", "other" : "{0} минуты назад" } }, "second" : { "past" : { "other" : "{0} секунды назад", "many" : "{0} секунд назад", "one" : "{0} секунду назад" }, "current" : "сейчас", "future" : { "other" : "через {0} секунды", "one" : "через {0} секунду", "many" : "через {0} секунд" } }, "day" : { "previous" : "вчера", "past" : { "many" : "{0} дней назад", "other" : "{0} дня назад", "one" : "{0} день назад" }, "future" : { "other" : "через {0} дня", "one" : "через {0} день", "many" : "через {0} дней" }, "next" : "завтра", "current" : "сегодня" }, "year" : { "next" : "в следующем году", "current" : "в этом году", "future" : { "one" : "через {0} год", "other" : "через {0} года", "many" : "через {0} лет" }, "previous" : "в прошлом году", "past" : { "many" : "{0} лет назад", "other" : "{0} года назад", "one" : "{0} год назад" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/sah.json ================================================ { "narrow" : { "day" : { "next" : "Сарсын", "future" : "{0} күнүнэн", "previous" : "Бэҕэһээ", "current" : "Бүгүн", "past" : "{0} күн ынараа өттүгэр" }, "quarter" : { "next" : "кэлэр кыбаартал", "past" : "{0} кыб. анараа өттүгэр", "future" : "{0} кыбаарталынан", "previous" : "ааспыт кыбаартал", "current" : "бу кыбаартал" }, "hour" : { "past" : "{0} чаас ынараа өттүгэр", "future" : "{0} чааһынан", "current" : "this hour" }, "year" : { "next" : "эһиил", "past" : "{0} сыл ынараа өттүгэр", "current" : "быйыл", "previous" : "Былырыын", "future" : "{0} сылынан" }, "now" : "билигин", "month" : { "current" : "бу ый", "future" : "{0} ыйынан", "past" : "{0} ый ынараа өттүгэр", "next" : "аныгыскы ый", "previous" : "ааспыт ый" }, "week" : { "previous" : "ааспыт нэдиэлэ", "current" : "бу нэдиэлэ", "past" : "{0} нэдиэлэ анараа өттүгэр", "future" : "{0} нэдиэлэннэн", "next" : "кэлэр нэдиэлэ" }, "minute" : { "past" : "{0} мүнүүтэ ынараа өттүгэр", "current" : "this minute", "future" : "{0} мүнүүтэннэн" }, "second" : { "future" : "{0} сөкүүндэннэн", "current" : "билигин", "past" : "{0} сөк. анараа өттүгэр" } }, "long" : { "day" : { "previous" : "Бэҕэһээ", "current" : "Бүгүн", "next" : "Сарсын", "past" : "{0} күн ынараа өттүгэр", "future" : "{0} күнүнэн" }, "week" : { "current" : "бу нэдиэлэ", "future" : "{0} нэдиэлэннэн", "past" : "{0} нэдиэлэ анараа өттүгэр", "previous" : "ааспыт нэдиэлэ", "next" : "кэлэр нэдиэлэ" }, "minute" : { "future" : "{0} мүнүүтэннэн", "current" : "this minute", "past" : "{0} мүнүүтэ ынараа өттүгэр" }, "month" : { "future" : "{0} ыйынан", "next" : "аныгыскы ый", "previous" : "ааспыт ый", "current" : "бу ый", "past" : "{0} ый ынараа өттүгэр" }, "hour" : { "current" : "this hour", "past" : "{0} чаас ынараа өттүгэр", "future" : "{0} чааһынан" }, "year" : { "past" : "{0} сыл ынараа өттүгэр", "future" : "{0} сылынан", "previous" : "Былырыын", "next" : "эһиил", "current" : "быйыл" }, "second" : { "past" : "{0} сөкүүндэ ынараа өттүгэр", "current" : "билигин", "future" : "{0} сөкүүндэннэн" }, "now" : "билигин", "quarter" : { "past" : "{0} кыбаартал анараа өттүгэр", "current" : "бу кыбаартал", "previous" : "ааспыт кыбаартал", "future" : "{0} кыбаарталынан", "next" : "кэлэр кыбаартал" } }, "short" : { "quarter" : { "future" : "{0} кыбаарталынан", "next" : "кэлэр кыбаартал", "previous" : "ааспыт кыбаартал", "current" : "бу кыбаартал", "past" : "{0} кыб. анараа өттүгэр" }, "minute" : { "past" : "{0} мүнүүтэ ынараа өттүгэр", "future" : "{0} мүнүүтэннэн", "current" : "this minute" }, "year" : { "next" : "эһиил", "future" : "{0} сылынан", "current" : "быйыл", "past" : "{0} сыл ынараа өттүгэр", "previous" : "Былырыын" }, "second" : { "current" : "билигин", "past" : "{0} сөк. анараа өттүгэр", "future" : "{0} сөкүүндэннэн" }, "hour" : { "current" : "this hour", "past" : "{0} чаас ынараа өттүгэр", "future" : "{0} чааһынан" }, "now" : "билигин", "month" : { "previous" : "ааспыт ый", "next" : "аныгыскы ый", "past" : "{0} ый ынараа өттүгэр", "current" : "бу ый", "future" : "{0} ыйынан" }, "week" : { "previous" : "ааспыт нэдиэлэ", "current" : "бу нэдиэлэ", "next" : "кэлэр нэдиэлэ", "past" : "{0} нэдиэлэ анараа өттүгэр", "future" : "{0} нэдиэлэннэн" }, "day" : { "next" : "Сарсын", "past" : "{0} күн ынараа өттүгэр", "future" : "{0} күнүнэн", "previous" : "Бэҕэһээ", "current" : "Бүгүн" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/sd.json ================================================ { "narrow" : { "year" : { "future" : "{0} سالن ۾", "previous" : "پويون سال", "next" : "پويون سال", "current" : "پويون سال", "past" : "{0} سال پهرين" }, "week" : { "past" : "{0} هفتا پهرين", "previous" : "پوئين هفتي", "future" : "{0} هفتن ۾", "current" : "هن هفتي", "next" : "اڳين هفتي" }, "hour" : { "future" : "{0} ڪلاڪ ۾", "current" : "هن ڪلڪ", "past" : "{0} ڪلاڪ پهرين" }, "quarter" : { "current" : "هن ٽي ماهي", "previous" : "پوئين ٽي ماهي", "future" : "{0} ٽي ماهي ۾", "past" : "{0} ٽي ماهي پهرين", "next" : "اڳين ٽي ماهي" }, "second" : { "current" : "هاڻي", "future" : "{0} سيڪنڊن ۾", "past" : "{0} سيڪنڊ پهرين" }, "month" : { "current" : "هن مهيني", "future" : "{0} مهينن ۾", "past" : "{0} مهينا پهرين", "next" : "اڳين مهيني", "previous" : "پوئين مهيني" }, "day" : { "previous" : "ڪل", "future" : "{0} ڏينهن ۾", "next" : "سڀاڻي", "current" : "اڄ", "past" : "{0} ڏينهن پهرين" }, "minute" : { "past" : "{0} منٽ پهرين", "future" : "{0} منٽن ۾", "current" : "هن منٽ" }, "now" : "هاڻي" }, "long" : { "quarter" : { "next" : "اڳين ٽي ماهي", "current" : "هن ٽي ماهي", "previous" : "پوئين ٽي ماهي", "past" : "{0} ٽي ماهي پهرين", "future" : "{0} ٽي ماهي ۾" }, "year" : { "previous" : "پويون سال", "past" : "{0} سال پهرين", "future" : "{0} سالن ۾", "next" : "پويون سال", "current" : "پويون سال" }, "minute" : { "past" : "{0} منٽ پهرين", "future" : { "one" : "{0} منٽن ۾", "other" : "+{0} min" }, "current" : "هن منٽ" }, "month" : { "previous" : "پوئين مهيني", "current" : "هن مهيني", "future" : "{0} مهينن ۾", "past" : "{0} مهينا پهرين", "next" : "اڳين مهيني" }, "now" : "هاڻي", "hour" : { "past" : "{0} ڪلاڪ پهرين", "future" : "{0} ڪلاڪ ۾", "current" : "هن ڪلڪ" }, "second" : { "future" : "{0} سيڪنڊن ۾", "current" : "هاڻي", "past" : "{0} سيڪنڊ پهرين" }, "week" : { "past" : "{0} هفتا پهرين", "current" : "هن هفتي", "next" : "اڳين هفتي", "previous" : "پوئين هفتي", "future" : "{0} هفتن ۾" }, "day" : { "previous" : "ڪل", "future" : "{0} ڏينهن ۾", "next" : "سڀاڻي", "past" : "{0} ڏينهن پهرين", "current" : "اڄ" } }, "short" : { "second" : { "current" : "هاڻي", "past" : "{0} سيڪنڊ پهرين", "future" : "{0} سيڪنڊن ۾" }, "now" : "هاڻي", "month" : { "future" : "{0} مهينن ۾", "current" : "هن مهيني", "past" : "{0} مهينا پهرين", "previous" : "پوئين مهيني", "next" : "اڳين مهيني" }, "day" : { "previous" : "ڪل", "current" : "اڄ", "next" : "سڀاڻي", "future" : "{0} ڏينهن ۾", "past" : "{0} ڏينهن پهرين" }, "minute" : { "current" : "هن منٽ", "future" : "{0} منٽن ۾", "past" : "{0} منٽ پهرين" }, "quarter" : { "past" : "{0} ٽي ماهي پهرين", "previous" : "پوئين ٽي ماهي", "current" : "هن ٽي ماهي", "future" : "{0} ٽي ماهي ۾", "next" : "اڳين ٽي ماهي" }, "hour" : { "current" : "هن ڪلڪ", "future" : "{0} ڪلاڪ ۾", "past" : "{0} ڪلاڪ پهرين" }, "week" : { "future" : "{0} هفتن ۾", "previous" : "پوئين هفتي", "next" : "اڳين هفتي", "past" : "{0} هفتا پهرين", "current" : "هن هفتي" }, "year" : { "next" : "پويون سال", "current" : "پويون سال", "past" : "{0} سال پهرين", "future" : "{0} سالن ۾", "previous" : "پويون سال" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/se.json ================================================ { "narrow" : { "second" : { "future" : { "one" : "{0} sekunda maŋŋilit", "other" : "{0} sekundda maŋŋilit" }, "past" : { "one" : "{0} sekunda árat", "other" : "{0} sekundda árat" }, "current" : "now" }, "week" : { "past" : { "one" : "{0} vahku árat", "other" : "{0} vahkku árat" }, "next" : "next week", "previous" : "last week", "current" : "this week", "future" : { "one" : "{0} vahku maŋŋilit", "other" : "{0} vahkku maŋŋilit" } }, "month" : { "current" : "this month", "next" : "next month", "future" : "{0} mánotbadji maŋŋilit", "previous" : "last month", "past" : "{0} mánotbadji árat" }, "quarter" : { "previous" : "last quarter", "current" : "this quarter", "past" : "-{0} Q", "future" : "+{0} Q", "next" : "next quarter" }, "year" : { "current" : "this year", "previous" : "last year", "future" : { "other" : "{0} jahkki maŋŋilit", "one" : "{0} jahki maŋŋilit" }, "next" : "next year", "past" : { "other" : "{0} jahkki árat", "one" : "{0} jahki árat" } }, "hour" : { "current" : "this hour", "past" : { "one" : "{0} diibmu árat", "other" : "{0} diibmur árat" }, "future" : { "one" : "{0} diibmu maŋŋilit", "other" : "{0} diibmur maŋŋilit" } }, "day" : { "current" : "odne", "next" : "ihttin", "previous" : "ikte", "past" : { "other" : "{0} jándora árat", "one" : "{0} jándor árat" }, "future" : { "other" : "{0} jándora maŋŋilit", "one" : "{0} jándor maŋŋilit", "two" : "{0} jándor amaŋŋilit" } }, "minute" : { "past" : { "one" : "{0} minuhta árat", "other" : "{0} minuhtta árat" }, "current" : "this minute", "future" : { "one" : "{0} minuhta maŋŋilit", "other" : "{0} minuhtta maŋŋilit" } }, "now" : "now" }, "long" : { "minute" : { "past" : { "one" : "{0} minuhta árat", "other" : "{0} minuhtta árat" }, "current" : "this minute", "future" : { "one" : "{0} minuhta maŋŋilit", "other" : "{0} minuhtta maŋŋilit" } }, "hour" : { "current" : "this hour", "past" : { "other" : "{0} diibmur árat", "one" : "{0} diibmu árat" }, "future" : { "one" : "{0} diibmu maŋŋilit", "other" : "{0} diibmur maŋŋilit" } }, "quarter" : { "previous" : "last quarter", "current" : "this quarter", "next" : "next quarter", "future" : "+{0} Q", "past" : "-{0} Q" }, "year" : { "previous" : "last year", "next" : "next year", "past" : { "one" : "{0} jahki árat", "other" : "{0} jahkki árat" }, "current" : "this year", "future" : { "one" : "{0} jahki maŋŋilit", "other" : "{0} jahkki maŋŋilit" } }, "week" : { "next" : "next week", "previous" : "last week", "current" : "this week", "past" : { "one" : "{0} vahku árat", "other" : "{0} vahkku árat" }, "future" : { "other" : "{0} vahkku maŋŋilit", "one" : "{0} vahku maŋŋilit" } }, "day" : { "current" : "odne", "future" : { "one" : "{0} jándor maŋŋilit", "other" : "{0} jándora maŋŋilit", "two" : "{0} jándor amaŋŋilit" }, "past" : { "other" : "{0} jándora árat", "one" : "{0} jándor árat" }, "next" : "ihttin", "previous" : "ikte" }, "second" : { "current" : "now", "past" : { "one" : "{0} sekunda árat", "other" : "{0} sekundda árat" }, "future" : { "other" : "{0} sekundda maŋŋilit", "one" : "{0} sekunda maŋŋilit" } }, "now" : "now", "month" : { "previous" : "last month", "future" : "{0} mánotbadji maŋŋilit", "past" : "{0} mánotbadji árat", "current" : "this month", "next" : "next month" } }, "short" : { "now" : "now", "minute" : { "current" : "this minute", "future" : { "one" : "{0} minuhta maŋŋilit", "other" : "{0} minuhtta maŋŋilit" }, "past" : { "other" : "{0} minuhtta árat", "one" : "{0} minuhta árat" } }, "year" : { "previous" : "last year", "current" : "this year", "next" : "next year", "past" : { "other" : "{0} jahkki árat", "one" : "{0} jahki árat" }, "future" : { "one" : "{0} jahki maŋŋilit", "other" : "{0} jahkki maŋŋilit" } }, "second" : { "current" : "now", "future" : { "one" : "{0} sekunda maŋŋilit", "other" : "{0} sekundda maŋŋilit" }, "past" : { "one" : "{0} sekunda árat", "other" : "{0} sekundda árat" } }, "hour" : { "past" : { "other" : "{0} diibmur árat", "one" : "{0} diibmu árat" }, "future" : { "other" : "{0} diibmur maŋŋilit", "one" : "{0} diibmu maŋŋilit" }, "current" : "this hour" }, "month" : { "future" : "{0} mánotbadji maŋŋilit", "previous" : "last month", "next" : "next month", "past" : "{0} mánotbadji árat", "current" : "this month" }, "quarter" : { "current" : "this quarter", "future" : "+{0} Q", "previous" : "last quarter", "next" : "next quarter", "past" : "-{0} Q" }, "week" : { "next" : "next week", "past" : { "other" : "{0} vahkku árat", "one" : "{0} vahku árat" }, "current" : "this week", "previous" : "last week", "future" : { "other" : "{0} vahkku maŋŋilit", "one" : "{0} vahku maŋŋilit" } }, "day" : { "next" : "ihttin", "current" : "odne", "previous" : "ikte", "past" : { "other" : "{0} jándora árat", "one" : "{0} jándor árat" }, "future" : { "two" : "{0} jándor amaŋŋilit", "other" : "{0} jándora maŋŋilit", "one" : "{0} jándor maŋŋilit" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/se_FI.json ================================================ { "narrow" : { "year" : { "next" : "boahtte jagi", "past" : { "other" : "{0} j. dás ovdal", "two" : "{0} jagi dás ovdal" }, "future" : "{0} jagi siste", "previous" : "diibmá", "current" : "dán jagi" }, "quarter" : { "past" : { "two" : "-{0} njealjádasjagi dás ovdal", "other" : "{0} njealj.j. dás ovdal" }, "previous" : "mannan njealjádasjagi", "current" : "dán njealjádasjagi", "next" : "boahtte njealjádasjagi", "future" : { "other" : "boahtte {0} njealj.j.", "two" : "boahtte {0} njealjádasjagi" } }, "hour" : { "past" : { "two" : "{0} diimmu áigi", "other" : "{0} dmu áigi" }, "future" : { "other" : "{0} dmu siste", "two" : "{0} diimmu siste" }, "current" : "dán diimmu" }, "second" : { "past" : { "two" : "{0} sekundda áigi", "other" : "{0} sek. áigi" }, "current" : "dál", "future" : { "two" : "{0} sekundda siste", "other" : "{0} sek. siste" } }, "week" : { "previous" : "mannan vahku", "past" : { "two" : "{0} vahku dás ovdal", "one" : "{0} vahkku dás ovdal", "other" : "{0} v(k) dás ovdal" }, "next" : "boahtte vahku", "future" : "{0} v(k) geahčen", "current" : "dán vahku" }, "month" : { "past" : { "other" : "{0} mánu dás ovdal", "one" : "{0} mánnu dás ovdal" }, "current" : "dán mánu", "future" : "{0} mánu geahčen", "next" : "boahtte mánu", "previous" : "mannan mánu" }, "day" : { "future" : "{0} beaivve siste", "current" : "odne", "past" : { "two" : "ovddet beaivve", "one" : "ikte", "other" : "{0} beaivve dás ovdal" }, "previous" : "ikte", "next" : "ihttin" }, "minute" : { "current" : "dán minuhta", "past" : { "other" : "{0} min. áigi", "two" : "{0} minuhta áigi" }, "future" : { "other" : "{0} min. siste", "two" : "{0} minuhta siste" } }, "now" : "dál" }, "long" : { "month" : { "past" : { "one" : "{0} mánnu dás ovdal", "other" : "{0} mánu dás ovdal" }, "previous" : "mannan mánu", "future" : "{0} mánu siste", "next" : "boahtte mánu", "current" : "dán mánu" }, "now" : "dál", "year" : { "current" : "dán jagi", "past" : { "other" : "{0} jagi dás ovdal", "one" : "diibmá", "two" : "ovddet jagi" }, "future" : "{0} jagi siste", "next" : "boahtte jagi", "previous" : "diibmá" }, "quarter" : { "past" : "-{0} njealjádasjagi dás ovdal", "future" : "čuovvovaš {0} njealjádasjagi", "current" : "dán njealjádasjagi", "next" : "boahtte njealjádasjagi", "previous" : "mannan njealjádasjagi" }, "minute" : { "current" : "dán minuhta", "past" : { "other" : "{0} minuhta áigi", "one" : "{0} minuhtta áigi" }, "future" : "{0} minuhta siste" }, "week" : { "future" : "{0} vahku geahčen", "previous" : "mannan vahku", "current" : "dán vahku", "next" : "boahtte vahku", "past" : { "one" : "{0} vahkku dás ovdal", "other" : "{0} vahku dás ovdal" } }, "second" : { "future" : "{0} sekundda siste", "current" : "dál", "past" : { "one" : "{0} sekunda áigi", "other" : "{0} sekundda áigi" } }, "hour" : { "future" : "{0} diimmu siste", "current" : "dán diimmu", "past" : { "other" : "{0} diimmu áigi", "one" : "{0} diibmu áigi" } }, "day" : { "next" : "ihttin", "past" : { "other" : "{0} beaivve dás ovdal", "two" : "ovddet beaivve", "one" : "ikte" }, "future" : "{0} beaivve siste", "previous" : "ikte", "current" : "odne" } }, "short" : { "hour" : { "current" : "dán diimmu", "past" : { "other" : "{0} dmu áigi", "two" : "{0} diimmu áigi" }, "future" : { "two" : "{0} diimmu siste", "other" : "{0} dmu siste" } }, "second" : { "future" : { "two" : "{0} sekundda siste", "other" : "{0} sek. siste" }, "past" : { "other" : "{0} sek. áigi", "two" : "{0} sekundda áigi" }, "current" : "dál" }, "now" : "dál", "year" : { "past" : { "one" : "diibmá", "two" : "ovddet jagi", "other" : "{0} j. dás ovdal" }, "future" : { "two" : "{0} jagi siste", "other" : "{0} j. siste" }, "previous" : "diibmá", "current" : "dán jagi", "next" : "boahtte jagi" }, "quarter" : { "previous" : "mannan njealjádasjagi", "future" : { "other" : "boahtte {0} njealj.j.", "two" : "boahtte {0} njealjádasjagi" }, "next" : "boahtte njealjádasjagi", "past" : { "two" : "{0} njealjádasjagi dás ovdal", "other" : "{0} njealj.j. dás ovdal" }, "current" : "dán njealjádasjagi" }, "week" : { "previous" : "mannan vahku", "current" : "dán vahku", "next" : "boahtte vahku", "past" : { "two" : "{0} vahku dás ovdal", "other" : "{0} v(k) dás ovdal" }, "future" : { "other" : "{0} v(k) siste", "two" : "{0} vahku siste" } }, "day" : { "next" : "ihttin", "past" : { "one" : "ikte", "other" : "{0} beaivve dás ovdal", "two" : "ovddet beaivve" }, "previous" : "ikte", "future" : "{0} beaivve siste", "current" : "odne" }, "minute" : { "past" : { "two" : "{0} minuhta áigi", "other" : "{0} min. áigi" }, "future" : { "two" : "{0} minuhta siste", "other" : "{0} min. siste" }, "current" : "dán minuhta" }, "month" : { "next" : "boahtte mánu", "past" : { "other" : "{0} mánu dás ovdal", "one" : "{0} mánnu dás ovdal" }, "future" : "{0} mánu siste", "previous" : "mannan mánu", "current" : "dán mánu" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/si.json ================================================ { "long" : { "hour" : { "future" : "පැය {0}කින්", "past" : "පැය {0}කට පෙර", "current" : "මෙම පැය" }, "second" : { "past" : "තත්පර {0}කට පෙර", "future" : "තත්පර {0}කින්", "current" : "දැන්" }, "week" : { "next" : "ඊළඟ සතිය", "current" : "මෙම සතිය", "previous" : "පසුගිය සතිය", "future" : "සති {0}කින්", "past" : "සති {0}කට පෙර" }, "minute" : { "current" : "මෙම මිනිත්තුව", "past" : "මිනිත්තු {0}කට පෙර", "future" : "මිනිත්තු {0}කින්" }, "year" : { "current" : "මෙම වසර", "past" : "වසර {0}කට පෙර", "future" : "වසර {0}කින්", "previous" : "පසුගිය වසර", "next" : "ඊළඟ වසර" }, "quarter" : { "next" : "ඊළඟ කාර්තුව", "current" : "මෙම කාර්තුව", "future" : "කාර්තු {0}කින්", "previous" : "පසුගිය කාර්තුව", "past" : "කාර්තු {0}කට පෙර" }, "month" : { "previous" : "පසුගිය මාසය", "future" : "මාස {0}කින්", "current" : "මෙම මාසය", "next" : "ඊළඟ මාසය", "past" : "මාස {0}කට පෙර" }, "day" : { "next" : "හෙට", "past" : "දින {0}කට පෙර", "current" : "අද", "future" : "දින {0}න්", "previous" : "ඊයේ" }, "now" : "දැන්" }, "narrow" : { "week" : { "current" : "මෙම සති.", "next" : "ඊළඟ සති.", "past" : "සති {0}කට පෙර", "previous" : "පසුගිය සති.", "future" : "සති {0}කින්" }, "minute" : { "current" : "මෙම මිනිත්තුව", "future" : "මිනිත්තු {0}කින්", "past" : "මිනිත්තු {0}කට පෙර" }, "month" : { "current" : "මෙම මාස.", "previous" : "පසුගිය මාස.", "past" : "මාස {0}කට පෙර", "next" : "ඊළඟ මාස.", "future" : "මාස {0}කින්" }, "now" : "දැන්", "year" : { "current" : "මෙම වසර", "next" : "ඊළඟ වසර", "past" : "වසර {0}කට පෙර", "future" : "වසර {0}කින්", "previous" : "පසුගිය වසර" }, "hour" : { "future" : "පැය {0}කින්", "past" : "පැය {0}කට පෙර", "current" : "මෙම පැය" }, "quarter" : { "previous" : "පසුගිය කාර්.", "current" : "මෙම කාර්.", "past" : "කාර්. {0}කට පෙර", "future" : "කාර්. {0}කින්", "next" : "ඊළඟ කාර්." }, "second" : { "current" : "දැන්", "past" : "තත්පර {0}කට පෙර", "future" : "තත්පර {0}කින්" }, "day" : { "past" : "දින {0}කට පෙර", "current" : "අද", "future" : "දින {0}න්", "previous" : "ඊයේ", "next" : "හෙට" } }, "short" : { "month" : { "previous" : "පසුගිය මාස.", "current" : "මෙම මාස.", "next" : "ඊළඟ මාස.", "past" : "මාස {0}කට පෙර", "future" : "මාස {0}කින්" }, "now" : "දැන්", "day" : { "next" : "හෙට", "current" : "අද", "previous" : "ඊයේ", "past" : "දින {0}කට පෙර", "future" : "දින {0}න්" }, "year" : { "current" : "මෙම වසර", "future" : "වසර {0}කින්", "previous" : "පසුගිය වසර", "next" : "ඊළඟ වසර", "past" : "වසර {0}කට පෙර" }, "hour" : { "past" : "පැය {0}කට පෙර", "current" : "මෙම පැය", "future" : "පැය {0}කින්" }, "minute" : { "past" : "මිනිත්තු {0}කට පෙර", "future" : "මිනිත්තු {0}කින්", "current" : "මෙම මිනිත්තුව" }, "second" : { "future" : "තත්පර {0}කින්", "current" : "දැන්", "past" : "තත්පර {0}කට පෙර" }, "quarter" : { "future" : "කාර්. {0}කින්", "previous" : "පසුගිය කාර්.", "next" : "ඊළඟ කාර්.", "past" : "කාර්. {0}කට පෙර", "current" : "මෙම කාර්." }, "week" : { "next" : "ඊළඟ සති.", "past" : "සති {0}කට පෙර", "current" : "මෙම සති.", "previous" : "පසුගිය සති.", "future" : "සති {0}කින්" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/sk.json ================================================ { "narrow" : { "quarter" : { "current" : "tento štvrťr.", "past" : "pred {0} štvrťr.", "next" : "budúci štvrťr.", "future" : "o {0} štvrťr.", "previous" : "minulý štvrťr." }, "day" : { "next" : "zajtra", "previous" : "včera", "future" : "o {0} d.", "past" : "pred {0} d.", "current" : "dnes" }, "year" : { "past" : "pred {0} r.", "previous" : "minulý rok", "future" : "o {0} r.", "next" : "budúci rok", "current" : "tento rok" }, "minute" : { "past" : "pred {0} min", "current" : "v tejto minúte", "future" : "o {0} min" }, "now" : "teraz", "week" : { "current" : "tento týž.", "previous" : "minulý týž.", "past" : "pred {0} týž.", "next" : "budúci týž.", "future" : "o {0} týž." }, "second" : { "future" : "o {0} s", "current" : "teraz", "past" : "pred {0} s" }, "month" : { "future" : "o {0} mes.", "current" : "tento mes.", "past" : "pred {0} mes.", "previous" : "minulý mes.", "next" : "budúci mes." }, "hour" : { "future" : "o {0} h", "past" : "pred {0} h", "current" : "v tejto hodine" } }, "long" : { "now" : "teraz", "week" : { "current" : "tento týždeň", "past" : { "other" : "pred {0} týždňami", "one" : "pred {0} týždňom" }, "previous" : "minulý týždeň", "future" : { "one" : "o {0} týždeň", "few" : "o {0} týždne", "other" : "o {0} týždňov" }, "next" : "budúci týždeň" }, "hour" : { "past" : { "one" : "pred {0} hodinou", "other" : "pred {0} hodinami" }, "future" : { "one" : "o {0} hodinu", "few" : "o {0} hodiny", "other" : "o {0} hodín" }, "current" : "v tejto hodine" }, "quarter" : { "previous" : "minulý štvrťrok", "future" : { "other" : "o {0} štvrťrokov", "few" : "o {0} štvrťroky", "one" : "o {0} štvrťrok" }, "past" : { "one" : "pred {0} štvrťrokom", "other" : "pred {0} štvrťrokmi" }, "current" : "tento štvrťrok", "next" : "budúci štvrťrok" }, "minute" : { "current" : "v tejto minúte", "future" : { "one" : "o {0} minútu", "other" : "o {0} minút", "few" : "o {0} minúty" }, "past" : { "other" : "pred {0} minútami", "one" : "pred {0} minútou" } }, "year" : { "past" : { "other" : "pred {0} rokmi", "one" : "pred {0} rokom" }, "future" : { "few" : "o {0} roky", "one" : "o {0} rok", "other" : "o {0} rokov" }, "current" : "tento rok", "next" : "budúci rok", "previous" : "minulý rok" }, "month" : { "future" : { "many" : "o {0} mesiacov", "one" : "o {0} mesiac", "few" : "o {0} mesiace", "other" : "o {0} mesiacov" }, "current" : "tento mesiac", "previous" : "minulý mesiac", "next" : "budúci mesiac", "past" : { "many" : "pred {0} mesiacmi", "other" : "pred {0} mesiacmi", "one" : "pred {0} mesiacom" } }, "second" : { "current" : "teraz", "past" : { "one" : "pred {0} sekundou", "other" : "pred {0} sekundami" }, "future" : { "few" : "o {0} sekundy", "one" : "o {0} sekundu", "other" : "o {0} sekúnd" } }, "day" : { "previous" : "včera", "next" : "zajtra", "past" : { "one" : "pred {0} dňom", "other" : "pred {0} dňami" }, "current" : "dnes", "future" : { "one" : "o {0} deň", "few" : "o {0} dni", "other" : "o {0} dní" } } }, "short" : { "minute" : { "future" : "o {0} min", "current" : "v tejto minúte", "past" : "pred {0} min" }, "week" : { "future" : "o {0} týž.", "previous" : "minulý týž.", "next" : "budúci týž.", "current" : "tento týž.", "past" : "pred {0} týž." }, "year" : { "current" : "tento rok", "previous" : "minulý rok", "future" : "o {0} r.", "past" : "pred {0} r.", "next" : "budúci rok" }, "month" : { "next" : "budúci mes.", "past" : "pred {0} mes.", "future" : "o {0} mes.", "previous" : "minulý mes.", "current" : "tento mes." }, "quarter" : { "next" : "budúci štvrťr.", "past" : "pred {0} štvrťr.", "previous" : "minulý štvrťr.", "current" : "tento štvrťr.", "future" : "o {0} štvrťr." }, "day" : { "current" : "dnes", "previous" : "včera", "past" : "pred {0} d.", "next" : "zajtra", "future" : "o {0} d." }, "hour" : { "past" : "pred {0} h", "current" : "v tejto hodine", "future" : "o {0} h" }, "second" : { "past" : "pred {0} s", "current" : "teraz", "future" : "o {0} s" }, "now" : "teraz" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/sl.json ================================================ { "long" : { "day" : { "next" : "jutri", "past" : { "other" : "pred {0} dnevi", "one" : "pred {0} dnevom", "two" : "pred {0} dnevoma" }, "future" : { "two" : "čez {0} dneva", "one" : "čez {0} dan", "other" : "čez {0} dni" }, "previous" : "včeraj", "current" : "danes" }, "month" : { "future" : { "few" : "čez {0} mesece", "other" : "čez {0} mesecev", "two" : "čez {0} meseca", "one" : "čez {0} mesec" }, "next" : "naslednji mesec", "current" : "ta mesec", "past" : { "other" : "pred {0} meseci", "two" : "pred {0} mesecema", "one" : "pred {0} mesecem" }, "previous" : "prejšnji mesec" }, "second" : { "past" : { "two" : "pred {0} sekundama", "other" : "pred {0} sekundami", "one" : "pred {0} sekundo" }, "current" : "zdaj", "future" : { "other" : "čez {0} sekund", "one" : "čez {0} sekundo", "few" : "čez {0} sekunde", "two" : "čez {0} sekundi" } }, "now" : "zdaj", "hour" : { "past" : { "two" : "pred {0} urama", "other" : "pred {0} urami", "one" : "pred {0} uro" }, "current" : "v tej uri", "future" : { "few" : "čez {0} ure", "one" : "čez {0} uro", "other" : "čez {0} ur", "two" : "čez {0} uri" } }, "minute" : { "past" : { "two" : "pred {0} minutama", "one" : "pred {0} minuto", "other" : "pred {0} minutami" }, "current" : "to minuto", "future" : { "two" : "čez {0} minuti", "one" : "čez {0} minuto", "few" : "čez {0} minute", "other" : "čez {0} minut" } }, "week" : { "past" : { "other" : "pred {0} tedni", "one" : "pred {0} tednom", "two" : "pred {0} tednoma" }, "next" : "naslednji teden", "previous" : "prejšnji teden", "current" : "ta teden", "future" : { "one" : "čez {0} teden", "two" : "čez {0} tedna", "few" : "čez {0} tedne", "other" : "čez {0} tednov" } }, "year" : { "previous" : "lani", "future" : { "two" : "čez {0} leti", "other" : "čez {0} let", "one" : "čez {0} leto", "few" : "čez {0} leta" }, "past" : { "one" : "pred {0} letom", "other" : "pred {0} leti", "two" : "pred {0} letoma" }, "current" : "letos", "next" : "naslednje leto" }, "quarter" : { "current" : "to četrtletje", "previous" : "zadnje četrtletje", "future" : { "few" : "čez {0} četrtletja", "one" : "čez {0} četrtletje", "other" : "čez {0} četrtletij", "two" : "čez {0} četrtletji" }, "next" : "naslednje četrtletje", "past" : { "one" : "pred {0} četrtletjem", "two" : "pred {0} četrtletjema", "other" : "pred {0} četrtletji" } } }, "narrow" : { "quarter" : { "next" : "naslednje četrtletje", "past" : "pred {0} četr.", "future" : "čez {0} četr.", "previous" : "zadnje četrtletje", "current" : "to četrtletje" }, "hour" : { "past" : "pred {0} h", "future" : "čez {0} h", "current" : "v tej uri" }, "day" : { "previous" : "včeraj", "past" : { "two" : "pred {0} dnevoma", "other" : "pred {0} dnevi", "one" : "pred {0} dnevom" }, "future" : { "one" : "čez {0} dan", "two" : "čez {0} dneva", "other" : "čez {0} dni" }, "next" : "jutri", "current" : "danes" }, "year" : { "current" : "letos", "future" : { "few" : "čez {0} leta", "one" : "čez {0} leto", "two" : "čez {0} leti", "other" : "čez {0} let" }, "next" : "naslednje leto", "previous" : "lani", "past" : { "other" : "pred {0} leti", "one" : "pred {0} letom", "two" : "pred {0} letoma" } }, "week" : { "next" : "naslednji teden", "future" : "čez {0} ted.", "past" : "pred {0} ted.", "previous" : "prejšnji teden", "current" : "ta teden" }, "minute" : { "current" : "to minuto", "past" : "pred {0} min", "future" : "čez {0} min" }, "month" : { "next" : "naslednji mesec", "current" : "ta mesec", "previous" : "prejšnji mesec", "past" : "pred {0} mes.", "future" : "čez {0} mes." }, "second" : { "current" : "zdaj", "future" : "čez {0} s", "past" : "pred {0} s" }, "now" : "zdaj" }, "short" : { "week" : { "previous" : "prejšnji teden", "current" : "ta teden", "next" : "naslednji teden", "past" : "pred {0} ted.", "future" : "čez {0} ted." }, "minute" : { "past" : "pred {0} min.", "current" : "to minuto", "future" : "čez {0} min." }, "day" : { "past" : { "one" : "pred {0} dnevom", "two" : "pred {0} dnevoma", "other" : "pred {0} dnevi" }, "next" : "jutri", "previous" : "včeraj", "current" : "danes", "future" : { "other" : "čez {0} dni", "one" : "čez {0} dan", "two" : "čez {0} dneva" } }, "now" : "zdaj", "second" : { "past" : "pred {0} s", "current" : "zdaj", "future" : "čez {0} s" }, "hour" : { "current" : "v tej uri", "past" : { "other" : "pred {0} urami", "two" : "pred {0} urama", "one" : "pred {0} uro" }, "future" : { "two" : "čez {0} uri", "other" : "čez {0} ur", "few" : "čez {0} ure", "one" : "čez {0} uro" } }, "month" : { "future" : "čez {0} mes.", "next" : "naslednji mesec", "current" : "ta mesec", "past" : "pred {0} mes.", "previous" : "prejšnji mesec" }, "year" : { "previous" : "lani", "current" : "letos", "past" : { "one" : "pred {0} letom", "two" : "pred {0} letoma", "other" : "pred {0} leti" }, "next" : "naslednje leto", "future" : { "few" : "čez {0} leta", "two" : "čez {0} leti", "other" : "čez {0} let", "one" : "čez {0} leto" } }, "quarter" : { "past" : "pred {0} četrtl.", "future" : "čez {0} četrtl.", "next" : "naslednje četrtletje", "previous" : "zadnje četrtletje", "current" : "to četrtletje" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/sq.json ================================================ { "narrow" : { "quarter" : { "current" : "këtë tremujor", "previous" : "tremujorin e kaluar", "past" : { "other" : "{0} tremujorë më parë", "one" : "{0} tremujor më parë" }, "future" : { "one" : "pas {0} tremujori", "other" : "pas {0} tremujorësh" }, "next" : "tremujorin e ardhshëm" }, "now" : "tani", "minute" : { "past" : "{0} min. më parë", "future" : "pas {0} min.", "current" : "këtë minutë" }, "month" : { "future" : { "other" : "pas {0} muajsh", "one" : "pas {0} muaji" }, "previous" : "muajin e kaluar", "next" : "muajin e ardhshëm", "current" : "këtë muaj", "past" : "{0} muaj më parë" }, "second" : { "future" : "pas {0} sek.", "current" : "tani", "past" : "{0} sek. më parë" }, "week" : { "past" : "{0} javë më parë", "future" : { "one" : "pas {0} jave", "other" : "pas {0} javësh" }, "previous" : "javën e kaluar", "current" : "këtë javë", "next" : "javën e ardhshme" }, "hour" : { "past" : "{0} orë më parë", "future" : { "one" : "pas {0} ore", "other" : "pas {0} orësh" }, "current" : "këtë orë" }, "day" : { "previous" : "dje", "past" : "{0} ditë më parë", "current" : "sot", "future" : { "other" : "pas {0} ditësh", "one" : "pas {0} dite" }, "next" : "nesër" }, "year" : { "next" : "vitin e ardhshëm", "past" : { "other" : "{0} vjet më parë", "one" : "{0} vit më parë" }, "previous" : "vitin e kaluar", "current" : "këtë vit", "future" : { "one" : "pas {0} viti", "other" : "pas {0} vjetësh" } } }, "long" : { "day" : { "current" : "sot", "past" : "{0} ditë më parë", "previous" : "dje", "next" : "nesër", "future" : { "one" : "pas {0} dite", "other" : "pas {0} ditësh" } }, "now" : "tani", "quarter" : { "current" : "këtë tremujor", "past" : { "one" : "{0} tremujor më parë", "other" : "{0} tremujorë më parë" }, "future" : { "one" : "pas {0} tremujori", "other" : "pas {0} tremujorësh" }, "next" : "tremujorin e ardhshëm", "previous" : "tremujorin e kaluar" }, "year" : { "current" : "këtë vit", "past" : { "other" : "{0} vjet më parë", "one" : "{0} vit më parë" }, "previous" : "vitin e kaluar", "future" : { "other" : "pas {0} vjetësh", "one" : "pas {0} viti" }, "next" : "vitin e ardhshëm" }, "week" : { "current" : "këtë javë", "next" : "javën e ardhshme", "past" : "{0} javë më parë", "future" : { "other" : "pas {0} javësh", "one" : "pas {0} jave" }, "previous" : "javën e kaluar" }, "hour" : { "past" : "{0} orë më parë", "future" : { "one" : "pas {0} ore", "other" : "pas {0} orësh" }, "current" : "këtë orë" }, "second" : { "future" : { "one" : "pas {0} sekonde", "other" : "pas {0} sekondash" }, "current" : "tani", "past" : { "one" : "{0} sekondë më parë", "other" : "{0} sekonda më parë" } }, "minute" : { "past" : { "other" : "{0} minuta më parë", "one" : "{0} minutë më parë" }, "current" : "këtë minutë", "future" : { "one" : "pas {0} minute", "other" : "pas {0} minutash" } }, "month" : { "past" : "{0} muaj më parë", "next" : "muajin e ardhshëm", "future" : { "one" : "pas {0} muaji", "other" : "pas {0} muajsh" }, "current" : "këtë muaj", "previous" : "muajin e kaluar" } }, "short" : { "week" : { "previous" : "javën e kaluar", "next" : "javën e ardhshme", "future" : { "other" : "pas {0} javësh", "one" : "pas {0} jave" }, "current" : "këtë javë", "past" : "{0} javë më parë" }, "day" : { "current" : "sot", "previous" : "dje", "next" : "nesër", "past" : "{0} ditë më parë", "future" : { "other" : "pas {0} ditësh", "one" : "pas {0} dite" } }, "second" : { "future" : "pas {0} sek.", "past" : "{0} sek. më parë", "current" : "tani" }, "year" : { "previous" : "vitin e kaluar", "next" : "vitin e ardhshëm", "current" : "këtë vit", "future" : { "one" : "pas {0} viti", "other" : "pas {0} vjetësh" }, "past" : { "other" : "{0} vjet më parë", "one" : "{0} vit më parë" } }, "hour" : { "current" : "këtë orë", "past" : "{0} orë më parë", "future" : { "one" : "pas {0} ore", "other" : "pas {0} orësh" } }, "now" : "tani", "quarter" : { "future" : { "other" : "pas {0} tremujorësh", "one" : "pas {0} tremujori" }, "previous" : "tremujorin e kaluar", "current" : "këtë tremujor", "next" : "tremujorin e ardhshëm", "past" : { "one" : "{0} tremujor më parë", "other" : "{0} tremujorë më parë" } }, "minute" : { "current" : "këtë minutë", "past" : "{0} min. më parë", "future" : "pas {0} min." }, "month" : { "previous" : "muajin e kaluar", "current" : "këtë muaj", "next" : "muajin e ardhshëm", "past" : "{0} muaj më parë", "future" : { "one" : "pas {0} muaji", "other" : "pas {0} muajsh" } } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/sr.json ================================================ { "narrow" : { "year" : { "future" : "za {0} g.", "previous" : "prošle g.", "next" : "sledeće g.", "current" : "ove g.", "past" : "pre {0} g." }, "week" : { "past" : "pre {0} n.", "previous" : "prošle n.", "future" : "za {0} n.", "current" : "ove n.", "next" : "sledeće n." }, "hour" : { "future" : "za {0} č.", "current" : "ovog sata", "past" : "pre {0} č." }, "quarter" : { "current" : "ovog kvartala", "previous" : "prošlog kvartala", "future" : "za {0} kv.", "past" : "pre {0} kv.", "next" : "sledećeg kvartala" }, "second" : { "past" : "pre {0} s.", "future" : "za {0} s.", "current" : "sada" }, "month" : { "current" : "ovog m.", "future" : "za {0} m.", "past" : "pre {0} m.", "next" : "sledećeg m.", "previous" : "prošlog m." }, "day" : { "previous" : "juče", "future" : "za {0} d.", "next" : "sutra", "current" : "danas", "past" : "pre {0} d." }, "minute" : { "future" : "za {0} min.", "current" : "ovog minuta", "past" : "pre {0} min." }, "now" : "sada" }, "long" : { "now" : "sada", "week" : { "previous" : "prošle nedelje", "current" : "ove nedelje", "future" : { "one" : "za {0} nedelju", "few" : "za {0} nedelje", "other" : "za {0} nedelja" }, "next" : "sledeće nedelje", "past" : { "other" : "pre {0} nedelja", "few" : "pre {0} nedelje", "one" : "pre {0} nedelje" } }, "quarter" : { "current" : "ovog kvartala", "previous" : "prošlog kvartala", "past" : "pre {0} kvartala", "next" : "sledećeg kvartala", "future" : { "other" : "za {0} kvartala", "one" : "za {0} kvartal" } }, "month" : { "current" : "ovog meseca", "future" : { "one" : "za {0} mesec", "few" : "za {0} meseca", "other" : "za {0} meseci" }, "past" : { "one" : "pre {0} meseca", "other" : "pre {0} meseci", "few" : "pre {0} meseca" }, "previous" : "prošlog meseca", "next" : "sledećeg meseca" }, "day" : { "future" : { "other" : "za {0} dana", "one" : "za {0} dan" }, "previous" : "juče", "current" : "danas", "next" : "sutra", "past" : "pre {0} dana" }, "hour" : { "future" : { "one" : "za {0} sat", "few" : "za {0} sata", "other" : "za {0} sati" }, "current" : "ovog sata", "past" : { "one" : "pre {0} sata", "few" : "pre {0} sata", "other" : "pre {0} sati" } }, "minute" : { "past" : "pre {0} minuta", "current" : "ovog minuta", "future" : { "one" : "za {0} minut", "other" : "za {0} minuta" } }, "second" : { "current" : "sada", "past" : { "few" : "pre {0} sekunde", "other" : "pre {0} sekundi", "one" : "pre {0} sekunde" }, "future" : { "other" : "za {0} sekundi", "one" : "za {0} sekundu", "few" : "za {0} sekunde" } }, "year" : { "future" : { "few" : "za {0} godine", "one" : "za {0} godinu", "other" : "za {0} godina" }, "previous" : "prošle godine", "next" : "sledeće godine", "past" : { "one" : "pre {0} godine", "few" : "pre {0} godine", "other" : "pre {0} godina" }, "current" : "ove godine" } }, "short" : { "second" : { "current" : "sada", "past" : "pre {0} sek.", "future" : "za {0} sek." }, "now" : "sada", "month" : { "future" : "za {0} mes.", "current" : "ovog mes.", "past" : "pre {0} mes.", "previous" : "prošlog mes.", "next" : "sledećeg mes." }, "day" : { "previous" : "juče", "current" : "danas", "next" : "sutra", "future" : "za {0} d.", "past" : "pre {0} d." }, "minute" : { "current" : "ovog minuta", "future" : "za {0} min.", "past" : "pre {0} min." }, "quarter" : { "past" : "pre {0} kv.", "previous" : "prošlog kvartala", "current" : "ovog kvartala", "future" : "za {0} kv.", "next" : "sledećeg kvartala" }, "hour" : { "current" : "ovog sata", "future" : "za {0} č.", "past" : "pre {0} č." }, "week" : { "future" : "za {0} ned.", "previous" : "prošle ned.", "next" : "sledeće ned.", "past" : "pre {0} ned.", "current" : "ove ned." }, "year" : { "next" : "sledeće god.", "current" : "ove god.", "past" : "pre {0} god.", "future" : "za {0} god.", "previous" : "prošle god." } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/sr_Latn.json ================================================ { "narrow" : { "year" : { "future" : "za {0} g.", "previous" : "prošle g.", "next" : "sledeće g.", "current" : "ove g.", "past" : "pre {0} g." }, "week" : { "past" : "pre {0} n.", "previous" : "prošle n.", "future" : "za {0} n.", "current" : "ove n.", "next" : "sledeće n." }, "hour" : { "future" : "za {0} č.", "current" : "ovog sata", "past" : "pre {0} č." }, "quarter" : { "current" : "ovog kvartala", "previous" : "prošlog kvartala", "future" : "za {0} kv.", "past" : "pre {0} kv.", "next" : "sledećeg kvartala" }, "second" : { "past" : "pre {0} s.", "future" : "za {0} s.", "current" : "sada" }, "month" : { "current" : "ovog m.", "future" : "za {0} m.", "past" : "pre {0} m.", "next" : "sledećeg m.", "previous" : "prošlog m." }, "day" : { "previous" : "juče", "future" : "za {0} d.", "next" : "sutra", "current" : "danas", "past" : "pre {0} d." }, "minute" : { "future" : "za {0} min.", "current" : "ovog minuta", "past" : "pre {0} min." }, "now" : "sada" }, "long" : { "now" : "sada", "week" : { "previous" : "prošle nedelje", "current" : "ove nedelje", "future" : { "one" : "za {0} nedelju", "few" : "za {0} nedelje", "other" : "za {0} nedelja" }, "next" : "sledeće nedelje", "past" : { "other" : "pre {0} nedelja", "few" : "pre {0} nedelje", "one" : "pre {0} nedelje" } }, "quarter" : { "current" : "ovog kvartala", "previous" : "prošlog kvartala", "past" : "pre {0} kvartala", "next" : "sledećeg kvartala", "future" : { "other" : "za {0} kvartala", "one" : "za {0} kvartal" } }, "month" : { "current" : "ovog meseca", "future" : { "one" : "za {0} mesec", "few" : "za {0} meseca", "other" : "za {0} meseci" }, "past" : { "one" : "pre {0} meseca", "other" : "pre {0} meseci", "few" : "pre {0} meseca" }, "previous" : "prošlog meseca", "next" : "sledećeg meseca" }, "day" : { "future" : { "other" : "za {0} dana", "one" : "za {0} dan" }, "previous" : "juče", "current" : "danas", "next" : "sutra", "past" : "pre {0} dana" }, "hour" : { "future" : { "one" : "za {0} sat", "few" : "za {0} sata", "other" : "za {0} sati" }, "current" : "ovog sata", "past" : { "one" : "pre {0} sata", "few" : "pre {0} sata", "other" : "pre {0} sati" } }, "minute" : { "past" : "pre {0} minuta", "current" : "ovog minuta", "future" : { "one" : "za {0} minut", "other" : "za {0} minuta" } }, "second" : { "current" : "sada", "past" : { "few" : "pre {0} sekunde", "other" : "pre {0} sekundi", "one" : "pre {0} sekunde" }, "future" : { "other" : "za {0} sekundi", "one" : "za {0} sekundu", "few" : "za {0} sekunde" } }, "year" : { "future" : { "few" : "za {0} godine", "one" : "za {0} godinu", "other" : "za {0} godina" }, "previous" : "prošle godine", "next" : "sledeće godine", "past" : { "one" : "pre {0} godine", "few" : "pre {0} godine", "other" : "pre {0} godina" }, "current" : "ove godine" } }, "short" : { "second" : { "current" : "sada", "past" : "pre {0} sek.", "future" : "za {0} sek." }, "now" : "sada", "month" : { "future" : "za {0} mes.", "current" : "ovog mes.", "past" : "pre {0} mes.", "previous" : "prošlog mes.", "next" : "sledećeg mes." }, "day" : { "previous" : "juče", "current" : "danas", "next" : "sutra", "future" : "za {0} d.", "past" : "pre {0} d." }, "minute" : { "current" : "ovog minuta", "future" : "za {0} min.", "past" : "pre {0} min." }, "quarter" : { "past" : "pre {0} kv.", "previous" : "prošlog kvartala", "current" : "ovog kvartala", "future" : "za {0} kv.", "next" : "sledećeg kvartala" }, "hour" : { "current" : "ovog sata", "future" : "za {0} č.", "past" : "pre {0} č." }, "week" : { "future" : "za {0} ned.", "previous" : "prošle ned.", "next" : "sledeće ned.", "past" : "pre {0} ned.", "current" : "ove ned." }, "year" : { "next" : "sledeće god.", "current" : "ove god.", "past" : "pre {0} god.", "future" : "za {0} god.", "previous" : "prošle god." } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/sv.json ================================================ { "short" : { "now" : "nu", "minute" : { "past" : "för {0} min sen", "current" : "denna minut", "future" : "om {0} min" }, "year" : { "current" : "i år", "future" : "om {0} år", "previous" : "i fjol", "next" : "nästa år", "past" : "för {0} år sen" }, "second" : { "past" : "för {0} s sen", "future" : { "one" : "om {0} sek", "other" : "om {0} sek" }, "current" : "nu" }, "hour" : { "past" : "för {0} tim sedan", "future" : "om {0} tim", "current" : "denna timme" }, "month" : { "current" : "denna mån.", "next" : "nästa mån.", "past" : "för {0} mån. sen", "future" : "om {0} mån.", "previous" : "förra mån." }, "quarter" : { "future" : "om {0} kv.", "previous" : "förra kv.", "next" : "nästa kv.", "past" : "för {0} kv. sen", "current" : "detta kv." }, "week" : { "next" : "nästa v.", "past" : "för {0} v. sedan", "current" : "denna v.", "previous" : "förra v.", "future" : "om {0} v." }, "day" : { "next" : "i morgon", "current" : "i dag", "previous" : "i går", "past" : { "one" : "för {0} d sedan", "other" : "för {0} d sedan" }, "future" : "om {0} d" } }, "long" : { "minute" : { "current" : "denna minut", "past" : { "one" : "för {0} minut sedan", "other" : "för {0} minuter sedan" }, "future" : { "other" : "om {0} minuter", "one" : "om {0} minut" } }, "quarter" : { "current" : "detta kvartal", "past" : "för {0} kvartal sedan", "next" : "nästa kvartal", "future" : "om {0} kvartal", "previous" : "förra kvartalet" }, "week" : { "future" : { "other" : "om {0} veckor", "one" : "om {0} vecka" }, "previous" : "förra veckan", "current" : "denna vecka", "next" : "nästa vecka", "past" : { "one" : "för {0} vecka sedan", "other" : "för {0} veckor sedan" } }, "second" : { "current" : "nu", "future" : { "one" : "om {0} sekund", "other" : "om {0} sekunder" }, "past" : { "one" : "för {0} sekund sedan", "other" : "för {0} sekunder sedan" } }, "hour" : { "current" : "denna timme", "past" : { "other" : "för {0} timmar sedan", "one" : "för {0} timme sedan" }, "future" : { "other" : "om {0} timmar", "one" : "om {0} timme" } }, "now" : "nu", "year" : { "past" : "för {0} år sedan", "previous" : "i fjol", "current" : "i år", "next" : "nästa år", "future" : "om {0} år" }, "day" : { "current" : "i dag", "previous" : "i går", "next" : "i morgon", "past" : { "other" : "för {0} dagar sedan", "one" : "för {0} dag sedan" }, "future" : { "other" : "om {0} dagar", "one" : "om {0} dag" } }, "month" : { "next" : "nästa månad", "past" : { "other" : "för {0} månader sedan", "one" : "för {0} månad sedan" }, "future" : { "other" : "om {0} månader", "one" : "om {0} månad" }, "current" : "denna månad", "previous" : "förra månaden" } }, "narrow" : { "month" : { "next" : "nästa mån.", "past" : "−{0} mån", "current" : "denna mån.", "future" : "+{0} mån.", "previous" : "förra mån." }, "day" : { "current" : "idag", "past" : "−{0} d", "future" : "+{0} d", "previous" : "igår", "next" : "imorgon" }, "second" : { "past" : "−{0} s", "future" : "+{0} s", "current" : "nu" }, "quarter" : { "current" : "detta kv.", "previous" : "förra kv.", "past" : "−{0} kv", "next" : "nästa kv.", "future" : "+{0} kv." }, "year" : { "previous" : "i fjol", "current" : "i år", "past" : "−{0} år", "future" : "+{0} år", "next" : "nästa år" }, "now" : "nu", "minute" : { "current" : "denna minut", "past" : "−{0} min", "future" : "+{0} min" }, "week" : { "past" : "−{0} v", "future" : "+{0} v.", "next" : "nästa v.", "current" : "denna v.", "previous" : "förra v." }, "hour" : { "past" : "−{0} h", "future" : "+{0} h", "current" : "denna timme" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/sw.json ================================================ { "narrow" : { "year" : { "next" : "mwaka ujao", "past" : { "one" : "mwaka {0} uliopita", "other" : "miaka {0} iliyopita" }, "future" : { "one" : "baada ya mwaka {0}", "other" : "baada ya miaka {0}" }, "previous" : "mwaka uliopita", "current" : "mwaka huu" }, "quarter" : { "past" : { "one" : "robo {0} iliyopita", "other" : "robo {0} zilizopita" }, "previous" : "robo ya mwaka iliyopita", "current" : "robo hii ya mwaka", "next" : "robo ya mwaka inayofuata", "future" : "baada ya robo {0}" }, "hour" : { "past" : { "other" : "Saa {0} zilizopita", "one" : "Saa {0} iliyopita" }, "future" : "baada ya saa {0}", "current" : "saa hii" }, "second" : { "past" : { "one" : "sekunde {0} iliyopita", "other" : "sekunde {0} zilizopita" }, "current" : "sasa hivi", "future" : "baada ya sekunde {0}" }, "week" : { "previous" : "wiki iliyopita", "past" : { "other" : "wiki {0} zilizopita", "one" : "wiki {0} iliyopita" }, "next" : "wiki ijayo", "future" : "baada ya wiki {0}", "current" : "wiki hii" }, "month" : { "past" : { "one" : "mwezi {0} uliopita", "other" : "miezi {0} iliyopita" }, "current" : "mwezi huu", "future" : { "one" : "baada ya mwezi {0}", "other" : "baada ya miezi {0}" }, "next" : "mwezi ujao", "previous" : "mwezi uliopita" }, "day" : { "future" : "baada ya siku {0}", "current" : "leo", "past" : { "one" : "siku {0} iliyopita", "other" : "siku {0} zilizopita" }, "previous" : "jana", "next" : "kesho" }, "minute" : { "current" : "dakika hii", "past" : { "other" : "dakika {0} zilizopita", "one" : "dakika {0} iliyopita" }, "future" : "baada ya dakika {0}" }, "now" : "sasa hivi" }, "long" : { "hour" : { "future" : "baada ya saa {0}", "current" : "saa hii", "past" : { "one" : "saa {0} iliyopita", "other" : "saa {0} zilizopita" } }, "week" : { "previous" : "wiki iliyopita", "past" : { "other" : "wiki {0} zilizopita", "one" : "wiki {0} iliyopita" }, "current" : "wiki hii", "next" : "wiki ijayo", "future" : "baada ya wiki {0}" }, "month" : { "previous" : "mwezi uliopita", "future" : { "one" : "baada ya mwezi {0}", "other" : "baada ya miezi {0}" }, "next" : "mwezi ujao", "current" : "mwezi huu", "past" : { "one" : "mwezi {0} uliopita", "other" : "miezi {0} iliyopita" } }, "day" : { "previous" : "jana", "current" : "leo", "next" : "kesho", "past" : { "one" : "siku {0} iliyopita", "other" : "siku {0} zilizopita" }, "future" : "baada ya siku {0}" }, "second" : { "future" : "baada ya sekunde {0}", "current" : "sasa hivi", "past" : { "one" : "Sekunde {0} iliyopita", "other" : "Sekunde {0} zilizopita" } }, "minute" : { "past" : { "one" : "dakika {0} iliyopita", "other" : "dakika {0} zilizopita" }, "future" : "baada ya dakika {0}", "current" : "dakika hii" }, "year" : { "current" : "mwaka huu", "past" : { "one" : "mwaka {0} uliopita", "other" : "miaka {0} iliyopita" }, "future" : { "other" : "baada ya miaka {0}", "one" : "baada ya mwaka {0}" }, "next" : "mwaka ujao", "previous" : "mwaka uliopita" }, "now" : "sasa hivi", "quarter" : { "past" : { "other" : "robo {0} zilizopita", "one" : "robo {0} iliyopita" }, "future" : "baada ya robo {0}", "current" : "robo hii ya mwaka", "next" : "robo ya mwaka inayofuata", "previous" : "robo ya mwaka iliyopita" } }, "short" : { "hour" : { "current" : "saa hii", "past" : { "other" : "saa {0} zilizopita", "one" : "saa {0} iliyopita" }, "future" : "baada ya saa {0}" }, "second" : { "future" : "baada ya sekunde {0}", "past" : { "other" : "sekunde {0} zilizopita", "one" : "sekunde {0} iliyopita" }, "current" : "sasa hivi" }, "now" : "sasa hivi", "year" : { "past" : { "one" : "mwaka {0} uliopita", "other" : "miaka {0} iliyopita" }, "future" : { "other" : "baada ya miaka {0}", "one" : "baada ya mwaka {0}" }, "previous" : "mwaka uliopita", "current" : "mwaka huu", "next" : "mwaka ujao" }, "quarter" : { "previous" : "robo ya mwaka iliyopita", "future" : "baada ya robo {0}", "next" : "robo ya mwaka inayofuata", "past" : { "one" : "robo {0} iliyopita", "other" : "robo {0} zilizopita" }, "current" : "robo hii ya mwaka" }, "week" : { "previous" : "wiki iliyopita", "current" : "wiki hii", "next" : "wiki ijayo", "past" : { "one" : "wiki {0} iliyopita", "other" : "wiki {0} zilizopita" }, "future" : "baada ya wiki {0}" }, "day" : { "next" : "kesho", "past" : { "other" : "siku {0} zilizopita", "one" : "siku {0} iliyopita" }, "previous" : "jana", "future" : "baada ya siku {0}", "current" : "leo" }, "minute" : { "past" : { "one" : "dakika {0} iliyopita", "other" : "dakika {0} zilizopita" }, "future" : "baada ya dakika {0}", "current" : "dakika hii" }, "month" : { "next" : "mwezi ujao", "past" : { "one" : "mwezi {0} uliopita", "other" : "miezi {0} iliyopita" }, "future" : { "one" : "baada ya mwezi {0}", "other" : "baada ya miezi {0}" }, "previous" : "mwezi uliopita", "current" : "mwezi huu" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ta.json ================================================ { "narrow" : { "second" : { "future" : "{0} வி.", "past" : "{0} வி. முன்", "current" : "இப்போது" }, "year" : { "next" : "அடுத்த ஆண்டு", "past" : "{0} ஆ. முன்", "future" : "{0} ஆ.", "previous" : "கடந்த ஆண்டு", "current" : "இந்த ஆண்டு" }, "month" : { "past" : "{0} மா. முன்", "next" : "அடுத்த மாதம்", "future" : "{0} மா.", "previous" : "கடந்த மாதம்", "current" : "இந்த மாதம்" }, "minute" : { "past" : "{0} நி. முன்", "current" : "இந்த ஒரு நிமிடத்தில்", "future" : "{0} நி." }, "quarter" : { "previous" : "இறுதி காலாண்டு", "current" : "இந்த காலாண்டு", "next" : "அடுத்த காலாண்டு", "past" : "{0} கா. முன்", "future" : "{0} கா." }, "hour" : { "future" : "{0} ம.", "past" : "{0} ம. முன்", "current" : "இந்த ஒரு மணிநேரத்தில்" }, "now" : "இப்போது", "week" : { "next" : "அடுத்த வாரம்", "previous" : "கடந்த வாரம்", "past" : "{0} வா. முன்", "future" : "{0} வா.", "current" : "இந்த வாரம்" }, "day" : { "previous" : "நேற்று", "future" : "{0} நா.", "past" : "{0} நா. முன்", "current" : "இன்று", "next" : "நாளை" } }, "long" : { "day" : { "previous" : "நேற்று", "past" : { "other" : "{0} நாட்களுக்கு முன்", "one" : "{0} நாளுக்கு முன்" }, "future" : { "other" : "{0} நாட்களில்", "one" : "{0} நாளில்" }, "next" : "நாளை", "current" : "இன்று" }, "hour" : { "future" : "{0} மணிநேரத்தில்", "current" : "இந்த ஒரு மணிநேரத்தில்", "past" : "{0} மணிநேரம் முன்" }, "month" : { "next" : "அடுத்த மாதம்", "current" : "இந்த மாதம்", "past" : { "one" : "{0} மாதத்துக்கு முன்", "other" : "{0} மாதங்களுக்கு முன்" }, "previous" : "கடந்த மாதம்", "future" : { "one" : "{0} மாதத்தில்", "other" : "{0} மாதங்களில்" } }, "quarter" : { "next" : "அடுத்த காலாண்டு", "past" : { "one" : "{0} காலாண்டுக்கு முன்", "other" : "{0} காலாண்டுகளுக்கு முன்" }, "previous" : "கடந்த காலாண்டு", "current" : "இந்த காலாண்டு", "future" : { "one" : "+{0} காலாண்டில்", "other" : "{0} காலாண்டுகளில்" } }, "year" : { "next" : "அடுத்த ஆண்டு", "current" : "இந்த ஆண்டு", "future" : { "other" : "{0} ஆண்டுகளில்", "one" : "{0} ஆண்டில்" }, "previous" : "கடந்த ஆண்டு", "past" : { "other" : "{0} ஆண்டுகளுக்கு முன்", "one" : "{0} ஆண்டிற்கு முன்" } }, "minute" : { "current" : "இந்த ஒரு நிமிடத்தில்", "future" : { "one" : "{0} நிமிடத்தில்", "other" : "{0} நிமிடங்களில்" }, "past" : { "other" : "{0} நிமிடங்களுக்கு முன்", "one" : "{0} நிமிடத்திற்கு முன்" } }, "second" : { "future" : { "one" : "{0} விநாடியில்", "other" : "{0} விநாடிகளில்" }, "past" : { "one" : "{0} விநாடிக்கு முன்", "other" : "{0} விநாடிகளுக்கு முன்" }, "current" : "இப்போது" }, "week" : { "future" : { "one" : "{0} வாரத்தில்", "other" : "{0} வாரங்களில்" }, "previous" : "கடந்த வாரம்", "current" : "இந்த வாரம்", "next" : "அடுத்த வாரம்", "past" : { "other" : "{0} வாரங்களுக்கு முன்", "one" : "{0} வாரத்திற்கு முன்" } }, "now" : "இப்போது" }, "short" : { "minute" : { "future" : "{0} நிமி.", "current" : "இந்த ஒரு நிமிடத்தில்", "past" : "{0} நிமி. முன்" }, "month" : { "current" : "இந்த மாதம்", "past" : "{0} மாத. முன்", "future" : "{0} மாத.", "next" : "அடுத்த மாதம்", "previous" : "கடந்த மாதம்" }, "week" : { "current" : "இந்த வாரம்", "past" : "{0} வார. முன்", "future" : "{0} வார.", "next" : "அடுத்த வாரம்", "previous" : "கடந்த வாரம்" }, "hour" : { "future" : "{0} மணி.", "current" : "இந்த ஒரு மணிநேரத்தில்", "past" : "{0} மணி. முன்" }, "day" : { "next" : "நாளை", "current" : "இன்று", "previous" : "நேற்று", "past" : { "one" : "{0} நாளுக்கு முன்", "other" : "{0} நாட்களுக்கு முன்" }, "future" : { "one" : "{0} நாளில்", "other" : "{0} நாட்களில்" } }, "second" : { "future" : "{0} விநா.", "current" : "இப்போது", "past" : "{0} விநா. முன்" }, "now" : "இப்போது", "year" : { "future" : { "one" : "{0} ஆண்டில்", "other" : "{0} ஆண்டுகளில்" }, "previous" : "கடந்த ஆண்டு", "next" : "அடுத்த ஆண்டு", "current" : "இந்த ஆண்டு", "past" : { "other" : "{0} ஆண்டுகளுக்கு முன்", "one" : "{0} ஆண்டிற்கு முன்" } }, "quarter" : { "current" : "இந்த காலாண்டு", "future" : "{0} காலா.", "previous" : "இறுதி காலாண்டு", "next" : "அடுத்த காலாண்டு", "past" : "{0} காலா. முன்" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/te.json ================================================ { "narrow" : { "now" : "ప్రస్తుతం", "hour" : { "past" : "{0} గం. క్రితం", "future" : "{0} గం.లో", "current" : "ఈ గంట" }, "year" : { "future" : { "one" : "{0} సం.లో", "other" : "{0} సం.ల్లో" }, "current" : "ఈ సంవత్సరం", "past" : "{0} సం. క్రితం", "previous" : "గత సంవత్సరం", "next" : "తదుపరి సంవత్సరం" }, "month" : { "past" : { "other" : "{0} నెలల క్రితం", "one" : "{0} నెల క్రితం" }, "future" : { "other" : "{0} నెలల్లో", "one" : "{0} నెలలో" }, "previous" : "గత నెల", "current" : "ఈ నెల", "next" : "తదుపరి నెల" }, "day" : { "current" : "ఈ రోజు", "future" : { "other" : "{0} రోజుల్లో", "one" : "{0} రోజులో" }, "previous" : "నిన్న", "next" : "రేపు", "past" : { "other" : "{0} రోజుల క్రితం", "one" : "{0} రోజు క్రితం" } }, "second" : { "past" : "{0} సెక. క్రితం", "future" : { "other" : "{0} సెక. లో", "one" : "{0} సెక.లో" }, "current" : "ప్రస్తుతం" }, "minute" : { "current" : "ఈ నిమిషం", "past" : "{0} నిమి. క్రితం", "future" : "{0} నిమి.లో" }, "quarter" : { "previous" : "గత త్రైమాసికం", "past" : "{0} త్రైమా. క్రితం", "current" : "ఈ త్రైమాసికం", "next" : "తదుపరి త్రైమాసికం", "future" : { "one" : "{0} త్రైమాసికంలో", "other" : "{0} త్రైమాసికాల్లో" } }, "week" : { "next" : "తదుపరి వారం", "current" : "ఈ వారం", "past" : { "one" : "{0} వారం క్రితం", "other" : "{0} వారాల క్రితం" }, "future" : { "one" : "{0} వారంలో", "other" : "{0} వారాల్లో" }, "previous" : "గత వారం" } }, "long" : { "month" : { "future" : { "one" : "{0} నెలలో", "other" : "{0} నెలల్లో" }, "previous" : "గత నెల", "current" : "ఈ నెల", "next" : "తదుపరి నెల", "past" : { "other" : "{0} నెలల క్రితం", "one" : "{0} నెల క్రితం" } }, "quarter" : { "previous" : "గత త్రైమాసికం", "next" : "తదుపరి త్రైమాసికం", "current" : "ఈ త్రైమాసికం", "past" : { "other" : "{0} త్రైమాసికాల క్రితం", "one" : "{0} త్రైమాసికం క్రితం" }, "future" : { "other" : "{0} త్రైమాసికాల్లో", "one" : "{0} త్రైమాసికంలో" } }, "hour" : { "current" : "ఈ గంట", "past" : { "other" : "{0} గంటల క్రితం", "one" : "{0} గంట క్రితం" }, "future" : { "one" : "{0} గంటలో", "other" : "{0} గంటల్లో" } }, "second" : { "current" : "ప్రస్తుతం", "past" : { "other" : "{0} సెకన్ల క్రితం", "one" : "{0} సెకను క్రితం" }, "future" : { "one" : "{0} సెకనులో", "other" : "{0} సెకన్లలో" } }, "year" : { "past" : { "one" : "{0} సంవత్సరం క్రితం", "other" : "{0} సంవత్సరాల క్రితం" }, "future" : { "one" : "{0} సంవత్సరంలో", "other" : "{0} సంవత్సరాల్లో" }, "previous" : "గత సంవత్సరం", "current" : "ఈ సంవత్సరం", "next" : "తదుపరి సంవత్సరం" }, "day" : { "future" : { "one" : "{0} రోజులో", "other" : "{0} రోజుల్లో" }, "current" : "ఈ రోజు", "previous" : "నిన్న", "next" : "రేపు", "past" : { "one" : "{0} రోజు క్రితం", "other" : "{0} రోజుల క్రితం" } }, "minute" : { "current" : "ఈ నిమిషం", "past" : { "other" : "{0} నిమిషాల క్రితం", "one" : "{0} నిమిషం క్రితం" }, "future" : { "one" : "{0} నిమిషంలో", "other" : "{0} నిమిషాల్లో" } }, "week" : { "past" : { "one" : "{0} వారం క్రితం", "other" : "{0} వారాల క్రితం" }, "next" : "తదుపరి వారం", "current" : "ఈ వారం", "future" : { "one" : "{0} వారంలో", "other" : "{0} వారాల్లో" }, "previous" : "గత వారం" }, "now" : "ప్రస్తుతం" }, "short" : { "month" : { "past" : { "one" : "{0} నెల క్రితం", "other" : "{0} నెలల క్రితం" }, "next" : "తదుపరి నెల", "future" : { "one" : "{0} నెలలో", "other" : "{0} నెలల్లో" }, "previous" : "గత నెల", "current" : "ఈ నెల" }, "week" : { "next" : "తదుపరి వారం", "previous" : "గత వారం", "past" : { "one" : "{0} వారం క్రితం", "other" : "{0} వారాల క్రితం" }, "current" : "ఈ వారం", "future" : { "one" : "{0} వారంలో", "other" : "{0} వారాల్లో" } }, "hour" : { "future" : "{0} గం.లో", "current" : "ఈ గంట", "past" : "{0} గం. క్రితం" }, "quarter" : { "next" : "తదుపరి త్రైమాసికం", "previous" : "గత త్రైమాసికం", "past" : "{0} త్రైమా. క్రితం", "future" : { "one" : "{0} త్రైమా.లో", "other" : "{0} త్రైమా.ల్లో" }, "current" : "ఈ త్రైమాసికం" }, "minute" : { "current" : "ఈ నిమిషం", "past" : "{0} నిమి. క్రితం", "future" : "{0} నిమి.లో" }, "now" : "ప్రస్తుతం", "day" : { "previous" : "నిన్న", "past" : { "other" : "{0} రోజుల క్రితం", "one" : "{0} రోజు క్రితం" }, "current" : "ఈ రోజు", "next" : "రేపు", "future" : { "one" : "{0} రోజులో", "other" : "{0} రోజుల్లో" } }, "year" : { "next" : "తదుపరి సంవత్సరం", "current" : "ఈ సంవత్సరం", "past" : "{0} సం. క్రితం", "previous" : "గత సంవత్సరం", "future" : { "one" : "{0} సం.లో", "other" : "{0} సం.ల్లో" } }, "second" : { "current" : "ప్రస్తుతం", "future" : { "other" : "{0} సెకన్లలో", "one" : "{0} సెకనులో" }, "past" : "{0} సెక. క్రితం" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/th.json ================================================ { "long" : { "week" : { "past" : "{0} สัปดาห์ที่ผ่านมา", "next" : "สัปดาห์หน้า", "current" : "สัปดาห์นี้", "future" : "ในอีก {0} สัปดาห์", "previous" : "สัปดาห์ที่แล้ว" }, "hour" : { "future" : "ในอีก {0} ชั่วโมง", "past" : "{0} ชั่วโมงที่ผ่านมา", "current" : "ชั่วโมงนี้" }, "second" : { "future" : "ในอีก {0} วินาที", "current" : "ขณะนี้", "past" : "{0} วินาทีที่ผ่านมา" }, "day" : { "future" : "ในอีก {0} วัน", "previous" : "เมื่อวาน", "current" : "วันนี้", "next" : "พรุ่งนี้", "past" : "{0} วันที่ผ่านมา" }, "month" : { "previous" : "เดือนที่แล้ว", "current" : "เดือนนี้", "future" : "ในอีก {0} เดือน", "past" : "{0} เดือนที่ผ่านมา", "next" : "เดือนหน้า" }, "year" : { "current" : "ปีนี้", "next" : "ปีหน้า", "previous" : "ปีที่แล้ว", "past" : "{0} ปีที่แล้ว", "future" : "ในอีก {0} ปี" }, "now" : "ขณะนี้", "minute" : { "past" : "{0} นาทีที่ผ่านมา", "future" : "ในอีก {0} นาที", "current" : "นาทีนี้" }, "quarter" : { "previous" : "ไตรมาสที่แล้ว", "future" : "ในอีก {0} ไตรมาส", "next" : "ไตรมาสหน้า", "past" : "{0} ไตรมาสที่แล้ว", "current" : "ไตรมาสนี้" } }, "narrow" : { "month" : { "next" : "เดือนหน้า", "past" : "{0} เดือนที่แล้ว", "current" : "เดือนนี้", "future" : "ใน {0} เดือน", "previous" : "เดือนที่แล้ว" }, "day" : { "current" : "วันนี้", "past" : "{0} วันที่แล้ว", "future" : "ใน {0} วัน", "previous" : "เมื่อวาน", "next" : "พรุ่งนี้" }, "second" : { "future" : "ใน {0} วินาที", "past" : "{0} วินาทีที่แล้ว", "current" : "ขณะนี้" }, "quarter" : { "current" : "ไตรมาสนี้", "previous" : "ไตรมาสที่แล้ว", "past" : "{0} ไตรมาสที่แล้ว", "next" : "ไตรมาสหน้า", "future" : "ใน {0} ไตรมาส" }, "year" : { "previous" : "ปีที่แล้ว", "current" : "ปีนี้", "past" : "{0} ปีที่แล้ว", "future" : "ใน {0} ปี", "next" : "ปีหน้า" }, "now" : "ขณะนี้", "minute" : { "future" : "ใน {0} นาที", "past" : "{0} นาทีที่แล้ว", "current" : "นาทีนี้" }, "week" : { "past" : "{0} สัปดาห์ที่แล้ว", "future" : "ใน {0} สัปดาห์", "next" : "สัปดาห์หน้า", "current" : "สัปดาห์นี้", "previous" : "สัปดาห์ที่แล้ว" }, "hour" : { "current" : "ชั่วโมงนี้", "future" : "ใน {0} ชม.", "past" : "{0} ชม. ที่แล้ว" } }, "short" : { "now" : "ขณะนี้", "minute" : { "current" : "นาทีนี้", "past" : "{0} นาทีที่แล้ว", "future" : "ใน {0} นาที" }, "year" : { "current" : "ปีนี้", "future" : "ใน {0} ปี", "previous" : "ปีที่แล้ว", "next" : "ปีหน้า", "past" : "{0} ปีที่แล้ว" }, "second" : { "past" : "{0} วินาทีที่แล้ว", "future" : "ใน {0} วินาที", "current" : "ขณะนี้" }, "hour" : { "past" : "{0} ชม. ที่แล้ว", "future" : "ใน {0} ชม.", "current" : "ชั่วโมงนี้" }, "month" : { "current" : "เดือนนี้", "next" : "เดือนหน้า", "past" : "{0} เดือนที่แล้ว", "future" : "ใน {0} เดือน", "previous" : "เดือนที่แล้ว" }, "quarter" : { "future" : "ใน {0} ไตรมาส", "previous" : "ไตรมาสที่แล้ว", "next" : "ไตรมาสหน้า", "past" : "{0} ไตรมาสที่แล้ว", "current" : "ไตรมาสนี้" }, "week" : { "next" : "สัปดาห์หน้า", "past" : "{0} สัปดาห์ที่แล้ว", "current" : "สัปดาห์นี้", "previous" : "สัปดาห์ที่แล้ว", "future" : "ใน {0} สัปดาห์" }, "day" : { "next" : "พรุ่งนี้", "current" : "วันนี้", "previous" : "เมื่อวาน", "past" : "{0} วันที่แล้ว", "future" : "ใน {0} วัน" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ti.json ================================================ { "short" : { "month" : { "future" : "ኣብ {0} ወርሒ", "previous" : "last month", "current" : "ህሉው ወርሒ", "next" : "ዝመጽእ ወርሒ", "past" : "ቅድሚ {0} ወርሒ" }, "week" : { "previous" : "ዝሓለፈ ሰሙን", "current" : "ህሉው ሰሙን", "future" : "ኣብ {0} ሰሙን", "past" : "ቅድሚ {0} ሰሙን", "next" : "ዝመጽእ ሰሙን" }, "hour" : { "past" : "ቅድሚ {0} ሰዓት", "current" : "ኣብዚ ሰዓት", "future" : "ኣብ {0} ሰዓት" }, "quarter" : { "future" : "ኣብ {0} ርብዒ", "previous" : "ዝሓለፈ ርብዒ", "current" : "ህሉው ርብዒ", "next" : "ዝመጽእ ርብዒ", "past" : "ቅድሚ {0} ርብዒ" }, "minute" : { "current" : "ኣብዚ ደቒቕ", "past" : "ቅድሚ {0} ደቒቕ", "future" : "ኣብ {0} ደቒቕ" }, "now" : "ሕጂ", "day" : { "past" : "ቅድሚ {0} መዓልቲ", "previous" : "ትማሊ", "current" : "ሎሚ", "next" : "ጽባሕ", "future" : "ኣብ {0} መዓልቲ" }, "year" : { "previous" : "ዓሚ", "next" : "ንዓመታ", "current" : "ሎሚ ዓመት", "future" : "ኣብ {0} ዓ", "past" : { "other" : "ቅድሚ {0} ዓ", "one" : "ቅድሚ -{0} ዓ" } }, "second" : { "current" : "ሕጂ", "past" : "ቅድሚ {0} ካልኢት", "future" : "ኣብ {0} ካልኢት" } }, "narrow" : { "day" : { "next" : "ጽባሕ", "current" : "ሎሚ", "future" : "ኣብ {0} መዓልቲ", "past" : "ቅድሚ {0} መዓልቲ", "previous" : "ትማሊ" }, "week" : { "current" : "ህሉው ሰሙን", "next" : "ዝመጽእ ሰሙን", "previous" : "ዝሓለፈ ሰሙን", "past" : "ቅድሚ {0} ሰሙን", "future" : "ኣብ {0} ሰሙን" }, "quarter" : { "past" : "ቅድሚ {0} ርብዒ", "future" : "ኣብ {0} ርብዒ", "previous" : "ዝሓለፈ ርብዒ", "current" : "ህሉው ርብዒ", "next" : "ዝመጽእ ርብዒ" }, "month" : { "previous" : "last month", "current" : "ህሉው ወርሒ", "next" : "ዝመጽእ ወርሒ", "past" : "ቅድሚ {0} ወርሒ", "future" : "ኣብ {0} ወርሒ" }, "hour" : { "past" : "ቅድሚ {0} ሰዓት", "future" : "ኣብ {0} ሰዓት", "current" : "ኣብዚ ሰዓት" }, "year" : { "current" : "ሎሚ ዓመት", "past" : "ቅድሚ {0} ዓ", "future" : "ኣብ {0} ዓ", "next" : "ንዓመታ", "previous" : "ዓሚ" }, "now" : "ሕጂ", "minute" : { "future" : "ኣብ {0} ደቒቕ", "current" : "ኣብዚ ደቒቕ", "past" : "ቅድሚ {0} ደቒቕ" }, "second" : { "current" : "ሕጂ", "past" : "ቅድሚ {0} ካልኢት", "future" : "ኣብ {0} ካልኢት" } }, "long" : { "minute" : { "past" : "ቅድሚ {0} ደቒቕ", "future" : "ኣብ {0} ደቒቕ", "current" : "ኣብዚ ደቒቕ" }, "quarter" : { "next" : "ዝመጽእ ርብዒ", "past" : "ቅድሚ {0} ርብዒ", "future" : "ኣብ {0} ርብዒ", "previous" : "ዝሓለፈ ርብዒ", "current" : "ህሉው ርብዒ" }, "year" : { "past" : "ቅድሚ {0} ዓ", "current" : "ሎሚ ዓመት", "previous" : "ዓሚ", "next" : "ንዓመታ", "future" : "ኣብ {0} ዓ" }, "month" : { "past" : "ቅድሚ {0} ወርሒ", "future" : "ኣብ {0} ወርሒ", "current" : "ህሉው ወርሒ", "next" : "ዝመጽእ ወርሒ", "previous" : "last month" }, "hour" : { "future" : "ኣብ {0} ሰዓት", "past" : "ቅድሚ {0} ሰዓት", "current" : "ኣብዚ ሰዓት" }, "now" : "ሕጂ", "week" : { "future" : "ኣብ {0} ሰሙን", "past" : "ቅድሚ {0} ሰሙን", "previous" : "ዝሓለፈ ሰሙን", "current" : "ህሉው ሰሙን", "next" : "ዝመጽእ ሰሙን" }, "day" : { "current" : "ሎሚ", "previous" : "ትማሊ", "next" : "ጽባሕ", "past" : { "other" : "ኣብ {0} መዓልቲ", "one" : "ቅድሚ {0} መዓልቲ" }, "future" : "ኣብ {0} መዓልቲ" }, "second" : { "future" : "ኣብ {0} ካልኢት", "current" : "ሕጂ", "past" : "ቅድሚ {0} ካልኢት" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/tk.json ================================================ { "narrow" : { "quarter" : { "past" : "{0} ç. öň", "future" : "{0} ç.-den", "current" : "şu çärýek", "previous" : "geçen çärýek", "next" : "indiki çärýek" }, "year" : { "previous" : "geçen ýyl", "next" : "indiki ýyl", "future" : "{0}ý.-dan", "current" : "şu ýyl", "past" : "{0}ý. öň" }, "week" : { "future" : "{0} h-den", "previous" : "geçen hepde", "next" : "indiki hepde", "past" : "{0} h. öň", "current" : "şu hepde" }, "day" : { "current" : "şu gün", "past" : "{0} g. öň", "next" : "ertir", "future" : "{0} g-den", "previous" : "düýn" }, "hour" : { "current" : "şu sagat", "past" : "{0} sag. öň", "future" : "{0} sag-dan" }, "minute" : { "future" : "{0} min-dan", "current" : "şu minut", "past" : "{0} min. öň" }, "month" : { "next" : "indiki aý", "current" : "şu aý", "past" : "{0} aý öň", "future" : "{0} aýdan", "previous" : "geçen aý" }, "now" : "häzir", "second" : { "future" : "{0} sek-dan", "current" : "häzir", "past" : "{0} sek. öň" } }, "short" : { "minute" : { "past" : "{0} min. öň", "future" : "{0} min-dan", "current" : "şu minut" }, "week" : { "current" : "şu hepde", "past" : "{0} hep. öň", "future" : "{0} hep-den", "next" : "indiki hepde", "previous" : "geçen hepde" }, "year" : { "future" : "{0}ý.-dan", "previous" : "geçen ýyl", "next" : "indiki ýyl", "current" : "şu ýyl", "past" : "{0}ý. öň" }, "day" : { "next" : "ertir", "current" : "şu gün", "previous" : "düýn", "past" : "{0} g. öň", "future" : "{0} g-den" }, "hour" : { "future" : "{0} sag-dan", "current" : "şu sagat", "past" : "{0} sag. öň" }, "quarter" : { "current" : "şu çärýek", "future" : "{0} çär.-den", "previous" : "geçen çärýek", "next" : "indiki çärýek", "past" : "{0} çär. öň" }, "second" : { "past" : "{0} sek. öň", "current" : "häzir", "future" : "{0} sek-dan" }, "month" : { "current" : "şu aý", "past" : "{0} aý öň", "future" : "{0} aýdan", "next" : "indiki aý", "previous" : "geçen aý" }, "now" : "häzir" }, "long" : { "hour" : { "future" : "{0} sagatdan", "current" : "şu sagat", "past" : "{0} sagat öň" }, "day" : { "next" : "ertir", "past" : "{0} gün öň", "previous" : "düýn", "future" : "{0} günden", "current" : "şu gün" }, "second" : { "past" : "{0} sekunt öň", "current" : "häzir", "future" : "{0} sekuntdan" }, "week" : { "future" : "{0} hepdeden", "past" : "{0} hepde öň", "current" : "şu hepde", "next" : "indiki hepde", "previous" : "geçen hepde" }, "minute" : { "future" : "{0} minutdan", "past" : "{0} minut öň", "current" : "şu minut" }, "month" : { "future" : "{0} aýdan", "past" : "{0} aý öň", "current" : "şu aý", "previous" : "geçen aý", "next" : "indiki aý" }, "now" : "häzir", "year" : { "current" : "şu ýyl", "next" : "indiki ýyl", "previous" : "geçen ýyl", "future" : "{0} ýyldan", "past" : "{0} ýyl öň" }, "quarter" : { "future" : "{0} çärýekden", "previous" : "geçen çärýek", "past" : "{0} çärýek öň", "next" : "indiki çärýek", "current" : "şu çärýek" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/to.json ================================================ { "narrow" : { "year" : { "future" : "ʻi he taʻu ʻe {0}", "previous" : "taʻu kuoʻosi", "next" : "taʻu kahaʻu", "current" : "taʻú ni", "past" : "taʻu ʻe {0} kuoʻosi" }, "week" : { "past" : "uike ʻe {0} kuoʻosi", "previous" : "uike kuoʻosi", "future" : "ʻi he uike ʻe {0}", "current" : "uiké ni", "next" : "uike kahaʻu" }, "hour" : { "future" : "ʻi he houa ʻe {0}", "current" : "this hour", "past" : "houa ʻe {0} kuoʻosi" }, "quarter" : { "current" : "kuata koʻeni", "previous" : "kuata kuoʻosi", "future" : "ʻi he kuata ʻe {0}", "past" : "kuata ʻe {0} kuoʻosi", "next" : "kuata hoko" }, "second" : { "past" : "sekoni ʻe {0} kuoʻosi", "future" : "ʻi he sekoni ʻe {0}", "current" : "taimí ni" }, "month" : { "current" : "māhiná ni", "future" : "ʻi he māhina ʻe {0}", "past" : "māhina ʻe {0} kuoʻosi", "next" : "māhina kahaʻu", "previous" : "māhina kuoʻosi" }, "day" : { "previous" : "ʻaneafi", "future" : "ʻi he ʻaho ʻe {0}", "next" : "ʻapongipongi", "current" : "ʻahó ni", "past" : "ʻaho ʻe {0} kuoʻosi" }, "minute" : { "past" : "miniti ʻe {0} kuoʻosi", "future" : "ʻi he miniti ʻe {0}", "current" : "this minute" }, "now" : "taimí ni" }, "long" : { "quarter" : { "next" : "kuata hoko", "current" : "kuata koʻeni", "previous" : "kuata kuoʻosi", "past" : "kuata ʻe {0} kuoʻosi", "future" : "ʻi he kuata ʻe {0}" }, "now" : "taimí ni", "year" : { "previous" : "taʻu kuoʻosi", "past" : "taʻu ʻe {0} kuoʻosi", "future" : "ʻi he taʻu ʻe {0}", "next" : "taʻu kahaʻu", "current" : "taʻú ni" }, "month" : { "previous" : "māhina kuoʻosi", "current" : "māhiná ni", "future" : "ʻi he māhina ʻe {0}", "past" : "māhina ʻe {0} kuoʻosi", "next" : "māhina kahaʻu" }, "day" : { "previous" : "ʻaneafi", "future" : "ʻi he ʻaho ʻe {0}", "next" : "ʻapongipongi", "past" : "ʻaho ʻe {0} kuoʻosi", "current" : "ʻahó ni" }, "second" : { "future" : "ʻi he sekoni ʻe {0}", "current" : "taimí ni", "past" : "sekoni ʻe {0} kuoʻosi" }, "week" : { "past" : "uike ʻe {0} kuoʻosi", "current" : "uiké ni", "next" : "uike kahaʻu", "previous" : "uike kuoʻosi", "future" : "ʻi he uike ʻe {0}" }, "hour" : { "past" : "houa ʻe {0} kuoʻosi", "future" : "ʻi he houa ʻe {0}", "current" : "this hour" }, "minute" : { "current" : "this minute", "future" : "ʻi he miniti ʻe {0}", "past" : "miniti ʻe {0} kuoʻosi" } }, "short" : { "second" : { "current" : "taimí ni", "past" : "sekoni ʻe {0} kuoʻosi", "future" : "ʻi he sekoni ʻe {0}" }, "now" : "taimí ni", "month" : { "future" : "ʻi he māhina ʻe {0}", "current" : "māhiná ni", "past" : "māhina ʻe {0} kuoʻosi", "previous" : "māhina kuoʻosi", "next" : "māhina kahaʻu" }, "day" : { "previous" : "ʻaneafi", "current" : "ʻahó ni", "next" : "ʻapongipongi", "future" : "ʻi he ʻaho ʻe {0}", "past" : "ʻaho ʻe {0} kuoʻosi" }, "minute" : { "current" : "this minute", "future" : "ʻi he miniti ʻe {0}", "past" : "miniti ʻe {0} kuoʻosi" }, "quarter" : { "past" : "kuata ʻe {0} kuoʻosi", "previous" : "kuata kuoʻosi", "current" : "kuata koʻeni", "future" : "ʻi he kuata ʻe {0}", "next" : "kuata hoko" }, "hour" : { "current" : "this hour", "future" : "ʻi he houa ʻe {0}", "past" : "houa ʻe {0} kuoʻosi" }, "week" : { "future" : "ʻi he uike ʻe {0}", "previous" : "uike kuoʻosi", "next" : "uike kahaʻu", "past" : "uike ʻe {0} kuoʻosi", "current" : "uiké ni" }, "year" : { "next" : "taʻu kahaʻu", "current" : "taʻú ni", "past" : "taʻu ʻe {0} kuoʻosi", "future" : "ʻi he taʻu ʻe {0}", "previous" : "taʻu kuoʻosi" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/tr.json ================================================ { "narrow" : { "day" : { "next" : "yarın", "future" : "{0} gün sonra", "previous" : "dün", "current" : "bugün", "past" : "{0} gün önce" }, "quarter" : { "next" : "gelecek çeyrek", "past" : "{0} çyr. önce", "future" : "{0} çyr. sonra", "previous" : "geçen çeyrek", "current" : "bu çeyrek" }, "hour" : { "past" : "{0} sa. önce", "future" : "{0} sa. sonra", "current" : "bu saat" }, "year" : { "next" : "gelecek yıl", "past" : "{0} yıl önce", "current" : "bu yıl", "previous" : "geçen yıl", "future" : "{0} yıl sonra" }, "now" : "şimdi", "month" : { "current" : "bu ay", "future" : "{0} ay sonra", "past" : "{0} ay önce", "next" : "gelecek ay", "previous" : "geçen ay" }, "week" : { "previous" : "geçen hafta", "current" : "bu hafta", "past" : "{0} hf. önce", "future" : "{0} hf. sonra", "next" : "gelecek hafta" }, "minute" : { "current" : "bu dakika", "past" : "{0} dk. önce", "future" : "{0} dk. sonra" }, "second" : { "current" : "şimdi", "past" : "{0} sn. önce", "future" : "{0} sn. sonra" } }, "long" : { "day" : { "previous" : "dün", "current" : "bugün", "next" : "yarın", "past" : "{0} gün önce", "future" : "{0} gün sonra" }, "week" : { "current" : "bu hafta", "future" : "{0} hafta sonra", "past" : "{0} hafta önce", "previous" : "geçen hafta", "next" : "gelecek hafta" }, "minute" : { "current" : "bu dakika", "past" : "{0} dakika önce", "future" : "{0} dakika sonra" }, "month" : { "future" : "{0} ay sonra", "next" : "gelecek ay", "previous" : "geçen ay", "current" : "bu ay", "past" : "{0} ay önce" }, "hour" : { "current" : "bu saat", "past" : "{0} saat önce", "future" : "{0} saat sonra" }, "year" : { "past" : "{0} yıl önce", "future" : "{0} yıl sonra", "previous" : "geçen yıl", "next" : "gelecek yıl", "current" : "bu yıl" }, "second" : { "current" : "şimdi", "past" : "{0} saniye önce", "future" : "{0} saniye sonra" }, "now" : "şimdi", "quarter" : { "past" : "{0} çeyrek önce", "current" : "bu çeyrek", "previous" : "geçen çeyrek", "future" : "{0} çeyrek sonra", "next" : "gelecek çeyrek" } }, "short" : { "quarter" : { "future" : "{0} çyr. sonra", "next" : "gelecek çeyrek", "previous" : "geçen çeyrek", "current" : "bu çeyrek", "past" : "{0} çyr. önce" }, "minute" : { "past" : "{0} dk. önce", "current" : "bu dakika", "future" : "{0} dk. sonra" }, "year" : { "next" : "gelecek yıl", "future" : "{0} yıl sonra", "current" : "bu yıl", "past" : "{0} yıl önce", "previous" : "geçen yıl" }, "second" : { "past" : "{0} sn. önce", "future" : "{0} sn. sonra", "current" : "şimdi" }, "hour" : { "past" : "{0} sa. önce", "current" : "bu saat", "future" : "{0} sa. sonra" }, "now" : "şimdi", "month" : { "previous" : "geçen ay", "next" : "gelecek ay", "past" : "{0} ay önce", "current" : "bu ay", "future" : "{0} ay sonra" }, "week" : { "previous" : "geçen hafta", "current" : "bu hafta", "next" : "gelecek hafta", "past" : "{0} hf. önce", "future" : "{0} hf. sonra" }, "day" : { "next" : "yarın", "past" : "{0} gün önce", "future" : "{0} gün sonra", "previous" : "dün", "current" : "bugün" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ug.json ================================================ { "narrow" : { "quarter" : { "current" : "this quarter", "past" : "-{0} Q", "next" : "next quarter", "future" : "+{0} Q", "previous" : "last quarter" }, "day" : { "next" : "ئەتە", "previous" : "تۈنۈگۈن", "future" : "{0} كۈندىن كېيىن", "past" : "{0} كۈن ئىلگىرى", "current" : "بۈگۈن" }, "year" : { "past" : "{0} يىل ئىلگىرى", "previous" : "ئۆتكەن يىل", "future" : "{0} يىلدىن كېيىن", "next" : "كېلەر يىل", "current" : "بۇ يىل" }, "minute" : { "past" : "{0} مىنۇت ئىلگىرى", "future" : "{0} مىنۇتتىن كېيىن", "current" : "this minute" }, "now" : "now", "week" : { "current" : "بۇ ھەپتە", "previous" : "ئۆتكەن ھەپتە", "past" : "{0} ھەپتە ئىلگىرى", "next" : "كېلەر ھەپتە", "future" : "{0} ھەپتىدىن كېيىن" }, "second" : { "past" : "{0} سېكۇنت ئىلگىرى", "future" : "{0} سېكۇنتتىن كېيىن", "current" : "now" }, "month" : { "future" : "{0} ئايدىن كېيىن", "current" : "بۇ ئاي", "past" : "{0} ئاي ئىلگىرى", "previous" : "ئۆتكەن ئاي", "next" : "كېلەر ئاي" }, "hour" : { "current" : "this hour", "past" : "{0} سائەت ئىلگىرى", "future" : "{0} سائەتتىن كېيىن" } }, "long" : { "year" : { "next" : "كېلەر يىل", "previous" : "ئۆتكەن يىل", "past" : "{0} يىل ئىلگىرى", "future" : "{0} يىلدىن كېيىن", "current" : "بۇ يىل" }, "now" : "now", "quarter" : { "next" : "next quarter", "past" : "-{0} Q", "future" : "+{0} Q", "current" : "this quarter", "previous" : "last quarter" }, "month" : { "past" : "{0} ئاي ئىلگىرى", "next" : "كېلەر ئاي", "previous" : "ئۆتكەن ئاي", "future" : "{0} ئايدىن كېيىن", "current" : "بۇ ئاي" }, "second" : { "current" : "now", "future" : "{0} سېكۇنتتىن كېيىن", "past" : "{0} سېكۇنت ئىلگىرى" }, "week" : { "past" : "{0} ھەپتە ئىلگىرى", "previous" : "ئۆتكەن ھەپتە", "future" : "{0} ھەپتىدىن كېيىن", "next" : "كېلەر ھەپتە", "current" : "بۇ ھەپتە" }, "day" : { "next" : "ئەتە", "future" : "{0} كۈندىن كېيىن", "previous" : "تۈنۈگۈن", "current" : "بۈگۈن", "past" : "{0} كۈن ئىلگىرى" }, "minute" : { "current" : "this minute", "past" : "{0} مىنۇت ئىلگىرى", "future" : "{0} مىنۇتتىن كېيىن" }, "hour" : { "past" : "{0} سائەت ئىلگىرى", "future" : "{0} سائەتتىن كېيىن", "current" : "this hour" } }, "short" : { "minute" : { "current" : "this minute", "past" : "{0} مىنۇت ئىلگىرى", "future" : "{0} مىنۇتتىن كېيىن" }, "week" : { "future" : "{0} ھەپتىدىن كېيىن", "previous" : "ئۆتكەن ھەپتە", "next" : "كېلەر ھەپتە", "current" : "بۇ ھەپتە", "past" : "{0} ھەپتە ئىلگىرى" }, "year" : { "current" : "بۇ يىل", "previous" : "ئۆتكەن يىل", "future" : "{0} يىلدىن كېيىن", "past" : "{0} يىل ئىلگىرى", "next" : "كېلەر يىل" }, "month" : { "next" : "كېلەر ئاي", "past" : "{0} ئاي ئىلگىرى", "future" : "{0} ئايدىن كېيىن", "previous" : "ئۆتكەن ئاي", "current" : "بۇ ئاي" }, "quarter" : { "next" : "next quarter", "past" : "-{0} Q", "previous" : "last quarter", "current" : "this quarter", "future" : "+{0} Q" }, "day" : { "current" : "بۈگۈن", "previous" : "تۈنۈگۈن", "past" : "{0} كۈن ئىلگىرى", "next" : "ئەتە", "future" : "{0} كۈندىن كېيىن" }, "hour" : { "current" : "this hour", "past" : "{0} سائەت ئىلگىرى", "future" : "{0} سائەتتىن كېيىن" }, "second" : { "future" : "{0} سېكۇنتتىن كېيىن", "current" : "now", "past" : "{0} سېكۇنت ئىلگىرى" }, "now" : "now" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/uk.json ================================================ { "long" : { "day" : { "future" : { "many" : "через {0} днів", "one" : "через {0} день", "few" : "через {0} дні", "other" : "через {0} дня" }, "previous" : "учора", "current" : "сьогодні", "past" : { "many" : "{0} днів тому", "other" : "{0} дня тому", "few" : "{0} дні тому", "one" : "{0} день тому" }, "next" : "завтра" }, "minute" : { "current" : "цієї хвилини", "past" : { "one" : "{0} хвилину тому", "many" : "{0} хвилин тому", "other" : "{0} хвилини тому" }, "future" : { "many" : "через {0} хвилин", "one" : "через {0} хвилину", "other" : "через {0} хвилини" } }, "now" : "зараз", "second" : { "past" : { "one" : "{0} секунду тому", "other" : "{0} секунди тому", "many" : "{0} секунд тому" }, "future" : { "one" : "через {0} секунду", "other" : "через {0} секунди", "many" : "через {0} секунд" }, "current" : "зараз" }, "week" : { "current" : "цього тижня", "next" : "наступного тижня", "previous" : "минулого тижня", "future" : { "many" : "через {0} тижнів", "few" : "через {0} тижні", "other" : "через {0} тижня", "one" : "через {0} тиждень" }, "past" : { "many" : "{0} тижнів тому", "other" : "{0} тижня тому", "few" : "{0} тижні тому", "one" : "{0} тиждень тому" } }, "quarter" : { "previous" : "минулого кварталу", "past" : { "few" : "{0} квартали тому", "one" : "{0} квартал тому", "other" : "{0} кварталу тому", "many" : "{0} кварталів тому" }, "future" : { "one" : "через {0} квартал", "many" : "через {0} кварталів", "other" : "через {0} кварталу", "few" : "через {0} квартали" }, "current" : "цього кварталу", "next" : "наступного кварталу" }, "month" : { "current" : "цього місяця", "next" : "наступного місяця", "previous" : "минулого місяця", "past" : { "many" : "{0} місяців тому", "one" : "{0} місяць тому", "few" : "{0} місяці тому", "other" : "{0} місяця тому" }, "future" : { "many" : "через {0} місяців", "one" : "через {0} місяць", "few" : "через {0} місяці", "other" : "через {0} місяця" } }, "hour" : { "future" : { "many" : "через {0} годин", "one" : "через {0} годину", "other" : "через {0} години" }, "current" : "цієї години", "past" : { "one" : "{0} годину тому", "many" : "{0} годин тому", "other" : "{0} години тому" } }, "year" : { "past" : { "other" : "{0} року тому", "many" : "{0} років тому", "few" : "{0} роки тому", "one" : "{0} рік тому" }, "future" : { "one" : "через {0} рік", "other" : "через {0} року", "many" : "через {0} років", "few" : "через {0} роки" }, "previous" : "торік", "current" : "цього року", "next" : "наступного року" } }, "short" : { "quarter" : { "future" : "через {0} кв.", "next" : "наступного кв.", "previous" : "минулого кв.", "current" : "цього кв.", "past" : "{0} кв. тому" }, "minute" : { "current" : "цієї хвилини", "past" : "{0} хв тому", "future" : "через {0} хв" }, "year" : { "next" : "наступного року", "future" : "через {0} р.", "current" : "цього року", "past" : "{0} р. тому", "previous" : "торік" }, "second" : { "current" : "зараз", "past" : "{0} с тому", "future" : "через {0} с" }, "hour" : { "future" : "через {0} год", "current" : "цієї години", "past" : "{0} год тому" }, "now" : "зараз", "month" : { "previous" : "минулого місяця", "next" : "наступного місяця", "past" : "{0} міс. тому", "current" : "цього місяця", "future" : "через {0} міс." }, "week" : { "previous" : "минулого тижня", "current" : "цього тижня", "next" : "наступного тижня", "past" : "{0} тиж. тому", "future" : "через {0} тиж." }, "day" : { "next" : "завтра", "past" : "{0} дн. тому", "future" : "через {0} дн.", "previous" : "учора", "current" : "сьогодні" } }, "narrow" : { "week" : { "previous" : "минулого тижня", "current" : "цього тижня", "past" : "{0} тиж. тому", "future" : "за {0} тиж.", "next" : "наступного тижня" }, "quarter" : { "next" : "наступного кв.", "past" : "{0} кв. тому", "future" : "за {0} кв.", "previous" : "минулого кв.", "current" : "цього кв." }, "minute" : { "future" : "за {0} хв", "current" : "цієї хвилини", "past" : "{0} хв тому" }, "day" : { "previous" : "учора", "current" : "сьогодні", "next" : "завтра", "past" : { "other" : "-{0} дн.", "one" : "{0} д. тому" }, "future" : "за {0} д." }, "year" : { "next" : "наступного року", "past" : "{0} р. тому", "current" : "цього року", "previous" : "торік", "future" : "за {0} р." }, "second" : { "future" : "за {0} с", "current" : "зараз", "past" : "{0} с тому" }, "month" : { "current" : "цього місяця", "future" : "за {0} міс.", "past" : "{0} міс. тому", "next" : "наступного місяця", "previous" : "минулого місяця" }, "now" : "зараз", "hour" : { "past" : "{0} год тому", "current" : "цієї години", "future" : "за {0} год" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ur.json ================================================ { "narrow" : { "quarter" : { "previous" : "گزشتہ سہ ماہی", "current" : "اس سہ ماہی", "next" : "اگلے سہ ماہی", "past" : "{0} سہ ماہی پہلے", "future" : "{0} سہ ماہی میں" }, "month" : { "past" : "{0} ماہ پہلے", "next" : "اگلے مہینہ", "future" : "{0} ماہ میں", "previous" : "پچھلے مہینہ", "current" : "اس مہینہ" }, "week" : { "next" : "اگلے ہفتہ", "previous" : "پچھلے ہفتہ", "past" : { "one" : "{0} ہفتہ پہلے", "other" : "{0} ہفتے پہلے" }, "future" : { "other" : "{0} ہفتے میں", "one" : "{0} ہفتہ میں" }, "current" : "اس ہفتہ" }, "second" : { "future" : "{0} سیکنڈ میں", "current" : "اب", "past" : "{0} سیکنڈ پہلے" }, "day" : { "current" : "آج", "future" : { "one" : "{0} دن میں", "other" : "{0} دنوں میں" }, "next" : "آئندہ کل", "previous" : "گزشتہ کل", "past" : "{0} دن پہلے" }, "now" : "اب", "hour" : { "current" : "اس گھنٹے", "future" : { "one" : "{0} گھنٹہ میں", "other" : "{0} گھنٹوں میں" }, "past" : { "one" : "{0} گھنٹہ پہلے", "other" : "{0} گھنٹے پہلے" } }, "minute" : { "past" : "{0} منٹ پہلے", "current" : "اس منٹ", "future" : "{0} منٹ میں" }, "year" : { "next" : "اگلے سال", "past" : "{0} سال پہلے", "future" : "{0} سال میں", "previous" : "گزشتہ سال", "current" : "اس سال" } }, "short" : { "minute" : { "current" : "اس منٹ", "past" : "{0} منٹ پہلے", "future" : "{0} منٹ میں" }, "month" : { "current" : "اس مہینہ", "past" : "{0} ماہ قبل", "future" : "{0} ماہ میں", "next" : "اگلے مہینہ", "previous" : "پچھلے مہینہ" }, "week" : { "current" : "اس ہفتہ", "past" : "{0} ہفتے پہلے", "future" : "{0} ہفتے میں", "next" : "اگلے ہفتہ", "previous" : "پچھلے ہفتہ" }, "hour" : { "past" : "{0} گھنٹے پہلے", "current" : "اس گھنٹے", "future" : "{0} گھنٹے میں" }, "day" : { "next" : "آئندہ کل", "current" : "آج", "previous" : "گزشتہ کل", "past" : { "other" : "{0} دنوں پہلے", "one" : "{0} دن پہلے" }, "future" : { "one" : "{0} دن میں", "other" : "{0} دنوں میں" } }, "second" : { "future" : "{0} سیکنڈ میں", "current" : "اب", "past" : "{0} سیکنڈ پہلے" }, "now" : "اب", "year" : { "current" : "اس سال", "future" : "{0} سال میں", "past" : "{0} سال پہلے", "next" : "اگلے سال", "previous" : "گزشتہ سال" }, "quarter" : { "current" : "اس سہ ماہی", "future" : "{0} سہ ماہی میں", "previous" : "گزشتہ سہ ماہی", "next" : "اگلے سہ ماہی", "past" : "{0} سہ ماہی قبل" } }, "long" : { "day" : { "previous" : "گزشتہ کل", "past" : { "other" : "{0} دنوں پہلے", "one" : "{0} دن پہلے" }, "future" : { "one" : "{0} دن میں", "other" : "{0} دنوں میں" }, "next" : "آئندہ کل", "current" : "آج" }, "hour" : { "past" : { "other" : "{0} گھنٹے پہلے", "one" : "{0} گھنٹہ پہلے" }, "future" : "{0} گھنٹے میں", "current" : "اس گھنٹے" }, "month" : { "next" : "اگلا مہینہ", "current" : "اس مہینہ", "past" : { "one" : "{0} مہینہ پہلے", "other" : "{0} مہینے پہلے" }, "previous" : "پچھلا مہینہ", "future" : { "one" : "{0} مہینہ میں", "other" : "{0} مہینے میں" } }, "quarter" : { "current" : "اس سہ ماہی", "past" : "{0} سہ ماہی پہلے", "previous" : "گزشتہ سہ ماہی", "future" : "{0} سہ ماہی میں", "next" : "اگلے سہ ماہی" }, "year" : { "past" : "{0} سال پہلے", "previous" : "گزشتہ سال", "next" : "اگلے سال", "future" : "{0} سال میں", "current" : "اس سال" }, "minute" : { "current" : "اس منٹ", "future" : "{0} منٹ میں", "past" : "{0} منٹ پہلے" }, "second" : { "future" : "{0} سیکنڈ میں", "current" : "اب", "past" : "{0} سیکنڈ پہلے" }, "week" : { "future" : { "one" : "{0} ہفتہ میں", "other" : "{0} ہفتے میں" }, "previous" : "پچھلے ہفتہ", "current" : "اس ہفتہ", "next" : "اگلے ہفتہ", "past" : { "one" : "{0} ہفتہ پہلے", "other" : "{0} ہفتے پہلے" } }, "now" : "اب" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/ur_IN.json ================================================ { "narrow" : { "quarter" : { "previous" : "گزشتہ سہ ماہی", "current" : "اس سہ ماہی", "next" : "اگلے سہ ماہی", "past" : "{0} سہ ماہی پہلے", "future" : "{0} سہ ماہی میں" }, "month" : { "past" : "{0} ماہ قبل", "next" : "اگلے مہینہ", "future" : "{0} ماہ میں", "previous" : "پچھلے مہینہ", "current" : "اس مہینہ" }, "week" : { "next" : "اگلے ہفتہ", "previous" : "پچھلے ہفتہ", "past" : { "other" : "{0} ہفتے قبل", "one" : "{0} ہفتہ قبل" }, "future" : { "one" : "{0} ہفتہ میں", "other" : "{0} ہفتے میں" }, "current" : "اس ہفتہ" }, "second" : { "future" : "{0} سیکنڈ میں", "current" : "اب", "past" : "{0} سیکنڈ قبل" }, "day" : { "current" : "آج", "future" : { "one" : "{0} دن میں", "other" : "{0} دنوں میں" }, "next" : "آئندہ کل", "previous" : "گزشتہ کل", "past" : "{0} دن قبل" }, "now" : "اب", "hour" : { "future" : { "other" : "{0} گھنٹوں میں", "one" : "{0} گھنٹہ میں" }, "past" : { "one" : "{0} گھنٹہ قبل", "other" : "{0} گھنٹے قبل" }, "current" : "اس گھنٹے" }, "minute" : { "future" : "{0} منٹ میں", "current" : "اس منٹ", "past" : "{0} منٹ قبل" }, "year" : { "next" : "اگلے سال", "past" : "{0} سال پہلے", "future" : "{0} سال میں", "previous" : "گزشتہ سال", "current" : "اس سال" } }, "long" : { "day" : { "previous" : "گزشتہ کل", "past" : { "other" : "{0} دنوں پہلے", "one" : "{0} دن پہلے" }, "future" : { "one" : "{0} دن میں", "other" : "{0} دنوں میں" }, "next" : "آئندہ کل", "current" : "آج" }, "hour" : { "past" : { "other" : "{0} گھنٹے پہلے", "one" : "{0} گھنٹہ پہلے" }, "future" : "{0} گھنٹے میں", "current" : "اس گھنٹے" }, "month" : { "future" : "{0} ماہ میں", "past" : "{0} ماہ قبل", "next" : "اگلے ماہ", "current" : "اس ماہ", "previous" : "گزشتہ ماہ" }, "quarter" : { "previous" : "گزشتہ سہ ماہی", "next" : "اگلے سہ ماہی", "past" : "{0} سہ ماہی پہلے", "future" : "{0} سہ ماہی میں", "current" : "اس سہ ماہی" }, "year" : { "past" : "{0} سال پہلے", "previous" : "گزشتہ سال", "next" : "اگلے سال", "future" : { "one" : "{0} سال میں", "other" : "{0} سالوں میں" }, "current" : "اس سال" }, "minute" : { "current" : "اس منٹ", "future" : "{0} منٹ میں", "past" : "{0} منٹ قبل" }, "second" : { "future" : "{0} سیکنڈ میں", "current" : "اب", "past" : "{0} سیکنڈ قبل" }, "week" : { "future" : { "one" : "{0} ہفتہ میں", "other" : "{0} ہفتوں میں" }, "previous" : "گزشتہ ہفتہ", "current" : "اس ہفتہ", "next" : "اگلے ہفتہ", "past" : { "one" : "{0} ہفتہ قبل", "other" : "{0} ہفتے قبل" } }, "now" : "اب" }, "short" : { "minute" : { "future" : "{0} منٹ میں", "current" : "اس منٹ", "past" : "{0} منٹ قبل" }, "month" : { "current" : "اس مہینہ", "past" : "{0} ماہ قبل", "future" : "{0} ماہ میں", "next" : "اگلے مہینہ", "previous" : "پچھلے مہینہ" }, "week" : { "current" : "اس ہفتہ", "past" : "{0} ہفتے قبل", "future" : "{0} ہفتے میں", "next" : "اگلے ہفتہ", "previous" : "پچھلے ہفتہ" }, "hour" : { "current" : "اس گھنٹے", "past" : "{0} گھنٹے قبل", "future" : "{0} گھنٹے میں" }, "day" : { "next" : "آئندہ کل", "current" : "آج", "previous" : "گزشتہ کل", "past" : { "one" : "{0} دن پہلے", "other" : "{0} دنوں پہلے" }, "future" : "{0} دنوں میں" }, "second" : { "future" : "{0} سیکنڈ میں", "current" : "اب", "past" : "{0} سیکنڈ قبل" }, "now" : "اب", "year" : { "future" : { "one" : "{0} سال میں", "other" : "{0} سالوں میں" }, "previous" : "گزشتہ سال", "next" : "اگلے سال", "current" : "اس سال", "past" : { "other" : "{0} سالوں پہلے", "one" : "{0} سال پہلے" } }, "quarter" : { "current" : "اس سہ ماہی", "future" : "{0} سہ ماہی میں", "previous" : "گزشتہ سہ ماہی", "next" : "اگلے سہ ماہی", "past" : "{0} سہ ماہی قبل" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/uz.json ================================================ { "narrow" : { "quarter" : { "past" : "{0} chorak oldin", "future" : "{0} chorakdan keyin", "current" : "shu chorak", "previous" : "o‘tgan chorak", "next" : "keyingi chorak" }, "year" : { "previous" : "oʻtgan yil", "next" : "keyingi yil", "future" : "{0} yildan keyin", "current" : "bu yil", "past" : "{0} yil oldin" }, "week" : { "future" : "{0} haftadan keyin", "previous" : "o‘tgan hafta", "next" : "keyingi hafta", "past" : "{0} hafta oldin", "current" : "shu hafta" }, "day" : { "current" : "bugun", "past" : "{0} kun oldin", "next" : "ertaga", "future" : "{0} kundan keyin", "previous" : "kecha" }, "hour" : { "current" : "shu soatda", "past" : "{0} soat oldin", "future" : "{0} soatdan keyin" }, "minute" : { "future" : "{0} daqiqadan keyin", "current" : "shu daqiqada", "past" : "{0} daqiqa oldin" }, "month" : { "next" : "keyingi oy", "current" : "shu oy", "past" : "{0} oy oldin", "future" : "{0} oydan keyin", "previous" : "o‘tgan oy" }, "now" : "hozir", "second" : { "future" : "{0} soniyadan keyin", "current" : "hozir", "past" : "{0} soniya oldin" } }, "short" : { "minute" : { "past" : "{0} daqiqa oldin", "future" : "{0} daqiqadan keyin", "current" : "shu daqiqada" }, "week" : { "current" : "shu hafta", "past" : "{0} hafta oldin", "future" : "{0} haftadan keyin", "next" : "keyingi hafta", "previous" : "o‘tgan hafta" }, "year" : { "future" : "{0} yildan keyin", "previous" : "oʻtgan yil", "next" : "keyingi yil", "current" : "bu yil", "past" : "{0} yil oldin" }, "day" : { "next" : "ertaga", "current" : "bugun", "previous" : "kecha", "past" : "{0} kun oldin", "future" : "{0} kundan keyin" }, "hour" : { "future" : "{0} soatdan keyin", "current" : "shu soatda", "past" : "{0} soat oldin" }, "quarter" : { "current" : "shu chorak", "future" : "{0} chorakdan keyin", "previous" : "o‘tgan chorak", "next" : "keyingi chorak", "past" : "{0} chorak oldin" }, "second" : { "past" : "{0} soniya oldin", "current" : "hozir", "future" : "{0} soniyadan keyin" }, "month" : { "current" : "shu oy", "past" : "{0} oy oldin", "future" : "{0} oydan keyin", "next" : "keyingi oy", "previous" : "o‘tgan oy" }, "now" : "hozir" }, "long" : { "hour" : { "future" : "{0} soatdan keyin", "current" : "shu soatda", "past" : "{0} soat oldin" }, "day" : { "next" : "ertaga", "past" : "{0} kun oldin", "previous" : "kecha", "future" : "{0} kundan keyin", "current" : "bugun" }, "second" : { "past" : "{0} soniya oldin", "current" : "hozir", "future" : "{0} soniyadan keyin" }, "week" : { "future" : "{0} haftadan keyin", "past" : "{0} hafta oldin", "current" : "shu hafta", "next" : "keyingi hafta", "previous" : "o‘tgan hafta" }, "minute" : { "future" : "{0} daqiqadan keyin", "past" : "{0} daqiqa oldin", "current" : "shu daqiqada" }, "month" : { "future" : "{0} oydan keyin", "past" : "{0} oy oldin", "current" : "shu oy", "previous" : "o‘tgan oy", "next" : "keyingi oy" }, "now" : "hozir", "year" : { "current" : "shu yil", "next" : "keyingi yil", "previous" : "o‘tgan yil", "future" : "{0} yildan keyin", "past" : "{0} yil oldin" }, "quarter" : { "future" : "{0} chorakdan keyin", "previous" : "o‘tgan chorak", "past" : "{0} chorak oldin", "next" : "keyingi chorak", "current" : "shu chorak" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/uz_Cyrl.json ================================================ { "narrow" : { "quarter" : { "past" : "-{0} Q", "future" : "+{0} Q", "current" : "this quarter", "previous" : "last quarter", "next" : "next quarter" }, "year" : { "previous" : "ўтган йил", "next" : "кейинги йил", "future" : "{0} йилдан сўнг", "current" : "бу йил", "past" : "{0} йил аввал" }, "week" : { "future" : "{0} ҳафтадан сўнг", "previous" : "ўтган ҳафта", "next" : "кейинги ҳафта", "past" : "{0} ҳафта олдин", "current" : "бу ҳафта" }, "day" : { "current" : "бугун", "past" : "{0} кун олдин", "next" : "эртага", "future" : "{0} кундан сўнг", "previous" : "кеча" }, "hour" : { "current" : "this hour", "past" : "{0} соат олдин", "future" : "{0} соатдан сўнг" }, "minute" : { "future" : "{0} дақиқадан сўнг", "current" : "this minute", "past" : "{0} дақиқа олдин" }, "month" : { "next" : "кейинги ой", "current" : "бу ой", "past" : "{0} ой аввал", "future" : "{0} ойдан сўнг", "previous" : "ўтган ой" }, "now" : "ҳозир", "second" : { "future" : "{0} сониядан сўнг", "current" : "ҳозир", "past" : "{0} сония олдин" } }, "long" : { "hour" : { "future" : "{0} соатдан сўнг", "current" : "this hour", "past" : "{0} соат олдин" }, "day" : { "next" : "эртага", "past" : "{0} кун олдин", "previous" : "кеча", "future" : "{0} кундан сўнг", "current" : "бугун" }, "second" : { "past" : "{0} сония олдин", "current" : "ҳозир", "future" : "{0} сониядан сўнг" }, "week" : { "future" : "{0} ҳафтадан сўнг", "past" : "{0} ҳафта олдин", "current" : "бу ҳафта", "next" : "кейинги ҳафта", "previous" : "ўтган ҳафта" }, "minute" : { "future" : "{0} дақиқадан сўнг", "past" : "{0} дақиқа олдин", "current" : "this minute" }, "month" : { "future" : "{0} ойдан сўнг", "past" : "{0} ой аввал", "current" : "бу ой", "previous" : "ўтган ой", "next" : "кейинги ой" }, "now" : "ҳозир", "year" : { "current" : "бу йил", "next" : "кейинги йил", "previous" : "ўтган йил", "future" : "{0} йилдан сўнг", "past" : "{0} йил аввал" }, "quarter" : { "future" : "+{0} Q", "previous" : "last quarter", "past" : "-{0} Q", "next" : "next quarter", "current" : "this quarter" } }, "short" : { "minute" : { "past" : "{0} дақиқа олдин", "future" : "{0} дақиқадан сўнг", "current" : "this minute" }, "week" : { "current" : "бу ҳафта", "past" : "{0} ҳафта олдин", "future" : "{0} ҳафтадан сўнг", "next" : "кейинги ҳафта", "previous" : "ўтган ҳафта" }, "year" : { "future" : "{0} йилдан сўнг", "previous" : "ўтган йил", "next" : "кейинги йил", "current" : "бу йил", "past" : "{0} йил аввал" }, "day" : { "next" : "эртага", "current" : "бугун", "previous" : "кеча", "past" : "{0} кун олдин", "future" : "{0} кундан сўнг" }, "hour" : { "future" : "{0} соатдан сўнг", "current" : "this hour", "past" : "{0} соат олдин" }, "quarter" : { "current" : "this quarter", "future" : "+{0} Q", "previous" : "last quarter", "next" : "next quarter", "past" : "-{0} Q" }, "second" : { "past" : "{0} сония олдин", "current" : "ҳозир", "future" : "{0} сониядан сўнг" }, "month" : { "current" : "бу ой", "past" : "{0} ой аввал", "future" : "{0} ойдан сўнг", "next" : "кейинги ой", "previous" : "ўтган ой" }, "now" : "ҳозир" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/vi.json ================================================ { "narrow" : { "week" : { "past" : "{0} tuần trước", "future" : "sau {0} tuần nữa", "current" : "tuần này", "previous" : "tuần trước", "next" : "tuần sau" }, "hour" : { "current" : "giờ này", "past" : "{0} giờ trước", "future" : "sau {0} giờ nữa" }, "now" : "bây giờ", "day" : { "next" : "Ngày mai", "current" : "Hôm nay", "past" : "{0} ngày trước", "future" : "sau {0} ngày nữa", "previous" : "Hôm qua" }, "minute" : { "future" : "sau {0} phút nữa", "current" : "phút này", "past" : "{0} phút trước" }, "quarter" : { "next" : "quý sau", "future" : "sau {0} quý nữa", "previous" : "quý trước", "current" : "quý này", "past" : "{0} quý trước" }, "second" : { "future" : "sau {0} giây nữa", "current" : "bây giờ", "past" : "{0} giây trước" }, "year" : { "next" : "năm sau", "previous" : "năm ngoái", "current" : "năm nay", "future" : "sau {0} năm nữa", "past" : "{0} năm trước" }, "month" : { "previous" : "tháng trước", "next" : "tháng sau", "future" : "sau {0} tháng nữa", "current" : "tháng này", "past" : "{0} tháng trước" } }, "short" : { "hour" : { "future" : "sau {0} giờ nữa", "current" : "giờ này", "past" : "{0} giờ trước" }, "now" : "bây giờ", "quarter" : { "future" : "sau {0} quý nữa", "previous" : "quý trước", "next" : "quý sau", "current" : "quý này", "past" : "{0} quý trước" }, "day" : { "current" : "Hôm nay", "past" : "{0} ngày trước", "future" : "sau {0} ngày nữa", "next" : "Ngày mai", "previous" : "Hôm qua" }, "week" : { "current" : "tuần này", "future" : "sau {0} tuần nữa", "previous" : "tuần trước", "next" : "tuần sau", "past" : "{0} tuần trước" }, "minute" : { "past" : "{0} phút trước", "future" : "sau {0} phút nữa", "current" : "phút này" }, "second" : { "past" : "{0} giây trước", "current" : "bây giờ", "future" : "sau {0} giây nữa" }, "month" : { "current" : "tháng này", "future" : "sau {0} tháng nữa", "past" : "{0} tháng trước", "next" : "tháng sau", "previous" : "tháng trước" }, "year" : { "past" : "{0} năm trước", "future" : "sau {0} năm nữa", "previous" : "năm ngoái", "current" : "năm nay", "next" : "năm sau" } }, "long" : { "month" : { "current" : "tháng này", "next" : "tháng sau", "previous" : "tháng trước", "future" : "sau {0} tháng nữa", "past" : "{0} tháng trước" }, "quarter" : { "future" : "sau {0} quý nữa", "next" : "quý sau", "past" : "{0} quý trước", "previous" : "quý trước", "current" : "quý này" }, "minute" : { "future" : "sau {0} phút nữa", "past" : "{0} phút trước", "current" : "phút này" }, "day" : { "future" : "sau {0} ngày nữa", "past" : "{0} ngày trước", "current" : "Hôm nay", "previous" : "Hôm qua", "next" : "Ngày mai" }, "year" : { "previous" : "năm ngoái", "current" : "năm nay", "future" : "sau {0} năm nữa", "next" : "năm sau", "past" : "{0} năm trước" }, "week" : { "future" : "sau {0} tuần nữa", "previous" : "tuần trước", "past" : "{0} tuần trước", "next" : "tuần sau", "current" : "tuần này" }, "second" : { "past" : "{0} giây trước", "current" : "bây giờ", "future" : "sau {0} giây nữa" }, "hour" : { "future" : "sau {0} giờ nữa", "current" : "giờ này", "past" : "{0} giờ trước" }, "now" : "bây giờ" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/wae.json ================================================ { "narrow" : { "week" : { "next" : "next week", "current" : "this week", "previous" : "last week", "past" : { "one" : "vor {0} wuča", "other" : "cor {0} wučä" }, "future" : { "other" : "i {0} wučä", "one" : "i {0} wuča" } }, "hour" : { "past" : { "other" : "vor {0} stunde", "one" : "vor {0} stund" }, "future" : { "other" : "i {0} stunde", "one" : "i {0} stund" }, "current" : "this hour" }, "second" : { "current" : "now", "past" : { "one" : "vor {0} sekund", "other" : "vor {0} sekunde" }, "future" : { "one" : "i {0} sekund", "other" : "i {0} sekunde" } }, "quarter" : { "next" : "next quarter", "past" : "-{0} Q", "current" : "this quarter", "previous" : "last quarter", "future" : "+{0} Q" }, "now" : "now", "day" : { "future" : { "other" : "i {0} täg", "one" : "i {0} tag" }, "current" : "Hitte", "next" : "Móre", "past" : { "one" : "vor {0} tag", "other" : "vor {0} täg" }, "previous" : "Gešter" }, "month" : { "next" : "next month", "past" : "vor {0} mánet", "future" : "I {0} mánet", "previous" : "last month", "current" : "this month" }, "year" : { "previous" : "last year", "past" : { "one" : "vor {0} jár", "other" : "cor {0} jár" }, "current" : "this year", "next" : "next year", "future" : "I {0} jár" }, "minute" : { "past" : { "other" : "vor {0} minüte", "one" : "vor {0} minüta" }, "current" : "this minute", "future" : { "one" : "i {0} minüta", "other" : "i {0} minüte" } } }, "long" : { "day" : { "next" : "Móre", "future" : { "one" : "i {0} tag", "other" : "i {0} täg" }, "current" : "Hitte", "previous" : "Gešter", "past" : { "one" : "vor {0} tag", "other" : "vor {0} täg" } }, "minute" : { "current" : "this minute", "past" : { "other" : "vor {0} minüte", "one" : "vor {0} minüta" }, "future" : { "other" : "i {0} minüte", "one" : "i {0} minüta" } }, "second" : { "current" : "now", "future" : { "other" : "i {0} sekunde", "one" : "i {0} sekund" }, "past" : { "one" : "vor {0} sekund", "other" : "vor {0} sekunde" } }, "month" : { "past" : "vor {0} mánet", "previous" : "last month", "current" : "this month", "next" : "next month", "future" : "I {0} mánet" }, "week" : { "future" : { "other" : "i {0} wučä", "one" : "i {0} wuča" }, "next" : "next week", "current" : "this week", "past" : { "one" : "vor {0} wuča", "other" : "cor {0} wučä" }, "previous" : "last week" }, "now" : "now", "hour" : { "past" : { "one" : "vor {0} stund", "other" : "vor {0} stunde" }, "current" : "this hour", "future" : { "one" : "i {0} stund", "other" : "i {0} stunde" } }, "year" : { "previous" : "last year", "current" : "this year", "next" : "next year", "past" : { "one" : "vor {0} jár", "other" : "cor {0} jár" }, "future" : "I {0} jár" }, "quarter" : { "current" : "this quarter", "future" : "+{0} Q", "past" : "-{0} Q", "previous" : "last quarter", "next" : "next quarter" } }, "short" : { "week" : { "future" : { "other" : "i {0} wučä", "one" : "i {0} wuča" }, "next" : "next week", "current" : "this week", "past" : { "one" : "vor {0} wuča", "other" : "cor {0} wučä" }, "previous" : "last week" }, "minute" : { "current" : "this minute", "future" : { "one" : "i {0} minüta", "other" : "i {0} minüte" }, "past" : { "other" : "vor {0} minüte", "one" : "vor {0} minüta" } }, "day" : { "next" : "Móre", "current" : "Hitte", "previous" : "Gešter", "future" : { "one" : "i {0} tag", "other" : "i {0} täg" }, "past" : { "other" : "vor {0} täg", "one" : "vor {0} tag" } }, "now" : "now", "second" : { "past" : { "one" : "vor {0} sekund", "other" : "vor {0} sekunde" }, "future" : { "one" : "i {0} sekund", "other" : "i {0} sekunde" }, "current" : "now" }, "hour" : { "current" : "this hour", "past" : { "other" : "vor {0} stunde", "one" : "vor {0} stund" }, "future" : { "one" : "i {0} stund", "other" : "i {0} stunde" } }, "month" : { "past" : "vor {0} mánet", "future" : "I {0} mánet", "next" : "next month", "previous" : "last month", "current" : "this month" }, "year" : { "past" : { "one" : "vor {0} jár", "other" : "cor {0} jár" }, "future" : "I {0} jár", "previous" : "last year", "current" : "this year", "next" : "next year" }, "quarter" : { "previous" : "last quarter", "future" : "+{0} Q", "next" : "next quarter", "past" : "-{0} Q", "current" : "this quarter" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/yi.json ================================================ { "long" : { "quarter" : { "next" : "next quarter", "future" : "+{0} Q", "previous" : "last quarter", "past" : "-{0} Q", "current" : "this quarter" }, "month" : { "current" : "דעם חודש", "next" : "קומענדיקן חודש", "previous" : "פֿאַרגאנגענעם חודש", "past" : { "one" : "פֿאַר {0} חודש", "other" : "פֿאַר {0} חדשים" }, "future" : { "one" : "איבער {0} חודש", "other" : "איבער {0} חדשים" } }, "second" : { "future" : "+{0} s", "current" : "now", "past" : "-{0} s" }, "hour" : { "past" : "-{0} h", "current" : "this hour", "future" : "+{0} h" }, "day" : { "future" : { "one" : "אין {0} טאָג אַרום", "other" : "אין {0} טעג אַרום" }, "previous" : "נעכטן", "past" : "-{0} d", "next" : "מארגן", "current" : "היינט" }, "minute" : { "past" : "-{0} min", "future" : "+{0} min", "current" : "this minute" }, "week" : { "past" : "-{0} w", "previous" : "last week", "future" : "+{0} w", "current" : "this week", "next" : "איבער אַכט טאָג" }, "year" : { "next" : "איבער א יאָר", "current" : "הײַ יאָר", "previous" : "פֿאַראַיאָר", "future" : "איבער {0} יאָר", "past" : "פֿאַר {0} יאָר" }, "now" : "now" }, "narrow" : { "year" : { "current" : "הײַ יאָר", "next" : "איבער א יאָר", "past" : "פֿאַר {0} יאָר", "future" : "איבער {0} יאָר", "previous" : "פֿאַראַיאָר" }, "second" : { "past" : "-{0} s", "future" : "+{0} s", "current" : "now" }, "hour" : { "future" : "+{0} h", "current" : "this hour", "past" : "-{0} h" }, "week" : { "current" : "this week", "previous" : "last week", "past" : "-{0} w", "next" : "איבער אַכט טאָג", "future" : "+{0} w" }, "day" : { "current" : "היינט", "next" : "מארגן", "past" : "-{0} d", "previous" : "נעכטן", "future" : { "one" : "אין {0} טאָג אַרום", "other" : "אין {0} טעג אַרום" } }, "month" : { "current" : "דעם חודש", "next" : "קומענדיקן חודש", "past" : { "one" : "פֿאַר {0} חודש", "other" : "פֿאַר {0} חדשים" }, "previous" : "פֿאַרגאנגענעם חודש", "future" : { "one" : "איבער {0} חודש", "other" : "איבער {0} חדשים" } }, "now" : "now", "minute" : { "future" : "+{0} min", "current" : "this minute", "past" : "-{0} min" }, "quarter" : { "previous" : "last quarter", "current" : "this quarter", "past" : "-{0} Q", "future" : "+{0} Q", "next" : "next quarter" } }, "short" : { "month" : { "next" : "קומענדיקן חודש", "past" : { "one" : "פֿאַר {0} חודש", "other" : "פֿאַר {0} חדשים" }, "current" : "דעם חודש", "previous" : "פֿאַרגאנגענעם חודש", "future" : { "one" : "איבער {0} חודש", "other" : "איבער {0} חדשים" } }, "now" : "now", "day" : { "next" : "מארגן", "current" : "היינט", "previous" : "נעכטן", "past" : "-{0} d", "future" : { "other" : "אין {0} טעג אַרום", "one" : "אין {0} טאָג אַרום" } }, "year" : { "previous" : "פֿאַראַיאָר", "current" : "הײַ יאָר", "next" : "איבער א יאָר", "past" : "פֿאַר {0} יאָר", "future" : { "other" : "איבער {0} יאָר", "one" : "איבער א יאָר" } }, "hour" : { "past" : "-{0} h", "future" : "+{0} h", "current" : "this hour" }, "minute" : { "past" : "-{0} min", "future" : "+{0} min", "current" : "this minute" }, "second" : { "current" : "now", "future" : "+{0} s", "past" : "-{0} s" }, "quarter" : { "current" : "this quarter", "future" : "+{0} Q", "previous" : "last quarter", "next" : "next quarter", "past" : "-{0} Q" }, "week" : { "future" : "+{0} w", "previous" : "last week", "next" : "איבער אַכט טאָג", "past" : "-{0} w", "current" : "this week" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/yue_Hans.json ================================================ { "narrow" : { "day" : { "next" : "听日", "future" : "{0} 日后", "previous" : "寻日", "current" : "今日", "past" : "{0} 日前" }, "quarter" : { "next" : "下季", "past" : "{0} 季前", "future" : "{0} 季后", "previous" : "上季", "current" : "今季" }, "hour" : { "future" : "{0} 小时后", "current" : "呢个小时", "past" : "{0} 小时前" }, "year" : { "next" : "下年", "past" : "{0} 年前", "current" : "今年", "previous" : "旧年", "future" : "{0} 年后" }, "now" : "宜家", "month" : { "current" : "今个月", "future" : "{0} 个月后", "past" : "{0} 个月前", "next" : "下个月", "previous" : "上个月" }, "week" : { "previous" : "上星期", "current" : "今个星期", "past" : "{0} 个星期前", "future" : "{0} 个星期后", "next" : "下星期" }, "minute" : { "past" : "{0} 分钟前", "future" : "{0} 分钟后", "current" : "呢分钟" }, "second" : { "past" : "{0} 秒前", "current" : "宜家", "future" : "{0} 秒后" } }, "long" : { "day" : { "previous" : "寻日", "current" : "今日", "next" : "听日", "past" : "{0} 日前", "future" : "{0} 日后" }, "week" : { "current" : "今个星期", "future" : "{0} 个星期后", "past" : "{0} 个星期前", "previous" : "上星期", "next" : "下星期" }, "minute" : { "current" : "呢分钟", "past" : "{0} 分钟前", "future" : "{0} 分钟后" }, "month" : { "future" : "{0} 个月后", "next" : "下个月", "previous" : "上个月", "current" : "今个月", "past" : "{0} 个月前" }, "hour" : { "past" : "{0} 小时前", "current" : "呢个小时", "future" : "{0} 小时后" }, "year" : { "past" : "{0} 年前", "future" : "{0} 年后", "previous" : "旧年", "next" : "下年", "current" : "今年" }, "second" : { "current" : "宜家", "past" : "{0} 秒前", "future" : "{0} 秒后" }, "now" : "宜家", "quarter" : { "past" : "{0} 季前", "current" : "今季", "previous" : "上一季", "future" : "{0} 季后", "next" : "下一季" } }, "short" : { "quarter" : { "future" : "{0} 季后", "next" : "下季", "previous" : "上季", "current" : "今季", "past" : "{0} 季前" }, "minute" : { "current" : "呢分钟", "past" : "{0} 分钟前", "future" : "{0} 分钟后" }, "year" : { "next" : "下年", "future" : "{0} 年后", "current" : "今年", "past" : "{0} 年前", "previous" : "旧年" }, "second" : { "future" : "{0} 秒后", "current" : "宜家", "past" : "{0} 秒前" }, "hour" : { "past" : "{0} 小时前", "current" : "呢个小时", "future" : "{0} 小时后" }, "now" : "宜家", "month" : { "previous" : "上个月", "next" : "下个月", "past" : "{0} 个月前", "current" : "今个月", "future" : "{0} 个月后" }, "week" : { "previous" : "上星期", "current" : "今个星期", "next" : "下星期", "past" : "{0} 个星期前", "future" : "{0} 个星期后" }, "day" : { "next" : "听日", "past" : "{0} 日前", "future" : "{0} 日后", "previous" : "寻日", "current" : "今日" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/yue_Hant.json ================================================ { "long" : { "hour" : { "current" : "呢個小時", "past" : "{0} 小時前", "future" : "{0} 小時後" }, "second" : { "future" : "{0} 秒後", "past" : "{0} 秒前", "current" : "宜家" }, "week" : { "next" : "下星期", "current" : "今個星期", "previous" : "上星期", "future" : "{0} 個星期後", "past" : "{0} 個星期前" }, "minute" : { "current" : "呢分鐘", "future" : "{0} 分鐘後", "past" : "{0} 分鐘前" }, "year" : { "current" : "今年", "past" : "{0} 年前", "future" : "{0} 年後", "previous" : "舊年", "next" : "下年" }, "quarter" : { "next" : "下一季", "current" : "今季", "future" : "{0} 季後", "previous" : "上一季", "past" : "{0} 季前" }, "month" : { "previous" : "上個月", "future" : "{0} 個月後", "current" : "今個月", "next" : "下個月", "past" : "{0} 個月前" }, "day" : { "next" : "聽日", "past" : "{0} 日前", "current" : "今日", "future" : "{0} 日後", "previous" : "尋日" }, "now" : "宜家" }, "narrow" : { "week" : { "current" : "今個星期", "next" : "下星期", "past" : "{0} 個星期前", "previous" : "上星期", "future" : "{0} 個星期後" }, "minute" : { "past" : "{0} 分鐘前", "future" : "{0} 分鐘後", "current" : "呢分鐘" }, "month" : { "current" : "今個月", "previous" : "上個月", "past" : "{0} 個月前", "next" : "下個月", "future" : "{0} 個月後" }, "now" : "宜家", "year" : { "previous" : "舊年", "current" : "今年", "next" : "下年", "past" : "{0} 年前", "future" : "{0} 年後" }, "hour" : { "past" : "{0} 小時前", "future" : "{0} 小時後", "current" : "呢個小時" }, "quarter" : { "previous" : "上季", "current" : "今季", "past" : "{0} 季前", "future" : "{0} 季後", "next" : "下季" }, "second" : { "future" : "{0} 秒後", "current" : "宜家", "past" : "{0} 秒前" }, "day" : { "past" : "{0} 日前", "current" : "今日", "future" : "{0} 日後", "previous" : "尋日", "next" : "聽日" } }, "short" : { "month" : { "current" : "今個月", "next" : "下個月", "past" : "{0} 個月前", "future" : "{0} 個月後", "previous" : "上個月" }, "now" : "宜家", "day" : { "next" : "聽日", "current" : "今日", "previous" : "尋日", "past" : "{0} 日前", "future" : "{0} 日後" }, "year" : { "current" : "今年", "future" : "{0} 年後", "previous" : "舊年", "next" : "下年", "past" : "{0} 年前" }, "hour" : { "future" : "{0} 小時後", "current" : "呢個小時", "past" : "{0} 小時前" }, "minute" : { "current" : "呢分鐘", "future" : "{0} 分鐘後", "past" : "{0} 分鐘前" }, "second" : { "future" : "{0} 秒後", "current" : "宜家", "past" : "{0} 秒前" }, "quarter" : { "future" : "{0} 季後", "previous" : "上季", "next" : "下季", "past" : "{0} 季前", "current" : "今季" }, "week" : { "next" : "下星期", "past" : "{0} 個星期前", "current" : "今個星期", "previous" : "上星期", "future" : "{0} 個星期後" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/zh.json ================================================ { "narrow" : { "quarter" : { "current" : "本季度", "past" : "{0}个季度前", "next" : "下季度", "future" : "{0}个季度后", "previous" : "上季度" }, "day" : { "next" : "明天", "previous" : "昨天", "future" : "{0}天后", "past" : "{0}天前", "current" : "今天" }, "year" : { "past" : "{0}年前", "previous" : "去年", "future" : "{0}年后", "next" : "明年", "current" : "今年" }, "minute" : { "past" : "{0}分钟前", "future" : "{0}分钟后", "current" : "此刻" }, "now" : "现在", "week" : { "current" : "本周", "previous" : "上周", "past" : "{0}周前", "next" : "下周", "future" : "{0}周后" }, "second" : { "future" : "{0}秒后", "current" : "现在", "past" : "{0}秒前" }, "month" : { "future" : "{0}个月后", "current" : "本月", "past" : "{0}个月前", "previous" : "上个月", "next" : "下个月" }, "hour" : { "current" : "这一时间 \/ 此时", "past" : "{0}小时前", "future" : "{0}小时后" } }, "long" : { "year" : { "next" : "明年", "previous" : "去年", "past" : "{0}年前", "future" : "{0}年后", "current" : "今年" }, "now" : "现在", "quarter" : { "next" : "下季度", "past" : "{0}个季度前", "future" : "{0}个季度后", "current" : "本季度", "previous" : "上季度" }, "month" : { "past" : "{0}个月前", "next" : "下个月", "previous" : "上个月", "future" : "{0}个月后", "current" : "本月" }, "second" : { "current" : "现在", "past" : "{0}秒钟前", "future" : "{0}秒钟后" }, "week" : { "past" : "{0}周前", "previous" : "上周", "future" : "{0}周后", "next" : "下周", "current" : "本周" }, "day" : { "next" : "明天", "future" : "{0}天后", "previous" : "昨天", "current" : "今天", "past" : "{0}天前" }, "minute" : { "past" : "{0}分钟前", "future" : "{0}分钟后", "current" : "此刻" }, "hour" : { "future" : "{0}小时后", "current" : "这一时间 \/ 此时", "past" : "{0}小时前" } }, "short" : { "minute" : { "past" : "{0}分钟前", "current" : "此刻", "future" : "{0}分钟后" }, "week" : { "future" : "{0}周后", "previous" : "上周", "next" : "下周", "current" : "本周", "past" : "{0}周前" }, "year" : { "current" : "今年", "previous" : "去年", "future" : "{0}年后", "past" : "{0}年前", "next" : "明年" }, "month" : { "next" : "下个月", "past" : "{0}个月前", "future" : "{0}个月后", "previous" : "上个月", "current" : "本月" }, "quarter" : { "next" : "下季度", "past" : "{0}个季度前", "previous" : "上季度", "current" : "本季度", "future" : "{0}个季度后" }, "day" : { "current" : "今天", "previous" : "昨天", "past" : "{0}天前", "next" : "明天", "future" : "{0}天后" }, "hour" : { "past" : "{0}小时前", "future" : "{0}小时后", "current" : "这一时间 \/ 此时" }, "second" : { "future" : "{0}秒后", "past" : "{0}秒前", "current" : "现在" }, "now" : "现在" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/zh_Hans.json ================================================ { "narrow" : { "quarter" : { "current" : "本季度", "past" : "{0}个季度前", "next" : "下季度", "future" : "{0}个季度后", "previous" : "上季度" }, "day" : { "next" : "明天", "previous" : "昨天", "future" : "{0}天后", "past" : "{0}天前", "current" : "今天" }, "year" : { "past" : "{0}年前", "previous" : "去年", "future" : "{0}年后", "next" : "明年", "current" : "今年" }, "minute" : { "past" : "{0}分钟前", "future" : "{0}分钟后", "current" : "此刻" }, "now" : "现在", "week" : { "current" : "本周", "previous" : "上周", "past" : "{0}周前", "next" : "下周", "future" : "{0}周后" }, "second" : { "future" : "{0}秒后", "current" : "现在", "past" : "{0}秒前" }, "month" : { "future" : "{0}个月后", "current" : "本月", "past" : "{0}个月前", "previous" : "上个月", "next" : "下个月" }, "hour" : { "current" : "这一时间 \/ 此时", "past" : "{0}小时前", "future" : "{0}小时后" } }, "long" : { "year" : { "next" : "明年", "previous" : "去年", "past" : "{0}年前", "future" : "{0}年后", "current" : "今年" }, "now" : "现在", "quarter" : { "next" : "下季度", "past" : "{0}个季度前", "future" : "{0}个季度后", "current" : "本季度", "previous" : "上季度" }, "month" : { "past" : "{0}个月前", "next" : "下个月", "previous" : "上个月", "future" : "{0}个月后", "current" : "本月" }, "second" : { "current" : "现在", "past" : "{0}秒钟前", "future" : "{0}秒钟后" }, "week" : { "past" : "{0}周前", "previous" : "上周", "future" : "{0}周后", "next" : "下周", "current" : "本周" }, "day" : { "next" : "明天", "future" : "{0}天后", "previous" : "昨天", "current" : "今天", "past" : "{0}天前" }, "minute" : { "past" : "{0}分钟前", "future" : "{0}分钟后", "current" : "此刻" }, "hour" : { "future" : "{0}小时后", "current" : "这一时间 \/ 此时", "past" : "{0}小时前" } }, "short" : { "minute" : { "past" : "{0}分钟前", "current" : "此刻", "future" : "{0}分钟后" }, "week" : { "future" : "{0}周后", "previous" : "上周", "next" : "下周", "current" : "本周", "past" : "{0}周前" }, "year" : { "current" : "今年", "previous" : "去年", "future" : "{0}年后", "past" : "{0}年前", "next" : "明年" }, "month" : { "next" : "下个月", "past" : "{0}个月前", "future" : "{0}个月后", "previous" : "上个月", "current" : "本月" }, "quarter" : { "next" : "下季度", "past" : "{0}个季度前", "previous" : "上季度", "current" : "本季度", "future" : "{0}个季度后" }, "day" : { "current" : "今天", "previous" : "昨天", "past" : "{0}天前", "next" : "明天", "future" : "{0}天后" }, "hour" : { "past" : "{0}小时前", "future" : "{0}小时后", "current" : "这一时间 \/ 此时" }, "second" : { "future" : "{0}秒后", "past" : "{0}秒前", "current" : "现在" }, "now" : "现在" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/zh_Hans_HK.json ================================================ { "narrow" : { }, "short" : { }, "long" : { "minute" : { "past" : "{0}分钟前", "future" : "{0}分钟后", "current" : "此刻" }, "week" : { "current" : "本周", "past" : "{0}周前", "future" : "{0}周后", "next" : "下周", "previous" : "上周" }, "year" : { "current" : "今年", "future" : "{0}年后", "past" : "{0}年前", "next" : "明年", "previous" : "去年" }, "day" : { "next" : "明天", "current" : "今天", "previous" : "昨天", "past" : "{0}天前", "future" : "{0}天后" }, "hour" : { "future" : "{0}小时后", "current" : "这一时间 \/ 此时", "past" : "{0}小时前" }, "quarter" : { "current" : "本季度", "future" : "{0}个季度后", "previous" : "上季度", "next" : "下季度", "past" : "{0}个季度前" }, "second" : { "past" : "{0}秒前", "current" : "现在", "future" : "{0}秒后" }, "month" : { "current" : "本月", "past" : "{0}个月前", "future" : "{0}个月后", "next" : "下个月", "previous" : "上个月" }, "now" : "现在" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/zh_Hans_MO.json ================================================ { "narrow" : { }, "long" : { "second" : { "future" : "{0}秒后", "current" : "现在", "past" : "{0}秒前" }, "hour" : { "future" : "{0}小时后", "past" : "{0}小时前", "current" : "这一时间 \/ 此时" }, "day" : { "previous" : "昨天", "past" : "{0}天前", "future" : "{0}天后", "current" : "今天", "next" : "明天" }, "minute" : { "past" : "{0}分钟前", "current" : "此刻", "future" : "{0}分钟后" }, "now" : "现在", "year" : { "future" : "{0}年后", "past" : "{0}年前", "previous" : "去年", "next" : "明年", "current" : "今年" }, "quarter" : { "current" : "本季度", "next" : "下季度", "previous" : "上季度", "future" : "{0}个季度后", "past" : "{0}个季度前" }, "week" : { "past" : "{0}周前", "next" : "下周", "future" : "{0}周后", "current" : "本周", "previous" : "上周" }, "month" : { "next" : "下个月", "future" : "{0}个月后", "previous" : "上个月", "past" : "{0}个月前", "current" : "本月" } }, "short" : { } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/zh_Hans_SG.json ================================================ { "narrow" : { }, "short" : { }, "long" : { "minute" : { "past" : "{0}分钟前", "future" : "{0}分钟后", "current" : "此刻" }, "week" : { "current" : "本周", "past" : "{0}周前", "future" : "{0}周后", "next" : "下周", "previous" : "上周" }, "year" : { "current" : "今年", "future" : "{0}年后", "past" : "{0}年前", "next" : "明年", "previous" : "去年" }, "day" : { "next" : "明天", "current" : "今天", "previous" : "昨天", "past" : "{0}天前", "future" : "{0}天后" }, "hour" : { "future" : "{0}小时后", "current" : "这一时间 \/ 此时", "past" : "{0}小时前" }, "quarter" : { "current" : "本季度", "future" : "{0}个季度后", "previous" : "上季度", "next" : "下季度", "past" : "{0}个季度前" }, "second" : { "past" : "{0}秒前", "current" : "现在", "future" : "{0}秒后" }, "month" : { "current" : "本月", "past" : "{0}个月前", "future" : "{0}个月后", "next" : "下个月", "previous" : "上个月" }, "now" : "现在" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/zh_Hant.json ================================================ { "narrow" : { "now" : "現在", "week" : { "next" : "下週", "current" : "本週", "past" : "{0} 週前", "future" : "{0} 週後", "previous" : "上週" }, "quarter" : { "previous" : "上一季", "next" : "下一季", "future" : "{0} 季後", "current" : "本季", "past" : "{0} 季前" }, "day" : { "future" : "{0} 天後", "previous" : "昨天", "next" : "明天", "past" : "{0} 天前", "current" : "今天" }, "hour" : { "current" : "這個小時", "past" : "{0} 小時前", "future" : "{0} 小時後" }, "second" : { "future" : "{0} 秒後", "current" : "現在", "past" : "{0} 秒前" }, "month" : { "past" : "{0} 個月前", "future" : "{0} 個月後", "current" : "本月", "previous" : "上個月", "next" : "下個月" }, "minute" : { "future" : "{0} 分鐘後", "current" : "現在", "past" : "{0} 分鐘前" }, "year" : { "next" : "明年", "future" : "{0} 年後", "previous" : "去年", "current" : "今年", "past" : "{0} 年前" } }, "long" : { "day" : { "future" : "{0} 天後", "past" : "{0} 天前", "current" : "今天", "next" : "明天", "previous" : "昨天" }, "second" : { "past" : "{0} 秒前", "current" : "現在", "future" : "{0} 秒後" }, "quarter" : { "current" : "本季", "next" : "下一季", "previous" : "上一季", "future" : "{0} 季後", "past" : "{0} 季前" }, "month" : { "future" : "{0} 個月後", "previous" : "上個月", "past" : "{0} 個月前", "next" : "下個月", "current" : "本月" }, "week" : { "future" : "{0} 週後", "past" : "{0} 週前", "current" : "本週", "previous" : "上週", "next" : "下週" }, "hour" : { "future" : "{0} 小時後", "current" : "這個小時", "past" : "{0} 小時前" }, "minute" : { "future" : "{0} 分鐘後", "past" : "{0} 分鐘前", "current" : "現在" }, "now" : "現在", "year" : { "future" : "{0} 年後", "next" : "明年", "past" : "{0} 年前", "previous" : "去年", "current" : "今年" } }, "short" : { "hour" : { "future" : "{0} 小時後", "current" : "這個小時", "past" : "{0} 小時前" }, "now" : "現在", "quarter" : { "current" : "本季", "future" : "{0} 季後", "past" : "{0} 季前", "next" : "下一季", "previous" : "上一季" }, "day" : { "current" : "今天", "past" : "{0} 天前", "future" : "{0} 天後", "next" : "明天", "previous" : "昨天" }, "week" : { "current" : "本週", "past" : "{0} 週前", "future" : "{0} 週後", "next" : "下週", "previous" : "上週" }, "minute" : { "past" : "{0} 分鐘前", "future" : "{0} 分鐘後", "current" : "現在" }, "second" : { "past" : "{0} 秒前", "current" : "現在", "future" : "{0} 秒後" }, "month" : { "current" : "本月", "future" : "{0} 個月後", "previous" : "上個月", "next" : "下個月", "past" : "{0} 個月前" }, "year" : { "future" : "{0} 年後", "previous" : "去年", "next" : "明年", "current" : "今年", "past" : "{0} 年前" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/zh_Hant_HK.json ================================================ { "narrow" : { "quarter" : { "current" : "今季", "past" : "-{0}Q", "next" : "下季", "future" : "+{0}Q", "previous" : "上季" }, "day" : { "next" : "明日", "previous" : "昨日", "future" : "{0}日後", "past" : "{0}日前", "current" : "今日" }, "year" : { "past" : "{0}年前", "previous" : "上年", "future" : "{0}年後", "next" : "下年", "current" : "今年" }, "minute" : { "current" : "這分鐘", "future" : "{0}分後", "past" : "{0}分前" }, "now" : "現在", "week" : { "current" : "本星期", "previous" : "上星期", "past" : "{0}星期前", "next" : "下星期", "future" : "{0}星期後" }, "second" : { "current" : "現在", "past" : "{0}秒前", "future" : "{0}秒後" }, "month" : { "future" : "{0}個月後", "current" : "本月", "past" : "{0}個月前", "previous" : "上個月", "next" : "下個月" }, "hour" : { "past" : "{0}小時前", "future" : "{0}小時後", "current" : "這個小時" } }, "long" : { "year" : { "next" : "下年", "previous" : "上年", "past" : "{0} 年前", "future" : "{0} 年後", "current" : "今年" }, "now" : "現在", "quarter" : { "next" : "下一季", "past" : "{0} 季前", "future" : "{0} 季後", "current" : "今季", "previous" : "上一季" }, "month" : { "past" : "{0} 個月前", "next" : "下個月", "previous" : "上個月", "future" : "{0} 個月後", "current" : "本月" }, "second" : { "past" : "{0} 秒前", "future" : "{0} 秒後", "current" : "現在" }, "week" : { "past" : "{0} 星期前", "previous" : "上星期", "future" : "{0} 星期後", "next" : "下星期", "current" : "本星期" }, "day" : { "next" : "明日", "future" : "{0} 日後", "previous" : "昨日", "current" : "今日", "past" : "{0} 日前" }, "minute" : { "past" : "{0} 分鐘前", "future" : "{0} 分鐘後", "current" : "這分鐘" }, "hour" : { "current" : "這個小時", "past" : "{0} 小時前", "future" : "{0} 小時後" } }, "short" : { "minute" : { "future" : "{0} 分鐘後", "current" : "這分鐘", "past" : "{0} 分鐘前" }, "week" : { "future" : "{0} 星期後", "previous" : "上星期", "next" : "下星期", "current" : "本星期", "past" : "{0} 星期前" }, "year" : { "current" : "今年", "previous" : "上年", "future" : "{0} 年後", "past" : "{0} 年前", "next" : "下年" }, "month" : { "next" : "下個月", "past" : "{0} 個月前", "future" : "{0} 個月後", "previous" : "上個月", "current" : "本月" }, "quarter" : { "next" : "下季", "past" : "{0} 季前", "previous" : "上季", "current" : "今季", "future" : "{0} 季後" }, "day" : { "current" : "今日", "previous" : "昨日", "past" : "{0} 日前", "next" : "明日", "future" : "{0} 日後" }, "hour" : { "current" : "這個小時", "past" : "{0} 小時前", "future" : "{0} 小時後" }, "second" : { "current" : "現在", "past" : "{0} 秒前", "future" : "{0} 秒後" }, "now" : "現在" } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Formatters/RelativeFormatter/langs/zu.json ================================================ { "short" : { "day" : { "previous" : "izolo", "current" : "namhlanje", "past" : { "other" : "{0} izinsuku ezedlule", "one" : "{0} usuku olwedlule" }, "future" : { "one" : "osukwini olungu-{0} oluzayo", "other" : "ezinsukwini ezingu-{0} ezizayo" }, "next" : "kusasa" }, "week" : { "next" : "iviki elizayo", "current" : "leli viki", "previous" : "iviki eledlule", "past" : "amaviki angu-{0} edlule", "future" : { "other" : "emavikini angu-{0} ezayo", "one" : "evikini elingu-{0} elizayo" } }, "quarter" : { "next" : "ikota ezayo", "past" : { "one" : "{0} amakota adlule", "other" : "{0} amakota edlule" }, "current" : "le kota", "previous" : "ikota edlule", "future" : { "one" : "kwikota engu-{0} ezayo", "other" : "kumakota angu-{0} ezayo" } }, "minute" : { "future" : { "one" : "kuminithi elingu-{0} elizayo", "other" : "kumaminithi angu-{0} ezayo" }, "past" : { "one" : "{0} iminithi eledlule", "other" : "{0} amaminithi edlule" }, "current" : "leli minithi" }, "year" : { "current" : "kulo nyaka", "next" : "unyaka ozayo", "past" : "{0} unyaka odlule", "future" : { "other" : "eminyakeni engu-{0} ezayo", "one" : "onyakeni ongu-{0} ozayo" }, "previous" : "onyakeni odlule" }, "hour" : { "past" : { "one" : "{0} ihora eledlule", "other" : "emahoreni angu-{0} edlule" }, "future" : { "other" : "emahoreni angu-{0} ezayo", "one" : "ehoreni elingu-{0} elizayo" }, "current" : "leli hora" }, "month" : { "current" : "le nyanga", "future" : "ezinyangeni ezingu-{0} ezizayo", "previous" : "inyanga edlule", "next" : "inyanga ezayo", "past" : "{0} izinyanga ezedlule" }, "second" : { "future" : { "other" : "kumasekhondi angu-{0} ezayo", "one" : "kusekhondi elingu-{0} elizayo" }, "current" : "manje", "past" : { "one" : "{0} isekhondi eledlule", "other" : "{0} amasekhondi edlule" } }, "now" : "manje" }, "long" : { "month" : { "previous" : "inyanga edlule", "next" : "inyanga ezayo", "future" : { "one" : "enyangeni engu-{0}", "other" : "ezinyangeni ezingu-{0} ezizayo" }, "current" : "le nyanga", "past" : { "other" : "{0} izinyanga ezedlule", "one" : "{0} inyanga edlule" } }, "minute" : { "past" : { "one" : "{0} iminithi eledlule", "other" : "{0} amaminithi edlule" }, "future" : { "one" : "kuminithi elingu-{0} elizayo", "other" : "kumaminithi angu-{0} ezayo" }, "current" : "leli minithi" }, "quarter" : { "previous" : "ikota edlule", "past" : { "other" : "{0} amakota adlule", "one" : "{0} ikota edlule" }, "current" : "le kota", "next" : "ikota ezayo", "future" : { "other" : "kumakota angu-{0} ezayo", "one" : "kwikota engu-{0} ezayo" } }, "week" : { "current" : "leli viki", "past" : { "other" : "amaviki angu-{0} edlule", "one" : "evikini elingu-{0} eledlule" }, "previous" : "iviki eledlule", "future" : { "one" : "evikini elingu-{0}", "other" : "emavikini angu-{0}" }, "next" : "iviki elizayo" }, "now" : "manje", "second" : { "future" : { "other" : "kumasekhondi angu-{0} ezayo", "one" : "kusekhondi elingu-{0} elizayo" }, "current" : "manje", "past" : { "other" : "{0} amasekhondi edlule", "one" : "{0} isekhondi eledlule" } }, "year" : { "past" : { "other" : "{0} iminyaka edlule", "one" : "{0} unyaka odlule" }, "next" : "unyaka ozayo", "current" : "kulo nyaka", "future" : { "other" : "eminyakeni engu-{0} ezayo", "one" : "onyakeni ongu-{0} ozayo" }, "previous" : "onyakeni odlule" }, "hour" : { "current" : "leli hora", "past" : { "one" : "{0} ihora eledlule", "other" : "emahoreni angu-{0} edlule" }, "future" : { "one" : "ehoreni elingu-{0} elizayo", "other" : "emahoreni angu-{0} ezayo" } }, "day" : { "past" : { "one" : "osukwini olungu-{0} olwedlule", "other" : "ezinsukwini ezingu-{0} ezedlule." }, "future" : { "other" : "ezinsukwini ezingu-{0} ezizayo", "one" : "osukwini olungu-{0} oluzayo" }, "current" : "namhlanje", "previous" : "izolo", "next" : "kusasa" } }, "narrow" : { "year" : { "past" : "{0} unyaka odlule", "future" : { "other" : "eminyakeni engu-{0} ezayo", "one" : "onyakeni ongu-{0} ozayo" }, "next" : "unyaka ozayo", "current" : "kulo nyaka", "previous" : "onyakeni odlule" }, "quarter" : { "past" : { "other" : "{0} amakota edlule", "one" : "{0} amakota adlule" }, "current" : "le kota", "next" : "ikota ezayo", "future" : "kumakota angu-{0}", "previous" : "ikota edlule" }, "day" : { "current" : "namhlanje", "next" : "kusasa", "past" : { "one" : "{0} usuku olwedlule", "other" : "{0} izinsuku ezedlule" }, "future" : { "one" : "osukwini olungu-{0} oluzayo", "other" : "ezinsukwini ezingu-{0} ezizayo" }, "previous" : "izolo" }, "month" : { "previous" : "inyanga edlule", "next" : "inyanga ezayo", "future" : "enyangeni engu-{0} ezayo", "current" : "le nyanga", "past" : "{0} izinyanga ezedlule" }, "hour" : { "current" : "leli hora", "past" : { "one" : "{0} ihora eledlule", "other" : "{0} amahora edlule" }, "future" : { "one" : "ehoreni elingu-{0} elizayo", "other" : "emahoreni angu-{0} ezayo" } }, "second" : { "past" : { "one" : "{0} isekhondi eledlule", "other" : "{0} amasekhondi edlule" }, "current" : "manje", "future" : { "one" : "kusekhondi elingu-{0} elizayo", "other" : "kumasekhondi angu-{0} ezayo" } }, "minute" : { "past" : { "one" : "{0} iminithi eledlule", "other" : "{0} amaminithi edlule" }, "future" : { "one" : "kuminithi elingu-{0} elizayo", "other" : "kumaminithi angu-{0} ezayo" }, "current" : "leli minithi" }, "now" : "manje", "week" : { "next" : "iviki elizayo", "future" : "emavikini angu-{0} ezayo", "previous" : "iviki eledlule", "current" : "leli viki", "past" : "amaviki angu-{0} edlule" } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Foundation+Extras/DateComponents+Extras.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation // MARK: - Date Components Extensions public extension Calendar.Component { /// Return a description of the calendar component in seconds. /// Note: Values for `era`,`weekday`,`weekdayOrdinal`, `yearForWeekOfYear`, `calendar`, `timezone` are `nil`. /// For `weekOfYear` it return the same value of `weekOfMonth`. var timeInterval: Double? { switch self { case .era: return nil case .year: return (Calendar.Component.day.timeInterval! * 365.0) case .month: return (Calendar.Component.minute.timeInterval! * 43800) case .day: return 86400 case .hour: return 3600 case .minute: return 60 case .second: return 1 case .quarter: return (Calendar.Component.day.timeInterval! * 91.25) case .weekOfMonth, .weekOfYear: return (Calendar.Component.day.timeInterval! * 7) case .nanosecond: return 1e-9 default: return nil } } /// Return the localized identifier of a calendar component /// /// - parameter unit: unit /// - parameter value: value /// /// - returns: return the plural or singular form of the time unit used to compose a valid identifier for search a localized /// string in resource bundle internal func localizedKey(forValue value: Int) -> String { let locKey = localizedKey let absValue = abs(value) switch absValue { case 0: // zero difference for this unit return "0\(locKey)" case 1: // one unit of difference return locKey default: // more than 1 unit of difference return "\(locKey)\(locKey)" } } internal var localizedKey: String { switch self { case .year: return "y" case .month: return "m" case .weekOfYear: return "w" case .day: return "d" case .hour: return "h" case .minute: return "M" case .second: return "s" default: return "" } } } public extension DateComponents { /// Shortcut for 'all calendar components'. static var allComponentsSet: Set { return [.era, .year, .month, .day, .hour, .minute, .second, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .nanosecond, .calendar, .timeZone] } internal static let allComponents: [Calendar.Component] = [.nanosecond, .second, .minute, .hour, .day, .month, .year, .yearForWeekOfYear, .weekOfYear, .weekday, .quarter, .weekdayOrdinal, .weekOfMonth] /// This function return the absolute amount of seconds described by the components of the receiver. /// Note: evaluated value maybe not strictly exact because it ignore the context (calendar/date) of /// the date components. In details: /// - The following keys are ignored: `era`,`weekday`,`weekdayOrdinal`, /// `weekOfYear`, `yearForWeekOfYear`, `calendar`, `timezone /// /// Some other values dependant from dates are fixed. This is a complete table: /// - `year` is 365.0 `days` /// - `month` is 30.4167 `days` (or 43800 minutes) /// - `quarter` is 91.25 `days` /// - `weekOfMonth` is 7 `days` /// - `day` is 86400 `seconds` /// - `hour` is 3600 `seconds` /// - `minute` is 60 `seconds` /// - `nanosecond` is 1e-9 `seconds` var timeInterval: TimeInterval { var totalAmount: TimeInterval = 0 DateComponents.allComponents.forEach { if let multipler = $0.timeInterval, let value = value(for: $0), value != Int(NSDateComponentUndefined) { totalAmount += (TimeInterval(value) * multipler) } } return totalAmount } /// Create a new `DateComponents` instance with builder pattern. /// /// - Parameter builder: callback for builder /// - Returns: new instance static func create(_ builder: ((inout DateComponents) -> Void)) -> DateComponents { var components = DateComponents() builder(&components) return components } /// Return the current date plus the receive's interval /// The default calendar used is the `SwiftDate.defaultRegion`'s calendar. var fromNow: Date { return SwiftDate.defaultRegion.calendar.date(byAdding: (self as DateComponents) as DateComponents, to: Date() as Date)! } /// Returns the current date minus the receiver's interval /// The default calendar used is the `SwiftDate.defaultRegion`'s calendar. var ago: Date { return SwiftDate.defaultRegion.calendar.date(byAdding: -self as DateComponents, to: Date())! } /// - returns: the date that will occur once the receiver's components pass after the provide date. func from(_ date: DateRepresentable) -> Date? { return date.calendar.date(byAdding: self, to: date.date) } /// Return `true` if all interval components are zeroes var isZero: Bool { for component in DateComponents.allComponents { if let value = value(for: component), value != 0 { return false } } return true } /// Transform a `DateComponents` instance to a dictionary where key is the `Calendar.Component` and value is the /// value associated. /// /// - returns: a new `[Calendar.Component : Int]` dict representing source `DateComponents` instance internal func toDict() -> [Calendar.Component: Int] { var list: [Calendar.Component: Int] = [:] DateComponents.allComponents.forEach { component in let value = self.value(for: component) if value != nil && value != Int(NSDateComponentUndefined) { list[component] = value! } } return list } /// Alter date components specified into passed dictionary. /// /// - Parameter components: components dictionary with their values. internal mutating func alterComponents(_ components: [Calendar.Component: Int?]) { components.forEach { if let v = $0.value { setValue(v, for: $0.key) } } } /// Adds two NSDateComponents and returns their combined individual components. static func + (lhs: DateComponents, rhs: DateComponents) -> DateComponents { return combine(lhs, rhs: rhs, transform: +) } /// Subtracts two NSDateComponents and returns the relative difference between them. static func - (lhs: DateComponents, rhs: DateComponents) -> DateComponents { return lhs + (-rhs) } /// Applies the `transform` to the two `T` provided, defaulting either of them if it's /// `nil` internal static func bimap(_ a: T?, _ b: T?, default: T, _ transform: (T, T) -> T) -> T? { if a == nil && b == nil { return nil } return transform(a ?? `default`, b ?? `default`) } /// - returns: a new `NSDateComponents` that represents the negative of all values within the /// components that are not `NSDateComponentUndefined`. static prefix func - (rhs: DateComponents) -> DateComponents { var components = DateComponents() components.era = rhs.era.map(-) components.year = rhs.year.map(-) components.month = rhs.month.map(-) components.day = rhs.day.map(-) components.hour = rhs.hour.map(-) components.minute = rhs.minute.map(-) components.second = rhs.second.map(-) components.nanosecond = rhs.nanosecond.map(-) components.weekday = rhs.weekday.map(-) components.weekdayOrdinal = rhs.weekdayOrdinal.map(-) components.quarter = rhs.quarter.map(-) components.weekOfMonth = rhs.weekOfMonth.map(-) components.weekOfYear = rhs.weekOfYear.map(-) components.yearForWeekOfYear = rhs.yearForWeekOfYear.map(-) return components } /// Combines two date components using the provided `transform` on all /// values within the components that are not `NSDateComponentUndefined`. private static func combine(_ lhs: DateComponents, rhs: DateComponents, transform: (Int, Int) -> Int) -> DateComponents { var components = DateComponents() components.era = bimap(lhs.era, rhs.era, default: 0, transform) components.year = bimap(lhs.year, rhs.year, default: 0, transform) components.month = bimap(lhs.month, rhs.month, default: 0, transform) components.day = bimap(lhs.day, rhs.day, default: 0, transform) components.hour = bimap(lhs.hour, rhs.hour, default: 0, transform) components.minute = bimap(lhs.minute, rhs.minute, default: 0, transform) components.second = bimap(lhs.second, rhs.second, default: 0, transform) components.nanosecond = bimap(lhs.nanosecond, rhs.nanosecond, default: 0, transform) components.weekday = bimap(lhs.weekday, rhs.weekday, default: 0, transform) components.weekdayOrdinal = bimap(lhs.weekdayOrdinal, rhs.weekdayOrdinal, default: 0, transform) components.quarter = bimap(lhs.quarter, rhs.quarter, default: 0, transform) components.weekOfMonth = bimap(lhs.weekOfMonth, rhs.weekOfMonth, default: 0, transform) components.weekOfYear = bimap(lhs.weekOfYear, rhs.weekOfYear, default: 0, transform) components.yearForWeekOfYear = bimap(lhs.yearForWeekOfYear, rhs.yearForWeekOfYear, default: 0, transform) return components } /// Subscription support for `DateComponents` instances. /// ie. `cmps[.day] = 5` /// /// Note: This does not take into account any built-in errors, `Int.max` returned instead of `nil`. /// /// - Parameter component: component to get subscript(component: Calendar.Component) -> Int? { switch component { case .era: return era case .year: return year case .month: return month case .day: return day case .hour: return hour case .minute: return minute case .second: return second case .weekday: return weekday case .weekdayOrdinal: return weekdayOrdinal case .quarter: return quarter case .weekOfMonth: return weekOfMonth case .weekOfYear: return weekOfYear case .yearForWeekOfYear: return yearForWeekOfYear case .nanosecond: return nanosecond default: return nil // `calendar` and `timezone` are ignored in this context } } /// Express a `DateComponents` instance in another time unit you choose. /// /// - parameter component: time component /// - parameter calendar: context calendar to use /// /// - returns: the value of interval expressed in selected `Calendar.Component` func `in`(_ component: Calendar.Component, of calendar: CalendarConvertible? = nil) -> Int? { let cal = (calendar?.toCalendar() ?? SwiftDate.defaultRegion.calendar) let dateFrom = Date() let dateTo = (dateFrom + self) let components: Set = [component] let value = cal.dateComponents(components, from: dateFrom, to: dateTo).value(for: component) return value } /// Express a `DateComponents` instance in a set of time units you choose. /// /// - Parameters: /// - component: time component /// - calendar: context calendar to use /// - Returns: a dictionary of extract values. func `in`(_ components: Set, of calendar: CalendarConvertible? = nil) -> [Calendar.Component: Int] { let cal = (calendar?.toCalendar() ?? SwiftDate.defaultRegion.calendar) let dateFrom = Date() let dateTo = (dateFrom + self) let extractedCmps = cal.dateComponents(components, from: dateFrom, to: dateTo) return extractedCmps.toDict() } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Foundation+Extras/Int+DateComponents.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation // MARK: Int Extension /// This allows us to transform a literal number in a `DateComponents` and use it in math operations /// For example `5.days` will create a new `DateComponents` where `.day = 5`. public extension Int { /// Internal transformation function /// /// - parameter type: component to use /// /// - returns: return self value in form of `DateComponents` where given `Calendar.Component` has `self` as value internal func toDateComponents(type: Calendar.Component) -> DateComponents { var dateComponents = DateComponents() DateComponents.allComponents.forEach( { dateComponents.setValue(0, for: $0 )}) dateComponents.setValue(self, for: type) dateComponents.setValue(0, for: .era) return dateComponents } /// Create a `DateComponents` with `self` value set as nanoseconds var nanoseconds: DateComponents { return toDateComponents(type: .nanosecond) } /// Create a `DateComponents` with `self` value set as seconds var seconds: DateComponents { return toDateComponents(type: .second) } /// Create a `DateComponents` with `self` value set as minutes var minutes: DateComponents { return toDateComponents(type: .minute) } /// Create a `DateComponents` with `self` value set as hours var hours: DateComponents { return toDateComponents(type: .hour) } /// Create a `DateComponents` with `self` value set as days var days: DateComponents { return toDateComponents(type: .day) } /// Create a `DateComponents` with `self` value set as weeks var weeks: DateComponents { return toDateComponents(type: .weekOfYear) } /// Create a `DateComponents` with `self` value set as months var months: DateComponents { return toDateComponents(type: .month) } /// Create a `DateComponents` with `self` value set as years var years: DateComponents { return toDateComponents(type: .year) } /// Create a `DateComponents` with `self` value set as quarters var quarters: DateComponents { return toDateComponents(type: .quarter) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Foundation+Extras/String+Parser.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation // MARK: - DataParsable Protocol public protocol DateParsable { /// Convert a string to a `DateInRegion` instance by parsing it with given parser /// or using one of the built-in parser (if you know the format of the date you /// should consider explicitly pass it to avoid unecessary computations). /// /// - Parameters: /// - format: format of the date, `nil` to leave the library to found the best /// one via `SwiftDate.autoFormats` /// - region: region in which the date should be expressed in. /// Region's locale is used to format the date when using long readable unit names (like MMM /// for month). /// - Returns: date in region representation, `nil` if parse fails func toDate(_ format: String?, region: Region) -> DateInRegion? /// Convert a string to a `DateInRegion` instance by parsing it with the ordered /// list of provided formats. /// If `formats` array is not provided it uses the `SwiftDate.autoFormats` array instead. /// Note: if you knwo the format of the date you should consider explicitly pass it to avoid /// unecessary computations. /// /// - Parameters: /// - format: ordered formats to parse date (if you don't have a list of formats you can pass `SwiftDate.autoFormats`) /// - region: region in which the date should be expressed in. /// Region's locale is used to format the date when using long readable unit names (like MMM /// for month). /// - Returns: date in region representation, `nil` if parse fails func toDate(_ formats: [String], region: Region) -> DateInRegion? /// Convert a string to a valid `DateInRegion` using passed style. /// /// - Parameters: /// - style: parsing style. /// - region: region in which the date should be expressed in /// - Returns: date in region representation, `nil` if parse fails func toDate(style: StringToDateStyles, region: Region) -> DateInRegion? /// Convert to date from a valid ISO8601 string /// /// - Parameters: /// - options: options of the parser /// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically) /// - Returns: date in region representation, `nil` if parse fails func toISODate(_ options: ISOParser.Options?, region: Region?) -> DateInRegion? /// Convert to date from a valid DOTNET string /// /// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically) /// - Returns: date in region representation, `nil` if parse fails func toDotNETDate(region: Region) -> DateInRegion? /// Convert to a date from a valid RSS/ALT RSS string /// /// - Parameters: /// - alt: `true` if string represent an ALT RSS formatted date, `false` if a standard RSS formatted date. /// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically) /// - Returns: date in region representation, `nil` if parse fails func toRSSDate(alt: Bool, region: Region) -> DateInRegion? /// Convert to a date from a valid SQL format string. /// /// - Parameters: /// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically) /// - Returns: date in region representation, `nil` if parse fails func toSQLDate(region: Region) -> DateInRegion? } // MARK: - DataParsable Implementation for Strings extension String: DateParsable { public func toDate(_ format: String? = nil, region: Region = SwiftDate.defaultRegion) -> DateInRegion? { return DateInRegion(self, format: format, region: region) } public func toDate(_ formats: [String], region: Region) -> DateInRegion? { return DateInRegion(self, formats: formats, region: region) } public func toDate(style: StringToDateStyles, region: Region = SwiftDate.defaultRegion) -> DateInRegion? { return style.toDate(self, region: region) } public func toISODate(_ options: ISOParser.Options? = nil, region: Region? = nil) -> DateInRegion? { return ISOParser.parse(self, region: region, options: options) } public func toDotNETDate(region: Region = Region.ISO) -> DateInRegion? { return DOTNETParser.parse(self, region: region, options: nil) } public func toRSSDate(alt: Bool, region: Region = Region.ISO) -> DateInRegion? { switch alt { case true: return StringToDateStyles.altRSS.toDate(self, region: region) case false: return StringToDateStyles.rss.toDate(self, region: region) } } public func toSQLDate(region: Region = Region.ISO) -> DateInRegion? { return StringToDateStyles.sql.toDate(self, region: region) } public func asLocale() -> Locale { Locale(identifier: self) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Foundation+Extras/TimeInterval+Formatter.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public extension TimeInterval { struct ComponentsFormatterOptions { /// Fractional units may be used when a value cannot be exactly represented using the available units. /// For example, if minutes are not allowed, the value “1h 30m” could be formatted as “1.5h”. public var allowsFractionalUnits: Bool? /// Specify the units that can be used in the output. public var allowedUnits: NSCalendar.Unit? /// A Boolean value indicating whether to collapse the largest unit into smaller units when a certain threshold is met. public var collapsesLargestUnit: Bool? /// The maximum number of time units to include in the output string. /// If 0 does not cause the elimination of any units. public var maximumUnitCount: Int? /// The formatting style for units whose value is 0. public var zeroFormattingBehavior: DateComponentsFormatter.ZeroFormattingBehavior? /// The preferred style for units. public var unitsStyle: DateComponentsFormatter.UnitsStyle? /// Locale of the formatter public var locale: LocaleConvertible? { set { calendar.locale = newValue?.toLocale() } get { return calendar.locale } } /// Calendar public var calendar = Calendar.autoupdatingCurrent public func apply(toFormatter formatter: DateComponentsFormatter) { formatter.calendar = calendar if let allowsFractionalUnits = self.allowsFractionalUnits { formatter.allowsFractionalUnits = allowsFractionalUnits } if let allowedUnits = self.allowedUnits { formatter.allowedUnits = allowedUnits } if let collapsesLargestUnit = self.collapsesLargestUnit { formatter.collapsesLargestUnit = collapsesLargestUnit } if let maximumUnitCount = self.maximumUnitCount { formatter.maximumUnitCount = maximumUnitCount } if let zeroFormattingBehavior = self.zeroFormattingBehavior { formatter.zeroFormattingBehavior = zeroFormattingBehavior } if let unitsStyle = self.unitsStyle { formatter.unitsStyle = unitsStyle } } public init() {} } /// Return the local thread shared formatter for date components private static func sharedFormatter() -> DateComponentsFormatter { let name = "SwiftDate_\(NSStringFromClass(DateComponentsFormatter.self))" return threadSharedObject(key: name, create: { let formatter = DateComponentsFormatter() formatter.includesApproximationPhrase = false formatter.includesTimeRemainingPhrase = false return formatter }) } //@available(*, deprecated: 5.0.13, obsoleted: 5.1, message: "Use toIntervalString function instead") func toString(options callback: ((inout ComponentsFormatterOptions) -> Void)? = nil) -> String { return self.toIntervalString(options: callback) } /// Format a time interval in a string with desidered components with passed style. /// /// - Parameters: /// - units: units to include in string. /// - style: style of the units, by default is `.abbreviated` /// - Returns: string representation func toIntervalString(options callback: ((inout ComponentsFormatterOptions) -> Void)? = nil) -> String { let formatter = DateComponentsFormatter() var options = ComponentsFormatterOptions() callback?(&options) options.apply(toFormatter: formatter) let formattedValue = (formatter.string(from: self) ?? "") if options.zeroFormattingBehavior?.contains(.pad) ?? false { // for some strange reason padding is not added at the very beginning positional item. // we'll add it manually if necessaru if let index = formattedValue.firstIndex(of: ":"), index.utf16Offset(in: formattedValue) < 2 { return "0\(formattedValue)" } } return formattedValue } /// Format a time interval in a string with desidered components with passed style. /// /// - Parameter options: options for formatting. /// - Returns: string representation func toString(options: ComponentsFormatterOptions) -> String { let formatter = TimeInterval.sharedFormatter() options.apply(toFormatter: formatter) return (formatter.string(from: self) ?? "") } /// Return a string representation of the time interval in form of clock countdown (ie. 57:00:00) /// /// - Parameter zero: behaviour with zero. /// - Returns: string representation func toClock(zero: DateComponentsFormatter.ZeroFormattingBehavior = [.pad, .dropLeading]) -> String { return toIntervalString(options: { $0.collapsesLargestUnit = true $0.maximumUnitCount = 0 $0.unitsStyle = .positional $0.locale = Locales.englishUnitedStatesComputer $0.zeroFormattingBehavior = zero }) } /// Extract requeste time units components from given interval. /// Reference date's calendar is used to make the extraction. /// /// NOTE: /// Extraction is calendar/date based; if you specify a `refDate` calculation is made /// between the `refDate` and `refDate + interval`. /// If `refDate` is `nil` evaluation is made from `now()` and `now() + interval` in the context /// of the `SwiftDate.defaultRegion` set. /// /// - Parameters: /// - units: units to extract /// - from: starting reference date, `nil` means `now()` in the context of the default region set. /// - Returns: dictionary with extracted components func toUnits(_ units: Set, to refDate: DateInRegion? = nil) -> [Calendar.Component: Int] { let dateTo = (refDate ?? DateInRegion()) let dateFrom = dateTo.addingTimeInterval(-self) let components = dateFrom.calendar.dateComponents(units, from: dateFrom.date, to: dateTo.date) return components.toDict() } /// Express a time interval (expressed in seconds) in another time unit you choose. /// Reference date's calendar is used to make the extraction. /// /// - parameter component: time unit in which you want to express the calendar component /// - parameter from: starting reference date, `nil` means `now()` in the context of the default region set. /// /// - returns: the value of interval expressed in selected `Calendar.Component` func toUnit(_ component: Calendar.Component, to refDate: DateInRegion? = nil) -> Int? { return toUnits([component], to: refDate)[component] } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Supports/AssociatedValues.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // #if os(Linux) #else import Foundation import ObjectiveC.runtime internal func getAssociatedValue(key: String, object: AnyObject) -> T? { return (objc_getAssociatedObject(object, key.address) as? AssociatedValue)?.value as? T } internal func getAssociatedValue(key: String, object: AnyObject, initialValue: @autoclosure () -> T) -> T { return getAssociatedValue(key: key, object: object) ?? setAndReturn(initialValue: initialValue(), key: key, object: object) } internal func getAssociatedValue(key: String, object: AnyObject, initialValue: () -> T) -> T { return getAssociatedValue(key: key, object: object) ?? setAndReturn(initialValue: initialValue(), key: key, object: object) } private func setAndReturn(initialValue: T, key: String, object: AnyObject) -> T { set(associatedValue: initialValue, key: key, object: object) return initialValue } internal func set(associatedValue: T?, key: String, object: AnyObject) { set(associatedValue: AssociatedValue(associatedValue), key: key, object: object) } internal func set(weakAssociatedValue: T?, key: String, object: AnyObject) { set(associatedValue: AssociatedValue(weak: weakAssociatedValue), key: key, object: object) } extension String { fileprivate var address: UnsafeRawPointer { return UnsafeRawPointer(bitPattern: abs(hashValue))! } } private func set(associatedValue: AssociatedValue, key: String, object: AnyObject) { objc_setAssociatedObject(object, key.address, associatedValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } private class AssociatedValue { weak var _weakValue: AnyObject? var _value: Any? var value: Any? { return _weakValue ?? _value } init(_ value: Any?) { _value = value } init(weak: AnyObject?) { _weakValue = weak } } #endif ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Supports/Calendars.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public typealias Calendars = Calendar.Identifier public protocol CalendarConvertible { func toCalendar() -> Calendar } extension Calendar: CalendarConvertible { public func toCalendar() -> Calendar { return self } internal static func newCalendar(_ calendar: CalendarConvertible, configure: ((inout Calendar) -> Void)? = nil) -> Calendar { var cal = calendar.toCalendar() configure?(&cal) return cal } } extension Calendar.Identifier: CalendarConvertible { public func toCalendar() -> Calendar { return Calendar(identifier: self) } } // MARK: - Support for Calendar.Identifier encoding with Codable extension Calendar.Identifier: CustomStringConvertible { public var description: String { switch self { case .gregorian: return "gregorian" case .buddhist: return "buddhist" case .chinese: return "chinese" case .coptic: return "coptic" case .ethiopicAmeteMihret: return "ethiopicAmeteMihret" case .ethiopicAmeteAlem: return "ethiopicAmeteAlem" case .hebrew: return "hebrew" case .iso8601: return "iso8601" case .indian: return "indian" case .islamic: return "islamic" case .islamicCivil: return "islamicCivil" case .japanese: return "japanese" case .persian: return "persian" case .republicOfChina: return "republicOfChina" case .islamicTabular: return "islamicTabular" case .islamicUmmAlQura: return "islamicUmmAlQura" @unknown default: fatalError("Unsupported calendar \(self)") } } public init(_ rawValue: String) { switch rawValue { case Calendar.Identifier.gregorian.description: self = .gregorian case Calendar.Identifier.buddhist.description: self = .buddhist case Calendar.Identifier.chinese.description: self = .chinese case Calendar.Identifier.coptic.description: self = .coptic case Calendar.Identifier.ethiopicAmeteMihret.description: self = .ethiopicAmeteMihret case Calendar.Identifier.ethiopicAmeteAlem.description: self = .ethiopicAmeteAlem case Calendar.Identifier.hebrew.description: self = .hebrew case Calendar.Identifier.iso8601.description: self = .iso8601 case Calendar.Identifier.indian.description: self = .indian case Calendar.Identifier.islamic.description: self = .islamic case Calendar.Identifier.islamicCivil.description: self = .islamicCivil case Calendar.Identifier.japanese.description: self = .japanese case Calendar.Identifier.persian.description: self = .persian case Calendar.Identifier.republicOfChina.description: self = .republicOfChina case Calendar.Identifier.islamicTabular.description: self = .islamicTabular case Calendar.Identifier.islamicTabular.description: self = .islamicTabular case Calendar.Identifier.islamicUmmAlQura.description: self = .islamicUmmAlQura default: let defaultCalendar = SwiftDate.defaultRegion.calendar.identifier debugPrint("Calendar Identifier '\(rawValue)' not recognized. Using default (\(defaultCalendar))") self = defaultCalendar } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Supports/Commons.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation // MARK: - Atomic Variable Support @propertyWrapper internal struct Atomic { private let queue = DispatchQueue(label: "com.vadimbulavin.atomic") private var value: Value init(wrappedValue: Value) { self.value = wrappedValue } var wrappedValue: Value { get { return queue.sync { value } } set { queue.sync { value = newValue } } } } // MARK: - DateFormatter public extension DateFormatter { /// Return the local thread shared formatter initialized with the configuration of the region passed. /// /// - Parameters: /// - region: region used to pre-configure the cell. /// - format: optional format used to set the `dateFormat` property. /// - Returns: date formatter instance static func sharedFormatter(forRegion region: Region?, format: String? = nil) -> DateFormatter { let name = "SwiftDate_\(NSStringFromClass(DateFormatter.self))" let formatter: DateFormatter = threadSharedObject(key: name, create: { return DateFormatter() }) if let region = region { formatter.timeZone = region.timeZone formatter.calendar = region.calendar formatter.locale = region.locale } formatter.dateFormat = (format ?? DateFormats.iso8601) return formatter } /// Returned number formatter instance shared along calling thread to format ordinal numbers. /// /// - Parameter locale: locale to set /// - Returns: number formatter instance @available(iOS 9.0, macOS 10.11, *) static func sharedOrdinalNumberFormatter(locale: LocaleConvertible) -> NumberFormatter { let name = "SwiftDate_\(NSStringFromClass(NumberFormatter.self))" let formatter = threadSharedObject(key: name, create: { return NumberFormatter() }) formatter.numberStyle = .ordinal formatter.locale = locale.toLocale() return formatter } } /// This function create (if necessary) and return a thread singleton instance of the /// object you want. /// /// - Parameters: /// - key: identifier of the object. /// - create: create routine used the first time you are about to create the object in thread. /// - Returns: instance of the object for caller's thread. internal func threadSharedObject(key: String, create: () -> T) -> T { if let cachedObj = Thread.current.threadDictionary[key] as? T { return cachedObj } else { let newObject = create() Thread.current.threadDictionary[key] = newObject return newObject } } /// Style used to format month, weekday, quarter symbols. /// Stand-alone properties are for use in places like calendar headers. /// Non-stand-alone properties are for use in context (for example, “Saturday, November 12th”). /// /// - `default`: Default formatter (ie. `4th quarter` for quarter, `April` for months and `Wednesday` for weekdays) /// - defaultStandalone: See `default`; See `short`; stand-alone properties are for use in places like calendar headers. /// - short: Short symbols (ie. `Jun` for months, `Fri` for weekdays, `Q1` for quarters). /// - veryShort: Very short symbols (ie. `J` for months, `F` for weekdays, for quarter it just return `short` variant). /// - standaloneShort: See `short`; stand-alone properties are for use in places like calendar headers. /// - standaloneVeryShort: See `veryShort`; stand-alone properties are for use in places like calendar headers. public enum SymbolFormatStyle { case `default` case defaultStandalone case short case veryShort case standaloneShort case standaloneVeryShort } /// Encapsulate the logic to use date format strings public struct DateFormats { /// This is the built-in list of all supported formats for auto-parsing of a string to a date. internal static let builtInAutoFormat: [String] = [ DateFormats.iso8601, "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ", "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-dd", "h:mm:ss A", "h:mm A", "MM/dd/yyyy", "MMMM d, yyyy", "MMMM d, yyyy LT", "dddd, MMMM D, yyyy LT", "yyyyyy-MM-dd", "yyyy-MM-dd", "yyyy-'W'ww-E", "GGGG-'['W']'ww-E", "yyyy-'W'ww", "GGGG-'['W']'ww", "yyyy'W'ww", "yyyy-ddd", "HH:mm:ss.SSSS", "HH:mm:ss", "HH:mm", "HH" ] /// This is the ordered list of all formats SwiftDate can use in order to attempt parsing a passaed /// date expressed as string. Evaluation is made in order; you can add or remove new formats as you wish. /// In order to reset the list call `resetAutoFormats()` function. public static var autoFormats: [String] = DateFormats.builtInAutoFormat /// Default ISO8601 format string public static let iso8601: String = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" /// Extended format public static let extended: String = "eee dd-MMM-yyyy GG HH:mm:ss.SSS zzz" /// The Alternative RSS formatted date "d MMM yyyy HH:mm:ss ZZZ" i.e. "09 Sep 2011 15:26:08 +0200" public static let altRSS: String = "d MMM yyyy HH:mm:ss ZZZ" /// The RSS formatted date "EEE, d MMM yyyy HH:mm:ss ZZZ" i.e. "Fri, 09 Sep 2011 15:26:08 +0200" public static let rss: String = "EEE, d MMM yyyy HH:mm:ss ZZZ" /// The http header formatted date "EEE, dd MMM yyyy HH:mm:ss zzz" i.e. "Tue, 15 Nov 1994 12:45:26 GMT" public static let httpHeader: String = "EEE, dd MMM yyyy HH:mm:ss zzz" /// A generic standard format date i.e. "EEE MMM dd HH:mm:ss Z yyyy" public static let standard: String = "EEE MMM dd HH:mm:ss Z yyyy" /// SQL date format public static let sql: String = "yyyy-MM-dd'T'HH:mm:ss.SSSX" /// Reset the list of auto formats to the initial settings. public static func resetAutoFormats() { autoFormats = DateFormats.builtInAutoFormat } /// Parse a new string optionally passing the format in which is encoded. If no format is passed /// an attempt is made by cycling all the formats set in `autoFormats` property. /// /// - Parameters: /// - string: date expressed as string. /// - suggestedFormat: optional format of the date expressed by the string (set it if you can in order to optimize the parse task). /// - region: region in which the date is expressed. /// - Returns: parsed absolute `Date`, `nil` if parse fails. public static func parse(string: String, format: String?, region: Region) -> Date? { let formats = (format != nil ? [format!] : DateFormats.autoFormats) return DateFormats.parse(string: string, formats: formats, region: region) } public static func parse(string: String, formats: [String], region: Region) -> Date? { let formatter = DateFormatter.sharedFormatter(forRegion: region) var parsedDate: Date? for format in formats { formatter.dateFormat = format formatter.locale = region.locale if let date = formatter.date(from: string) { parsedDate = date break } } return parsedDate } } // MARK: - Calendar Extension public extension Calendar.Component { internal static func toSet(_ src: [Calendar.Component]) -> Set { var l: Set = [] src.forEach { l.insert($0) } return l } internal var nsCalendarUnit: NSCalendar.Unit { switch self { case .era: return NSCalendar.Unit.era case .year: return NSCalendar.Unit.year case .month: return NSCalendar.Unit.month case .day: return NSCalendar.Unit.day case .hour: return NSCalendar.Unit.hour case .minute: return NSCalendar.Unit.minute case .second: return NSCalendar.Unit.second case .weekday: return NSCalendar.Unit.weekday case .weekdayOrdinal: return NSCalendar.Unit.weekdayOrdinal case .quarter: return NSCalendar.Unit.quarter case .weekOfMonth: return NSCalendar.Unit.weekOfMonth case .weekOfYear: return NSCalendar.Unit.weekOfYear case .yearForWeekOfYear: return NSCalendar.Unit.yearForWeekOfYear case .nanosecond: return NSCalendar.Unit.nanosecond case .calendar: return NSCalendar.Unit.calendar case .timeZone: return NSCalendar.Unit.timeZone @unknown default: fatalError("Unsupported type \(self)") } } } /// Rounding mode for dates. /// Round off/up (ceil) or down (floor) target date. public enum RoundDateMode { case to5Mins case to10Mins case to30Mins case toMins(_: Int) case toCeil5Mins case toCeil10Mins case toCeil30Mins case toCeilMins(_: Int) case toFloor5Mins case toFloor10Mins case toFloor30Mins case toFloorMins(_: Int) } /// Related type enum to get derivated date from a receiver date. public enum DateRelatedType { case startOfDay case endOfDay case startOfWeek case endOfWeek case startOfMonth case endOfMonth case tomorrow case tomorrowAtStart case yesterday case yesterdayAtStart case nearestMinute(minute: Int) case nearestHour(hour :Int) case nextWeekday(_: WeekDay) case nextDSTDate case prevMonth case nextMonth case prevWeek case nextWeek case nextYear case prevYear case nextDSTTransition } public struct TimeCalculationOptions { /// Specifies the technique the search algorithm uses to find result public var matchingPolicy: Calendar.MatchingPolicy /// Specifies the behavior when multiple matches are found public var repeatedTimePolicy: Calendar.RepeatedTimePolicy /// Specifies the direction in time to search public var direction: Calendar.SearchDirection public init(matching: Calendar.MatchingPolicy = .nextTime, timePolicy: Calendar.RepeatedTimePolicy = .first, direction: Calendar.SearchDirection = .forward) { self.matchingPolicy = matching self.repeatedTimePolicy = timePolicy self.direction = direction } } // MARK: - compactMap for Swift 4.0 (not necessary > 4.0) #if swift(>=4.1) #else extension Collection { func compactMap( _ transform: (Element) throws -> ElementOfResult? ) rethrows -> [ElementOfResult] { return try flatMap(transform) } } #endif // MARK: - Foundation Bundle private class BundleFinder {} extension Foundation.Bundle { /// Returns the resource bundle associated with the current Swift module. /// This is used instead of `module` to allows compatibility outside the SwiftPM environment (ie. CocoaPods). static var appModule: Bundle? = { let bundleName = "SwiftDate_SwiftDate" let candidates = [ // Bundle should be present here when the package is linked into an App. Bundle.main.resourceURL, // Bundle should be present here when the package is linked into a framework. Bundle(for: BundleFinder.self).resourceURL, // For command-line tools. Bundle.main.bundleURL, ] for candidate in candidates { let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle") if let bundle = bundlePath.flatMap(Bundle.init(url:)) { return bundle } } return nil }() } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Supports/Locales.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // // swiftlint:disable file_length import Foundation public protocol LocaleConvertible { func toLocale() -> Locale } extension Locale: LocaleConvertible { public func toLocale() -> Locale { return self } } // swiftlint:disable type_body_length public enum Locales: String, LocaleConvertible { case current = "current" case autoUpdating = "currentAutoUpdating" case afrikaans = "af" case afrikaansNamibia = "af_NA" case afrikaansSouthAfrica = "af_ZA" case aghem = "agq" case aghemCameroon = "agq_CM" case akan = "ak" case akanGhana = "ak_GH" case albanian = "sq" case albanianAlbania = "sq_AL" case albanianKosovo = "sq_XK" case albanianMacedonia = "sq_MK" case amharic = "am" case amharicEthiopia = "am_ET" case arabic = "ar" case arabicAlgeria = "ar_DZ" case arabicBahrain = "ar_BH" case arabicChad = "ar_TD" case arabicComoros = "ar_KM" case arabicDjibouti = "ar_DJ" case arabicEgypt = "ar_EG" case arabicEritrea = "ar_ER" case arabicIraq = "ar_IQ" case arabicIsrael = "ar_IL" case arabicJordan = "ar_JO" case arabicKuwait = "ar_KW" case arabicLebanon = "ar_LB" case arabicLibya = "ar_LY" case arabicMauritania = "ar_MR" case arabicMorocco = "ar_MA" case arabicOman = "ar_OM" case arabicPalestinianTerritories = "ar_PS" case arabicQatar = "ar_QA" case arabicSaudiArabia = "ar_SA" case arabicSomalia = "ar_SO" case arabicSouthSudan = "ar_SS" case arabicSudan = "ar_SD" case arabicSyria = "ar_SY" case arabicTunisia = "ar_TN" case arabicUnitedArabEmirates = "ar_AE" case arabicWesternSahara = "ar_EH" case arabicWorld = "ar_001" case arabicYemen = "ar_YE" case armenian = "hy" case armenianArmenia = "hy_AM" case assamese = "as" case assameseIndia = "as_IN" case asu = "asa" case asuTanzania = "asa_TZ" case azerbaijani = "az_Latn" case azerbaijaniAzerbaijan = "az_Latn_AZ" case azerbaijaniCyrillic = "az_Cyrl" case azerbaijaniCyrillicAzerbaijan = "az_Cyrl_AZ" case bafia = "ksf" case bafiaCameroon = "ksf_CM" case bambara = "bm_Latn" case bambaraMali = "bm_Latn_ML" case basaa = "bas" case basaaCameroon = "bas_CM" case basque = "eu" case basqueSpain = "eu_ES" case belarusian = "be" case belarusianBelarus = "be_BY" case bemba = "bem" case bembaZambia = "bem_ZM" case bena = "bez" case benaTanzania = "bez_TZ" case bengali = "bn" case bengaliBangladesh = "bn_BD" case engaliIndia = "bn_IN" case bodo = "brx" case bodoIndia = "brx_IN" case bosnian = "bs_Latn" case bosnianBosniaHerzegovina = "bs_Latn_BA" case bosnianCyrillic = "bs_Cyrl" case bosnianCyrillicBosniaHerzegovina = "bs_Cyrl_BA" case breton = "br" case bretonFrance = "br_FR" case bulgarian = "bg" case bulgarianBulgaria = "bg_BG" case burmese = "my" case burmeseMyanmarBurma = "my_MM" case catalan = "ca" case catalanAndorra = "ca_AD" case catalanFrance = "ca_FR" case catalanItaly = "ca_IT" case catalanSpain = "ca_ES" case centralAtlasTamazight = "tzm_Latn" case centralAtlasTamazightMorocco = "tzm_Latn_MA" case centralKurdish = "ckb" case centralKurdishIran = "ckb_IR" case centralKurdishIraq = "ckb_IQ" case cherokee = "chr" case cherokeeUnitedStates = "chr_US" case chiga = "cgg" case chigaUganda = "cgg_UG" case chinese = "zh" case chineseChina = "zh_Hans_CN" case chineseHongKongSarChina = "zh_Hant_HK" case chineseMacauSarChina = "zh_Hant_MO" case chineseSimplified = "zh_Hans" case chineseSimplifiedHongKongSarChina = "zh_Hans_HK" case chineseSimplifiedMacauSarChina = "zh_Hans_MO" case chineseSingapore = "zh_Hans_SG" case chineseTaiwan = "zh_Hant_TW" case chineseTraditional = "zh_Hant" case colognian = "ksh" case colognianGermany = "ksh_DE" case cornish = "kw" case cornishUnitedKingdom = "kw_GB" case croatian = "hr" case croatianBosniaHerzegovina = "hr_BA" case croatianCroatia = "hr_HR" case czech = "cs" case czechCzechRepublic = "cs_CZ" case danish = "da" case danishDenmark = "da_DK" case danishGreenland = "da_GL" case duala = "dua" case dualaCameroon = "dua_CM" case dutch = "nl" case dutchAruba = "nl_AW" case dutchBelgium = "nl_BE" case dutchCaribbeanNetherlands = "nl_BQ" case dutchCuraao = "nl_CW" case dutchNetherlands = "nl_NL" case dutchSintMaarten = "nl_SX" case dutchSuriname = "nl_SR" case dzongkha = "dz" case dzongkhaBhutan = "dz_BT" case embu = "ebu" case embuKenya = "ebu_KE" case english = "en" case englishAlbania = "en_AL" case englishAmericanSamoa = "en_AS" case englishAndorra = "en_AD" case englishAnguilla = "en_AI" case englishAntiguaBarbuda = "en_AG" case englishAustralia = "en_AU" case englishAustria = "en_AT" case englishBahamas = "en_BS" case englishBarbados = "en_BB" case englishBelgium = "en_BE" case englishBelize = "en_BZ" case englishBermuda = "en_BM" case englishBosniaHerzegovina = "en_BA" case englishBotswana = "en_BW" case englishBritishIndianOceanTerritory = "en_IO" case englishBritishVirginIslands = "en_VG" case englishCameroon = "en_CM" case englishCanada = "en_CA" case englishCaymanIslands = "en_KY" case englishChristmasIsland = "en_CX" case englishCocosKeelingIslands = "en_CC" case englishCookIslands = "en_CK" case englishCroatia = "en_HR" case englishCyprus = "en_CY" case englishCzechRepublic = "en_CZ" case englishDenmark = "en_DK" case englishDiegoGarcia = "en_DG" case englishDominica = "en_DM" case englishEritrea = "en_ER" case englishEstonia = "en_EE" case englishEurope = "en_150" case englishFalklandIslands = "en_FK" case englishFiji = "en_FJ" case englishFinland = "en_FI" case englishFrance = "en_FR" case englishGambia = "en_GM" case englishGermany = "en_DE" case englishGhana = "en_GH" case englishGibraltar = "en_GI" case englishGreece = "en_GR" case englishGrenada = "en_GD" case englishGuam = "en_GU" case englishGuernsey = "en_GG" case englishGuyana = "en_GY" case englishHongKongSarChina = "en_HK" case englishHungary = "en_HU" case englishIceland = "en_IS" case englishIndia = "en_IN" case englishIreland = "en_IE" case englishIsleOfMan = "en_IM" case englishIsrael = "en_IL" case englishItaly = "en_IT" case englishJamaica = "en_JM" case englishJersey = "en_JE" case englishKenya = "en_KE" case englishKiribati = "en_KI" case englishLatvia = "en_LV" case englishLesotho = "en_LS" case englishLiberia = "en_LR" case englishLithuania = "en_LT" case englishLuxembourg = "en_LU" case englishMacauSarChina = "en_MO" case englishMadagascar = "en_MG" case englishMalawi = "en_MW" case englishMalaysia = "en_MY" case englishMalta = "en_MT" case englishMarshallIslands = "en_MH" case englishMauritius = "en_MU" case englishMicronesia = "en_FM" case englishMontenegro = "en_ME" case englishMontserrat = "en_MS" case englishNamibia = "en_NA" case englishNauru = "en_NR" case englishNetherlands = "en_NL" case englishNewZealand = "en_NZ" case englishNigeria = "en_NG" case englishNiue = "en_NU" case englishNorfolkIsland = "en_NF" case englishNorthernMarianaIslands = "en_MP" case englishNorway = "en_NO" case englishPakistan = "en_PK" case englishPalau = "en_PW" case englishPapuaNewGuinea = "en_PG" case englishPhilippines = "en_PH" case englishPitcairnIslands = "en_PN" case englishPoland = "en_PL" case englishPortugal = "en_PT" case englishPuertoRico = "en_PR" case englishRomania = "en_RO" case englishRussia = "en_RU" case englishRwanda = "en_RW" case englishSamoa = "en_WS" case englishSeychelles = "en_SC" case englishSierraLeone = "en_SL" case englishSingapore = "en_SG" case englishSintMaarten = "en_SX" case englishSlovakia = "en_SK" case englishSlovenia = "en_SI" case englishSolomonIslands = "en_SB" case englishSouthAfrica = "en_ZA" case englishSouthSudan = "en_SS" case englishSpain = "en_ES" case englishStHelena = "en_SH" case englishStKittsNevis = "en_KN" case englishStLucia = "en_LC" case englishStVincentGrenadines = "en_VC" case englishSudan = "en_SD" case englishSwaziland = "en_SZ" case englishSweden = "en_SE" case englishSwitzerland = "en_CH" case englishTanzania = "en_TZ" case englishTokelau = "en_TK" case englishTonga = "en_TO" case englishTrinidadTobago = "en_TT" case englishTurkey = "en_TR" case englishTurksCaicosIslands = "en_TC" case englishTuvalu = "en_TV" case englishUSOutlyingIslands = "en_UM" case englishUSVirginIslands = "en_VI" case englishUganda = "en_UG" case englishUnitedKingdom = "en_GB" case englishUnitedStates = "en_US" case englishUnitedStatesComputer = "en_US_POSIX" case englishVanuatu = "en_VU" case englishWorld = "en_001" case englishZambia = "en_ZM" case englishZimbabwe = "en_ZW" case esperanto = "eo" case estonian = "et" case estonianEstonia = "et_EE" case ewe = "ee" case eweGhana = "ee_GH" case eweTogo = "ee_TG" case ewondo = "ewo" case ewondoCameroon = "ewo_CM" case faroese = "fo" case faroeseFaroeIslands = "fo_FO" case filipino = "fil" case filipinoPhilippines = "fil_PH" case finnish = "fi" case finnishFinland = "fi_FI" case french = "fr" case frenchAlgeria = "fr_DZ" case frenchBelgium = "fr_BE" case frenchBenin = "fr_BJ" case frenchBurkinaFaso = "fr_BF" case frenchBurundi = "fr_BI" case frenchCameroon = "fr_CM" case frenchCanada = "fr_CA" case frenchCentralAfricanRepublic = "fr_CF" case frenchChad = "fr_TD" case frenchComoros = "fr_KM" case frenchCongoBrazzaville = "fr_CG" case frenchCongoKinshasa = "fr_CD" case frenchCteDivoire = "fr_CI" case frenchDjibouti = "fr_DJ" case frenchEquatorialGuinea = "fr_GQ" case frenchFrance = "fr_FR" case frenchFrenchGuiana = "fr_GF" case frenchFrenchPolynesia = "fr_PF" case frenchGabon = "fr_GA" case frenchGuadeloupe = "fr_GP" case frenchGuinea = "fr_GN" case frenchHaiti = "fr_HT" case frenchLuxembourg = "fr_LU" case frenchMadagascar = "fr_MG" case frenchMali = "fr_ML" case frenchMartinique = "fr_MQ" case frenchMauritania = "fr_MR" case frenchMauritius = "fr_MU" case frenchMayotte = "fr_YT" case frenchMonaco = "fr_MC" case frenchMorocco = "fr_MA" case frenchNewCaledonia = "fr_NC" case frenchNiger = "fr_NE" case frenchRunion = "fr_RE" case frenchRwanda = "fr_RW" case frenchSenegal = "fr_SN" case frenchSeychelles = "fr_SC" case frenchStBarthlemy = "fr_BL" case frenchStMartin = "fr_MF" case frenchStPierreMiquelon = "fr_PM" case frenchSwitzerland = "fr_CH" case frenchSyria = "fr_SY" case frenchTogo = "fr_TG" case frenchTunisia = "fr_TN" case frenchVanuatu = "fr_VU" case frenchWallisFutuna = "fr_WF" case friulian = "fur" case friulianItaly = "fur_IT" case fulah = "ff" case fulahCameroon = "ff_CM" case fulahGuinea = "ff_GN" case fulahMauritania = "ff_MR" case fulahSenegal = "ff_SN" case galician = "gl" case galicianSpain = "gl_ES" case ganda = "lg" case gandaUganda = "lg_UG" case georgian = "ka" case georgianGeorgia = "ka_GE" case german = "de" case germanAustria = "de_AT" case germanBelgium = "de_BE" case germanGermany = "de_DE" case germanLiechtenstein = "de_LI" case germanLuxembourg = "de_LU" case germanSwitzerland = "de_CH" case greek = "el" case greekCyprus = "el_CY" case greekGreece = "el_GR" case gujarati = "gu" case gujaratiIndia = "gu_IN" case gusii = "guz" case gusiiKenya = "guz_KE" case hausa = "ha_Latn" case hausaGhana = "ha_Latn_GH" case hausaNiger = "ha_Latn_NE" case hausaNigeria = "ha_Latn_NG" case hawaiian = "haw" case hawaiianUnitedStates = "haw_US" case hebrew = "he" case hebrewIsrael = "he_IL" case hindi = "hi" case hindiIndia = "hi_IN" case hungarian = "hu" case hungarianHungary = "hu_HU" case icelandic = "is" case icelandicIceland = "is_IS" case igbo = "ig" case igboNigeria = "ig_NG" case inariSami = "smn" case inariSamiFinland = "smn_FI" case indonesian = "id" case indonesianIndonesia = "id_ID" case inuktitut = "iu" case inuktitutUnifiedCanadianAboriginalSyllabics = "iu_Cans" case inuktitutUnifiedCanadianAboriginalSyllabicsCanada = "iu_Cans_CA" case irish = "ga" case irishIreland = "ga_IE" case italian = "it" case italianItaly = "it_IT" case italianSanMarino = "it_SM" case italianSwitzerland = "it_CH" case japanese = "ja" case japaneseJapan = "ja_JP" case jolaFonyi = "dyo" case jolaFonyiSenegal = "dyo_SN" case kabuverdianu = "kea" case kabuverdianuCapeVerde = "kea_CV" case kabyle = "kab" case kabyleAlgeria = "kab_DZ" case kako = "kkj" case kakoCameroon = "kkj_CM" case kalaallisut = "kl" case kalaallisutGreenland = "kl_GL" case kalenjin = "kln" case kalenjinKenya = "kln_KE" case kamba = "kam" case kambaKenya = "kam_KE" case kannada = "kn" case kannadaIndia = "kn_IN" case kashmiri = "ks" case kashmiriArabic = "ks_Arab" case kashmiriArabicIndia = "ks_Arab_IN" case kazakh = "kk_Cyrl" case kazakhKazakhstan = "kk_Cyrl_KZ" case khmer = "km" case khmerCambodia = "km_KH" case kikuyu = "ki" case kikuyuKenya = "ki_KE" case kinyarwanda = "rw" case kinyarwandaRwanda = "rw_RW" case konkani = "kok" case konkaniIndia = "kok_IN" case korean = "ko" case koreanNorthKorea = "ko_KP" case koreanSouthKorea = "ko_KR" case koyraChiini = "khq" case koyraChiiniMali = "khq_ML" case koyraboroSenni = "ses" case koyraboroSenniMali = "ses_ML" case kwasio = "nmg" case kwasioCameroon = "nmg_CM" case kyrgyz = "ky_Cyrl" case kyrgyzKyrgyzstan = "ky_Cyrl_KG" case lakota = "lkt" case lakotaUnitedStates = "lkt_US" case langi = "lag" case langiTanzania = "lag_TZ" case lao = "lo" case laoLaos = "lo_LA" case latvian = "lv" case latvianLatvia = "lv_LV" case lingala = "ln" case lingalaAngola = "ln_AO" case lingalaCentralAfricanRepublic = "ln_CF" case lingalaCongoBrazzaville = "ln_CG" case lingalaCongoKinshasa = "ln_CD" case lithuanian = "lt" case lithuanianLithuania = "lt_LT" case lowerSorbian = "dsb" case lowerSorbianGermany = "dsb_DE" case lubaKatanga = "lu" case lubaKatangaCongoKinshasa = "lu_CD" case luo = "luo" case luoKenya = "luo_KE" case luxembourgish = "lb" case luxembourgishLuxembourg = "lb_LU" case luyia = "luy" case luyiaKenya = "luy_KE" case macedonian = "mk" case macedonianMacedonia = "mk_MK" case machame = "jmc" case machameTanzania = "jmc_TZ" case makhuwaMeetto = "mgh" case makhuwaMeettoMozambique = "mgh_MZ" case makonde = "kde" case makondeTanzania = "kde_TZ" case malagasy = "mg" case malagasyMadagascar = "mg_MG" case malay = "ms_Latn" case malayArabic = "ms_Arab" case malayArabicBrunei = "ms_Arab_BN" case malayArabicMalaysia = "ms_Arab_MY" case malayBrunei = "ms_Latn_BN" case malayMalaysia = "ms_Latn_MY" case malaySingapore = "ms_Latn_SG" case malayalam = "ml" case malayalamIndia = "ml_IN" case maltese = "mt" case malteseMalta = "mt_MT" case manx = "gv" case manxIsleOfMan = "gv_IM" case marathi = "mr" case marathiIndia = "mr_IN" case masai = "mas" case masaiKenya = "mas_KE" case masaiTanzania = "mas_TZ" case meru = "mer" case meruKenya = "mer_KE" case meta = "mgo" case metaCameroon = "mgo_CM" case mongolian = "mn_Cyrl" case mongolianMongolia = "mn_Cyrl_MN" case morisyen = "mfe" case morisyenMauritius = "mfe_MU" case mundang = "mua" case mundangCameroon = "mua_CM" case nama = "naq" case namaNamibia = "naq_NA" case nepali = "ne" case nepaliIndia = "ne_IN" case nepaliNepal = "ne_NP" case ngiemboon = "nnh" case ngiemboonCameroon = "nnh_CM" case ngomba = "jgo" case ngombaCameroon = "jgo_CM" case northNdebele = "nd" case northNdebeleZimbabwe = "nd_ZW" case northernSami = "se" case northernSamiFinland = "se_FI" case northernSamiNorway = "se_NO" case northernSamiSweden = "se_SE" case norwegianBokml = "nb" case norwegianBokmlNorway = "nb_NO" case norwegianBokmlSvalbardJanMayen = "nb_SJ" case norwegianNynorsk = "nn" case norwegianNynorskNorway = "nn_NO" case nuer = "nus" case nuerSudan = "nus_SD" case nyankole = "nyn" case nyankoleUganda = "nyn_UG" case oriya = "or" case oriyaIndia = "or_IN" case oromo = "om" case oromoEthiopia = "om_ET" case oromoKenya = "om_KE" case ossetic = "os" case osseticGeorgia = "os_GE" case osseticRussia = "os_RU" case pashto = "ps" case pashtoAfghanistan = "ps_AF" case persian = "fa" case persianAfghanistan = "fa_AF" case persianIran = "fa_IR" case polish = "pl" case polishPoland = "pl_PL" case portuguese = "pt" case portugueseAngola = "pt_AO" case portugueseBrazil = "pt_BR" case portugueseCapeVerde = "pt_CV" case portugueseGuineaBissau = "pt_GW" case portugueseMacauSarChina = "pt_MO" case portugueseMozambique = "pt_MZ" case portuguesePortugal = "pt_PT" case portugueseSoTomPrncipe = "pt_ST" case portugueseTimorLeste = "pt_TL" case punjabi = "pa_Guru" case punjabiArabic = "pa_Arab" case punjabiArabicPakistan = "pa_Arab_PK" case punjabiIndia = "pa_Guru_IN" case quechua = "qu" case quechuaBolivia = "qu_BO" case quechuaEcuador = "qu_EC" case quechuaPeru = "qu_PE" case romanian = "ro" case romanianMoldova = "ro_MD" case romanianRomania = "ro_RO" case romansh = "rm" case romanshSwitzerland = "rm_CH" case rombo = "rof" case romboTanzania = "rof_TZ" case rundi = "rn" case rundiBurundi = "rn_BI" case russian = "ru" case russianBelarus = "ru_BY" case russianKazakhstan = "ru_KZ" case russianKyrgyzstan = "ru_KG" case russianMoldova = "ru_MD" case russianRussia = "ru_RU" case russianUkraine = "ru_UA" case rwa = "rwk" case rwaTanzania = "rwk_TZ" case sakha = "sah" case sakhaRussia = "sah_RU" case samburu = "saq" case samburuKenya = "saq_KE" case sango = "sg" case sangoCentralAfricanRepublic = "sg_CF" case sangu = "sbp" case sanguTanzania = "sbp_TZ" case scottishGaelic = "gd" case scottishGaelicUnitedKingdom = "gd_GB" case sena = "seh" case senaMozambique = "seh_MZ" case serbian = "sr_Cyrl" case serbianBosniaHerzegovina = "sr_Cyrl_BA" case serbianKosovo = "sr_Cyrl_XK" case serbianLatin = "sr_Latn" case serbianLatinBosniaHerzegovina = "sr_Latn_BA" case serbianLatinKosovo = "sr_Latn_XK" case serbianLatinMontenegro = "sr_Latn_ME" case serbianLatinSerbia = "sr_Latn_RS" case serbianMontenegro = "sr_Cyrl_ME" case serbianSerbia = "sr_Cyrl_RS" case shambala = "ksb" case shambalaTanzania = "ksb_TZ" case shona = "sn" case shonaZimbabwe = "sn_ZW" case sichuanYi = "ii" case sichuanYiChina = "ii_CN" case sinhala = "si" case sinhalaSriLanka = "si_LK" case slovak = "sk" case slovakSlovakia = "sk_SK" case slovenian = "sl" case slovenianSlovenia = "sl_SI" case soga = "xog" case sogaUganda = "xog_UG" case somali = "so" case somaliDjibouti = "so_DJ" case somaliEthiopia = "so_ET" case somaliKenya = "so_KE" case somaliSomalia = "so_SO" case spanish = "es" case spanishArgentina = "es_AR" case spanishBolivia = "es_BO" case spanishCanaryIslands = "es_IC" case spanishCeutaMelilla = "es_EA" case spanishChile = "es_CL" case spanishColombia = "es_CO" case spanishCostaRica = "es_CR" case spanishCuba = "es_CU" case spanishDominicanRepublic = "es_DO" case spanishEcuador = "es_EC" case spanishElSalvador = "es_SV" case spanishEquatorialGuinea = "es_GQ" case spanishGuatemala = "es_GT" case spanishHonduras = "es_HN" case spanishLatinAmerica = "es_419" case spanishMexico = "es_MX" case spanishNicaragua = "es_NI" case spanishPanama = "es_PA" case spanishParaguay = "es_PY" case spanishPeru = "es_PE" case spanishPhilippines = "es_PH" case spanishPuertoRico = "es_PR" case spanishSpain = "es_ES" case spanishUnitedStates = "es_US" case spanishUruguay = "es_UY" case spanishVenezuela = "es_VE" case standardMoroccanTamazight = "zgh" case standardMoroccanTamazightMorocco = "zgh_MA" case swahili = "sw" case swahiliCongoKinshasa = "sw_CD" case swahiliKenya = "sw_KE" case swahiliTanzania = "sw_TZ" case swahiliUganda = "sw_UG" case swedish = "sv" case swedishlandIslands = "sv_AX" case swedishFinland = "sv_FI" case swedishSweden = "sv_SE" case swissGerman = "gsw" case swissGermanFrance = "gsw_FR" case swissGermanLiechtenstein = "gsw_LI" case swissGermanSwitzerland = "gsw_CH" case tachelhit = "shi_Latn" case tachelhitMorocco = "shi_Latn_MA" case tachelhitTifinagh = "shi_Tfng" case tachelhitTifinaghMorocco = "shi_Tfng_MA" case taita = "dav" case taitaKenya = "dav_KE" case tajik = "tg_Cyrl" case tajikTajikistan = "tg_Cyrl_TJ" case tamil = "ta" case tamilIndia = "ta_IN" case tamilMalaysia = "ta_MY" case tamilSingapore = "ta_SG" case tamilSriLanka = "ta_LK" case tasawaq = "twq" case tasawaqNiger = "twq_NE" case telugu = "te" case teluguIndia = "te_IN" case teso = "teo" case tesoKenya = "teo_KE" case tesoUganda = "teo_UG" case thai = "th" case thaiThailand = "th_TH" case tibetan = "bo" case tibetanChina = "bo_CN" case tibetanIndia = "bo_IN" case tigrinya = "ti" case tigrinyaEritrea = "ti_ER" case tigrinyaEthiopia = "ti_ET" case tongan = "to" case tonganTonga = "to_TO" case turkish = "tr" case turkishCyprus = "tr_CY" case turkishTurkey = "tr_TR" case turkmen = "tk_Latn" case turkmenTurkmenistan = "tk_Latn_TM" case ukrainian = "uk" case ukrainianUkraine = "uk_UA" case upperSorbian = "hsb" case upperSorbianGermany = "hsb_DE" case urdu = "ur" case urduIndia = "ur_IN" case urduPakistan = "ur_PK" case uyghur = "ug" case uyghurArabic = "ug_Arab" case uyghurArabicChina = "ug_Arab_CN" case uzbek = "uz_Cyrl" case uzbekArabic = "uz_Arab" case uzbekArabicAfghanistan = "uz_Arab_AF" case uzbekLatin = "uz_Latn" case uzbekLatinUzbekistan = "uz_Latn_UZ" case uzbekUzbekistan = "uz_Cyrl_UZ" case vai = "vai_Vaii" case vaiLatin = "vai_Latn" case vaiLatinLiberia = "vai_Latn_LR" case vaiLiberia = "vai_Vaii_LR" case vietnamese = "vi" case vietnameseVietnam = "vi_VN" case vunjo = "vun" case vunjoTanzania = "vun_TZ" case walser = "wae" case walserSwitzerland = "wae_CH" case welsh = "cy" case welshUnitedKingdom = "cy_GB" case westernFrisian = "fy" case westernFrisianNetherlands = "fy_NL" case yangben = "yav" case yangbenCameroon = "yav_CM" case yiddish = "yi" case yiddishWorld = "yi_001" case yoruba = "yo" case yorubaBenin = "yo_BJ" case yorubaNigeria = "yo_NG" case zarma = "dje" case zarmaNiger = "dje_NE" case zulu = "zu" case zuluSouthAfrica = "zu_ZA" /// Return a valid `Locale` instance from current selected locale enum public func toLocale() -> Locale { switch self { case .current: return Locale.current case .autoUpdating: return Locale.autoupdatingCurrent default: return Locale(identifier: rawValue) } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Supports/TimeStructures.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation // MARK: - Weekday /// This define the weekdays for some functions. public enum WeekDay: Int { case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday /// Returns the name of the day given a specific locale. /// For example, for the `Friday` enum value, the en_AU locale would return "Friday" and fr_FR would return "samedi" /// /// - Parameter locale: locale of the output, omit to use the `defaultRegion`'s locale. /// - Returns: display name public func name(style: SymbolFormatStyle = .`default`, locale: LocaleConvertible = SwiftDate.defaultRegion.locale) -> String { let region = Region(calendar: SwiftDate.defaultRegion.calendar, zone: SwiftDate.defaultRegion.timeZone, locale: locale) let formatter = DateFormatter.sharedFormatter(forRegion: region, format: nil) let idx = (self.rawValue - 1) switch style { case .default: return formatter.weekdaySymbols[idx] case .defaultStandalone: return formatter.standaloneWeekdaySymbols[idx] case .short: return formatter.shortWeekdaySymbols[idx] case .standaloneShort: return formatter.shortStandaloneWeekdaySymbols[idx] case .veryShort: return formatter.veryShortWeekdaySymbols[idx] case .standaloneVeryShort: return formatter.veryShortStandaloneWeekdaySymbols[idx] } } /// Adds a number of days to the current weekday and returns the new weekday. /// /// - Parameter months: number of months to add /// - Returns: new month. public func add(days: Int) -> WeekDay { let normalized = days % 7 return WeekDay(rawValue: ((self.rawValue + normalized + 7 - 1) % 7) + 1)! } /// Subtracts a number of days from the current weekday and returns the new weekday. /// /// - Parameter months: number of days to subtract. May be negative, in which case it will be added /// - Returns: new weekday. public func subtract(days: Int) -> WeekDay { return add(days: -(days % 7)) } } // MARK: - Year public struct Year: CustomStringConvertible, Equatable { let year: Int public var description: String { return "\(self.year)" } /// Constructs a `Year` from the passed value. /// /// - Parameter year: year value. Can be negative. public init(_ year: Int) { self.year = year } /// Returns whether this year is a leap year /// /// - Returns: A boolean indicating whether this year is a leap year public func isLeap() -> Bool { return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0) } /// Returns the number of days in this year /// /// - Returns: The number of days in this year public func numberOfDays() -> Int { return self.isLeap() ? 366 : 365 } } // MARK: - Month /// Defines months in a year public enum Month: Int, CustomStringConvertible, Equatable { case january = 0, february, march, april, may, june, july, august, september, october, november, december public var description: String { return self.name() } /// Returns the name of the month given a specific locale. /// For example, for the `January` enum value, the en_AU locale would return "January" and fr_FR would return "janvier" /// /// - Parameter locale: locale of the output, omit to use the `defaultRegion`'s locale. /// - Returns: display name public func name(style: SymbolFormatStyle = .`default`, locale: LocaleConvertible = SwiftDate.defaultRegion.locale) -> String { let region = Region(calendar: SwiftDate.defaultRegion.calendar, zone: SwiftDate.defaultRegion.timeZone, locale: locale) let formatter = DateFormatter.sharedFormatter(forRegion: region, format: nil) switch style { case .default: return formatter.monthSymbols[self.rawValue] case .defaultStandalone: return formatter.standaloneMonthSymbols[self.rawValue] case .short: return formatter.shortMonthSymbols[self.rawValue] case .standaloneShort: return formatter.shortStandaloneMonthSymbols[self.rawValue] case .veryShort: return formatter.veryShortMonthSymbols[self.rawValue] case .standaloneVeryShort: return formatter.veryShortStandaloneMonthSymbols[self.rawValue] } } /// Adds a number of months to the current month and returns the new month. /// /// - Parameter months: number of months to add /// - Returns: new month. public func add(months: Int) -> Month { let normalized = months % 12 return Month(rawValue: (self.rawValue + normalized + 12) % 12)! } /// Subtracts a number of months from the current month and returns the new month. /// /// - Parameter months: number of months to subtract. May be negative, in which case it will be added /// - Returns: new month. public func subtract(months: Int) -> Month { return add(months: -(months % 12)) } /// Returns the number of days in a this month for a given year /// /// - Parameter year: reference year. /// - Returns: The number of days in this month. public func numberOfDays(year: Int) -> Int { switch self { case .february: return Year(year).isLeap() ? 29 : 28 case .april, .june, .september, .november: return 30 default: return 31 } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/Supports/Zones.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public protocol ZoneConvertible { func toTimezone() -> TimeZone } extension TimeZone: ZoneConvertible { public func toTimezone() -> TimeZone { return self } } // swiftlint:disable type_body_length public enum Zones: String, ZoneConvertible { case current = "Current" case autoUpdating = "CurrentAutoUpdating" case africaAbidjan = "Africa/Abidjan" case africaAccra = "Africa/Accra" case africaAddisAbaba = "Africa/Addis_Ababa" case africaAlgiers = "Africa/Algiers" case africaAsmara = "Africa/Asmara" case africaBamako = "Africa/Bamako" case africaBangui = "Africa/Bangui" case africaBanjul = "Africa/Banjul" case africaBissau = "Africa/Bissau" case africaBlantyre = "Africa/Blantyre" case africaBrazzaville = "Africa/Brazzaville" case africaBujumbura = "Africa/Bujumbura" case africaCairo = "Africa/Cairo" case africaCasablanca = "Africa/Casablanca" case africaCeuta = "Africa/Ceuta" case africaConakry = "Africa/Conakry" case africaDakar = "Africa/Dakar" case africaDarEsSalaam = "Africa/Dar_es_Salaam" case africaDjibouti = "Africa/Djibouti" case africaDouala = "Africa/Douala" case africaElAaiun = "Africa/El_Aaiun" case africaFreetown = "Africa/Freetown" case africaGaborone = "Africa/Gaborone" case africaHarare = "Africa/Harare" case africaJohannesburg = "Africa/Johannesburg" case africaJuba = "Africa/Juba" case africaKampala = "Africa/Kampala" case africaKhartoum = "Africa/Khartoum" case fricaKigali = "Africa/Kigali" case africaKinshasa = "Africa/Kinshasa" case africaLagos = "Africa/Lagos" case africaLibreville = "Africa/Libreville" case africaLome = "Africa/Lome" case africaLuanda = "Africa/Luanda" case africaLubumbashi = "Africa/Lubumbashi" case africaLusaka = "Africa/Lusaka" case africaMalabo = "Africa/Malabo" case africaMaputo = "Africa/Maputo" case africaMaseru = "Africa/Maseru" case africaMbabane = "Africa/Mbabane" case africaMogadishu = "Africa/Mogadishu" case africaMonrovia = "Africa/Monrovia" case africaNairobi = "Africa/Nairobi" case africaNdjamena = "Africa/Ndjamena" case africaNiamey = "Africa/Niamey" case africaNouakchott = "Africa/Nouakchott" case africaOuagadougou = "Africa/Ouagadougou" case africaPortoNovo = "Africa/Porto-Novo" case africaSaoTome = "Africa/Sao_Tome" case africaTripoli = "Africa/Tripoli" case africaTunis = "Africa/Tunis" case africaWindhoek = "Africa/Windhoek" case americaAdak = "America/Adak" case americaAnchorage = "America/Anchorage" case americaAnguilla = "America/Anguilla" case americaAntigua = "America/Antigua" case americaAraguaina = "America/Araguaina" case americaArgentinaBuenosAires = "America/Argentina/Buenos_Aires" case americaArgentinaCatamarca = "America/Argentina/Catamarca" case americaArgentinaCordoba = "America/Argentina/Cordoba" case americaArgentinaJujuy = "America/Argentina/Jujuy" case americaArgentinaLaRioja = "America/Argentina/La_Rioja" case americaArgentinaMendoza = "America/Argentina/Mendoza" case americaArgentinaRioGallegos = "America/Argentina/Rio_Gallegos" case americaArgentinaSalta = "America/Argentina/Salta" case americaArgentinaSanJuan = "America/Argentina/San_Juan" case americaArgentinaSanLuis = "America/Argentina/San_Luis" case americaArgentinaTucuman = "America/Argentina/Tucuman" case americaArgentinaUshuaia = "America/Argentina/Ushuaia" case americaAruba = "America/Aruba" case americaAsuncion = "America/Asuncion" case americaAtikokan = "America/Atikokan" case americaBahia = "America/Bahia" case americaBahiaBanderas = "America/Bahia_Banderas" case americaBarbados = "America/Barbados" case americaBelem = "America/Belem" case americaBelize = "America/Belize" case americaBlancSablon = "America/Blanc-Sablon" case americaBoaVista = "America/Boa_Vista" case americaBogota = "America/Bogota" case americaBoise = "America/Boise" case americaCambridgeBay = "America/Cambridge_Bay" case americaCampoGrande = "America/Campo_Grande" case americaCancun = "America/Cancun" case americaCaracas = "America/Caracas" case americaCayenne = "America/Cayenne" case americaCayman = "America/Cayman" case americaChicago = "America/Chicago" case americaChihuahua = "America/Chihuahua" case americaCostaRica = "America/Costa_Rica" case americaCreston = "America/Creston" case americaCuiaba = "America/Cuiaba" case americaCuracao = "America/Curacao" case americaDanmarkshavn = "America/Danmarkshavn" case americaDawson = "America/Dawson" case americaDawsonCreek = "America/Dawson_Creek" case americaDenver = "America/Denver" case americaDetroit = "America/Detroit" case americaDominica = "America/Dominica" case americaEdmonton = "America/Edmonton" case americaEirunepe = "America/Eirunepe" case americaElSalvador = "America/El_Salvador" case americaFortNelson = "America/Fort_Nelson" case americaFortaleza = "America/Fortaleza" case americaGlaceBay = "America/Glace_Bay" case americaGodthab = "America/Godthab" case americaGooseBay = "America/Goose_Bay" case americaGrandTurk = "America/Grand_Turk" case americaGrenada = "America/Grenada" case americaGuadeloupe = "America/Guadeloupe" case americaGuatemala = "America/Guatemala" case americaGuayaquil = "America/Guayaquil" case americaGuyana = "America/Guyana" case americaHalifax = "America/Halifax" case americaHavana = "America/Havana" case americaHermosillo = "America/Hermosillo" case americaIndianaIndianapolis = "America/Indiana/Indianapolis" case americaIndianaKnox = "America/Indiana/Knox" case americaIndianaMarengo = "America/Indiana/Marengo" case americaIndianaPetersburg = "America/Indiana/Petersburg" case americaIndianaTellCity = "America/Indiana/Tell_City" case americaIndianaVevay = "America/Indiana/Vevay" case americaIndianaVincennes = "America/Indiana/Vincennes" case americaIndianaWinamac = "America/Indiana/Winamac" case americaInuvik = "America/Inuvik" case americaIqaluit = "America/Iqaluit" case americaJamaica = "America/Jamaica" case americaJuneau = "America/Juneau" case americaKentuckyLouisville = "America/Kentucky/Louisville" case americaKentuckyMonticello = "America/Kentucky/Monticello" case americaKralendijk = "America/Kralendijk" case americaLaPaz = "America/La_Paz" case americaLima = "America/Lima" case americaLosAngeles = "America/Los_Angeles" case americaLowerPrinces = "America/Lower_Princes" case americaMaceio = "America/Maceio" case americaManagua = "America/Managua" case americaManaus = "America/Manaus" case americaMarigot = "America/Marigot" case americaMartinique = "America/Martinique" case americaMatamoros = "America/Matamoros" case americaMazatlan = "America/Mazatlan" case americaMenominee = "America/Menominee" case americaMerida = "America/Merida" case americaMetlakatla = "America/Metlakatla" case americaMexicoCity = "America/Mexico_City" case americaMiquelon = "America/Miquelon" case americaMoncton = "America/Moncton" case americaMonterrey = "America/Monterrey" case americaMontevideo = "America/Montevideo" case americaMontreal = "America/Montreal" case americaMontserrat = "America/Montserrat" case americaNassau = "America/Nassau" case americaNewYork = "America/New_York" case americaNipigon = "America/Nipigon" case americaNome = "America/Nome" case americaNoronha = "America/Noronha" case americaNorthDakotaBeulah = "America/North_Dakota/Beulah" case americaNorthDakotaCenter = "America/North_Dakota/Center" case americaNorthDakotaNewSalem = "America/North_Dakota/New_Salem" case americaOjinaga = "America/Ojinaga" case americaPanama = "America/Panama" case americaPangnirtung = "America/Pangnirtung" case americaParamaribo = "America/Paramaribo" case americaPhoenix = "America/Phoenix" case americaPortAuPrince = "America/Port-au-Prince" case americaPortOfSpain = "America/Port_of_Spain" case americaPortoVelho = "America/Porto_Velho" case americaPuertoRico = "America/Puerto_Rico" case americaRainyRiver = "America/Rainy_River" case americaRankinInlet = "America/Rankin_Inlet" case americaRecife = "America/Recife" case americaRegina = "America/Regina" case americaResolute = "America/Resolute" case americaRioBranco = "America/Rio_Branco" case americaSantaIsabel = "America/Santa_Isabel" case americaSantarem = "America/Santarem" case americaSantiago = "America/Santiago" case americaSantoDomingo = "America/Santo_Domingo" case americaSaoPaulo = "America/Sao_Paulo" case americaScoresbysund = "America/Scoresbysund" case americaShiprock = "America/Shiprock" case americaSitka = "America/Sitka" case americaStBarthelemy = "America/St_Barthelemy" case americaStJohns = "America/St_Johns" case americaStKitts = "America/St_Kitts" case americaStLucia = "America/St_Lucia" case americaStThomas = "America/St_Thomas" case americaStVincent = "America/St_Vincent" case americaSwiftCurrent = "America/Swift_Current" case americaTegucigalpa = "America/Tegucigalpa" case americaThule = "America/Thule" case americaThunderBay = "America/Thunder_Bay" case americaTijuana = "America/Tijuana" case americaToronto = "America/Toronto" case americaTortola = "America/Tortola" case americaVancouver = "America/Vancouver" case americaWhitehorse = "America/Whitehorse" case americaWinnipeg = "America/Winnipeg" case americaYakutat = "America/Yakutat" case americaYellowknife = "America/Yellowknife" case antarcticaCasey = "Antarctica/Casey" case antarcticaDavis = "Antarctica/Davis" case antarcticaDumontdurville = "Antarctica/DumontDUrville" case antarcticaMacquarie = "Antarctica/Macquarie" case antarcticaMawson = "Antarctica/Mawson" case antarcticaMcmurdo = "Antarctica/McMurdo" case antarcticaPalmer = "Antarctica/Palmer" case antarcticaRothera = "Antarctica/Rothera" case antarcticaSouthPole = "Antarctica/South_Pole" case antarcticaSyowa = "Antarctica/Syowa" case antarcticaTroll = "Antarctica/Troll" case antarcticaVostok = "Antarctica/Vostok" case arcticLongyearbyen = "Arctic/Longyearbyen" case asiaAden = "Asia/Aden" case asiaAlmaty = "Asia/Almaty" case asiaAmman = "Asia/Amman" case asiaAnadyr = "Asia/Anadyr" case asiaAqtau = "Asia/Aqtau" case asiaAqtobe = "Asia/Aqtobe" case asiaAshgabat = "Asia/Ashgabat" case asiaBaghdad = "Asia/Baghdad" case asiaBahrain = "Asia/Bahrain" case asiaBaku = "Asia/Baku" case asiaBangkok = "Asia/Bangkok" case asiaBeirut = "Asia/Beirut" case asiaBishkek = "Asia/Bishkek" case asiaBrunei = "Asia/Brunei" case asiaChita = "Asia/Chita" case asiaChoibalsan = "Asia/Choibalsan" case asiaChongqing = "Asia/Chongqing" case asiaColombo = "Asia/Colombo" case asiaDamascus = "Asia/Damascus" case asiaDhaka = "Asia/Dhaka" case asiaDili = "Asia/Dili" case asiaDubai = "Asia/Dubai" case asiaDushanbe = "Asia/Dushanbe" case asiaGaza = "Asia/Gaza" case asiaHarbin = "Asia/Harbin" case asiaHebron = "Asia/Hebron" case asiaHoChiMinh = "Asia/Ho_Chi_Minh" case asiaSaigon = "Asia/Saigon" case asiaHongKong = "Asia/Hong_Kong" case asiaHovd = "Asia/Hovd" case asiaIrkutsk = "Asia/Irkutsk" case asiaJakarta = "Asia/Jakarta" case asiaJayapura = "Asia/Jayapura" case asiaJerusalem = "Asia/Jerusalem" case asiaKabul = "Asia/Kabul" case asiaKamchatka = "Asia/Kamchatka" case asiaKarachi = "Asia/Karachi" case asiaKashgar = "Asia/Kashgar" case asiaKathmandu = "Asia/Kathmandu" case asiaKatmandu = "Asia/Katmandu" case asiaKhandyga = "Asia/Khandyga" case asiaKolkata = "Asia/Kolkata" case asiaKrasnoyarsk = "Asia/Krasnoyarsk" case asiaKualaLumpur = "Asia/Kuala_Lumpur" case asiaKuching = "Asia/Kuching" case asiaKuwait = "Asia/Kuwait" case asiaMacau = "Asia/Macau" case asiaMagadan = "Asia/Magadan" case asiaMakassar = "Asia/Makassar" case asiaManila = "Asia/Manila" case asiaMuscat = "Asia/Muscat" case asiaNicosia = "Asia/Nicosia" case asiaNovokuznetsk = "Asia/Novokuznetsk" case asiaNovosibirsk = "Asia/Novosibirsk" case asiaOmsk = "Asia/Omsk" case asiaOral = "Asia/Oral" case asiaPhnomPenh = "Asia/Phnom_Penh" case asiaPontianak = "Asia/Pontianak" case asiaPyongyang = "Asia/Pyongyang" case asiaQatar = "Asia/Qatar" case asiaQyzylorda = "Asia/Qyzylorda" case asiaRangoon = "Asia/Rangoon" case asiaRiyadh = "Asia/Riyadh" case asiaSakhalin = "Asia/Sakhalin" case asiaSamarkand = "Asia/Samarkand" case asiaSeoul = "Asia/Seoul" case asiaShanghai = "Asia/Shanghai" case asiaSingapore = "Asia/Singapore" case asiaSrednekolymsk = "Asia/Srednekolymsk" case asiaTaipei = "Asia/Taipei" case asiaTashkent = "Asia/Tashkent" case asiaTbilisi = "Asia/Tbilisi" case asiaTehran = "Asia/Tehran" case asiaThimphu = "Asia/Thimphu" case asiaTokyo = "Asia/Tokyo" case asiaUlaanbaatar = "Asia/Ulaanbaatar" case asiaUrumqi = "Asia/Urumqi" case asiaUstNera = "Asia/Ust-Nera" case asiaVientiane = "Asia/Vientiane" case asiaVladivostok = "Asia/Vladivostok" case asiaYakutsk = "Asia/Yakutsk" case asiaYekaterinburg = "Asia/Yekaterinburg" case asiaYerevan = "Asia/Yerevan" case atlanticAzores = "Atlantic/Azores" case atlanticBermuda = "Atlantic/Bermuda" case atlanticCanary = "Atlantic/Canary" case atlanticCapeVerde = "Atlantic/Cape_Verde" case atlanticFaroe = "Atlantic/Faroe" case atlanticMadeira = "Atlantic/Madeira" case atlanticReykjavik = "Atlantic/Reykjavik" case atlanticSouthGeorgia = "Atlantic/South_Georgia" case atlanticStHelena = "Atlantic/St_Helena" case atlanticStanley = "Atlantic/Stanley" case australiaAdelaide = "Australia/Adelaide" case australiaBrisbane = "Australia/Brisbane" case australiaBrokenHill = "Australia/Broken_Hill" case australiaCurrie = "Australia/Currie" case australiaDarwin = "Australia/Darwin" case australiaEucla = "Australia/Eucla" case australiaHobart = "Australia/Hobart" case australiaLindeman = "Australia/Lindeman" case australiaLordHowe = "Australia/Lord_Howe" case australiaMelbourne = "Australia/Melbourne" case australiaPerth = "Australia/Perth" case australiaSydney = "Australia/Sydney" case europeAmsterdam = "Europe/Amsterdam" case europeAndorra = "Europe/Andorra" case europeAthens = "Europe/Athens" case europeBelgrade = "Europe/Belgrade" case europeBerlin = "Europe/Berlin" case europeBratislava = "Europe/Bratislava" case europeBrussels = "Europe/Brussels" case europeBucharest = "Europe/Bucharest" case europeBudapest = "Europe/Budapest" case europeBusingen = "Europe/Busingen" case europeChisinau = "Europe/Chisinau" case europeCopenhagen = "Europe/Copenhagen" case europeDublin = "Europe/Dublin" case europeGibraltar = "Europe/Gibraltar" case europeGuernsey = "Europe/Guernsey" case europeHelsinki = "Europe/Helsinki" case europeIsleOfMan = "Europe/Isle_of_Man" case europeIstanbul = "Europe/Istanbul" case europeJersey = "Europe/Jersey" case europeKaliningrad = "Europe/Kaliningrad" case europeKiev = "Europe/Kiev" case europeLisbon = "Europe/Lisbon" case europeLjubljana = "Europe/Ljubljana" case europeLondon = "Europe/London" case europeLuxembourg = "Europe/Luxembourg" case europeMadrid = "Europe/Madrid" case europeMalta = "Europe/Malta" case europeMariehamn = "Europe/Mariehamn" case europeMinsk = "Europe/Minsk" case europeMonaco = "Europe/Monaco" case europeMoscow = "Europe/Moscow" case europeOslo = "Europe/Oslo" case europeParis = "Europe/Paris" case europePodgorica = "Europe/Podgorica" case europePrague = "Europe/Prague" case europeRiga = "Europe/Riga" case europeRome = "Europe/Rome" case europeSamara = "Europe/Samara" case europeSanMarino = "Europe/San_Marino" case europeSarajevo = "Europe/Sarajevo" case europeSimferopol = "Europe/Simferopol" case europeSkopje = "Europe/Skopje" case europeSofia = "Europe/Sofia" case europeStockholm = "Europe/Stockholm" case europeTallinn = "Europe/Tallinn" case europeTirane = "Europe/Tirane" case europeUzhgorod = "Europe/Uzhgorod" case europeVaduz = "Europe/Vaduz" case europeVatican = "Europe/Vatican" case europeVienna = "Europe/Vienna" case europeVilnius = "Europe/Vilnius" case europeVolgograd = "Europe/Volgograd" case europeWarsaw = "Europe/Warsaw" case europeZagreb = "Europe/Zagreb" case europeZaporozhye = "Europe/Zaporozhye" case europeZurich = "Europe/Zurich" case gmt = "GMT" case indianAntananarivo = "Indian/Antananarivo" case indianChagos = "Indian/Chagos" case indianChristmas = "Indian/Christmas" case indianCocos = "Indian/Cocos" case indianComoro = "Indian/Comoro" case indianKerguelen = "Indian/Kerguelen" case indianMahe = "Indian/Mahe" case indianMaldives = "Indian/Maldives" case indianMauritius = "Indian/Mauritius" case indianMayotte = "Indian/Mayotte" case indianReunion = "Indian/Reunion" case pacificApia = "Pacific/Apia" case pacificAuckland = "Pacific/Auckland" case pacificBougainville = "Pacific/Bougainville" case pacificChatham = "Pacific/Chatham" case pacificChuuk = "Pacific/Chuuk" case pacificEaster = "Pacific/Easter" case pacificEfate = "Pacific/Efate" case pacificEnderbury = "Pacific/Enderbury" case pacificFakaofo = "Pacific/Fakaofo" case pacificFiji = "Pacific/Fiji" case pacificFunafuti = "Pacific/Funafuti" case pacificGalapagos = "Pacific/Galapagos" case pacificGambier = "Pacific/Gambier" case pacificGuadalcanal = "Pacific/Guadalcanal" case pacificGuam = "Pacific/Guam" case pacificHonolulu = "Pacific/Honolulu" case pacificJohnston = "Pacific/Johnston" case pacificKiritimati = "Pacific/Kiritimati" case pacificKosrae = "Pacific/Kosrae" case pacificKwajalein = "Pacific/Kwajalein" case pacificMajuro = "Pacific/Majuro" case pacificMarquesas = "Pacific/Marquesas" case pacificMidway = "Pacific/Midway" case pacificNauru = "Pacific/Nauru" case pacificNiue = "Pacific/Niue" case pacificNorfolk = "Pacific/Norfolk" case pacificNoumea = "Pacific/Noumea" case pacificPagoPago = "Pacific/Pago_Pago" case pacificPalau = "Pacific/Palau" case pacificPitcairn = "Pacific/Pitcairn" case pacificPohnpei = "Pacific/Pohnpei" case pacificPonape = "Pacific/Ponape" case pacificPortMoresby = "Pacific/Port_Moresby" case pacificRarotonga = "Pacific/Rarotonga" case pacificSaipan = "Pacific/Saipan" case pacificTahiti = "Pacific/Tahiti" case pacificTarawa = "Pacific/Tarawa" case pacificTongatapu = "Pacific/Tongatapu" case pacificTruk = "Pacific/Truk" case pacificWake = "Pacific/Wake" case pacificWallis = "Pacific/Wallis" public func toTimezone() -> TimeZone { switch self { case .current: return TimeZone.current case .autoUpdating: return TimeZone.autoupdatingCurrent default: return TimeZone(identifier: rawValue)! } } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/SwiftDate.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public struct SwiftDate { private init() { } /// The default region is used to manipulate and work with plain `Date` object and /// wherever a region parameter is optional. By default region is the to GMT timezone /// along with the default device's locale and calendar (both autoupdating). public static var defaultRegion = Region.UTC /// This is the ordered list of all formats SwiftDate can use in order to attempt parsing a passaed /// date expressed as string. Evaluation is made in order; you can add or remove new formats as you wish. /// In order to reset the list call `resetAutoFormats()` function. public static var autoFormats: [String] { set { DateFormats.autoFormats = newValue } get { return DateFormats.autoFormats } } /// Reset the list of all built-in auto formats patterns. public static func resetAutoFormats() { DateFormats.resetAutoFormats() } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/TimePeriod/Groups/TimePeriodChain.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation /// Time period chains serve as a tightly coupled set of time periods. /// They are always organized by start and end date, and have their own characteristics like /// a StartDate and EndDate that are extrapolated from the time periods within. /// Time period chains do not allow overlaps within their set of time periods. /// This type of group is ideal for modeling schedules like sequential meetings or appointments. open class TimePeriodChain: TimePeriodGroup { // MARK: - Chain Existence Manipulation /** * Append a TimePeriodProtocol to the periods array and update the Chain's * beginning and end. * * - parameter period: TimePeriodProtocol to add to the collection */ public func append(_ period: TimePeriodProtocol) { let beginning = (periods.count > 0) ? periods.last!.end! : period.start let newPeriod = TimePeriod(start: beginning!, duration: period.duration) periods.append(newPeriod) //Update updateExtremes if periods.count == 1 { start = period.start end = period.end } else { end = end?.addingTimeInterval(period.duration) } } /** * Append a TimePeriodProtocol array to the periods array and update the Chain's * beginning and end. * * - parameter periodArray: TimePeriodProtocol list to add to the collection */ public func append(contentsOf group: G) { for period in group.periods { let beginning = (periods.count > 0) ? periods.last!.end! : period.start let newPeriod = TimePeriod(start: beginning!, duration: period.duration) periods.append(newPeriod) //Update updateExtremes if periods.count == 1 { start = period.start end = period.end } else { end = end?.addingTimeInterval(period.duration) } } } /// Insert period into periods array at given index. /// /// - Parameters: /// - period: The period to insert /// - index: Index to insert period at public func insert(_ period: TimePeriodProtocol, at index: Int) { //Check for special zero case which takes the beginning date if index == 0 && period.start != nil && period.end != nil { //Insert new period periods.insert(period, at: index) } else if period.start != nil && period.end != nil { //Insert new period periods.insert(period, at: index) } else { print("All TimePeriods in a TimePeriodChain must contain a defined start and end date") return } //Shift all periods after inserted period for i in 0.. index && i > 0 { let currentPeriod = TimePeriod(start: period.start, end: period.end) periods[i].start = periods[i - 1].end periods[i].end = periods[i].start!.addingTimeInterval(currentPeriod.duration) } } updateExtremes() } /// Remove from period array at the given index. /// /// - Parameter index: The index in the collection to remove public func remove(at index: Int) { //Retrieve duration of period to be removed let duration = periods[index].duration //Remove period periods.remove(at: index) //Shift all periods after inserted period for i in index..(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] { return try periods.map(transform) } public override func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] { return try periods.filter(isIncluded) } internal override func reduce(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result { return try periods.reduce(initialResult, nextPartialResult) } /// Removes the last object from the `TimePeriodChain` and returns it public func pop() -> TimePeriodProtocol? { let period = periods.popLast() updateExtremes() return period } internal func updateExtremes() { start = periods.first?.start end = periods.last?.end } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/TimePeriod/Groups/TimePeriodCollection.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation /// Sort type /// /// - ascending: sort in ascending order /// - descending: sort in descending order public enum SortMode { case ascending case descending } /// Sorting type /// /// - start: sort by start date /// - end: sort by end date /// - duration: sort by duration /// - custom: sort using custom function public enum SortType { case start(_: SortMode) case end(_: SortMode) case duration(_: SortMode) case custom(_: ((TimePeriodProtocol, TimePeriodProtocol) -> Bool)) } /// Time period collections serve as loose sets of time periods. /// They are unorganized unless you decide to sort them, and have their own characteristics /// like a `start` and `end` that are extrapolated from the time periods within. /// Time period collections allow overlaps within their set of time periods. open class TimePeriodCollection: TimePeriodGroup { // MARK: - Collection Manipulation /// Append a TimePeriodProtocol to the periods array and check if the Collection's start and end should change. /// /// - Parameter period: TimePeriodProtocol to add to the collection public func append(_ period: TimePeriodProtocol) { periods.append(period) updateExtremes(period: period) } /// Append a TimePeriodProtocol array to the periods array and check if the Collection's /// start and end should change. /// /// - Parameter periodArray: TimePeriodProtocol list to add to the collection public func append(_ periodArray: [TimePeriodProtocol]) { for period in periodArray { periods.append(period) updateExtremes(period: period) } } /// Append a TimePeriodGroup's periods array to the periods array of self and check if the Collection's /// start and end should change. /// /// - Parameter newPeriods: TimePeriodGroup to merge periods arrays with public func append(contentsOf newPeriods: C) { for period in newPeriods as TimePeriodGroup { periods.append(period) updateExtremes(period: period) } } /// Insert period into periods array at given index. /// /// - Parameters: /// - newElement: The period to insert /// - index: Index to insert period at public func insert(_ newElement: TimePeriodProtocol, at index: Int) { periods.insert(newElement, at: index) updateExtremes(period: newElement) } /// Remove from period array at the given index. /// /// - Parameter at: The index in the collection to remove public func remove(at: Int) { periods.remove(at: at) updateExtremes() } /// Remove all periods from period array. public func removeAll() { periods.removeAll() updateExtremes() } // MARK: - Sorting /// Sort elements in place using given method. /// /// - Parameter type: sorting method public func sort(by type: SortType) { switch type { case .duration(let mode): periods.sort(by: sortFuncDuration(mode)) case .start(let mode): periods.sort(by: sortFunc(byStart: true, type: mode)) case .end(let mode): periods.sort(by: sortFunc(byStart: false, type: mode)) case .custom(let f): periods.sort(by: f) } } /// Generate a new `TimePeriodCollection` where items are sorted with specified method. /// /// - Parameters: /// - type: sorting method /// - Returns: collection ordered by given function public func sorted(by type: SortType) -> TimePeriodCollection { var sortedList: [TimePeriodProtocol]! switch type { case .duration(let mode): sortedList = periods.sorted(by: sortFuncDuration(mode)) case .start(let mode): sortedList = periods.sorted(by: sortFunc(byStart: true, type: mode)) case .end(let mode): sortedList = periods.sorted(by: sortFunc(byStart: false, type: mode)) case .custom(let f): sortedList = periods.sorted(by: f) } return TimePeriodCollection(sortedList) } // MARK: - Collection Relationship /// Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s /// whose start and end dates fall completely inside the interval of the given `TimePeriod`. /// /// - Parameter period: The period to compare each other period against /// - Returns: Collection of periods inside the given period public func periodsInside(period: TimePeriodProtocol) -> TimePeriodCollection { return TimePeriodCollection(periods.filter({ $0.isInside(period) })) } // Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s containing the given date. /// /// - Parameter date: The date to compare each period to /// - Returns: Collection of periods intersected by the given date public func periodsIntersected(by date: DateInRegion) -> TimePeriodCollection { return TimePeriodCollection(periods.filter({ $0.contains(date: date, interval: .closed) })) } /// Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s /// containing either the start date or the end date--or both--of the given `TimePeriod`. /// /// - Parameter period: The period to compare each other period to /// - Returns: Collection of periods intersected by the given period public func periodsIntersected(by period: TimePeriodProtocol) -> TimePeriodCollection { return TimePeriodCollection(periods.filter({ $0.intersects(with: period) })) } /// Returns an instance of DTTimePeriodCollection with all the time periods in the receiver that overlap a given time period. /// Overlap with the given time period does NOT include other time periods that simply touch it. /// (i.e. one's start date is equal to another's end date) /// /// - Parameter period: The time period to check against the receiver's time periods. /// - Returns: Collection of periods overlapped by the given period public func periodsOverlappedBy(_ period: TimePeriodProtocol) -> TimePeriodCollection { return TimePeriodCollection(periods.filter({ $0.overlaps(with: period) })) } // MARK: - Map public func map(_ transform: (TimePeriodProtocol) throws -> TimePeriodProtocol) rethrows -> TimePeriodCollection { var mappedArray = [TimePeriodProtocol]() mappedArray = try periods.map(transform) let mappedCollection = TimePeriodCollection() for period in mappedArray { mappedCollection.periods.append(period) mappedCollection.updateExtremes(period: period) } return mappedCollection } // MARK: - Helpers private func sortFuncDuration(_ type: SortMode) -> ((TimePeriodProtocol, TimePeriodProtocol) -> Bool) { switch type { case .ascending: return { $0.duration < $1.duration } case .descending: return { $0.duration > $1.duration } } } private func sortFunc(byStart start: Bool = true, type: SortMode) -> ((TimePeriodProtocol, TimePeriodProtocol) -> Bool) { return { let date0 = (start ? $0.start : $0.end) let date1 = (start ? $1.start : $1.end) if date0 == nil && date1 == nil { return false } else if date0 == nil { return true } else if date1 == nil { return false } else { return (type == .ascending ? date1! > date0! : date0! > date1!) } } } private func updateExtremes(period: TimePeriodProtocol) { //Check incoming period against previous start and end date guard count != 1 else { start = period.start end = period.end return } start = nilOrEarlier(date1: start, date2: period.start) end = nilOrLater(date1: end, date2: period.end) } private func updateExtremes() { guard periods.count > 0 else { start = nil end = nil return } start = periods.first!.start end = periods.first!.end for i in 1.. DateInRegion? { guard date1 != nil && date2 != nil else { return nil } return date1!.earlierDate(date2!) } private func nilOrLater(date1: DateInRegion?, date2: DateInRegion?) -> DateInRegion? { guard date1 != nil && date2 != nil else { return nil } return date1!.laterDate(date2!) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/TimePeriod/Groups/TimePeriodGroup.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation /// Time period groups are the final abstraction of date and time in DateTools. /// Here, time periods are gathered and organized into something useful. /// There are two main types of time period groups, `TimePeriodCollection` and `TimePeriodChain`. open class TimePeriodGroup: Sequence, Equatable { /// Array of periods that define the group. internal var periods: [TimePeriodProtocol] = [] /// The earliest beginning date of a `TimePeriod` in the group. /// `nil` if any `TimePeriod` in group has a nil beginning date (indefinite). public internal(set) var start: DateInRegion? /// The latest end date of a `TimePeriod` in the group. /// `nil` if any `TimePeriod` in group has a nil end date (indefinite). public internal(set) var end: DateInRegion? /// The total amount of time between the earliest and latest dates stored in the periods array. /// `nil` if any beginning or end date in any contained period is `nil`. public var duration: TimeInterval? { guard let start = start, let end = end else { return nil } return end.timeIntervalSince(start) } /// The number of periods in the periods array. public var count: Int { return periods.count } // MARK: - Equatable public static func == (lhs: TimePeriodGroup, rhs: TimePeriodGroup) -> Bool { return TimePeriodGroup.hasSameElements(array1: lhs.periods, rhs.periods) } // MARK: - Initializers public init(_ periods: [TimePeriodProtocol]? = nil) { self.periods = (periods ?? []) } // MARK: - Sequence Protocol public func makeIterator() -> IndexingIterator<[TimePeriodProtocol]> { return periods.makeIterator() } public func map(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] { return try periods.map(transform) } public func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] { return try periods.filter(isIncluded) } public func forEach(_ body: (TimePeriodProtocol) throws -> Void) rethrows { return try periods.forEach(body) } public func split(maxSplits: Int, omittingEmptySubsequences: Bool, whereSeparator isSeparator: (TimePeriodProtocol) throws -> Bool) rethrows -> [AnySequence] { return try periods.split(maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences, whereSeparator: isSeparator).map(AnySequence.init) } public subscript(index: Int) -> TimePeriodProtocol { get { return periods[index] } } internal func reduce(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result { return try periods.reduce(initialResult, nextPartialResult) } // MARK: - Internal Helper Functions internal static func hasSameElements(array1: [TimePeriodProtocol], _ array2: [TimePeriodProtocol]) -> Bool { guard array1.count == array2.count else { return false // No need to sorting if they already have different counts } let compArray1: [TimePeriodProtocol] = array1.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in if period1.start == nil && period2.start == nil { return false } else if period1.start == nil { return true } else if period2.start == nil { return false } else { return period2.start! < period1.start! } } let compArray2: [TimePeriodProtocol] = array2.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in if period1.start == nil && period2.start == nil { return false } else if period1.start == nil { return true } else if period2.start == nil { return false } else { return period2.start! < period1.start! } } for x in 0.. TimePeriod { return TimePeriod(start: DateInRegion.past(), end: DateInRegion.future()) } // MARK: - Shifted /// Shift the `TimePeriod` by a `TimeInterval` /// /// - Parameter timeInterval: The time interval to shift the period by /// - Returns: The new, shifted `TimePeriod` public func shifted(by timeInterval: TimeInterval) -> TimePeriod { let timePeriod = TimePeriod() timePeriod.start = start?.addingTimeInterval(timeInterval) timePeriod.end = end?.addingTimeInterval(timeInterval) return timePeriod } /// Shift the `TimePeriod` by the specified components value. /// ie. `let shifted = period.shifted(by: 3.days)` /// /// - Parameter components: components to shift /// - Returns: new period public func shifted(by components: DateComponents) -> TimePeriod { let timePeriod = TimePeriod() timePeriod.start = (hasStart ? (start! + components) : nil) timePeriod.end = (hasEnd ? (end! + components) : nil) return timePeriod } // MARK: - Lengthen / Shorten /// Lengthen the `TimePeriod` by a `TimeInterval` /// /// - Parameters: /// - timeInterval: The time interval to lengthen the period by /// - anchor: The anchor point from which to make the change /// - Returns: The new, lengthened `TimePeriod` public func lengthened(by timeInterval: TimeInterval, at anchor: TimePeriodAnchor) -> TimePeriod { let timePeriod = TimePeriod() switch anchor { case .beginning: timePeriod.start = start timePeriod.end = end?.addingTimeInterval(timeInterval) case .center: timePeriod.start = start?.addingTimeInterval(-timeInterval) timePeriod.end = end?.addingTimeInterval(timeInterval) case .end: timePeriod.start = start?.addingTimeInterval(-timeInterval) timePeriod.end = end } return timePeriod } /// Shorten the `TimePeriod` by a `TimeInterval` /// /// - Parameters: /// - timeInterval: The time interval to shorten the period by /// - anchor: The anchor point from which to make the change /// - Returns: The new, shortened `TimePeriod` public func shortened(by timeInterval: TimeInterval, at anchor: TimePeriodAnchor) -> TimePeriod { let timePeriod = TimePeriod() switch anchor { case .beginning: timePeriod.start = start timePeriod.end = end?.addingTimeInterval(-timeInterval) case .center: timePeriod.start = start?.addingTimeInterval(-timeInterval / 2) timePeriod.end = end?.addingTimeInterval(timeInterval / 2) case .end: timePeriod.start = start?.addingTimeInterval(timeInterval) timePeriod.end = end } return timePeriod } // MARK: - Operator Overloads /// Default anchor = beginning /// Operator overload for lengthening a `TimePeriod` by a `TimeInterval` public static func + (leftAddend: TimePeriod, rightAddend: TimeInterval) -> TimePeriod { return leftAddend.lengthened(by: rightAddend, at: .beginning) } /// Default anchor = beginning /// Operator overload for shortening a `TimePeriod` by a `TimeInterval` public static func - (minuend: TimePeriod, subtrahend: TimeInterval) -> TimePeriod { return minuend.shortened(by: subtrahend, at: .beginning) } /// Operator overload for checking if a `TimePeriod` is equal to a `TimePeriodProtocol` public static func == (left: TimePeriod, right: TimePeriodProtocol) -> Bool { return left.equals(right) } } public extension TimePeriod { /// The start date of the time period var startDate: Date? { return start?.date } /// The end date of the time period var endDate: Date? { return end?.date } /// Create a new time period with the given start date, end date and region (default is UTC) convenience init(startDate: Date, endDate: Date, region: Region = Region.UTC) { let start = DateInRegion(startDate, region: region) let end = DateInRegion(endDate, region: region) self.init(start: start, end: end) } } ================================================ FILE: JetChat/Pods/SwiftDate/Sources/SwiftDate/TimePeriod/TimePeriodProtocol.swift ================================================ // // SwiftDate // Parse, validate, manipulate, and display dates, time and timezones in Swift // // Created by Daniele Margutti // - Web: https://www.danielemargutti.com // - Twitter: https://twitter.com/danielemargutti // - Mail: hello@danielemargutti.com // // Copyright © 2019 Daniele Margutti. Licensed under MIT License. // import Foundation public protocol TimePeriodProtocol { /// The start date for a TimePeriod representing the starting boundary of the time period var start: DateInRegion? { get set } /// The end date for a TimePeriod representing the ending boundary of the time period var end: DateInRegion? { get set } } public extension TimePeriodProtocol { /// Return `true` if time period has both start and end dates var hasFiniteRange: Bool { guard start != nil && end != nil else { return false } return true } /// Return `true` if period has a start date var hasStart: Bool { return (start != nil) } /// Return `true` if period has a end date var hasEnd: Bool { return (end != nil) } /// Check if receiver is equal to given period (both start/end groups are equals) /// /// - Parameter period: period to compare against to. /// - Returns: true if are equals func equals(_ period: TimePeriodProtocol) -> Bool { return (start == period.start && end == period.end) } /// If the given `TimePeriod`'s beginning is before `beginning` and /// if the given 'TimePeriod`'s end is after `end`. /// /// - Parameter period: The time period to compare to self /// - Returns: True if self is inside of the given `TimePeriod` func isInside(_ period: TimePeriodProtocol) -> Bool { guard hasFiniteRange, period.hasFiniteRange else { return false } return (period.start! <= start! && period.end! >= end!) } /// If the given Date is after `beginning` and before `end`. /// /// - Parameters: /// - date: The time period to compare to self /// - interval: Whether the edge of the date is included in the calculation /// - Returns: True if the given `TimePeriod` is inside of self func contains(date: DateInRegion, interval: IntervalType = .closed) -> Bool { guard hasFiniteRange else { return false } switch interval { case .closed: return (start! <= date && end! >= date) case .open: return (start! < date && end! > date) } } /// If the given `TimePeriod`'s beginning is after `beginning` and /// if the given 'TimePeriod`'s after is after `end`. /// /// - Parameter period: The time period to compare to self /// - Returns: True if the given `TimePeriod` is inside of self func contains(_ period: TimePeriodProtocol) -> Bool { guard hasFiniteRange, period.hasFiniteRange else { return false } if period.start! < start! && period.end! > start! { return true // Outside -> Inside } else if period.start! >= start! && period.end! <= end! { return true // Enclosing } else if period.start! < end! && period.end! > end! { return true // Inside -> Out } return false } /// If self and the given `TimePeriod` share any sub-`TimePeriod`. /// /// - Parameter period: The time period to compare to self /// - Returns: True if there is a period of time that is shared by both `TimePeriod`s func overlaps(with period: TimePeriodProtocol) -> Bool { if period.start! < start! && period.end! > start! { return true // Outside -> Inside } else if period.start! >= start! && period.end! <= end! { return true // Enclosing } else if period.start! < end! && period.end! > end! { return true // Inside -> Out } return false } /// If self and the given `TimePeriod` overlap or the period's edges touch. /// /// - Parameter period: The time period to compare to self /// - Returns: True if there is a period of time or moment that is shared by both `TimePeriod`s func intersects(with period: TimePeriodProtocol) -> Bool { let relation = self.relation(to: period) return (relation != .after && relation != .before) } /// If self is before the given `TimePeriod` chronologically. (A gap must exist between the two). /// /// - Parameter period: The time period to compare to self /// - Returns: True if self is after the given `TimePeriod` func isBefore(_ period: TimePeriodProtocol) -> Bool { return (relation(to: period) == .before) } /// If self is after the given `TimePeriod` chronologically. (A gap must exist between the two). /// /// - Parameter period: The time period to compare to self /// - Returns: True if self is after the given `TimePeriod` func isAfter(_ period: TimePeriodProtocol) -> Bool { return (relation(to: period) == .after) } /// The period of time between self and the given `TimePeriod` not contained by either. /// /// - Parameter period: The time period to compare to self /// - Returns: The gap between the periods. Zero if there is no gap. func hasGap(between period: TimePeriodProtocol) -> Bool { return (isBefore(period) || isAfter(period)) } /// The period of time between self and the given `TimePeriod` not contained by either. /// /// - Parameter period: The time period to compare to self /// - Returns: The gap between the periods. Zero if there is no gap. func gap(between period: TimePeriodProtocol) -> TimeInterval { guard hasFiniteRange, period.hasFiniteRange else { return TimeInterval.greatestFiniteMagnitude } if end! < period.start! { return abs(end!.timeIntervalSince(period.start!)) } else if period.end! < start! { return abs(end!.timeIntervalSince(start!)) } return 0 } /// In place, shift the `TimePeriod` by a `TimeInterval` /// /// - Parameter timeInterval: The time interval to shift the period by mutating func shift(by timeInterval: TimeInterval) { start?.addTimeInterval(timeInterval) end?.addTimeInterval(timeInterval) } /// In place, lengthen the `TimePeriod`, anchored at the beginning, end or center /// /// - Parameters: /// - timeInterval: The time interval to lengthen the period by /// - anchor: The anchor point from which to make the change mutating func lengthen(by timeInterval: TimeInterval, at anchor: TimePeriodAnchor) { switch anchor { case .beginning: end?.addTimeInterval(timeInterval) case .end: start?.addTimeInterval(timeInterval) case .center: start = start?.addingTimeInterval(-timeInterval / 2.0) end = end?.addingTimeInterval(timeInterval / 2.0) } } /// In place, shorten the `TimePeriod`, anchored at the beginning, end or center /// /// - Parameters: /// - timeInterval: The time interval to shorten the period by /// - anchor: The anchor point from which to make the change mutating func shorten(by timeInterval: TimeInterval, at anchor: TimePeriodAnchor) { switch anchor { case .beginning: end?.addTimeInterval(-timeInterval) case .end: start?.addTimeInterval(timeInterval) case .center: start?.addTimeInterval(timeInterval / 2.0) end?.addTimeInterval(-timeInterval / 2.0) } } /// The relationship of the self `TimePeriod` to the given `TimePeriod`. /// Relations are stored in Enums.swift. Formal defnitions available in the provided /// links: /// [GitHub](https://github.com/MatthewYork/DateTools#relationships), /// [CodeProject](http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET) /// /// - Parameter period: The time period to compare to self /// - Returns: The relationship between self and the given time period func relation(to period: TimePeriodProtocol) -> TimePeriodRelation { //Make sure that all start and end points exist for comparison guard hasFiniteRange, period.hasFiniteRange else { return .none } //Make sure time periods are of positive durations guard start! < end! && period.start! < period.end! else { return .none } //Make comparisons if period.start! < start! { return .after } else if period.end! == start! { return .startTouching } else if period.start! < start! && period.end! < end! { return .startInside } else if period.start! == start! && period.end! > end! { return .insideStartTouching } else if period.start! == start! && period.end! < end! { return .enclosingStartTouching } else if period.start! > start! && period.end! < end! { return .enclosing } else if period.start! > start! && period.end! == end! { return .enclosingEndTouching } else if period.start == start! && period.end! == end! { return .exactMatch } else if period.start! < start! && period.end! > end! { return .inside } else if period.start! < start! && period.end! == end! { return .insideEndTouching } else if period.start! < end! && period.end! > end! { return .endInside } else if period.start! == end! && period.end! > end! { return .endTouching } else if period.start! > end! { return .before } return .none } /// Return `true` if period is zero-seconds long or less than specified precision. /// /// - Parameter precision: precision in seconds; by default is 0. /// - Returns: true if start/end has the same value or less than specified precision func isMoment(precision: TimeInterval = 0) -> Bool { guard hasFiniteRange else { return false } return (abs(start!.date.timeIntervalSince1970 - end!.date.timeIntervalSince1970) <= precision) } /// Returns the duration of the receiver expressed with given time unit. /// If time period has not a finite range it returns `nil`. /// /// - Parameter unit: unit of the duration /// - Returns: duration, `nil` if period has not a finite range func durationIn(_ units: Set) -> DateComponents? { guard hasFiniteRange else { return nil } return start!.calendar.dateComponents(units, from: start!.date, to: end!.date) } /// Returns the duration of the receiver expressed with given time unit. /// If time period has not a finite range it returns `nil`. /// /// - Parameter unit: unit of the duration /// - Returns: duration, `nil` if period has not a finite range func durationIn(_ unit: Calendar.Component) -> Int? { guard hasFiniteRange else { return nil } return start!.calendar.dateComponents([unit], from: start!.date, to: end!.date).value(for: unit) } /// The duration of the `TimePeriod` in years. /// Returns the `Int.max` if beginning or end are `nil`. var years: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.year, to: e) } /// The duration of the `TimePeriod` in months. /// Returns the `Int.max` if beginning or end are `nil`. var months: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.month, to: e) } /// The duration of the `TimePeriod` in weeks. /// Returns the `Int.max` if beginning or end are `nil`. var weeks: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.weekOfMonth, to: e) } /// The duration of the `TimePeriod` in days. /// Returns the `Int.max` if beginning or end are `nil`. var days: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.day, to: e) } /// The duration of the `TimePeriod` in hours. /// Returns the `Int.max` if beginning or end are `nil`. var hours: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.hour, to: e) } /// The duration of the `TimePeriod` in years. /// Returns the `Int.max` if beginning or end are `nil`. var minutes: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.minute, to: e) } /// The duration of the `TimePeriod` in seconds. /// Returns the `Int.max` if beginning or end are `nil`. var seconds: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.second, to: e) } /// The length of time between the beginning and end dates of the /// `TimePeriod` as a `TimeInterval`. /// If intervals are not nil returns `Double.greatestFiniteMagnitude` var duration: TimeInterval { guard let b = start, let e = end else { return TimeInterval(Double.greatestFiniteMagnitude) } return abs(b.date.timeIntervalSince(e.date)) } } ================================================ FILE: JetChat/Pods/SwiftDate.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 04552A46A2EAF300E1558D2EC781BD5B /* Date+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F738171711D7EAF45933D58EFA7602E /* Date+Create.swift */; }; 05BA96FFDB05D91220B538227903B9A3 /* DateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0776E215812B5AB4EF314CEF6412D6C /* DateInRegion+Components.swift */; }; 0ECA88B7835E763945CE5023DE69C279 /* Commons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389412B33EEC0DACE7C19FC2F5C82E7A /* Commons.swift */; }; 12220D1956CA542F89B10C2443E9F39D /* RelativeFormatterLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 933C513E0079ECB7F1814342C225D414 /* RelativeFormatterLanguage.swift */; }; 15F90B229B1E027627CB909E53B65B73 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067B6B38C481B84878ED1F4B55622A97 /* Date.swift */; }; 168504799F4BCD8370737D181DAF8032 /* ISOParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC96EA10AC3AF0D429824295F66D63C9 /* ISOParser.swift */; }; 1A45DEA218A73D86E8F9CA569FE40EB8 /* TimePeriodGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 490F9B3F226903EB3A4F544A7F81D542 /* TimePeriodGroup.swift */; }; 3F8BF48F896133C02C333773628EACE9 /* Formatter+Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89E210A45A8198C3306020C4638ED32 /* Formatter+Protocols.swift */; }; 4972927CC66129318F18E08F8325D116 /* Calendars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459978B46F06AF08EC1DAFB56AFF33B0 /* Calendars.swift */; }; 5289DB57E05BCB7B77D80ABEC73698A6 /* String+Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33E13985D1554E001E4AAFF2F2B1CBB6 /* String+Parser.swift */; }; 56A26D70821D5CF4B4438FF5278D4B2D /* TimeStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13713A4B408A1B272DABD217B4089190 /* TimeStructures.swift */; }; 58007C320B03FCEEB38136CC7568E0B0 /* TimePeriodCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5702AF7511B996F2558CDF416E1C83DF /* TimePeriodCollection.swift */; }; 5B6935326CF472D5B927E2B0C1E971E4 /* Int+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A6C5169843FAF6766C8DA20368007D5 /* Int+DateComponents.swift */; }; 5D8FC7D4AB0CCAAD3FCC913ED8404050 /* Locales.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E2DFBDE9B5B725361F6DDC5A0215E9 /* Locales.swift */; }; 5D90902DB184D55790ADBA1C5A7021EC /* SwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839ACD973C6D2ED0D3E11CDB004855FB /* SwiftDate.swift */; }; 6B0433A7C5E01E7C13ABD93162673104 /* Zones.swift in Sources */ = {isa = PBXBuildFile; fileRef = E663B964AE1572B99CD544809502F015 /* Zones.swift */; }; 71036D3BF6777369448489D82EF199EC /* DateRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318606DD0ADB11BEF455A663278B7CA6 /* DateRepresentable.swift */; }; 753209268C1F812663C41FD4CBFB79F1 /* TimeInterval+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69483CFAA4FEBE1B0B0C2E137BE0FBF0 /* TimeInterval+Formatter.swift */; }; 7EBA76AF7E6B978D7EA43CA567AF1868 /* AssociatedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCFE8AA0417C90F4387F08906675C26F /* AssociatedValues.swift */; }; 86E262757817B74C4C251E33846C7CFE /* TimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7CA1C0EAB90E4B0546CD79258DEFFB5 /* TimePeriod.swift */; }; 8D354F09B131C8C3C8B776330DE2CB19 /* TimePeriod+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FCDC30E9CCE3E0AC943C8F2F17322F /* TimePeriod+Support.swift */; }; 8F9154B18951A7AFA87DBEDD4E624922 /* RelativeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E39D3C4C5AF2F511D054C64B3B7020 /* RelativeFormatter.swift */; }; 931121076824D49A5D2B409962CD44BE /* DateComponents+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E61FBCFF2761B35BF2CBD3E95BCFEC0 /* DateComponents+Extras.swift */; }; 939A97DE9222EE9523C90047A0BE2F25 /* DateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF51A90634CF2808B3C8C003B26CF7EC /* DateInRegion.swift */; }; 93F5F672D257BDE00AAD29E63945540B /* SwiftDate-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FD4D37E580C7423C7FD961F092E80A97 /* SwiftDate-dummy.m */; }; B1371B879AF9ECA4058D3F63E0FB39AC /* SwiftDate-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A7000BDFFB98B1843DDF8178B0C46F0 /* SwiftDate-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; B18C0607F5CF235C03C289EDBFC43C5C /* DateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB30ADF5006FE0403DF11074D7F9BE2 /* DateInRegion+Math.swift */; }; B87AABDFBF96ED433C51598106016B90 /* RelativeFormatter+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFC0714D030ECA21A1D179A7FD57380 /* RelativeFormatter+Style.swift */; }; B8F48E769FA72A0E51FD17597298A66F /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510A0B712B4890CE97B741E1626EBAD2 /* Region.swift */; }; B974C58A2ED8BA1703228DE4148B723A /* DateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2149B74912D9CB72A5B019C195F9DE07 /* DateInRegion+Compare.swift */; }; BBE1D8F08CDC3977B1E4CDA1EA9A045C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F3F6D99819CC9B998199F7FF5DA3622 /* Foundation.framework */; }; BC0181DA80F82B7ADC9D16BE1969E492 /* ISOFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FE9E2209E8BB7BEA9DAC079969A6F8 /* ISOFormatter.swift */; }; BF5312B8372DF3DE5DACD24C3460AE38 /* TimePeriodProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47470B7A82F13EB0AD4CB1B88920032 /* TimePeriodProtocol.swift */; }; CA1DA6DB443E04EA5E1DD432379EDDC7 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0072C05B0F9CFE5718ACB921F69B19E /* Date+Components.swift */; }; CB6BAC34B10ADC7AD30F7E84AB9D2BA4 /* TimePeriodChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E22A87CA84A38BEF8BEE8DBF62D707 /* TimePeriodChain.swift */; }; E09103B837EA3F56F961FC8DB8EA17B4 /* Date+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E196D552C0917E27D5ACBB15CE08748 /* Date+Compare.swift */; }; F2A624D0506CD7DFB44835D9632659B3 /* DateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 737F67BE147AEA41FFDC41DCEB97A6AE /* DateInRegion+Create.swift */; }; F4A81244ABA5CD808403878984AE0532 /* DotNetParserFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F064054D3B568D74820327A37B559AE5 /* DotNetParserFormatter.swift */; }; F4DC15248ECB8E6D8C4674537F908C5A /* Date+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8FBEBF84FB6046308071909D982CF6 /* Date+Math.swift */; }; F55BAC777A58A22DE49A7E35C98E4671 /* langs in Resources */ = {isa = PBXBuildFile; fileRef = 05458FAA8CE784D25494CA640FD046A7 /* langs */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 01E22A87CA84A38BEF8BEE8DBF62D707 /* TimePeriodChain.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodChain.swift; path = Sources/SwiftDate/TimePeriod/Groups/TimePeriodChain.swift; sourceTree = ""; }; 02FCDC30E9CCE3E0AC943C8F2F17322F /* TimePeriod+Support.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TimePeriod+Support.swift"; path = "Sources/SwiftDate/TimePeriod/TimePeriod+Support.swift"; sourceTree = ""; }; 05458FAA8CE784D25494CA640FD046A7 /* langs */ = {isa = PBXFileReference; includeInIndex = 1; name = langs; path = Sources/SwiftDate/Formatters/RelativeFormatter/langs; sourceTree = ""; }; 067B6B38C481B84878ED1F4B55622A97 /* Date.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Date.swift; path = Sources/SwiftDate/Date/Date.swift; sourceTree = ""; }; 0A8FBEBF84FB6046308071909D982CF6 /* Date+Math.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Math.swift"; path = "Sources/SwiftDate/Date/Date+Math.swift"; sourceTree = ""; }; 13713A4B408A1B272DABD217B4089190 /* TimeStructures.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimeStructures.swift; path = Sources/SwiftDate/Supports/TimeStructures.swift; sourceTree = ""; }; 1A6C5169843FAF6766C8DA20368007D5 /* Int+DateComponents.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Int+DateComponents.swift"; path = "Sources/SwiftDate/Foundation+Extras/Int+DateComponents.swift"; sourceTree = ""; }; 2149B74912D9CB72A5B019C195F9DE07 /* DateInRegion+Compare.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Compare.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Compare.swift"; sourceTree = ""; }; 2E196D552C0917E27D5ACBB15CE08748 /* Date+Compare.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Compare.swift"; path = "Sources/SwiftDate/Date/Date+Compare.swift"; sourceTree = ""; }; 318606DD0ADB11BEF455A663278B7CA6 /* DateRepresentable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DateRepresentable.swift; path = Sources/SwiftDate/DateRepresentable.swift; sourceTree = ""; }; 33E13985D1554E001E4AAFF2F2B1CBB6 /* String+Parser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+Parser.swift"; path = "Sources/SwiftDate/Foundation+Extras/String+Parser.swift"; sourceTree = ""; }; 34E39D3C4C5AF2F511D054C64B3B7020 /* RelativeFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RelativeFormatter.swift; path = Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatter.swift; sourceTree = ""; }; 37FE9E2209E8BB7BEA9DAC079969A6F8 /* ISOFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ISOFormatter.swift; path = Sources/SwiftDate/Formatters/ISOFormatter.swift; sourceTree = ""; }; 389412B33EEC0DACE7C19FC2F5C82E7A /* Commons.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Commons.swift; path = Sources/SwiftDate/Supports/Commons.swift; sourceTree = ""; }; 3A7000BDFFB98B1843DDF8178B0C46F0 /* SwiftDate-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftDate-umbrella.h"; sourceTree = ""; }; 459978B46F06AF08EC1DAFB56AFF33B0 /* Calendars.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Calendars.swift; path = Sources/SwiftDate/Supports/Calendars.swift; sourceTree = ""; }; 490F9B3F226903EB3A4F544A7F81D542 /* TimePeriodGroup.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodGroup.swift; path = Sources/SwiftDate/TimePeriod/Groups/TimePeriodGroup.swift; sourceTree = ""; }; 4F3F6D99819CC9B998199F7FF5DA3622 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 4F738171711D7EAF45933D58EFA7602E /* Date+Create.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Create.swift"; path = "Sources/SwiftDate/Date/Date+Create.swift"; sourceTree = ""; }; 510A0B712B4890CE97B741E1626EBAD2 /* Region.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Region.swift; path = Sources/SwiftDate/DateInRegion/Region.swift; sourceTree = ""; }; 5702AF7511B996F2558CDF416E1C83DF /* TimePeriodCollection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodCollection.swift; path = Sources/SwiftDate/TimePeriod/Groups/TimePeriodCollection.swift; sourceTree = ""; }; 69483CFAA4FEBE1B0B0C2E137BE0FBF0 /* TimeInterval+Formatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TimeInterval+Formatter.swift"; path = "Sources/SwiftDate/Foundation+Extras/TimeInterval+Formatter.swift"; sourceTree = ""; }; 737F67BE147AEA41FFDC41DCEB97A6AE /* DateInRegion+Create.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Create.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Create.swift"; sourceTree = ""; }; 839ACD973C6D2ED0D3E11CDB004855FB /* SwiftDate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwiftDate.swift; path = Sources/SwiftDate/SwiftDate.swift; sourceTree = ""; }; 8AB30ADF5006FE0403DF11074D7F9BE2 /* DateInRegion+Math.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Math.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Math.swift"; sourceTree = ""; }; 8C78A73FBF001C1DB0597972B6AFA08A /* SwiftDate */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SwiftDate; path = SwiftDate.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8E61FBCFF2761B35BF2CBD3E95BCFEC0 /* DateComponents+Extras.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateComponents+Extras.swift"; path = "Sources/SwiftDate/Foundation+Extras/DateComponents+Extras.swift"; sourceTree = ""; }; 933C513E0079ECB7F1814342C225D414 /* RelativeFormatterLanguage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RelativeFormatterLanguage.swift; path = Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatterLanguage.swift; sourceTree = ""; }; A0072C05B0F9CFE5718ACB921F69B19E /* Date+Components.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Components.swift"; path = "Sources/SwiftDate/Date/Date+Components.swift"; sourceTree = ""; }; A47470B7A82F13EB0AD4CB1B88920032 /* TimePeriodProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodProtocol.swift; path = Sources/SwiftDate/TimePeriod/TimePeriodProtocol.swift; sourceTree = ""; }; AF51A90634CF2808B3C8C003B26CF7EC /* DateInRegion.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DateInRegion.swift; path = Sources/SwiftDate/DateInRegion/DateInRegion.swift; sourceTree = ""; }; BC612998A52258F8860AD578CB896175 /* SwiftDate.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftDate.release.xcconfig; sourceTree = ""; }; BC96EA10AC3AF0D429824295F66D63C9 /* ISOParser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ISOParser.swift; path = Sources/SwiftDate/Formatters/ISOParser.swift; sourceTree = ""; }; D89E210A45A8198C3306020C4638ED32 /* Formatter+Protocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Formatter+Protocols.swift"; path = "Sources/SwiftDate/Formatters/Formatter+Protocols.swift"; sourceTree = ""; }; E663B964AE1572B99CD544809502F015 /* Zones.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Zones.swift; path = Sources/SwiftDate/Supports/Zones.swift; sourceTree = ""; }; E7CA1C0EAB90E4B0546CD79258DEFFB5 /* TimePeriod.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriod.swift; path = Sources/SwiftDate/TimePeriod/TimePeriod.swift; sourceTree = ""; }; E8E2DFBDE9B5B725361F6DDC5A0215E9 /* Locales.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Locales.swift; path = Sources/SwiftDate/Supports/Locales.swift; sourceTree = ""; }; EF2BE2D9A938E81666997F4EBE28ACF9 /* SwiftDate.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftDate.debug.xcconfig; sourceTree = ""; }; F064054D3B568D74820327A37B559AE5 /* DotNetParserFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DotNetParserFormatter.swift; path = Sources/SwiftDate/Formatters/DotNetParserFormatter.swift; sourceTree = ""; }; F0776E215812B5AB4EF314CEF6412D6C /* DateInRegion+Components.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Components.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Components.swift"; sourceTree = ""; }; F9DA70A200BB729ED227CDAF47503332 /* SwiftDate.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SwiftDate.modulemap; sourceTree = ""; }; FC0B231A234912D8FC315794C96E732E /* SwiftDate-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftDate-prefix.pch"; sourceTree = ""; }; FCFE8AA0417C90F4387F08906675C26F /* AssociatedValues.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssociatedValues.swift; path = Sources/SwiftDate/Supports/AssociatedValues.swift; sourceTree = ""; }; FD1FA3AA21A65751C1688D7365D035A7 /* SwiftDate-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SwiftDate-Info.plist"; sourceTree = ""; }; FD4D37E580C7423C7FD961F092E80A97 /* SwiftDate-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SwiftDate-dummy.m"; sourceTree = ""; }; FEFC0714D030ECA21A1D179A7FD57380 /* RelativeFormatter+Style.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "RelativeFormatter+Style.swift"; path = "Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatter+Style.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8F23964745A52FACFA5E44BC11F2CB98 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BBE1D8F08CDC3977B1E4CDA1EA9A045C /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 024622CA23AB169F00B75A12702AABDB /* Frameworks */ = { isa = PBXGroup; children = ( 7CD3D58F318C7A35FD9D6F89210FD33D /* iOS */, ); name = Frameworks; sourceTree = ""; }; 27CE58AC37A619DA0642D3DC61E9F2B8 /* Support Files */ = { isa = PBXGroup; children = ( F9DA70A200BB729ED227CDAF47503332 /* SwiftDate.modulemap */, FD4D37E580C7423C7FD961F092E80A97 /* SwiftDate-dummy.m */, FD1FA3AA21A65751C1688D7365D035A7 /* SwiftDate-Info.plist */, FC0B231A234912D8FC315794C96E732E /* SwiftDate-prefix.pch */, 3A7000BDFFB98B1843DDF8178B0C46F0 /* SwiftDate-umbrella.h */, EF2BE2D9A938E81666997F4EBE28ACF9 /* SwiftDate.debug.xcconfig */, BC612998A52258F8860AD578CB896175 /* SwiftDate.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/SwiftDate"; sourceTree = ""; }; 2D4AFD73869343784F84A95EB0A8224D /* Resources */ = { isa = PBXGroup; children = ( 05458FAA8CE784D25494CA640FD046A7 /* langs */, ); name = Resources; sourceTree = ""; }; 4543671139C9E474DFF10DD697BCCB0E = { isa = PBXGroup; children = ( 024622CA23AB169F00B75A12702AABDB /* Frameworks */, D75EAE3C496CD0C2E6CC2A677B3AF3C0 /* Products */, 6C3B725EEC4F76EFEC95FF16F3833C1B /* SwiftDate */, ); sourceTree = ""; }; 6C3B725EEC4F76EFEC95FF16F3833C1B /* SwiftDate */ = { isa = PBXGroup; children = ( FCFE8AA0417C90F4387F08906675C26F /* AssociatedValues.swift */, 459978B46F06AF08EC1DAFB56AFF33B0 /* Calendars.swift */, 389412B33EEC0DACE7C19FC2F5C82E7A /* Commons.swift */, 067B6B38C481B84878ED1F4B55622A97 /* Date.swift */, 2E196D552C0917E27D5ACBB15CE08748 /* Date+Compare.swift */, A0072C05B0F9CFE5718ACB921F69B19E /* Date+Components.swift */, 4F738171711D7EAF45933D58EFA7602E /* Date+Create.swift */, 0A8FBEBF84FB6046308071909D982CF6 /* Date+Math.swift */, 8E61FBCFF2761B35BF2CBD3E95BCFEC0 /* DateComponents+Extras.swift */, AF51A90634CF2808B3C8C003B26CF7EC /* DateInRegion.swift */, 2149B74912D9CB72A5B019C195F9DE07 /* DateInRegion+Compare.swift */, F0776E215812B5AB4EF314CEF6412D6C /* DateInRegion+Components.swift */, 737F67BE147AEA41FFDC41DCEB97A6AE /* DateInRegion+Create.swift */, 8AB30ADF5006FE0403DF11074D7F9BE2 /* DateInRegion+Math.swift */, 318606DD0ADB11BEF455A663278B7CA6 /* DateRepresentable.swift */, F064054D3B568D74820327A37B559AE5 /* DotNetParserFormatter.swift */, D89E210A45A8198C3306020C4638ED32 /* Formatter+Protocols.swift */, 1A6C5169843FAF6766C8DA20368007D5 /* Int+DateComponents.swift */, 37FE9E2209E8BB7BEA9DAC079969A6F8 /* ISOFormatter.swift */, BC96EA10AC3AF0D429824295F66D63C9 /* ISOParser.swift */, E8E2DFBDE9B5B725361F6DDC5A0215E9 /* Locales.swift */, 510A0B712B4890CE97B741E1626EBAD2 /* Region.swift */, 34E39D3C4C5AF2F511D054C64B3B7020 /* RelativeFormatter.swift */, FEFC0714D030ECA21A1D179A7FD57380 /* RelativeFormatter+Style.swift */, 933C513E0079ECB7F1814342C225D414 /* RelativeFormatterLanguage.swift */, 33E13985D1554E001E4AAFF2F2B1CBB6 /* String+Parser.swift */, 839ACD973C6D2ED0D3E11CDB004855FB /* SwiftDate.swift */, 69483CFAA4FEBE1B0B0C2E137BE0FBF0 /* TimeInterval+Formatter.swift */, E7CA1C0EAB90E4B0546CD79258DEFFB5 /* TimePeriod.swift */, 02FCDC30E9CCE3E0AC943C8F2F17322F /* TimePeriod+Support.swift */, 01E22A87CA84A38BEF8BEE8DBF62D707 /* TimePeriodChain.swift */, 5702AF7511B996F2558CDF416E1C83DF /* TimePeriodCollection.swift */, 490F9B3F226903EB3A4F544A7F81D542 /* TimePeriodGroup.swift */, A47470B7A82F13EB0AD4CB1B88920032 /* TimePeriodProtocol.swift */, 13713A4B408A1B272DABD217B4089190 /* TimeStructures.swift */, E663B964AE1572B99CD544809502F015 /* Zones.swift */, 2D4AFD73869343784F84A95EB0A8224D /* Resources */, 27CE58AC37A619DA0642D3DC61E9F2B8 /* Support Files */, ); name = SwiftDate; path = SwiftDate; sourceTree = ""; }; 7CD3D58F318C7A35FD9D6F89210FD33D /* iOS */ = { isa = PBXGroup; children = ( 4F3F6D99819CC9B998199F7FF5DA3622 /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; D75EAE3C496CD0C2E6CC2A677B3AF3C0 /* Products */ = { isa = PBXGroup; children = ( 8C78A73FBF001C1DB0597972B6AFA08A /* SwiftDate */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 9C0316CF843F144B00C7D92331504B6D /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( B1371B879AF9ECA4058D3F63E0FB39AC /* SwiftDate-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 084C1733B4DEB4359B4EFB893E424972 /* SwiftDate */ = { isa = PBXNativeTarget; buildConfigurationList = 709D18045AD8211DFD3558F0727445CC /* Build configuration list for PBXNativeTarget "SwiftDate" */; buildPhases = ( 9C0316CF843F144B00C7D92331504B6D /* Headers */, C62F481C44A3D96821D331A353B337F1 /* Sources */, 8F23964745A52FACFA5E44BC11F2CB98 /* Frameworks */, FA41934FCA58DEFF6700ADD2A41BDE2F /* Resources */, ); buildRules = ( ); dependencies = ( ); name = SwiftDate; productName = SwiftDate; productReference = 8C78A73FBF001C1DB0597972B6AFA08A /* SwiftDate */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ C9D01DC6E20F002C529A63228712762F /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = EA52B03FAF35CA1FAB3674849233060A /* Build configuration list for PBXProject "SwiftDate" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 4543671139C9E474DFF10DD697BCCB0E; productRefGroup = D75EAE3C496CD0C2E6CC2A677B3AF3C0 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 084C1733B4DEB4359B4EFB893E424972 /* SwiftDate */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ FA41934FCA58DEFF6700ADD2A41BDE2F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( F55BAC777A58A22DE49A7E35C98E4671 /* langs in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ C62F481C44A3D96821D331A353B337F1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 7EBA76AF7E6B978D7EA43CA567AF1868 /* AssociatedValues.swift in Sources */, 4972927CC66129318F18E08F8325D116 /* Calendars.swift in Sources */, 0ECA88B7835E763945CE5023DE69C279 /* Commons.swift in Sources */, 15F90B229B1E027627CB909E53B65B73 /* Date.swift in Sources */, E09103B837EA3F56F961FC8DB8EA17B4 /* Date+Compare.swift in Sources */, CA1DA6DB443E04EA5E1DD432379EDDC7 /* Date+Components.swift in Sources */, 04552A46A2EAF300E1558D2EC781BD5B /* Date+Create.swift in Sources */, F4DC15248ECB8E6D8C4674537F908C5A /* Date+Math.swift in Sources */, 931121076824D49A5D2B409962CD44BE /* DateComponents+Extras.swift in Sources */, 939A97DE9222EE9523C90047A0BE2F25 /* DateInRegion.swift in Sources */, B974C58A2ED8BA1703228DE4148B723A /* DateInRegion+Compare.swift in Sources */, 05BA96FFDB05D91220B538227903B9A3 /* DateInRegion+Components.swift in Sources */, F2A624D0506CD7DFB44835D9632659B3 /* DateInRegion+Create.swift in Sources */, B18C0607F5CF235C03C289EDBFC43C5C /* DateInRegion+Math.swift in Sources */, 71036D3BF6777369448489D82EF199EC /* DateRepresentable.swift in Sources */, F4A81244ABA5CD808403878984AE0532 /* DotNetParserFormatter.swift in Sources */, 3F8BF48F896133C02C333773628EACE9 /* Formatter+Protocols.swift in Sources */, 5B6935326CF472D5B927E2B0C1E971E4 /* Int+DateComponents.swift in Sources */, BC0181DA80F82B7ADC9D16BE1969E492 /* ISOFormatter.swift in Sources */, 168504799F4BCD8370737D181DAF8032 /* ISOParser.swift in Sources */, 5D8FC7D4AB0CCAAD3FCC913ED8404050 /* Locales.swift in Sources */, B8F48E769FA72A0E51FD17597298A66F /* Region.swift in Sources */, 8F9154B18951A7AFA87DBEDD4E624922 /* RelativeFormatter.swift in Sources */, B87AABDFBF96ED433C51598106016B90 /* RelativeFormatter+Style.swift in Sources */, 12220D1956CA542F89B10C2443E9F39D /* RelativeFormatterLanguage.swift in Sources */, 5289DB57E05BCB7B77D80ABEC73698A6 /* String+Parser.swift in Sources */, 5D90902DB184D55790ADBA1C5A7021EC /* SwiftDate.swift in Sources */, 93F5F672D257BDE00AAD29E63945540B /* SwiftDate-dummy.m in Sources */, 753209268C1F812663C41FD4CBFB79F1 /* TimeInterval+Formatter.swift in Sources */, 86E262757817B74C4C251E33846C7CFE /* TimePeriod.swift in Sources */, 8D354F09B131C8C3C8B776330DE2CB19 /* TimePeriod+Support.swift in Sources */, CB6BAC34B10ADC7AD30F7E84AB9D2BA4 /* TimePeriodChain.swift in Sources */, 58007C320B03FCEEB38136CC7568E0B0 /* TimePeriodCollection.swift in Sources */, 1A45DEA218A73D86E8F9CA569FE40EB8 /* TimePeriodGroup.swift in Sources */, BF5312B8372DF3DE5DACD24C3460AE38 /* TimePeriodProtocol.swift in Sources */, 56A26D70821D5CF4B4438FF5278D4B2D /* TimeStructures.swift in Sources */, 6B0433A7C5E01E7C13ABD93162673104 /* Zones.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 226A0910E52B90D7C07421F8ABCEC18C /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = BC612998A52258F8860AD578CB896175 /* SwiftDate.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/SwiftDate/SwiftDate-prefix.pch"; INFOPLIST_FILE = "Target Support Files/SwiftDate/SwiftDate-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/SwiftDate/SwiftDate.modulemap"; PRODUCT_MODULE_NAME = SwiftDate; PRODUCT_NAME = SwiftDate; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.1; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 32AF5F0083429A8534912177AEFEE82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; 6EC13EAB29A29FDA27EC8CCF2A59153E /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = EF2BE2D9A938E81666997F4EBE28ACF9 /* SwiftDate.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/SwiftDate/SwiftDate-prefix.pch"; INFOPLIST_FILE = "Target Support Files/SwiftDate/SwiftDate-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/SwiftDate/SwiftDate.modulemap"; PRODUCT_MODULE_NAME = SwiftDate; PRODUCT_NAME = SwiftDate; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.1; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; ADB96C629DA58788B18762E78D9E96B6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 709D18045AD8211DFD3558F0727445CC /* Build configuration list for PBXNativeTarget "SwiftDate" */ = { isa = XCConfigurationList; buildConfigurations = ( 6EC13EAB29A29FDA27EC8CCF2A59153E /* Debug */, 226A0910E52B90D7C07421F8ABCEC18C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; EA52B03FAF35CA1FAB3674849233060A /* Build configuration list for PBXProject "SwiftDate" */ = { isa = XCConfigurationList; buildConfigurations = ( ADB96C629DA58788B18762E78D9E96B6 /* Debug */, 32AF5F0083429A8534912177AEFEE82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = C9D01DC6E20F002C529A63228712762F /* Project object */; } ================================================ FILE: JetChat/Pods/SwifterSwift/LICENSE ================================================ MIT License Copyright (c) 2015-2018 SwifterSwift (https://github.com/swifterswift) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/SwifterSwift/README.md ================================================

[![Build Status](https://github.com/SwifterSwift/SwifterSwift/workflows/SwifterSwift/badge.svg?branch=master)](https://github.com/SwifterSwift/SwifterSwift/actions) [![Platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20Linux-lightgrey.svg)](https://github.com/SwifterSwift/swifterSwift) [![Cocoapods](https://img.shields.io/cocoapods/v/SwifterSwift.svg)](https://cocoapods.org/pods/SwifterSwift) [![Carthage compatible](https://img.shields.io/badge/Carthage-Compatible-brightgreen.svg?style=flat)](https://github.com/Carthage/Carthage) [![SPM compatible](https://img.shields.io/badge/SPM-Compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager/) [![Accio supported](https://img.shields.io/badge/Accio-supported-0A7CF5.svg?style=flat)](https://github.com/JamitLabs/Accio) [![codecov](https://codecov.io/gh/SwifterSwift/SwifterSwift/branch/master/graph/badge.svg)](https://codecov.io/gh/SwifterSwift/SwifterSwift) [![docs](http://swifterswift.com/docs/badge.svg)](http://swifterswift.com/docs) [![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg)](https://swift.org) [![Xcode](https://img.shields.io/badge/Xcode-11.4-blue.svg)](https://developer.apple.com/xcode) [![MIT](https://img.shields.io/badge/License-MIT-red.svg)](https://opensource.org/licenses/MIT) [![Slack Channel](https://slackin-ppvrggbpgn.now.sh/badge.svg)](https://slackin-ppvrggbpgn.now.sh/) SwifterSwift is a collection of **over 500 native Swift extensions**, with handy methods, syntactic sugar, and performance improvements for wide range of primitive data types, UIKit and Cocoa classes –over 500 in 1– for iOS, macOS, tvOS, watchOS and Linux. ### [Whats New in v5.2?](https://github.com/SwifterSwift/SwifterSwift/blob/master/CHANGELOG.md#v520) ## Requirements - **iOS** 10.0+ / **tvOS** 9.0+ / **watchOS** 2.0+ / **macOS** 10.10+ / **Ubuntu** 14.04+ - Swift 5.0+ ## Looking to use SwifterSwift for older versions of Swift SwifterSwift is Swift v5.0+ compatible starting from v5 - To use with **Swift 3 / Xcode 8.x** please ensure you are using [**`v3.1.1`**](https://github.com/SwifterSwift/SwifterSwift/releases/tag/3.1.1). - To use with **Swift 3.2 / Xcode 9.x** please ensure you are using [**`v3.2.0`**](https://github.com/SwifterSwift/SwifterSwift/releases/tag/3.2.0). ## Installation
CocoaPods

To integrate SwifterSwift into your Xcode project using CocoaPods, specify it in your Podfile:

- Integrate All extensions (recommended):

pod 'SwifterSwift'

- Integrate SwiftStdlib extensions only:

pod 'SwifterSwift/SwiftStdlib'

- Integrate Foundation extensions only:

pod 'SwifterSwift/Foundation'

- Integrate UIKit extensions only:

pod 'SwifterSwift/UIKit'

- Integrate AppKit extensions only:

pod 'SwifterSwift/AppKit'

- Integrate MapKit extensions only:

pod 'SwifterSwift/MapKit'

- Integrate CoreGraphics extensions only:

pod 'SwifterSwift/CoreGraphics'

- Integrate CoreLocation extensions only:

pod 'SwifterSwift/CoreLocation'

- Integrate SpriteKit extensions only:

pod 'SwifterSwift/SpriteKit'

- Integrate SceneKit extensions only:

pod 'SwifterSwift/SceneKit'

- Integrate StoreKit extensions only:

pod 'SwifterSwift/StoreKit'

- Integrate Dispatch extensions only:

pod 'SwifterSwift/Dispatch'
Carthage

To integrate SwifterSwift into your Xcode project using Carthage, specify it in your Cartfile:

github "SwifterSwift/SwifterSwift" ~> 5.2
Swift Package Manager

You can use The Swift Package Manager to install SwifterSwift by adding the proper description to your Package.swift file:

import PackageDescription

let package = Package(
    name: "YOUR_PROJECT_NAME",
    targets: [],
    dependencies: [
        .package(url: "https://github.com/SwifterSwift/SwifterSwift.git", from: "5.2.0")
    ]
)

Next, add SwifterSwift to your targets dependencies like so:

.target(
    name: "YOUR_TARGET_NAME",
    dependencies: [
        "SwifterSwift",
    ]
),

Then run swift package update.

Note that the Swift Package Manager doesn't support building for iOS/tvOS/macOS/watchOS apps – see Accio in the next section for that.

Accio

Accio is a dependency manager based on SwiftPM which can build frameworks for iOS/macOS/tvOS/watchOS. Therefore the integration steps are exactly the same as described above. Once your Package.swift file is configured, you need to run accio update instead of swift package update though.

Manually

Add the SwifterSwift folder to your Xcode project to use all extensions, or a specific extension.

## List of All Extensions
SwiftStdlib Extensions
Foundation Extensions
UIKit Extensions
AppKit Extensions
CoreGraphics Extensions
CoreLocation Extensions
CoreAnimation Extensions
MapKit Extensions
SpriteKit Extensions
SceneKit Extensions
StoreKit Extensions
Dispatch Extensions
## How cool is this? SwifterSwift is a library of **over 500 properties and methods**, designed to extend Swift's functionality and productivity, staying faithful to the original Swift API design guidelines. Check Examples.playground from the project for some cool examples! ## Documentation Documentation for all extensions, with examples, is available at [swifterswift.com/docs](http://swifterswift.com/docs) ## Get involved We want your feedback. Please refer to [contributing guidelines](https://github.com/SwifterSwift/SwifterSwift/tree/master/CONTRIBUTING.md) before participating. ## Slack Channel: [![Slack](https://slackin-ppvrggbpgn.now.sh/badge.svg)](https://slackin-ppvrggbpgn.now.sh/) It is always nice to talk with other people using SwifterSwift and exchange experiences, so come [join our Slack channel](https://slackin-ppvrggbpgn.now.sh/). ## Thanks Special thanks to: - [Steven Deutsch](https://github.com/SD10), [Luciano Almeida](https://github.com/LucianoPAlmeida) and [Guy Kogus](https://github.com/guykogus) for their latest contributions to extensions, docs and tests. - [Paweł Urbanek](https://github.com/pawurb) for adding tvOS, watchOS, and macOS initial support and helping with extensions. - [Mert Akengin](https://github.com/pvtmert) and [Bashar Ghadanfar](https://www.behance.net/lionbytes) for designing [project website](http://swifterswift.com) and logo. - [Abdul Rahman Dabbour](https://github.com/ardabbour) for helping document the project. - Many thanks to all other [contributors](https://github.com/SwifterSwift/SwifterSwift/graphs/contributors) of this project. ## License SwifterSwift is released under the MIT license. See [LICENSE](https://github.com/SwifterSwift/SwifterSwift/blob/master/LICENSE) for more information. ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/AppKit/NSColorExtensions.swift ================================================ // // NSColorExtensions.swift // SwifterSwift // // Created by Max Haertwig on 10/06/19. // Copyright © 2019 SwifterSwift // #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit public extension NSColor { /// SwifterSwift: Create an NSColor with different colors for light and dark mode. /// /// - Parameters: /// - light: Color to use in light/unspecified mode. /// - dark: Color to use in dark mode. @available(OSX 10.15, *) convenience init(light: NSColor, dark: NSColor) { self.init(name: nil, dynamicProvider: { $0.name == .darkAqua ? dark : light }) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/AppKit/NSImageExtensions.swift ================================================ // // NSImageExtensions.swift // SwifterSwift-macOS // // Created by BUDDAx2 on 20.10.2017. // Copyright © 2017 SwifterSwift // #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit // MARK: - Methods public extension NSImage { /// SwifterSwift: NSImage scaled to maximum size with respect to aspect ratio /// /// - Parameter toMaxSize: maximum size /// - Returns: scaled NSImage func scaled(toMaxSize: NSSize) -> NSImage { var ratio: Float = 0.0 let imageWidth = Float(size.width) let imageHeight = Float(size.height) let maxWidth = Float(toMaxSize.width) let maxHeight = Float(toMaxSize.height) // Get ratio (landscape or portrait) if imageWidth > imageHeight { // Landscape ratio = maxWidth / imageWidth } else { // Portrait ratio = maxHeight / imageHeight } // Calculate new size based on the ratio let newWidth = imageWidth * ratio let newHeight = imageHeight * ratio // Create a new NSSize object with the newly calculated size let newSize = NSSize(width: Int(newWidth), height: Int(newHeight)) // Cast the NSImage to a CGImage var imageRect = CGRect(x: 0, y: 0, width: size.width, height: size.height) let imageRef = cgImage(forProposedRect: &imageRect, context: nil, hints: nil) // Create NSImage from the CGImage using the new size let imageWithNewSize = NSImage(cgImage: imageRef!, size: newSize) // Return the new image return imageWithNewSize } /// SwifterSwift: Write NSImage to url. /// /// - Parameters: /// - url: Desired file URL. /// - type: Type of image (default is .jpeg). /// - compressionFactor: used only for JPEG files. The value is a float between 0.0 and 1.0, with 1.0 resulting in no compression and 0.0 resulting in the maximum compression possible. func write(to url: URL, fileType type: NSBitmapImageRep.FileType = .jpeg, compressionFactor: NSNumber = 1.0) { // https://stackoverflow.com/a/45042611/3882644 guard let data = tiffRepresentation else { return } guard let imageRep = NSBitmapImageRep(data: data) else { return } guard let imageData = imageRep.representation(using: type, properties: [.compressionFactor: compressionFactor]) else { return } try? imageData.write(to: url) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/AppKit/NSViewExtensions.swift ================================================ // // NSViewExtensions.swift // SwifterSwift // // Created by Omar Albeik on 3/3/17. // Copyright © 2017 SwifterSwift // #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit // MARK: - Properties public extension NSView { /// SwifterSwift: Border color of view; also inspectable from Storyboard. @IBInspectable var borderColor: NSColor? { get { guard let color = layer?.borderColor else { return nil } return NSColor(cgColor: color) } set { wantsLayer = true layer?.borderColor = newValue?.cgColor } } /// SwifterSwift: Border width of view; also inspectable from Storyboard. @IBInspectable var borderWidth: CGFloat { get { return layer?.borderWidth ?? 0 } set { wantsLayer = true layer?.borderWidth = newValue } } /// SwifterSwift: Corner radius of view; also inspectable from Storyboard. @IBInspectable var cornerRadius: CGFloat { get { return layer?.cornerRadius ?? 0 } set { wantsLayer = true layer?.masksToBounds = true layer?.cornerRadius = abs(CGFloat(Int(newValue * 100)) / 100) } } // SwifterSwift: Height of view. var height: CGFloat { get { return frame.size.height } set { frame.size.height = newValue } } /// SwifterSwift: Shadow color of view; also inspectable from Storyboard. @IBInspectable var shadowColor: NSColor? { get { guard let color = layer?.shadowColor else { return nil } return NSColor(cgColor: color) } set { wantsLayer = true layer?.shadowColor = newValue?.cgColor } } /// SwifterSwift: Shadow offset of view; also inspectable from Storyboard. @IBInspectable var shadowOffset: CGSize { get { return layer?.shadowOffset ?? CGSize.zero } set { wantsLayer = true layer?.shadowOffset = newValue } } /// SwifterSwift: Shadow opacity of view; also inspectable from Storyboard. @IBInspectable var shadowOpacity: Float { get { return layer?.shadowOpacity ?? 0 } set { wantsLayer = true layer?.shadowOpacity = newValue } } /// SwifterSwift: Shadow radius of view; also inspectable from Storyboard. @IBInspectable var shadowRadius: CGFloat { get { return layer?.shadowRadius ?? 0 } set { wantsLayer = true layer?.shadowRadius = newValue } } /// SwifterSwift: Background color of the view; also inspectable from Storyboard. @IBInspectable var backgroundColor: NSColor? { get { if let colorRef = layer?.backgroundColor { return NSColor(cgColor: colorRef) } else { return nil } } set { wantsLayer = true layer?.backgroundColor = newValue?.cgColor } } /// SwifterSwift: Size of view. var size: CGSize { get { return frame.size } set { width = newValue.width height = newValue.height } } /// SwifterSwift: Width of view. var width: CGFloat { get { return frame.size.width } set { frame.size.width = newValue } } } // MARK: - Methods public extension NSView { /// SwifterSwift: Add array of subviews to view. /// /// - Parameter subviews: array of subviews to add to self. func addSubviews(_ subviews: [NSView]) { subviews.forEach { addSubview($0) } } /// SwifterSwift: Remove all subviews in view. func removeSubviews() { subviews.forEach { $0.removeFromSuperview() } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreAnimation/CAGradientLayerExtensions.swift ================================================ // // CAGradientLayerExtensions.swift // SwifterSwift // // Created by Jay Mehta on 11/10/19. // Copyright © 2019 SwifterSwift // #if !os(watchOS) && !os(Linux) && canImport(QuartzCore) import QuartzCore extension CAGradientLayer { /// SwifterSwift: Creates a CAGradientLayer with the specified colors, location, startPoint, endPoint, and type. /// - Parameter colors: An array of colors defining the color of each gradient stop /// - Parameter locations: An array of NSNumber defining the location of each /// gradient stop as a value in the range [0,1]. The values must be /// monotonically increasing. If a nil array is given, the stops are /// assumed to spread uniformly across the [0,1] range. When rendered, /// the colors are mapped to the output colorspace before being /// interpolated. (default is nil) /// - Parameter startPoint: start point corresponds to the first gradient stop (I.e. [0,0] is the bottom-corner of the layer, [1,1] is the top-right corner.) /// - Parameter endPoint: end point corresponds to the last gradient stop /// - Parameter type: The kind of gradient that will be drawn. Currently, the only allowed values are `axial' (the default value), `radial', and `conic'. convenience init(colors: [Color], locations: [CGFloat]? = nil, startPoint: CGPoint, endPoint: CGPoint, type: CAGradientLayerType = .axial) { self.init() self.colors = colors.map { $0.cgColor } self.locations = locations?.map { NSNumber(value: Double($0)) } self.startPoint = startPoint self.endPoint = endPoint self.type = type } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreAnimation/CATransform3DExtensions.swift ================================================ // // CATransform3DExtensions.swift // SwifterSwift // // Created by Guy Kogus on 19/3/20. // Copyright © 2020 SwifterSwift // #if canImport(QuartzCore) import QuartzCore // MARK: - Equatable extension CATransform3D: Equatable { // swiftlint:disable missing_swifterswift_prefix /// Returns a Boolean value indicating whether two values are equal. /// /// Equality is the inverse of inequality. For any values `a` and `b`, /// `a == b` implies that `a != b` is `false`. /// /// - Parameters: /// - lhs: A value to compare. /// - rhs: Another value to compare. @inlinable public static func == (lhs: CATransform3D, rhs: CATransform3D) -> Bool { CATransform3DEqualToTransform(lhs, rhs) } // swiftlint:disable missing_swifterswift_prefix } // MARK: - Static Properties public extension CATransform3D { /// SwifterSwift: The identity transform: [1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]. @inlinable static var identity: CATransform3D { CATransform3DIdentity } } // MARK: - Codable extension CATransform3D: Codable { // swiftlint:disable missing_swifterswift_prefix /// Creates a new instance by decoding from the given decoder. /// /// This initializer throws an error if reading from the decoder fails, or if the data read is corrupted or otherwise invalid. /// - Parameter decoder: The decoder to read data from. @inlinable public init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() self.init(m11: try container.decode(CGFloat.self), m12: try container.decode(CGFloat.self), m13: try container.decode(CGFloat.self), m14: try container.decode(CGFloat.self), m21: try container.decode(CGFloat.self), m22: try container.decode(CGFloat.self), m23: try container.decode(CGFloat.self), m24: try container.decode(CGFloat.self), m31: try container.decode(CGFloat.self), m32: try container.decode(CGFloat.self), m33: try container.decode(CGFloat.self), m34: try container.decode(CGFloat.self), m41: try container.decode(CGFloat.self), m42: try container.decode(CGFloat.self), m43: try container.decode(CGFloat.self), m44: try container.decode(CGFloat.self)) } /// Encodes this value into the given encoder. /// /// If the value fails to encode anything, encoder will encode an empty keyed container in its place. /// /// This function throws an error if any values are invalid for the given encoder’s format. /// - Parameter encoder: The encoder to write data to. @inlinable public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() try container.encode(m11) try container.encode(m12) try container.encode(m13) try container.encode(m14) try container.encode(m21) try container.encode(m22) try container.encode(m23) try container.encode(m24) try container.encode(m31) try container.encode(m32) try container.encode(m33) try container.encode(m34) try container.encode(m41) try container.encode(m42) try container.encode(m43) try container.encode(m44) } // swiftlint:enable missing_swifterswift_prefix } // MARK: - Initializers public extension CATransform3D { /// SwifterSwift: Returns a transform that translates by `(tx, ty, tz)`. /// - Parameters: /// - tx: x-axis translation /// - ty: y-axis translation /// - tz: z-axis translation @inlinable init(translationX tx: CGFloat, y ty: CGFloat, z tz: CGFloat) { // swiftlint:disable:this identifier_name self = CATransform3DMakeTranslation(tx, ty, tz) } /// SwifterSwift: Returns a transform that scales by `(sx, sy, sz)`. /// - Parameters: /// - sx: x-axis scale /// - sy: y-axis scale /// - sz: z-axis scale @inlinable init(scaleX sx: CGFloat, y sy: CGFloat, z sz: CGFloat) { // swiftlint:disable:this identifier_name self = CATransform3DMakeScale(sx, sy, sz) } /// SwifterSwift: Returns a transform that rotates by `angle` radians about the vector `(x, y, z)`. /// /// If the vector has zero length the behavior is undefined. /// - Parameters: /// - angle: The angle of rotation /// - x: x position of the vector /// - y: y position of the vector /// - z: z position of the vector @inlinable init(rotationAngle angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) { self = CATransform3DMakeRotation(angle, x, y, z) } } // MARK: - Properties public extension CATransform3D { /// SwifterSwift: Returns `true` if the receiver is the identity transform. @inlinable var isIdentity: Bool { CATransform3DIsIdentity(self) } } // MARK: - Methods public extension CATransform3D { /// SwifterSwift: Translate the receiver by `(tx, ty, tz)`. /// - Parameters: /// - tx: x-axis translation /// - ty: y-axis translation /// - tz: z-axis translation /// - Returns: The translated matrix. @inlinable func translatedBy(x tx: CGFloat, y ty: CGFloat, z tz: CGFloat) -> CATransform3D { // swiftlint:disable:this identifier_name CATransform3DTranslate(self, tx, ty, tz) } /// SwifterSwift: Scale the receiver by `(sx, sy, sz)`. /// - Parameters: /// - sx: x-axis scale /// - sy: y-axis scale /// - sz: z-axis scale /// - Returns: The scaled matrix. @inlinable func scaledBy(x sx: CGFloat, y sy: CGFloat, z sz: CGFloat) -> CATransform3D { // swiftlint:disable:this identifier_name CATransform3DScale(self, sx, sy, sz) } /// SwifterSwift: Rotate the receiver by `angle` radians about the vector `(x, y, z)`. /// /// If the vector has zero length the behavior is undefined. /// - Parameters: /// - angle: The angle of rotation /// - x: x position of the vector /// - y: y position of the vector /// - z: z position of the vector /// - Returns: The rotated matrix. @inlinable func rotated(by angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) -> CATransform3D { CATransform3DRotate(self, angle, x, y, z) } /// SwifterSwift: Invert the receiver. /// /// Returns the original matrix if the receiver has no inverse. /// - Returns: The inverted matrix of the receiver. @inlinable func inverted() -> CATransform3D { CATransform3DInvert(self) } /// SwifterSwift: Concatenate `transform` to the receiver. /// - Parameter t2: The transform to concatenate on to the receiver /// - Returns: The concatenated matrix. @inlinable func concatenating(_ t2: CATransform3D) -> CATransform3D { // swiftlint:disable:this identifier_name CATransform3DConcat(self, t2) } /// SwifterSwift: Translate the receiver by `(tx, ty, tz)`. /// - Parameters: /// - tx: x-axis translation /// - ty: y-axis translation /// - tz: z-axis translation @inlinable mutating func translateBy(x tx: CGFloat, y ty: CGFloat, z tz: CGFloat) { // swiftlint:disable:this identifier_name self = CATransform3DTranslate(self, tx, ty, tz) } /// SwifterSwift: Scale the receiver by `(sx, sy, sz)`. /// - Parameters: /// - sx: x-axis scale /// - sy: y-axis scale /// - sz: z-axis scale @inlinable mutating func scaleBy(x sx: CGFloat, y sy: CGFloat, z sz: CGFloat) { // swiftlint:disable:this identifier_name self = CATransform3DScale(self, sx, sy, sz) } /// SwifterSwift: Rotate the receiver by `angle` radians about the vector `(x, y, z)`. /// /// If the vector has zero length the behavior is undefined. /// - Parameters: /// - angle: The angle of rotation /// - x: x position of the vector /// - y: y position of the vector /// - z: z position of the vector @inlinable mutating func rotate(by angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) { self = CATransform3DRotate(self, angle, x, y, z) } /// SwifterSwift: Invert the receiver. /// /// Returns the original matrix if the receiver has no inverse. @inlinable mutating func invert() { self = CATransform3DInvert(self) } /// SwifterSwift: Concatenate `transform` to the receiver. /// - Parameter t2: The transform to concatenate on to the receiver @inlinable mutating func concatenate(_ t2: CATransform3D) { // swiftlint:disable:this identifier_name self = CATransform3DConcat(self, t2) } } #if canImport(CoreGraphics) import CoreGraphics // MARK: - CGAffineTransform public extension CATransform3D { /// SwifterSwift: Returns true if the receiver can be represented exactly by an affine transform. @inlinable var isAffine: Bool { CATransform3DIsAffine(self) } /// SwifterSwift: Returns the affine transform represented by the receiver. /// /// If the receiver can not be represented exactly by an affine transform the returned value is undefined. @inlinable func affineTransform() -> CGAffineTransform { CATransform3DGetAffineTransform(self) } } #endif #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreGraphics/CGAffineTransformExtensions.swift ================================================ // // CGAffineTransformExtensions.swift // SwifterSwift // // Created by Guy Kogus on 19/3/20. // Copyright © 2020 SwifterSwift // #if canImport(CoreGraphics) import CoreGraphics #if canImport(QuartzCore) import QuartzCore // MARK: - Methods public extension CGAffineTransform { /// SwifterSwift: Returns a transform with the same effect as the receiver. @inlinable func transform3D() -> CATransform3D { CATransform3DMakeAffineTransform(self) } } #endif #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreGraphics/CGColorExtensions.swift ================================================ // // CGColorExtensions.swift // SwifterSwift // // Created by Omar Albeik on 03/02/2017. // Copyright © 2017 SwifterSwift // #if canImport(CoreGraphics) import CoreGraphics #if canImport(UIKit) import UIKit #endif #if canImport(AppKit) import AppKit #endif // MARK: - Properties public extension CGColor { #if canImport(UIKit) /// SwifterSwift: UIColor. var uiColor: UIColor? { return UIColor(cgColor: self) } #endif #if canImport(AppKit) && !targetEnvironment(macCatalyst) /// SwifterSwift: NSColor. var nsColor: NSColor? { return NSColor(cgColor: self) } #endif } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreGraphics/CGFloatExtensions.swift ================================================ // // CGFloatExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/23/16. // Copyright © 2016 SwifterSwift // #if canImport(CoreGraphics) import CoreGraphics #if canImport(Foundation) import Foundation #endif // MARK: - Properties public extension CGFloat { /// SwifterSwift: Absolute of CGFloat value. var abs: CGFloat { return Swift.abs(self) } #if canImport(Foundation) /// SwifterSwift: Ceil of CGFloat value. var ceil: CGFloat { return Foundation.ceil(self) } #endif /// SwifterSwift: Radian value of degree input. var degreesToRadians: CGFloat { return .pi * self / 180.0 } #if canImport(Foundation) /// SwifterSwift: Floor of CGFloat value. var floor: CGFloat { return Foundation.floor(self) } #endif /// SwifterSwift: Check if CGFloat is positive. var isPositive: Bool { return self > 0 } /// SwifterSwift: Check if CGFloat is negative. var isNegative: Bool { return self < 0 } /// SwifterSwift: Int. var int: Int { return Int(self) } /// SwifterSwift: Float. var float: Float { return Float(self) } /// SwifterSwift: Double. var double: Double { return Double(self) } /// SwifterSwift: Degree value of radian input. var radiansToDegrees: CGFloat { return self * 180 / CGFloat.pi } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreGraphics/CGPointExtensions.swift ================================================ // // CGPointExtensions.swift // SwifterSwift // // Created by Omar Albeik on 07/12/2016. // Copyright © 2016 SwifterSwift // #if canImport(CoreGraphics) import CoreGraphics // MARK: - Methods public extension CGPoint { /// SwifterSwift: Distance from another CGPoint. /// /// let point1 = CGPoint(x: 10, y: 10) /// let point2 = CGPoint(x: 30, y: 30) /// let distance = point1.distance(from: point2) /// // distance = 28.28 /// /// - Parameter point: CGPoint to get distance from. /// - Returns: Distance between self and given CGPoint. func distance(from point: CGPoint) -> CGFloat { return CGPoint.distance(from: self, to: point) } /// SwifterSwift: Distance between two CGPoints. /// /// let point1 = CGPoint(x: 10, y: 10) /// let point2 = CGPoint(x: 30, y: 30) /// let distance = CGPoint.distance(from: point2, to: point1) /// // distance = 28.28 /// /// - Parameters: /// - point1: first CGPoint. /// - point2: second CGPoint. /// - Returns: distance between the two given CGPoints. static func distance(from point1: CGPoint, to point2: CGPoint) -> CGFloat { // http://stackoverflow.com/questions/6416101/calculate-the-distance-between-two-cgpoints return sqrt(pow(point2.x - point1.x, 2) + pow(point2.y - point1.y, 2)) } } // MARK: - Operators public extension CGPoint { /// SwifterSwift: Add two CGPoints. /// /// let point1 = CGPoint(x: 10, y: 10) /// let point2 = CGPoint(x: 30, y: 30) /// let point = point1 + point2 /// // point = CGPoint(x: 40, y: 40) /// /// - Parameters: /// - lhs: CGPoint to add to. /// - rhs: CGPoint to add. /// - Returns: result of addition of the two given CGPoints. static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) } /// SwifterSwift: Add a CGPoints to self. /// /// let point1 = CGPoint(x: 10, y: 10) /// let point2 = CGPoint(x: 30, y: 30) /// point1 += point2 /// // point1 = CGPoint(x: 40, y: 40) /// /// - Parameters: /// - lhs: self /// - rhs: CGPoint to add. static func += (lhs: inout CGPoint, rhs: CGPoint) { // swiftlint:disable:next shorthand_operator lhs = lhs + rhs } /// SwifterSwift: Subtract two CGPoints. /// /// let point1 = CGPoint(x: 10, y: 10) /// let point2 = CGPoint(x: 30, y: 30) /// let point = point1 - point2 /// // point = CGPoint(x: -20, y: -20) /// /// - Parameters: /// - lhs: CGPoint to subtract from. /// - rhs: CGPoint to subtract. /// - Returns: result of subtract of the two given CGPoints. static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint { return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) } /// SwifterSwift: Subtract a CGPoints from self. /// /// let point1 = CGPoint(x: 10, y: 10) /// let point2 = CGPoint(x: 30, y: 30) /// point1 -= point2 /// // point1 = CGPoint(x: -20, y: -20) /// /// - Parameters: /// - lhs: self /// - rhs: CGPoint to subtract. static func -= (lhs: inout CGPoint, rhs: CGPoint) { // swiftlint:disable:next shorthand_operator lhs = lhs - rhs } /// SwifterSwift: Multiply a CGPoint with a scalar /// /// let point1 = CGPoint(x: 10, y: 10) /// let scalar = point1 * 5 /// // scalar = CGPoint(x: 50, y: 50) /// /// - Parameters: /// - point: CGPoint to multiply. /// - scalar: scalar value. /// - Returns: result of multiplication of the given CGPoint with the scalar. static func * (point: CGPoint, scalar: CGFloat) -> CGPoint { return CGPoint(x: point.x * scalar, y: point.y * scalar) } /// SwifterSwift: Multiply self with a scalar /// /// let point1 = CGPoint(x: 10, y: 10) /// point *= 5 /// // point1 = CGPoint(x: 50, y: 50) /// /// - Parameters: /// - point: self. /// - scalar: scalar value. /// - Returns: result of multiplication of the given CGPoint with the scalar. static func *= (point: inout CGPoint, scalar: CGFloat) { // swiftlint:disable:next shorthand_operator point = point * scalar } /// SwifterSwift: Multiply a CGPoint with a scalar /// /// let point1 = CGPoint(x: 10, y: 10) /// let scalar = 5 * point1 /// // scalar = CGPoint(x: 50, y: 50) /// /// - Parameters: /// - scalar: scalar value. /// - point: CGPoint to multiply. /// - Returns: result of multiplication of the given CGPoint with the scalar. static func * (scalar: CGFloat, point: CGPoint) -> CGPoint { return CGPoint(x: point.x * scalar, y: point.y * scalar) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreGraphics/CGRectExtensions.swift ================================================ // // CGRectExtensions.swift // SwifterSwift // // Created by Chen Qizhi on 2020/03/11. // Copyright © 2020 SwifterSwift // #if canImport(CoreGraphics) import CoreGraphics // MARK: - Properties public extension CGRect { /// SwifterSwift: Return center of rect var center: CGPoint { CGPoint(x: midX, y: midY) } } // MARK: - Initializers public extension CGRect { /// SwifterSwift: Create a `CGRect` instance with center and size /// - Parameters: /// - center: center of the new rect /// - size: size of the new rect init(center: CGPoint, size: CGSize) { let origin = CGPoint(x: center.x - size.width / 2.0, y: center.y - size.height / 2.0) self.init(origin: origin, size: size) } } // MARK: - Methods public extension CGRect { /// SwifterSwift: Create a new `CGRect` by resizing with specified anchor /// - Parameters: /// - size: new size to be applied /// - anchor: specified anchor, a point in normalized coordinates - /// '(0, 0)' is the top left corner of rect,'(1, 1)' is the bottom right corner of rect, /// defaults to '(0.5, 0.5)'. excample: /// /// anchor = CGPoint(x: 0.0, y: 1.0): /// /// A2------B2 /// A----B | | /// | | --> | | /// C----D C-------D2 /// func resizing(to size: CGSize, anchor: CGPoint = CGPoint(x: 0.5, y: 0.5)) -> CGRect { let sizeDelta = CGSize(width: size.width - width, height: size.height - height) return CGRect(origin: CGPoint(x: minX - sizeDelta.width * anchor.x, y: minY - sizeDelta.height * anchor.y), size: size) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreGraphics/CGSizeExtensions.swift ================================================ // // CGSizeExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/22/16. // Copyright © 2016 SwifterSwift // #if canImport(CoreGraphics) import CoreGraphics // MARK: - Methods public extension CGSize { /// SwifterSwift: Returns the aspect ratio. var aspectRatio: CGFloat { return height == 0 ? 0 : width / height } /// SwifterSwift: Returns width or height, whichever is the bigger value. var maxDimension: CGFloat { return max(width, height) } /// SwifterSwift: Returns width or height, whichever is the smaller value. var minDimension: CGFloat { return min(width, height) } } // MARK: - Methods public extension CGSize { /// SwifterSwift: Aspect fit CGSize. /// /// let rect = CGSize(width: 120, height: 80) /// let parentRect = CGSize(width: 100, height: 50) /// let newRect = rect.aspectFit(to: parentRect) /// // newRect.width = 75 , newRect = 50 /// /// - Parameter boundingSize: bounding size to fit self to. /// - Returns: self fitted into given bounding size func aspectFit(to boundingSize: CGSize) -> CGSize { let minRatio = min(boundingSize.width / width, boundingSize.height / height) return CGSize(width: width * minRatio, height: height * minRatio) } /// SwifterSwift: Aspect fill CGSize. /// /// let rect = CGSize(width: 20, height: 120) /// let parentRect = CGSize(width: 100, height: 60) /// let newRect = rect.aspectFit(to: parentRect) /// // newRect.width = 100 , newRect = 60 /// /// - Parameter boundingSize: bounding size to fill self to. /// - Returns: self filled into given bounding size func aspectFill(to boundingSize: CGSize) -> CGSize { let minRatio = max(boundingSize.width / width, boundingSize.height / height) let aWidth = min(width * minRatio, boundingSize.width) let aHeight = min(height * minRatio, boundingSize.height) return CGSize(width: aWidth, height: aHeight) } } // MARK: - Operators public extension CGSize { /// SwifterSwift: Add two CGSize /// /// let sizeA = CGSize(width: 5, height: 10) /// let sizeB = CGSize(width: 3, height: 4) /// let result = sizeA + sizeB /// // result = CGSize(width: 8, height: 14) /// /// - Parameters: /// - lhs: CGSize to add to. /// - rhs: CGSize to add. /// - Returns: The result comes from the addition of the two given CGSize struct. static func + (lhs: CGSize, rhs: CGSize) -> CGSize { return CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) } /// SwifterSwift: Add a CGSize to self. /// /// let sizeA = CGSize(width: 5, height: 10) /// let sizeB = CGSize(width: 3, height: 4) /// sizeA += sizeB /// // sizeA = CGPoint(width: 8, height: 14) /// /// - Parameters: /// - lhs: self /// - rhs: CGSize to add. static func += (lhs: inout CGSize, rhs: CGSize) { lhs.width += rhs.width lhs.height += rhs.height } /// SwifterSwift: Subtract two CGSize /// /// let sizeA = CGSize(width: 5, height: 10) /// let sizeB = CGSize(width: 3, height: 4) /// let result = sizeA - sizeB /// // result = CGSize(width: 2, height: 6) /// /// - Parameters: /// - lhs: CGSize to subtract from. /// - rhs: CGSize to subtract. /// - Returns: The result comes from the subtract of the two given CGSize struct. static func - (lhs: CGSize, rhs: CGSize) -> CGSize { return CGSize(width: lhs.width - rhs.width, height: lhs.height - rhs.height) } /// SwifterSwift: Subtract a CGSize from self. /// /// let sizeA = CGSize(width: 5, height: 10) /// let sizeB = CGSize(width: 3, height: 4) /// sizeA -= sizeB /// // sizeA = CGPoint(width: 2, height: 6) /// /// - Parameters: /// - lhs: self /// - rhs: CGSize to subtract. static func -= (lhs: inout CGSize, rhs: CGSize) { lhs.width -= rhs.width lhs.height -= rhs.height } /// SwifterSwift: Multiply two CGSize /// /// let sizeA = CGSize(width: 5, height: 10) /// let sizeB = CGSize(width: 3, height: 4) /// let result = sizeA * sizeB /// // result = CGSize(width: 15, height: 40) /// /// - Parameters: /// - lhs: CGSize to multiply. /// - rhs: CGSize to multiply with. /// - Returns: The result comes from the multiplication of the two given CGSize structs. static func * (lhs: CGSize, rhs: CGSize) -> CGSize { return CGSize(width: lhs.width * rhs.width, height: lhs.height * rhs.height) } /// SwifterSwift: Multiply a CGSize with a scalar. /// /// let sizeA = CGSize(width: 5, height: 10) /// let result = sizeA * 5 /// // result = CGSize(width: 25, height: 50) /// /// - Parameters: /// - lhs: CGSize to multiply. /// - scalar: scalar value. /// - Returns: The result comes from the multiplication of the given CGSize and scalar. static func * (lhs: CGSize, scalar: CGFloat) -> CGSize { return CGSize(width: lhs.width * scalar, height: lhs.height * scalar) } /// SwifterSwift: Multiply a CGSize with a scalar. /// /// let sizeA = CGSize(width: 5, height: 10) /// let result = 5 * sizeA /// // result = CGSize(width: 25, height: 50) /// /// - Parameters: /// - scalar: scalar value. /// - rhs: CGSize to multiply. /// - Returns: The result comes from the multiplication of the given scalar and CGSize. static func * (scalar: CGFloat, rhs: CGSize) -> CGSize { return CGSize(width: scalar * rhs.width, height: scalar * rhs.height) } /// SwifterSwift: Multiply self with a CGSize. /// /// let sizeA = CGSize(width: 5, height: 10) /// let sizeB = CGSize(width: 3, height: 4) /// sizeA *= sizeB /// // result = CGSize(width: 15, height: 40) /// /// - Parameters: /// - lhs: self. /// - rhs: CGSize to multiply. static func *= (lhs: inout CGSize, rhs: CGSize) { lhs.width *= rhs.width lhs.height *= rhs.height } /// SwifterSwift: Multiply self with a scalar. /// /// let sizeA = CGSize(width: 5, height: 10) /// sizeA *= 3 /// // result = CGSize(width: 15, height: 30) /// /// - Parameters: /// - lhs: self. /// - scalar: scalar value. static func *= (lhs: inout CGSize, scalar: CGFloat) { lhs.width *= scalar lhs.height *= scalar } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreGraphics/CGVectorExtensions.swift ================================================ // // CGVectorExtensions.swift // SwifterSwift // // Created by Robbie Moyer on 7/25/18. // Copyright © 2018 SwifterSwift // #if canImport(CoreGraphics) import CoreGraphics // MARK: - Properties public extension CGVector { /// SwifterSwift: The angle of rotation (in radians) of the vector. The range of the angle is -π to π; an angle of 0 points to the right. /// /// https://en.wikipedia.org/wiki/Atan2 var angle: CGFloat { return atan2(dy, dx) } /// SwifterSwift: The magnitude (or length) of the vector. /// /// https://en.wikipedia.org/wiki/Euclidean_vector#Length var magnitude: CGFloat { return sqrt((dx * dx) + (dy * dy)) } } // MARK: - Initializers public extension CGVector { /// SwifterSwift: Creates a vector with the given magnitude and angle. /// /// let vector = CGVector(angle: .pi, magnitude: 1) /// /// - Parameters: /// - angle: The angle of rotation (in radians) counterclockwise from the positive x-axis. /// - magnitude: The lenth of the vector. /// init(angle: CGFloat, magnitude: CGFloat) { // https://www.grc.nasa.gov/WWW/K-12/airplane/vectpart.html self.init(dx: magnitude * cos(angle), dy: magnitude * sin(angle)) } } // MARK: - Operators public extension CGVector { /// SwifterSwift: Multiplies a scalar and a vector (commutative). /// /// let vector = CGVector(dx: 1, dy: 1) /// let largerVector = vector * 2 /// /// - Parameters: /// - vector: The vector to be multiplied /// - scalar: The scale by which the vector will be multiplied /// - Returns: The vector with its magnitude scaled static func * (vector: CGVector, scalar: CGFloat) -> CGVector { return CGVector(dx: vector.dx * scalar, dy: vector.dy * scalar) } /// SwifterSwift: Multiplies a scalar and a vector (commutative). /// /// let vector = CGVector(dx: 1, dy: 1) /// let largerVector = 2 * vector /// /// - Parameters: /// - scalar: The scalar by which the vector will be multiplied /// - vector: The vector to be multiplied /// - Returns: The vector with its magnitude scaled static func * (scalar: CGFloat, vector: CGVector) -> CGVector { return CGVector(dx: scalar * vector.dx, dy: scalar * vector.dy) } /// SwifterSwift: Compound assignment operator for vector-scalr multiplication /// /// var vector = CGVector(dx: 1, dy: 1) /// vector *= 2 /// /// - Parameters: /// - vector: The vector to be multiplied /// - scalar: The scale by which the vector will be multiplied static func *= (vector: inout CGVector, scalar: CGFloat) { // swiftlint:disable:next shorthand_operator vector = vector * scalar } /// SwifterSwift: Negates the vector. The direction is reversed, but magnitude remains the same. /// /// let vector = CGVector(dx: 1, dy: 1) /// let reversedVector = -vector /// /// - Parameter vector: The vector to be negated /// - Returns: The negated vector static prefix func - (vector: CGVector) -> CGVector { return CGVector(dx: -vector.dx, dy: -vector.dy) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreLocation/CLLocationArrayExtensions.swift ================================================ // // CLLocationArrayExtensions.swift // SwifterSwift // // Created by Trevor Phillips on 09/01/20. // Copyright © 2020 SwifterSwift // #if canImport(CoreLocation) import CoreLocation // MARK: - Methods public extension Array where Element: CLLocation { /// SwifterSwift: Calculates the sum of distances between each location in the array based on the curvature of the earth. /// /// - Parameter unitLength: The unit of length to return the distance in. /// - Returns: The distance in the specified unit. @available(tvOS 10.0, macOS 10.12, watchOS 3.0, *) func distance(unitLength unit: UnitLength) -> Measurement { guard count > 1 else { return Measurement(value: 0.0, unit: unit) } var distance = 0.0 for idx in 0.. CLLocation { let lat1 = Double.pi * start.coordinate.latitude / 180.0 let long1 = Double.pi * start.coordinate.longitude / 180.0 let lat2 = Double.pi * end.coordinate.latitude / 180.0 let long2 = Double.pi * end.coordinate.longitude / 180.0 // Formula // Bx = cos φ2 ⋅ cos Δλ // By = cos φ2 ⋅ sin Δλ // φm = atan2( sin φ1 + sin φ2, √(cos φ1 + Bx)² + By² ) // λm = λ1 + atan2(By, cos(φ1)+Bx) // Source: http://www.movable-type.co.uk/scripts/latlong.html let bxLoc = cos(lat2) * cos(long2 - long1) let byLoc = cos(lat2) * sin(long2 - long1) let mlat = atan2(sin(lat1) + sin(lat2), sqrt((cos(lat1) + bxLoc) * (cos(lat1) + bxLoc) + (byLoc * byLoc))) let mlong = (long1) + atan2(byLoc, cos(lat1) + bxLoc) return CLLocation(latitude: (mlat * 180 / Double.pi), longitude: (mlong * 180 / Double.pi)) } /// SwifterSwift: Calculate the half-way point along a great circle path between self and another points. /// /// - Parameter point: End location. /// - Returns: Location that represents the half-way point. func midLocation(to point: CLLocation) -> CLLocation { return CLLocation.midLocation(start: self, end: point) } /// SwifterSwift: Calculates the bearing to another CLLocation. /// /// - Parameters: /// - destination: Location to calculate bearing. /// - Returns: Calculated bearing degrees in the range 0° ... 360° func bearing(to destination: CLLocation) -> Double { // http://stackoverflow.com/questions/3925942/cllocation-category-for-calculating-bearing-w-haversine-function let lat1 = Double.pi * coordinate.latitude / 180.0 let long1 = Double.pi * coordinate.longitude / 180.0 let lat2 = Double.pi * destination.coordinate.latitude / 180.0 let long2 = Double.pi * destination.coordinate.longitude / 180.0 // Formula: θ = atan2( sin Δλ ⋅ cos φ2 , cos φ1 ⋅ sin φ2 − sin φ1 ⋅ cos φ2 ⋅ cos Δλ ) // Source: http://www.movable-type.co.uk/scripts/latlong.html let rads = atan2( sin(long2 - long1) * cos(lat2), cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(long2 - long1)) let degrees = rads * 180 / Double.pi return (degrees+360).truncatingRemainder(dividingBy: 360) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/CoreLocation/CLVisitExtensions.swift ================================================ // // CLVisitExtensions.swift // SwifterSwift // // Created by Trevor Phillips on 09/01/20. // Copyright © 2020 SwifterSwift // #if canImport(CoreLocation) && (os(iOS) || targetEnvironment(macCatalyst)) import CoreLocation // MARK: - Properties public extension CLVisit { /// SwifterSwift: Retrieves a visit's location. /// /// - Returns: CLLocation var location: CLLocation { return CLLocation(latitude: self.coordinate.latitude, longitude: self.coordinate.longitude) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Dispatch/DispatchQueueExtensions.swift ================================================ // // DispatchQueueExtensions.swift // SwifterSwift // // Created by Quentin Jin on 2018/10/13. // Copyright © 2018 SwifterSwift // #if canImport(Dispatch) import Dispatch // MARK: - Properties public extension DispatchQueue { /// SwifterSwift: A Boolean value indicating whether the current dispatch queue is the main queue. static var isMainQueue: Bool { enum Static { static var key: DispatchSpecificKey = { let key = DispatchSpecificKey() DispatchQueue.main.setSpecific(key: key, value: ()) return key }() } return DispatchQueue.getSpecific(key: Static.key) != nil } } // MARK: - Methods public extension DispatchQueue { /// SwifterSwift: Returns a Boolean value indicating whether the current dispatch queue is the specified queue. /// /// - Parameter queue: The queue to compare against. /// - Returns: `true` if the current queue is the specified queue, otherwise `false`. static func isCurrent(_ queue: DispatchQueue) -> Bool { let key = DispatchSpecificKey() queue.setSpecific(key: key, value: ()) defer { queue.setSpecific(key: key, value: nil) } return DispatchQueue.getSpecific(key: key) != nil } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/CalendarExtensions.swift ================================================ // // CalendarExtensions.swift // SwifterSwift // // Created by Chaithanya Prathyush on 09/11/17. // Copyright © 2017 SwifterSwift // #if canImport(Foundation) import Foundation // MARK: - Methods public extension Calendar { /// SwifterSwift: Return the number of days in the month for a specified 'Date'. /// /// let date = Date() // "Jan 12, 2017, 7:07 PM" /// Calendar.current.numberOfDaysInMonth(for: date) -> 31 /// /// - Parameter date: the date form which the number of days in month is calculated. /// - Returns: The number of days in the month of 'Date'. func numberOfDaysInMonth(for date: Date) -> Int { return range(of: .day, in: .month, for: date)!.count } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/DataExtensions.swift ================================================ // // DataExtensions.swift // SwifterSwift // // Created by Omar Albeik on 07/12/2016. // Copyright © 2016 SwifterSwift // #if canImport(Foundation) import Foundation // MARK: - Properties public extension Data { /// SwifterSwift: Return data as an array of bytes. var bytes: [UInt8] { // http://stackoverflow.com/questions/38097710/swift-3-changes-for-getbytes-method return [UInt8](self) } } // MARK: - Methods public extension Data { /// SwifterSwift: String by encoding Data using the given encoding (if applicable). /// /// - Parameter encoding: encoding. /// - Returns: String by encoding Data using the given encoding (if applicable). func string(encoding: String.Encoding) -> String? { return String(data: self, encoding: encoding) } /// SwifterSwift: Returns a Foundation object from given JSON data. /// /// - Parameter options: Options for reading the JSON data and creating the Foundation object. /// /// For possible values, see `JSONSerialization.ReadingOptions`. /// - Returns: A Foundation object from the JSON data in the receiver, or `nil` if an error occurs. /// - Throws: An `NSError` if the receiver does not represent a valid JSON object. func jsonObject(options: JSONSerialization.ReadingOptions = []) throws -> Any { return try JSONSerialization.jsonObject(with: self, options: options) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/DateExtensions.swift ================================================ // // DateExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/5/16. // Copyright © 2016 SwifterSwift // #if canImport(Foundation) import Foundation #if os(macOS) || os(iOS) import Darwin #elseif os(Linux) import Glibc #endif // MARK: - Enums public extension Date { /// SwifterSwift: Day name format. /// /// - threeLetters: 3 letter day abbreviation of day name. /// - oneLetter: 1 letter day abbreviation of day name. /// - full: Full day name. enum DayNameStyle { /// SwifterSwift: 3 letter day abbreviation of day name. case threeLetters /// SwifterSwift: 1 letter day abbreviation of day name. case oneLetter /// SwifterSwift: Full day name. case full } /// SwifterSwift: Month name format. /// /// - threeLetters: 3 letter month abbreviation of month name. /// - oneLetter: 1 letter month abbreviation of month name. /// - full: Full month name. enum MonthNameStyle { /// SwifterSwift: 3 letter month abbreviation of month name. case threeLetters /// SwifterSwift: 1 letter month abbreviation of month name. case oneLetter /// SwifterSwift: Full month name. case full } } // MARK: - Properties public extension Date { /// SwifterSwift: User’s current calendar. var calendar: Calendar { return Calendar(identifier: Calendar.current.identifier) // Workaround to segfault on corelibs foundation https://bugs.swift.org/browse/SR-10147 } /// SwifterSwift: Era. /// /// Date().era -> 1 /// var era: Int { return calendar.component(.era, from: self) } #if !os(Linux) /// SwifterSwift: Quarter. /// /// Date().quarter -> 3 // date in third quarter of the year. /// var quarter: Int { let month = Double(calendar.component(.month, from: self)) let numberOfMonths = Double(calendar.monthSymbols.count) let numberOfMonthsInQuarter = numberOfMonths / 4 return Int(ceil(month/numberOfMonthsInQuarter)) } #endif /// SwifterSwift: Week of year. /// /// Date().weekOfYear -> 2 // second week in the year. /// var weekOfYear: Int { return calendar.component(.weekOfYear, from: self) } /// SwifterSwift: Week of month. /// /// Date().weekOfMonth -> 3 // date is in third week of the month. /// var weekOfMonth: Int { return calendar.component(.weekOfMonth, from: self) } /// SwifterSwift: Year. /// /// Date().year -> 2017 /// /// var someDate = Date() /// someDate.year = 2000 // sets someDate's year to 2000 /// var year: Int { get { return calendar.component(.year, from: self) } set { guard newValue > 0 else { return } let currentYear = calendar.component(.year, from: self) let yearsToAdd = newValue - currentYear if let date = calendar.date(byAdding: .year, value: yearsToAdd, to: self) { self = date } } } /// SwifterSwift: Month. /// /// Date().month -> 1 /// /// var someDate = Date() /// someDate.month = 10 // sets someDate's month to 10. /// var month: Int { get { return calendar.component(.month, from: self) } set { let allowedRange = calendar.range(of: .month, in: .year, for: self)! guard allowedRange.contains(newValue) else { return } let currentMonth = calendar.component(.month, from: self) let monthsToAdd = newValue - currentMonth if let date = calendar.date(byAdding: .month, value: monthsToAdd, to: self) { self = date } } } /// SwifterSwift: Day. /// /// Date().day -> 12 /// /// var someDate = Date() /// someDate.day = 1 // sets someDate's day of month to 1. /// var day: Int { get { return calendar.component(.day, from: self) } set { let allowedRange = calendar.range(of: .day, in: .month, for: self)! guard allowedRange.contains(newValue) else { return } let currentDay = calendar.component(.day, from: self) let daysToAdd = newValue - currentDay if let date = calendar.date(byAdding: .day, value: daysToAdd, to: self) { self = date } } } /// SwifterSwift: Weekday. /// /// Date().weekday -> 5 // fifth day in the current week. /// var weekday: Int { return calendar.component(.weekday, from: self) } /// SwifterSwift: Hour. /// /// Date().hour -> 17 // 5 pm /// /// var someDate = Date() /// someDate.hour = 13 // sets someDate's hour to 1 pm. /// var hour: Int { get { return calendar.component(.hour, from: self) } set { let allowedRange = calendar.range(of: .hour, in: .day, for: self)! guard allowedRange.contains(newValue) else { return } let currentHour = calendar.component(.hour, from: self) let hoursToAdd = newValue - currentHour if let date = calendar.date(byAdding: .hour, value: hoursToAdd, to: self) { self = date } } } /// SwifterSwift: Minutes. /// /// Date().minute -> 39 /// /// var someDate = Date() /// someDate.minute = 10 // sets someDate's minutes to 10. /// var minute: Int { get { return calendar.component(.minute, from: self) } set { let allowedRange = calendar.range(of: .minute, in: .hour, for: self)! guard allowedRange.contains(newValue) else { return } let currentMinutes = calendar.component(.minute, from: self) let minutesToAdd = newValue - currentMinutes if let date = calendar.date(byAdding: .minute, value: minutesToAdd, to: self) { self = date } } } /// SwifterSwift: Seconds. /// /// Date().second -> 55 /// /// var someDate = Date() /// someDate.second = 15 // sets someDate's seconds to 15. /// var second: Int { get { return calendar.component(.second, from: self) } set { let allowedRange = calendar.range(of: .second, in: .minute, for: self)! guard allowedRange.contains(newValue) else { return } let currentSeconds = calendar.component(.second, from: self) let secondsToAdd = newValue - currentSeconds if let date = calendar.date(byAdding: .second, value: secondsToAdd, to: self) { self = date } } } /// SwifterSwift: Nanoseconds. /// /// Date().nanosecond -> 981379985 /// /// var someDate = Date() /// someDate.nanosecond = 981379985 // sets someDate's seconds to 981379985. /// var nanosecond: Int { get { return calendar.component(.nanosecond, from: self) } set { #if targetEnvironment(macCatalyst) // The `Calendar` implementation in `macCatalyst` does not know that a nanosecond is 1/1,000,000,000th of a second let allowedRange = 0..<1_000_000_000 #else let allowedRange = calendar.range(of: .nanosecond, in: .second, for: self)! #endif guard allowedRange.contains(newValue) else { return } let currentNanoseconds = calendar.component(.nanosecond, from: self) let nanosecondsToAdd = newValue - currentNanoseconds if let date = calendar.date(byAdding: .nanosecond, value: nanosecondsToAdd, to: self) { self = date } } } /// SwifterSwift: Milliseconds. /// /// Date().millisecond -> 68 /// /// var someDate = Date() /// someDate.millisecond = 68 // sets someDate's nanosecond to 68000000. /// var millisecond: Int { get { return calendar.component(.nanosecond, from: self) / 1_000_000 } set { let nanoSeconds = newValue * 1_000_000 #if targetEnvironment(macCatalyst) // The `Calendar` implementation in `macCatalyst` does not know that a nanosecond is 1/1,000,000,000th of a second let allowedRange = 0..<1_000_000_000 #else let allowedRange = calendar.range(of: .nanosecond, in: .second, for: self)! #endif guard allowedRange.contains(nanoSeconds) else { return } if let date = calendar.date(bySetting: .nanosecond, value: nanoSeconds, of: self) { self = date } } } /// SwifterSwift: Check if date is in future. /// /// Date(timeInterval: 100, since: Date()).isInFuture -> true /// var isInFuture: Bool { return self > Date() } /// SwifterSwift: Check if date is in past. /// /// Date(timeInterval: -100, since: Date()).isInPast -> true /// var isInPast: Bool { return self < Date() } /// SwifterSwift: Check if date is within today. /// /// Date().isInToday -> true /// var isInToday: Bool { return calendar.isDateInToday(self) } /// SwifterSwift: Check if date is within yesterday. /// /// Date().isInYesterday -> false /// var isInYesterday: Bool { return calendar.isDateInYesterday(self) } /// SwifterSwift: Check if date is within tomorrow. /// /// Date().isInTomorrow -> false /// var isInTomorrow: Bool { return calendar.isDateInTomorrow(self) } /// SwifterSwift: Check if date is within a weekend period. var isInWeekend: Bool { return calendar.isDateInWeekend(self) } /// SwifterSwift: Check if date is within a weekday period. var isWorkday: Bool { return !calendar.isDateInWeekend(self) } /// SwifterSwift: Check if date is within the current week. var isInCurrentWeek: Bool { return calendar.isDate(self, equalTo: Date(), toGranularity: .weekOfYear) } /// SwifterSwift: Check if date is within the current month. var isInCurrentMonth: Bool { return calendar.isDate(self, equalTo: Date(), toGranularity: .month) } /// SwifterSwift: Check if date is within the current year. var isInCurrentYear: Bool { return calendar.isDate(self, equalTo: Date(), toGranularity: .year) } /// SwifterSwift: ISO8601 string of format (yyyy-MM-dd'T'HH:mm:ss.SSS) from date. /// /// Date().iso8601String -> "2017-01-12T14:51:29.574Z" /// var iso8601String: String { // https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US_POSIX") dateFormatter.timeZone = TimeZone(abbreviation: "GMT") dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" return dateFormatter.string(from: self).appending("Z") } /// SwifterSwift: Nearest five minutes to date. /// /// var date = Date() // "5:54 PM" /// date.minute = 32 // "5:32 PM" /// date.nearestFiveMinutes // "5:30 PM" /// /// date.minute = 44 // "5:44 PM" /// date.nearestFiveMinutes // "5:45 PM" /// var nearestFiveMinutes: Date { var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond], from: self) let min = components.minute! components.minute! = min % 5 < 3 ? min - min % 5 : min + 5 - (min % 5) components.second = 0 components.nanosecond = 0 return calendar.date(from: components)! } /// SwifterSwift: Nearest ten minutes to date. /// /// var date = Date() // "5:57 PM" /// date.minute = 34 // "5:34 PM" /// date.nearestTenMinutes // "5:30 PM" /// /// date.minute = 48 // "5:48 PM" /// date.nearestTenMinutes // "5:50 PM" /// var nearestTenMinutes: Date { var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond], from: self) let min = components.minute! components.minute? = min % 10 < 6 ? min - min % 10 : min + 10 - (min % 10) components.second = 0 components.nanosecond = 0 return calendar.date(from: components)! } /// SwifterSwift: Nearest quarter hour to date. /// /// var date = Date() // "5:57 PM" /// date.minute = 34 // "5:34 PM" /// date.nearestQuarterHour // "5:30 PM" /// /// date.minute = 40 // "5:40 PM" /// date.nearestQuarterHour // "5:45 PM" /// var nearestQuarterHour: Date { var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond], from: self) let min = components.minute! components.minute! = min % 15 < 8 ? min - min % 15 : min + 15 - (min % 15) components.second = 0 components.nanosecond = 0 return calendar.date(from: components)! } /// SwifterSwift: Nearest half hour to date. /// /// var date = Date() // "6:07 PM" /// date.minute = 41 // "6:41 PM" /// date.nearestHalfHour // "6:30 PM" /// /// date.minute = 51 // "6:51 PM" /// date.nearestHalfHour // "7:00 PM" /// var nearestHalfHour: Date { var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond], from: self) let min = components.minute! components.minute! = min % 30 < 15 ? min - min % 30 : min + 30 - (min % 30) components.second = 0 components.nanosecond = 0 return calendar.date(from: components)! } /// SwifterSwift: Nearest hour to date. /// /// var date = Date() // "6:17 PM" /// date.nearestHour // "6:00 PM" /// /// date.minute = 36 // "6:36 PM" /// date.nearestHour // "7:00 PM" /// var nearestHour: Date { let min = calendar.component(.minute, from: self) let components: Set = [.year, .month, .day, .hour] let date = calendar.date(from: calendar.dateComponents(components, from: self))! if min < 30 { return date } return calendar.date(byAdding: .hour, value: 1, to: date)! } /// SwifterSwift: Yesterday date. /// /// let date = Date() // "Oct 3, 2018, 10:57:11" /// let yesterday = date.yesterday // "Oct 2, 2018, 10:57:11" /// var yesterday: Date { return calendar.date(byAdding: .day, value: -1, to: self) ?? Date() } /// SwifterSwift: Tomorrow's date. /// /// let date = Date() // "Oct 3, 2018, 10:57:11" /// let tomorrow = date.tomorrow // "Oct 4, 2018, 10:57:11" /// var tomorrow: Date { return calendar.date(byAdding: .day, value: 1, to: self) ?? Date() } /// SwifterSwift: UNIX timestamp from date. /// /// Date().unixTimestamp -> 1484233862.826291 /// var unixTimestamp: Double { return timeIntervalSince1970 } } // MARK: - Methods public extension Date { /// SwifterSwift: Date by adding multiples of calendar component. /// /// let date = Date() // "Jan 12, 2017, 7:07 PM" /// let date2 = date.adding(.minute, value: -10) // "Jan 12, 2017, 6:57 PM" /// let date3 = date.adding(.day, value: 4) // "Jan 16, 2017, 7:07 PM" /// let date4 = date.adding(.month, value: 2) // "Mar 12, 2017, 7:07 PM" /// let date5 = date.adding(.year, value: 13) // "Jan 12, 2030, 7:07 PM" /// /// - Parameters: /// - component: component type. /// - value: multiples of components to add. /// - Returns: original date + multiples of component added. func adding(_ component: Calendar.Component, value: Int) -> Date { return calendar.date(byAdding: component, value: value, to: self)! } /// SwifterSwift: Add calendar component to date. /// /// var date = Date() // "Jan 12, 2017, 7:07 PM" /// date.add(.minute, value: -10) // "Jan 12, 2017, 6:57 PM" /// date.add(.day, value: 4) // "Jan 16, 2017, 7:07 PM" /// date.add(.month, value: 2) // "Mar 12, 2017, 7:07 PM" /// date.add(.year, value: 13) // "Jan 12, 2030, 7:07 PM" /// /// - Parameters: /// - component: component type. /// - value: multiples of compnenet to add. mutating func add(_ component: Calendar.Component, value: Int) { if let date = calendar.date(byAdding: component, value: value, to: self) { self = date } } // swiftlint:disable cyclomatic_complexity function_body_length /// SwifterSwift: Date by changing value of calendar component. /// /// let date = Date() // "Jan 12, 2017, 7:07 PM" /// let date2 = date.changing(.minute, value: 10) // "Jan 12, 2017, 6:10 PM" /// let date3 = date.changing(.day, value: 4) // "Jan 4, 2017, 7:07 PM" /// let date4 = date.changing(.month, value: 2) // "Feb 12, 2017, 7:07 PM" /// let date5 = date.changing(.year, value: 2000) // "Jan 12, 2000, 7:07 PM" /// /// - Parameters: /// - component: component type. /// - value: new value of compnenet to change. /// - Returns: original date after changing given component to given value. func changing(_ component: Calendar.Component, value: Int) -> Date? { switch component { case .nanosecond: #if targetEnvironment(macCatalyst) // The `Calendar` implementation in `macCatalyst` does not know that a nanosecond is 1/1,000,000,000th of a second let allowedRange = 0..<1_000_000_000 #else let allowedRange = calendar.range(of: .nanosecond, in: .second, for: self)! #endif guard allowedRange.contains(value) else { return nil } let currentNanoseconds = calendar.component(.nanosecond, from: self) let nanosecondsToAdd = value - currentNanoseconds return calendar.date(byAdding: .nanosecond, value: nanosecondsToAdd, to: self) case .second: let allowedRange = calendar.range(of: .second, in: .minute, for: self)! guard allowedRange.contains(value) else { return nil } let currentSeconds = calendar.component(.second, from: self) let secondsToAdd = value - currentSeconds return calendar.date(byAdding: .second, value: secondsToAdd, to: self) case .minute: let allowedRange = calendar.range(of: .minute, in: .hour, for: self)! guard allowedRange.contains(value) else { return nil } let currentMinutes = calendar.component(.minute, from: self) let minutesToAdd = value - currentMinutes return calendar.date(byAdding: .minute, value: minutesToAdd, to: self) case .hour: let allowedRange = calendar.range(of: .hour, in: .day, for: self)! guard allowedRange.contains(value) else { return nil } let currentHour = calendar.component(.hour, from: self) let hoursToAdd = value - currentHour return calendar.date(byAdding: .hour, value: hoursToAdd, to: self) case .day: let allowedRange = calendar.range(of: .day, in: .month, for: self)! guard allowedRange.contains(value) else { return nil } let currentDay = calendar.component(.day, from: self) let daysToAdd = value - currentDay return calendar.date(byAdding: .day, value: daysToAdd, to: self) case .month: let allowedRange = calendar.range(of: .month, in: .year, for: self)! guard allowedRange.contains(value) else { return nil } let currentMonth = calendar.component(.month, from: self) let monthsToAdd = value - currentMonth return calendar.date(byAdding: .month, value: monthsToAdd, to: self) case .year: guard value > 0 else { return nil } let currentYear = calendar.component(.year, from: self) let yearsToAdd = value - currentYear return calendar.date(byAdding: .year, value: yearsToAdd, to: self) default: return calendar.date(bySetting: component, value: value, of: self) } } #if !os(Linux) // swiftlint:enable cyclomatic_complexity, function_body_length /// SwifterSwift: Data at the beginning of calendar component. /// /// let date = Date() // "Jan 12, 2017, 7:14 PM" /// let date2 = date.beginning(of: .hour) // "Jan 12, 2017, 7:00 PM" /// let date3 = date.beginning(of: .month) // "Jan 1, 2017, 12:00 AM" /// let date4 = date.beginning(of: .year) // "Jan 1, 2017, 12:00 AM" /// /// - Parameter component: calendar component to get date at the beginning of. /// - Returns: date at the beginning of calendar component (if applicable). func beginning(of component: Calendar.Component) -> Date? { if component == .day { return calendar.startOfDay(for: self) } var components: Set { switch component { case .second: return [.year, .month, .day, .hour, .minute, .second] case .minute: return [.year, .month, .day, .hour, .minute] case .hour: return [.year, .month, .day, .hour] case .weekOfYear, .weekOfMonth: return [.yearForWeekOfYear, .weekOfYear] case .month: return [.year, .month] case .year: return [.year] default: return [] } } guard !components.isEmpty else { return nil } return calendar.date(from: calendar.dateComponents(components, from: self)) } #endif // swiftlint:disable function_body_length /// SwifterSwift: Date at the end of calendar component. /// /// let date = Date() // "Jan 12, 2017, 7:27 PM" /// let date2 = date.end(of: .day) // "Jan 12, 2017, 11:59 PM" /// let date3 = date.end(of: .month) // "Jan 31, 2017, 11:59 PM" /// let date4 = date.end(of: .year) // "Dec 31, 2017, 11:59 PM" /// /// - Parameter component: calendar component to get date at the end of. /// - Returns: date at the end of calendar component (if applicable). func end(of component: Calendar.Component) -> Date? { switch component { case .second: var date = adding(.second, value: 1) date = calendar.date(from: calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date))! date.add(.second, value: -1) return date case .minute: var date = adding(.minute, value: 1) let after = calendar.date(from: calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date))! date = after.adding(.second, value: -1) return date case .hour: var date = adding(.hour, value: 1) let after = calendar.date(from: calendar.dateComponents([.year, .month, .day, .hour], from: date))! date = after.adding(.second, value: -1) return date case .day: var date = adding(.day, value: 1) date = calendar.startOfDay(for: date) date.add(.second, value: -1) return date case .weekOfYear, .weekOfMonth: var date = self let beginningOfWeek = calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: date))! date = beginningOfWeek.adding(.day, value: 7).adding(.second, value: -1) return date case .month: var date = adding(.month, value: 1) let after = calendar.date(from: calendar.dateComponents([.year, .month], from: date))! date = after.adding(.second, value: -1) return date case .year: var date = adding(.year, value: 1) let after = calendar.date(from: calendar.dateComponents([.year], from: date))! date = after.adding(.second, value: -1) return date default: return nil } } // swiftlint:enable function_body_length /// SwifterSwift: Check if date is in current given calendar component. /// /// Date().isInCurrent(.day) -> true /// Date().isInCurrent(.year) -> true /// /// - Parameter component: calendar component to check. /// - Returns: true if date is in current given calendar component. func isInCurrent(_ component: Calendar.Component) -> Bool { return calendar.isDate(self, equalTo: Date(), toGranularity: component) } /// SwifterSwift: Date string from date. /// /// Date().string(withFormat: "dd/MM/yyyy") -> "1/12/17" /// Date().string(withFormat: "HH:mm") -> "23:50" /// Date().string(withFormat: "dd/MM/yyyy HH:mm") -> "1/12/17 23:50" /// /// - Parameter format: Date format (default is "dd/MM/yyyy"). /// - Returns: date string. func string(withFormat format: String = "dd/MM/yyyy HH:mm") -> String { let dateFormatter = DateFormatter() dateFormatter.dateFormat = format return dateFormatter.string(from: self) } /// SwifterSwift: Date string from date. /// /// Date().dateString(ofStyle: .short) -> "1/12/17" /// Date().dateString(ofStyle: .medium) -> "Jan 12, 2017" /// Date().dateString(ofStyle: .long) -> "January 12, 2017" /// Date().dateString(ofStyle: .full) -> "Thursday, January 12, 2017" /// /// - Parameter style: DateFormatter style (default is .medium). /// - Returns: date string. func dateString(ofStyle style: DateFormatter.Style = .medium) -> String { let dateFormatter = DateFormatter() dateFormatter.timeStyle = .none dateFormatter.dateStyle = style return dateFormatter.string(from: self) } /// SwifterSwift: Date and time string from date. /// /// Date().dateTimeString(ofStyle: .short) -> "1/12/17, 7:32 PM" /// Date().dateTimeString(ofStyle: .medium) -> "Jan 12, 2017, 7:32:00 PM" /// Date().dateTimeString(ofStyle: .long) -> "January 12, 2017 at 7:32:00 PM GMT+3" /// Date().dateTimeString(ofStyle: .full) -> "Thursday, January 12, 2017 at 7:32:00 PM GMT+03:00" /// /// - Parameter style: DateFormatter style (default is .medium). /// - Returns: date and time string. func dateTimeString(ofStyle style: DateFormatter.Style = .medium) -> String { let dateFormatter = DateFormatter() dateFormatter.timeStyle = style dateFormatter.dateStyle = style return dateFormatter.string(from: self) } /// SwifterSwift: Time string from date /// /// Date().timeString(ofStyle: .short) -> "7:37 PM" /// Date().timeString(ofStyle: .medium) -> "7:37:02 PM" /// Date().timeString(ofStyle: .long) -> "7:37:02 PM GMT+3" /// Date().timeString(ofStyle: .full) -> "7:37:02 PM GMT+03:00" /// /// - Parameter style: DateFormatter style (default is .medium). /// - Returns: time string. func timeString(ofStyle style: DateFormatter.Style = .medium) -> String { let dateFormatter = DateFormatter() dateFormatter.timeStyle = style dateFormatter.dateStyle = .none return dateFormatter.string(from: self) } /// SwifterSwift: Day name from date. /// /// Date().dayName(ofStyle: .oneLetter) -> "T" /// Date().dayName(ofStyle: .threeLetters) -> "Thu" /// Date().dayName(ofStyle: .full) -> "Thursday" /// /// - Parameter Style: style of day name (default is DayNameStyle.full). /// - Returns: day name string (example: W, Wed, Wednesday). func dayName(ofStyle style: DayNameStyle = .full) -> String { // http://www.codingexplorer.com/swiftly-getting-human-readable-date-nsdateformatter/ let dateFormatter = DateFormatter() var format: String { switch style { case .oneLetter: return "EEEEE" case .threeLetters: return "EEE" case .full: return "EEEE" } } dateFormatter.setLocalizedDateFormatFromTemplate(format) return dateFormatter.string(from: self) } /// SwifterSwift: Month name from date. /// /// Date().monthName(ofStyle: .oneLetter) -> "J" /// Date().monthName(ofStyle: .threeLetters) -> "Jan" /// Date().monthName(ofStyle: .full) -> "January" /// /// - Parameter Style: style of month name (default is MonthNameStyle.full). /// - Returns: month name string (example: D, Dec, December). func monthName(ofStyle style: MonthNameStyle = .full) -> String { // http://www.codingexplorer.com/swiftly-getting-human-readable-date-nsdateformatter/ let dateFormatter = DateFormatter() var format: String { switch style { case .oneLetter: return "MMMMM" case .threeLetters: return "MMM" case .full: return "MMMM" } } dateFormatter.setLocalizedDateFormatFromTemplate(format) return dateFormatter.string(from: self) } /// SwifterSwift: get number of seconds between two date /// /// - Parameter date: date to compate self to. /// - Returns: number of seconds between self and given date. func secondsSince(_ date: Date) -> Double { return timeIntervalSince(date) } /// SwifterSwift: get number of minutes between two date /// /// - Parameter date: date to compate self to. /// - Returns: number of minutes between self and given date. func minutesSince(_ date: Date) -> Double { return timeIntervalSince(date)/60 } /// SwifterSwift: get number of hours between two date /// /// - Parameter date: date to compate self to. /// - Returns: number of hours between self and given date. func hoursSince(_ date: Date) -> Double { return timeIntervalSince(date)/3600 } /// SwifterSwift: get number of days between two date /// /// - Parameter date: date to compate self to. /// - Returns: number of days between self and given date. func daysSince(_ date: Date) -> Double { return timeIntervalSince(date)/(3600*24) } /// SwifterSwift: check if a date is between two other dates /// /// - Parameters: /// - startDate: start date to compare self to. /// - endDate: endDate date to compare self to. /// - includeBounds: true if the start and end date should be included (default is false) /// - Returns: true if the date is between the two given dates. func isBetween(_ startDate: Date, _ endDate: Date, includeBounds: Bool = false) -> Bool { if includeBounds { return startDate.compare(self).rawValue * compare(endDate).rawValue >= 0 } return startDate.compare(self).rawValue * compare(endDate).rawValue > 0 } /// SwifterSwift: check if a date is a number of date components of another date /// /// - Parameters: /// - value: number of times component is used in creating range /// - component: Calendar.Component to use. /// - date: Date to compare self to. /// - Returns: true if the date is within a number of components of another date func isWithin(_ value: UInt, _ component: Calendar.Component, of date: Date) -> Bool { let components = calendar.dateComponents([component], from: self, to: date) let componentValue = components.value(for: component)! return abs(componentValue) <= value } /// SwifterSwift: Returns a random date within the specified range. /// /// - Parameter range: The range in which to create a random date. `range` must not be empty. /// - Returns: A random date within the bounds of `range`. static func random(in range: Range) -> Date { return Date(timeIntervalSinceReferenceDate: TimeInterval.random(in: range.lowerBound.timeIntervalSinceReferenceDate..) -> Date { return Date(timeIntervalSinceReferenceDate: TimeInterval.random(in: range.lowerBound.timeIntervalSinceReferenceDate...range.upperBound.timeIntervalSinceReferenceDate)) } /// SwifterSwift: Returns a random date within the specified range, using the given generator as a source for randomness. /// /// - Parameters: /// - range: The range in which to create a random date. `range` must not be empty. /// - generator: The random number generator to use when creating the new random date. /// - Returns: A random date within the bounds of `range`. static func random(in range: Range, using generator: inout T) -> Date where T: RandomNumberGenerator { return Date(timeIntervalSinceReferenceDate: TimeInterval.random(in: range.lowerBound.timeIntervalSinceReferenceDate..(in range: ClosedRange, using generator: inout T) -> Date where T: RandomNumberGenerator { return Date(timeIntervalSinceReferenceDate: TimeInterval.random(in: range.lowerBound.timeIntervalSinceReferenceDate...range.upperBound.timeIntervalSinceReferenceDate, using: &generator)) } } // MARK: - Initializers public extension Date { /// SwifterSwift: Create a new date form calendar components. /// /// let date = Date(year: 2010, month: 1, day: 12) // "Jan 12, 2010, 7:45 PM" /// /// - Parameters: /// - calendar: Calendar (default is current). /// - timeZone: TimeZone (default is current). /// - era: Era (default is current era). /// - year: Year (default is current year). /// - month: Month (default is current month). /// - day: Day (default is today). /// - hour: Hour (default is current hour). /// - minute: Minute (default is current minute). /// - second: Second (default is current second). /// - nanosecond: Nanosecond (default is current nanosecond). init?( calendar: Calendar? = Calendar.current, timeZone: TimeZone? = NSTimeZone.default, era: Int? = Date().era, year: Int? = Date().year, month: Int? = Date().month, day: Int? = Date().day, hour: Int? = Date().hour, minute: Int? = Date().minute, second: Int? = Date().second, nanosecond: Int? = Date().nanosecond) { var components = DateComponents() components.calendar = calendar components.timeZone = timeZone components.era = era components.year = year components.month = month components.day = day components.hour = hour components.minute = minute components.second = second components.nanosecond = nanosecond guard let date = calendar?.date(from: components) else { return nil } self = date } /// SwifterSwift: Create date object from ISO8601 string. /// /// let date = Date(iso8601String: "2017-01-12T16:48:00.959Z") // "Jan 12, 2017, 7:48 PM" /// /// - Parameter iso8601String: ISO8601 string of format (yyyy-MM-dd'T'HH:mm:ss.SSSZ). init?(iso8601String: String) { // https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US_POSIX") dateFormatter.timeZone = TimeZone.current dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" guard let date = dateFormatter.date(from: iso8601String) else { return nil } self = date } /// SwifterSwift: Create new date object from UNIX timestamp. /// /// let date = Date(unixTimestamp: 1484239783.922743) // "Jan 12, 2017, 7:49 PM" /// /// - Parameter unixTimestamp: UNIX timestamp. init(unixTimestamp: Double) { self.init(timeIntervalSince1970: unixTimestamp) } /// SwifterSwift: Create date object from Int literal /// /// let date = Date(integerLiteral: 2017_12_25) // "2017-12-25 00:00:00 +0000" /// - Parameter value: Int value, e.g. 20171225, or 2017_12_25 etc. init?(integerLiteral value: Int) { let formatter = DateFormatter() formatter.dateFormat = "yyyyMMdd" guard let date = formatter.date(from: String(value)) else { return nil } self = date } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/FileManagerExtensions.swift ================================================ // // FileManagerExtensions.swift // SwifterSwift // // Created by Jason Jon E. Carreos on 05/02/2018. // Copyright © 2018 SwifterSwift // #if canImport(Foundation) import Foundation public extension FileManager { /// SwifterSwift: Read from a JSON file at a given path. /// /// - Parameters: /// - path: JSON file path. /// - readingOptions: JSONSerialization reading options. /// - Returns: Optional dictionary. /// - Throws: Throws any errors thrown by Data creation or JSON serialization. func jsonFromFile( atPath path: String, readingOptions: JSONSerialization.ReadingOptions = .allowFragments) throws -> [String: Any]? { let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) let json = try JSONSerialization.jsonObject(with: data, options: readingOptions) return json as? [String: Any] } #if !os(Linux) /// SwifterSwift: Read from a JSON file with a given filename. /// /// - Parameters: /// - filename: File to read. /// - bundleClass: Bundle where the file is associated. /// - readingOptions: JSONSerialization reading options. /// - Returns: Optional dictionary. /// - Throws: Throws any errors thrown by Data creation or JSON serialization. func jsonFromFile( withFilename filename: String, at bundleClass: AnyClass? = nil, readingOptions: JSONSerialization.ReadingOptions = .allowFragments) throws -> [String: Any]? { // https://stackoverflow.com/questions/24410881/reading-in-a-json-file-using-swift // To handle cases that provided filename has an extension let name = filename.components(separatedBy: ".")[0] let bundle = bundleClass != nil ? Bundle(for: bundleClass!) : Bundle.main if let path = bundle.path(forResource: name, ofType: "json") { let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) let json = try JSONSerialization.jsonObject(with: data, options: readingOptions) return json as? [String: Any] } return nil } #endif /// SwifterSwift: Creates a unique directory for saving temporary files. The directory can be used to create multiple temporary files used for a common purpose. /// /// let tempDirectory = try fileManager.createTemporaryDirectory() /// let tempFile1URL = tempDirectory.appendingPathComponent(ProcessInfo().globallyUniqueString) /// let tempFile2URL = tempDirectory.appendingPathComponent(ProcessInfo().globallyUniqueString) /// /// - Returns: A URL to a new directory for saving temporary files. /// - Throws: An error if a temporary directory cannot be found or created. func createTemporaryDirectory() throws -> URL { #if !os(Linux) let temporaryDirectoryURL: URL if #available(OSX 10.12, tvOS 10.0, watchOS 3.0, *) { temporaryDirectoryURL = temporaryDirectory } else { temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) } return try url(for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: temporaryDirectoryURL, create: true) #else let envs = ProcessInfo.processInfo.environment let env = envs["TMPDIR"] ?? envs["TEMP"] ?? envs["TMP"] ?? "/tmp" let dir = "/\(env)/file-temp.XXXXXX" var template = [UInt8](dir.utf8).map({ Int8($0) }) + [Int8(0)] guard mkdtemp(&template) != nil else { throw CocoaError.error(.featureUnsupported) } return URL(fileURLWithPath: String(cString: template)) #endif } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/LocaleExtensions.swift ================================================ // // LocalExtensions.swift // SwifterSwift // // Created by Basem Emara on 4/19/17. // Copyright © 2017 SwifterSwift // #if canImport(Foundation) import Foundation // MARK: - Properties public extension Locale { /// SwifterSwift: UNIX representation of locale usually used for normalizing. static var posix: Locale { return Locale(identifier: "en_US_POSIX") } /// SwifterSwift: Returns bool value indicating if locale has 12h format. var is12HourTimeFormat: Bool { let dateFormatter = DateFormatter() dateFormatter.timeStyle = .short dateFormatter.dateStyle = .none dateFormatter.locale = self let dateString = dateFormatter.string(from: Date()) return dateString.contains(dateFormatter.amSymbol) || dateString.contains(dateFormatter.pmSymbol) } } // MARK: - Functions public extension Locale { /// SwifterSwift: Get the flag emoji for a given country region code. /// - Parameter isoRegionCode: The IOS region code. /// /// Adapted from https://stackoverflow.com/a/30403199/1627511 static func flagEmoji(forRegionCode isoRegionCode: String) -> String? { #if !os(Linux) guard isoRegionCodes.contains(isoRegionCode) else { return nil } #endif return isoRegionCode.unicodeScalars.reduce(into: String()) { guard let flagScalar = UnicodeScalar(UInt32(127397) + $1.value) else { return } $0.unicodeScalars.append(flagScalar) } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/NSAttributedStringExtensions.swift ================================================ // // NSAttributedStringExtensions.swift // SwifterSwift // // Created by Omar Albeik on 26/11/2016. // Copyright © 2016 SwifterSwift // #if canImport(Foundation) import Foundation #if canImport(UIKit) import UIKit #endif #if canImport(AppKit) import AppKit #endif // MARK: - Properties public extension NSAttributedString { #if os(iOS) /// SwifterSwift: Bolded string. var bolded: NSAttributedString { return applying(attributes: [.font: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)]) } #endif #if !os(Linux) /// SwifterSwift: Underlined string. var underlined: NSAttributedString { return applying(attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue]) } #endif #if os(iOS) /// SwifterSwift: Italicized string. var italicized: NSAttributedString { return applying(attributes: [.font: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)]) } #endif #if !os(Linux) /// SwifterSwift: Struckthrough string. var struckthrough: NSAttributedString { return applying(attributes: [.strikethroughStyle: NSNumber(value: NSUnderlineStyle.single.rawValue as Int)]) } #endif #if !os(Linux) /// SwifterSwift: Dictionary of the attributes applied across the whole string var attributes: [NSAttributedString.Key: Any] { guard self.length > 0 else { return [:] } return attributes(at: 0, effectiveRange: nil) } #endif } // MARK: - Methods public extension NSAttributedString { #if !os(Linux) /// SwifterSwift: Applies given attributes to the new instance of NSAttributedString initialized with self object /// /// - Parameter attributes: Dictionary of attributes /// - Returns: NSAttributedString with applied attributes fileprivate func applying(attributes: [NSAttributedString.Key: Any]) -> NSAttributedString { let copy = NSMutableAttributedString(attributedString: self) let range = (string as NSString).range(of: string) copy.addAttributes(attributes, range: range) return copy } #endif #if canImport(AppKit) || canImport(UIKit) /// SwifterSwift: Add color to NSAttributedString. /// /// - Parameter color: text color. /// - Returns: a NSAttributedString colored with given color. func colored(with color: Color) -> NSAttributedString { return applying(attributes: [.foregroundColor: color]) } #endif #if !os(Linux) /// SwifterSwift: Apply attributes to substrings matching a regular expression /// /// - Parameters: /// - attributes: Dictionary of attributes /// - pattern: a regular expression to target /// - options: The regular expression options that are applied to the expression during matching. See NSRegularExpression.Options for possible values. /// - Returns: An NSAttributedString with attributes applied to substrings matching the pattern func applying(attributes: [NSAttributedString.Key: Any], toRangesMatching pattern: String, options: NSRegularExpression.Options = []) -> NSAttributedString { guard let pattern = try? NSRegularExpression(pattern: pattern, options: options) else { return self } let matches = pattern.matches(in: string, options: [], range: NSRange(0..(attributes: [NSAttributedString.Key: Any], toOccurrencesOf target: T) -> NSAttributedString { let pattern = "\\Q\(target)\\E" return applying(attributes: attributes, toRangesMatching: pattern) } #endif } // MARK: - Operators public extension NSAttributedString { /// SwifterSwift: Add a NSAttributedString to another NSAttributedString. /// /// - Parameters: /// - lhs: NSAttributedString to add to. /// - rhs: NSAttributedString to add. static func += (lhs: inout NSAttributedString, rhs: NSAttributedString) { let string = NSMutableAttributedString(attributedString: lhs) string.append(rhs) lhs = string } /// SwifterSwift: Add a NSAttributedString to another NSAttributedString and return a new NSAttributedString instance. /// /// - Parameters: /// - lhs: NSAttributedString to add. /// - rhs: NSAttributedString to add. /// - Returns: New instance with added NSAttributedString. static func + (lhs: NSAttributedString, rhs: NSAttributedString) -> NSAttributedString { let string = NSMutableAttributedString(attributedString: lhs) string.append(rhs) return NSAttributedString(attributedString: string) } /// SwifterSwift: Add a NSAttributedString to another NSAttributedString. /// /// - Parameters: /// - lhs: NSAttributedString to add to. /// - rhs: String to add. static func += (lhs: inout NSAttributedString, rhs: String) { lhs += NSAttributedString(string: rhs) } /// SwifterSwift: Add a NSAttributedString to another NSAttributedString and return a new NSAttributedString instance. /// /// - Parameters: /// - lhs: NSAttributedString to add. /// - rhs: String to add. /// - Returns: New instance with added NSAttributedString. static func + (lhs: NSAttributedString, rhs: String) -> NSAttributedString { return lhs + NSAttributedString(string: rhs) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/NSPredicateExtensions.swift ================================================ // // NSPredicateExtensions.swift // SwifterSwift // // Created by Max Härtwig on 04.10.17. // Copyright © 2017 SwifterSwift // #if canImport(Foundation) import Foundation // MARK: - Properties public extension NSPredicate { /// SwifterSwift: Returns a new predicate formed by NOT-ing the predicate. var not: NSCompoundPredicate { return NSCompoundPredicate(notPredicateWithSubpredicate: self) } } // MARK: - Methods public extension NSPredicate { /// SwifterSwift: Returns a new predicate formed by AND-ing the argument to the predicate. /// /// - Parameter predicate: NSPredicate /// - Returns: NSCompoundPredicate func and(_ predicate: NSPredicate) -> NSCompoundPredicate { return NSCompoundPredicate(andPredicateWithSubpredicates: [self, predicate]) } /// SwifterSwift: Returns a new predicate formed by OR-ing the argument to the predicate. /// /// - Parameter predicate: NSPredicate /// - Returns: NSCompoundPredicate func or(_ predicate: NSPredicate) -> NSCompoundPredicate { return NSCompoundPredicate(orPredicateWithSubpredicates: [self, predicate]) } } // MARK: - Operators public extension NSPredicate { /// SwifterSwift: Returns a new predicate formed by NOT-ing the predicate. /// - Parameters: rhs: NSPredicate to convert. /// - Returns: NSCompoundPredicate static prefix func ! (rhs: NSPredicate) -> NSCompoundPredicate { return rhs.not } /// SwifterSwift: Returns a new predicate formed by AND-ing the argument to the predicate. /// /// - Parameters: /// - lhs: NSPredicate. /// - rhs: NSPredicate. /// - Returns: NSCompoundPredicate static func + (lhs: NSPredicate, rhs: NSPredicate) -> NSCompoundPredicate { return lhs.and(rhs) } /// SwifterSwift: Returns a new predicate formed by OR-ing the argument to the predicate. /// /// - Parameters: /// - lhs: NSPredicate. /// - rhs: NSPredicate. /// - Returns: NSCompoundPredicate static func | (lhs: NSPredicate, rhs: NSPredicate) -> NSCompoundPredicate { return lhs.or(rhs) } /// SwifterSwift: Returns a new predicate formed by remove the argument to the predicate. /// /// - Parameters: /// - lhs: NSPredicate. /// - rhs: NSPredicate. /// - Returns: NSCompoundPredicate static func - (lhs: NSPredicate, rhs: NSPredicate) -> NSCompoundPredicate { return lhs + !rhs } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/NSRegularExpressionExtensions.swift ================================================ // // NSRegularExpressionExtensions.swift // SwifterSwift // // Created by Guy Kogus on 09/10/2019. // Copyright © 2019 SwifterSwift // #if canImport(Foundation) import Foundation public extension NSRegularExpression { /// SwifterSwift: Enumerates the string allowing the Block to handle each regular expression match. /// /// - Parameters: /// - string: The string. /// - options: The matching options to report. See `NSRegularExpression.MatchingOptions` for the supported values. /// - range: The range of the string to test. /// - block: The Block enumerates the matches of the regular expression in the string. /// The block takes three arguments and returns `Void`: /// - result: /// An `NSTextCheckingResult` specifying the match. This result gives the overall matched range via its `range` property, and the range of each individual capture group via its `range(at:)` method. The range {NSNotFound, 0} is returned if one of the capture groups did not participate in this particular match. /// - flags: /// The current state of the matching progress. See `NSRegularExpression.MatchingFlags` for the possible values. /// - stop: /// A reference to a Boolean value. The Block can set the value to true to stop further processing of the array. The stop argument is an out-only argument. You should only ever set this Boolean to true within the Block. #if os(Linux) func enumerateMatches(in string: String, options: MatchingOptions = [], range: Range, using block: @escaping (_ result: NSTextCheckingResult?, _ flags: MatchingFlags, _ stop: inout Bool) -> Void) { enumerateMatches(in: string, options: options, range: NSRange(range, in: string)) { result, flags, stop in var shouldStop = false block(result, flags, &shouldStop) if shouldStop { stop.pointee = true } } } #else func enumerateMatches(in string: String, options: MatchingOptions = [], range: Range, using block: (_ result: NSTextCheckingResult?, _ flags: MatchingFlags, _ stop: inout Bool) -> Void) { enumerateMatches(in: string, options: options, range: NSRange(range, in: string)) { result, flags, stop in var shouldStop = false block(result, flags, &shouldStop) if shouldStop { stop.pointee = true } } } #endif /// SwifterSwift: Returns an array containing all the matches of the regular expression in the string. /// /// - Parameters: /// - string: The string to search. /// - options: The matching options to use. See NSRegularExpression.MatchingOptions for possible values. /// - range: The range of the string to search. /// - Returns: An array of `NSTextCheckingResult` objects. Each result gives the overall matched range via its `range` property, and the range of each individual capture group via its `range(at:)` method. The range {NSNotFound, 0} is returned if one of the capture groups did not participate in this particular match. func matches(in string: String, options: MatchingOptions = [], range: Range) -> [NSTextCheckingResult] { return matches(in: string, options: options, range: NSRange(range, in: string)) } /// SwifterSwift: Returns the number of matches of the regular expression within the specified range of the string. /// /// - Parameters: /// - string: The string to search. /// - options: The matching options to use. See NSRegularExpression.MatchingOptions for possible values. /// - range: The range of the string to search. /// - Returns: The number of matches of the regular expression. func numberOfMatches(in string: String, options: MatchingOptions = [], range: Range) -> Int { return numberOfMatches(in: string, options: options, range: NSRange(range, in: string)) } /// SwifterSwift: Returns the first match of the regular expression within the specified range of the string. /// /// - Parameters: /// - string: The string to search. /// - options: The matching options to use. See `NSRegularExpression.MatchingOptions` for possible values. /// - range: The range of the string to search. /// - Returns: An `NSTextCheckingResult` object. This result gives the overall matched range via its `range` property, and the range of each individual capture group via its `range(at:)` method. The range {NSNotFound, 0} is returned if one of the capture groups did not participate in this particular match. func firstMatch(in string: String, options: MatchingOptions = [], range: Range) -> NSTextCheckingResult? { return firstMatch(in: string, options: options, range: NSRange(range, in: string)) } /// SwifterSwift: Returns the range of the first match of the regular expression within the specified range of the string. /// /// - Parameters: /// - string: The string to search. /// - options: The matching options to use. See `NSRegularExpression.MatchingOptions` for possible values. /// - range: The range of the string to search. /// - Returns: The range of the first match. Returns `nil` if no match is found. func rangeOfFirstMatch(in string: String, options: MatchingOptions = [], range: Range) -> Range? { return Range(rangeOfFirstMatch(in: string, options: options, range: NSRange(range, in: string)), in: string) } /// SwifterSwift: Returns a new string containing matching regular expressions replaced with the template string. /// /// - Parameters: /// - string: The string to search for values within. /// - options: The matching options to use. See `NSRegularExpression.MatchingOptions` for possible values. /// - range: The range of the string to search. /// - templ: The substitution template used when replacing matching instances. /// - Returns: A string with matching regular expressions replaced by the template string. func stringByReplacingMatches(in string: String, options: MatchingOptions = [], range: Range, withTemplate templ: String) -> String { return stringByReplacingMatches(in: string, options: options, range: NSRange(range, in: string), withTemplate: templ) } /// SwifterSwift: Replaces regular expression matches within the mutable string using the template string. /// /// - Parameters: /// - string: The mutable string to search and replace values within. /// - options: The matching options to use. See `NSRegularExpression.MatchingOptions` for possible values. /// - range: The range of the string to search. /// - templ: The substitution template used when replacing matching instances. /// - Returns: The number of matches. @discardableResult func replaceMatches(in string: inout String, options: MatchingOptions = [], range: Range, withTemplate templ: String) -> Int { let mutableString = NSMutableString(string: string) let matches = replaceMatches(in: mutableString, options: options, range: NSRange(range, in: string), withTemplate: templ) string = mutableString.copy() as! String // swiftlint:disable:this force_cast return matches } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/NotificationCenterExtensions.swift ================================================ // // NotificationCenterExtensions.swift // SwifterSwift // // Created by Guy Kogus on 6/3/20. // Copyright © 2020 SwifterSwift // #if canImport(Foundation) import Foundation public extension NotificationCenter { /// SwifterSwift: Adds a one-time entry to the notification center's dispatch table that includes a notification queue and a block to add to the queue, and an optional notification name and sender. /// - Parameters: /// - name: The name of the notification for which to register the observer; that is, only notifications with this name are used to add the block to the operation queue. /// /// If you pass `nil`, the notification center doesn’t use a notification’s name to decide whether to add the block to the operation queue. /// - obj: The object whose notifications the observer wants to receive; that is, only notifications sent by this sender are delivered to the observer. /// /// If you pass `nil`, the notification center doesn’t use a notification’s sender to decide whether to deliver it to the observer. /// - queue: The operation queue to which block should be added. /// /// If you pass `nil`, the block is run synchronously on the posting thread. /// - block: The block to be executed when the notification is received. /// /// The block is copied by the notification center and (the copy) held until the observer registration is removed. /// /// The block takes one argument: /// - notification: The notification. func observeOnce(forName name: NSNotification.Name?, object obj: Any? = nil, queue: OperationQueue? = nil, using block: @escaping (_ notification: Notification) -> Void) { var handler: NSObjectProtocol! handler = addObserver(forName: name, object: obj, queue: queue) { [unowned self] in self.removeObserver(handler!) block($0) } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/URLExtensions.swift ================================================ // // URLExtensions.swift // SwifterSwift // // Created by Omar Albeik on 03/02/2017. // Copyright © 2017 SwifterSwift // #if canImport(Foundation) import Foundation #if canImport(UIKit) && canImport(AVFoundation) import UIKit import AVFoundation #endif // MARK: - Properties public extension URL { /// SwifterSwift: Dictionary of the URL's query parameters var queryParameters: [String: String]? { guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false), let queryItems = components.queryItems else { return nil } var items: [String: String] = [:] for queryItem in queryItems { items[queryItem.name] = queryItem.value } return items } } // MARK: - Initializers public extension URL { /// SwifterSwift: Initializes an `URL` object with a base URL and a relative string. If `string` was malformed, returns `nil`. /// - Parameters: /// - string: The URL string with which to initialize the `URL` object. Must conform to RFC 2396. `string` is interpreted relative to `url`. /// - url: The base URL for the `URL` object. init?(string: String?, relativeTo url: URL? = nil) { guard let string = string else { return nil } self.init(string: string, relativeTo: url) } } // MARK: - Methods public extension URL { /// SwifterSwift: URL with appending query parameters. /// /// let url = URL(string: "https://google.com")! /// let param = ["q": "Swifter Swift"] /// url.appendingQueryParameters(params) -> "https://google.com?q=Swifter%20Swift" /// /// - Parameter parameters: parameters dictionary. /// - Returns: URL with appending given query parameters. func appendingQueryParameters(_ parameters: [String: String]) -> URL { var urlComponents = URLComponents(url: self, resolvingAgainstBaseURL: true)! var items = urlComponents.queryItems ?? [] items += parameters.map({ URLQueryItem(name: $0, value: $1) }) urlComponents.queryItems = items return urlComponents.url! } /// SwifterSwift: Append query parameters to URL. /// /// var url = URL(string: "https://google.com")! /// let param = ["q": "Swifter Swift"] /// url.appendQueryParameters(params) /// print(url) // prints "https://google.com?q=Swifter%20Swift" /// /// - Parameter parameters: parameters dictionary. mutating func appendQueryParameters(_ parameters: [String: String]) { self = appendingQueryParameters(parameters) } /// SwifterSwift: Get value of a query key. /// /// var url = URL(string: "https://google.com?code=12345")! /// queryValue(for: "code") -> "12345" /// /// - Parameter key: The key of a query value. func queryValue(for key: String) -> String? { return URLComponents(string: absoluteString)? .queryItems? .first(where: { $0.name == key })? .value } /// SwifterSwift: Returns a new URL by removing all the path components. /// /// let url = URL(string: "https://domain.com/path/other")! /// print(url.deletingAllPathComponents()) // prints "https://domain.com/" /// /// - Returns: URL with all path components removed. func deletingAllPathComponents() -> URL { var url: URL = self for _ in 0.. URL? { if let scheme = scheme { let droppedScheme = String(absoluteString.dropFirst(scheme.count + 3)) return URL(string: droppedScheme) } guard host != nil else { return self } let droppedScheme = String(absoluteString.dropFirst(2)) return URL(string: droppedScheme) } } // MARK: - Methods public extension URL { #if os(iOS) || os(tvOS) /// SwifterSwift: Generate a thumbnail image from given url. Returns nil if no thumbnail could be created. This function may take some time to complete. It's recommended to dispatch the call if the thumbnail is not generated from a local resource. /// /// var url = URL(string: "https://video.golem.de/files/1/1/20637/wrkw0718-sd.mp4")! /// var thumbnail = url.thumbnail() /// thumbnail = url.thumbnail(fromTime: 5) /// /// DisptachQueue.main.async { /// someImageView.image = url.thumbnail() /// } /// /// - Parameter time: Seconds into the video where the image should be generated. /// - Returns: The UIImage result of the AVAssetImageGenerator func thumbnail(fromTime time: Float64 = 0) -> UIImage? { let imageGenerator = AVAssetImageGenerator(asset: AVAsset(url: self)) let time = CMTimeMakeWithSeconds(time, preferredTimescale: 1) var actualTime = CMTimeMake(value: 0, timescale: 0) guard let cgImage = try? imageGenerator.copyCGImage(at: time, actualTime: &actualTime) else { return nil } return UIImage(cgImage: cgImage) } #endif } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/URLRequestExtensions.swift ================================================ // // URLRequestExtensions.swift // SwifterSwift // // Created by Omar Albeik on 9/5/17. // Copyright © 2017 SwifterSwift // #if canImport(Foundation) import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif // MARK: - Initializers public extension URLRequest { /// SwifterSwift: Create URLRequest from URL string. /// /// - Parameter urlString: URL string to initialize URL request from init?(urlString: String) { guard let url = URL(string: urlString) else { return nil } self.init(url: url) } /// SwifterSwift: cURL command representation of this URL request. var curlString: String { guard let url = url else { return "" } var baseCommand = "curl \(url.absoluteString)" if httpMethod == "HEAD" { baseCommand += " --head" } var command = [baseCommand] if let method = httpMethod, method != "GET" && method != "HEAD" { command.append("-X \(method)") } if let headers = allHTTPHeaderFields { for (key, value) in headers where key != "Cookie" { command.append("-H '\(key): \(value)'") } } if let data = httpBody, let body = String(data: data, encoding: .utf8) { command.append("-d '\(body)'") } return command.joined(separator: " \\\n\t") } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Foundation/UserDefaultsExtensions.swift ================================================ // // UserDefaultsExtensions.swift // SwifterSwift // // Created by Omar Albeik on 9/5/17. // Copyright © 2017 SwifterSwift // #if canImport(Foundation) && !os(Linux) import Foundation // MARK: - Methods public extension UserDefaults { /// SwifterSwift: get object from UserDefaults by using subscript /// /// - Parameter key: key in the current user's defaults database. subscript(key: String) -> Any? { get { return object(forKey: key) } set { set(newValue, forKey: key) } } /// SwifterSwift: Float from UserDefaults. /// /// - Parameter forKey: key to find float for. /// - Returns: Float object for key (if exists). func float(forKey key: String) -> Float? { return object(forKey: key) as? Float } /// SwifterSwift: Date from UserDefaults. /// /// - Parameter forKey: key to find date for. /// - Returns: Date object for key (if exists). func date(forKey key: String) -> Date? { return object(forKey: key) as? Date } /// SwifterSwift: Retrieves a Codable object from UserDefaults. /// /// - Parameters: /// - type: Class that conforms to the Codable protocol. /// - key: Identifier of the object. /// - decoder: Custom JSONDecoder instance. Defaults to `JSONDecoder()`. /// - Returns: Codable object for key (if exists). func object(_ type: T.Type, with key: String, usingDecoder decoder: JSONDecoder = JSONDecoder()) -> T? { guard let data = value(forKey: key) as? Data else { return nil } return try? decoder.decode(type.self, from: data) } /// SwifterSwift: Allows storing of Codable objects to UserDefaults. /// /// - Parameters: /// - object: Codable object to store. /// - key: Identifier of the object. /// - encoder: Custom JSONEncoder instance. Defaults to `JSONEncoder()`. func set(object: T, forKey key: String, usingEncoder encoder: JSONEncoder = JSONEncoder()) { let data = try? encoder.encode(object) set(data, forKey: key) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/MapKit/MKMapViewExtensions.swift ================================================ // // MKMapViewExtensions.swift // SwifterSwift // // Created by Hannes Staffler on 24.01.19. // Copyright © 2019 SwifterSwift // #if canImport(MapKit) import MapKit #if !os(watchOS) @available(tvOS 9.2, *) public extension MKMapView { /// SwifterSwift: Dequeue reusable MKAnnotationView using class type /// /// - Parameters: /// - name: MKAnnotationView type. /// - Returns: optional MKAnnotationView object. func dequeueReusableAnnotationView(withClass name: T.Type) -> T? { return dequeueReusableAnnotationView(withIdentifier: String(describing: name)) as? T } /// SwifterSwift: Register MKAnnotationView using class type /// /// - Parameter name: MKAnnotationView type. @available(iOS 11.0, tvOS 11.0, macOS 10.13, *) func register(annotationViewWithClass name: T.Type) { register(T.self, forAnnotationViewWithReuseIdentifier: String(describing: name)) } /// SwifterSwift: Dequeue reusable MKAnnotationView using class type /// /// - Parameters: /// - name: MKAnnotationView type. /// - annotation: annotation of the mapView. /// - Returns: optional MKAnnotationView object. @available(iOS 11.0, tvOS 11.0, macOS 10.13, *) func dequeueReusableAnnotationView(withClass name: T.Type, for annotation: MKAnnotation) -> T? { guard let annotationView = dequeueReusableAnnotationView(withIdentifier: String(describing: name), for: annotation) as? T else { fatalError("Couldn't find MKAnnotationView for \(String(describing: name))") } return annotationView } /// SwifterSwift: Zooms in on multiple mapView coordinates. /// /// - Parameters: /// - coordinates: Gets the array of type CLLocationCoordinate2D. /// - meter: If arrays have a single item, they take the value of meters (Double). The map zooms in at the given meters. /// - edgePadding: The amount of additional space (measured in screen points) to make visible around the specified rectangle /// - animated: The animation control takes the Boolean value. Enter the true value for zooming with the animation. func zoom(to coordinates: [CLLocationCoordinate2D], meter: Double, edgePadding: EdgeInsets, animated: Bool) { guard !coordinates.isEmpty else { return } if coordinates.count == 1 { let coordinateRegion = MKCoordinateRegion(center: coordinates.first!, latitudinalMeters: meter, longitudinalMeters: meter) setRegion(coordinateRegion, animated: true) } else { let mkPolygon = MKPolygon(coordinates: coordinates, count: coordinates.count) setVisibleMapRect(mkPolygon.boundingMapRect, edgePadding: edgePadding, animated: animated) } } } #endif #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/MapKit/MKPolylineExtensions.swift ================================================ // // MKPolylineExtensions.swift // SwifterSwift // // Created by Shai Mishali on 3/8/18. // Copyright © 2018 SwifterSwift // #if canImport(MapKit) && !os(watchOS) import MapKit // MARK: - Initializers @available(tvOS 9.2, *) public extension MKPolyline { /// SwifterSwift: Create a new MKPolyline from a provided Array of coordinates. /// /// - Parameter coordinates: Array of CLLocationCoordinate2D(s). convenience init(coordinates: [CLLocationCoordinate2D]) { var refCoordinates = coordinates self.init(coordinates: &refCoordinates, count: refCoordinates.count) } } // MARK: - Properties @available(tvOS 9.2, *) public extension MKPolyline { /// SwifterSwift: Return an Array of coordinates representing the provided polyline. var coordinates: [CLLocationCoordinate2D] { var coords = [CLLocationCoordinate2D](repeating: kCLLocationCoordinate2DInvalid, count: pointCount) getCoordinates(&coords, range: NSRange(location: 0, length: pointCount)) return coords } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SceneKit/SCNBoxExtensions.swift ================================================ // // SCNBoxExtensions.swift // SwifterSwift // // Created by Max Härtwig on 06.04.19. // Copyright © 2019 SwifterSwift // #if canImport(SceneKit) import SceneKit // MARK: - Methods public extension SCNBox { /// SwifterSwift: Creates a box geometry with the specified width, height, and length. /// /// - Parameters: /// - width: The width of the box along the x-axis of its local coordinate space. /// - height: The height of the box along the y-axis of its local coordinate space. /// - length: The length of the box along the z-axis of its local coordinate space. convenience init(width: CGFloat, height: CGFloat, length: CGFloat) { self.init(width: width, height: height, length: length, chamferRadius: 0) } /// SwifterSwift: Creates a cube geometry with the specified side length, and chamfer radius. /// /// - Parameters: /// - sideLength: The width, height, and length of the box in its local coordinate space. /// - chamferRadius: The radius of curvature for the edges and corners of the box. convenience init(sideLength: CGFloat, chamferRadius: CGFloat = 0) { self.init(width: sideLength, height: sideLength, length: sideLength, chamferRadius: chamferRadius) } /// SwifterSwift: Creates a box geometry with the specified width, height, length, chamfer radius, and material. /// /// - Parameters: /// - width: The width of the box along the x-axis of its local coordinate space. /// - height: The height of the box along the y-axis of its local coordinate space. /// - length: The length of the box along the z-axis of its local coordinate space. /// - chamferRadius: The radius of curvature for the edges and corners of the box. /// - material: The material of the geometry. convenience init(width: CGFloat, height: CGFloat, length: CGFloat, chamferRadius: CGFloat = 0, material: SCNMaterial) { self.init(width: width, height: height, length: length, chamferRadius: chamferRadius) materials = [material] } /// SwifterSwift: Creates a cube geometry with the specified side length, chamfer radius, and material. /// /// - Parameters: /// - sideLength: The width, height, and length of the box in its local coordinate space. /// - chamferRadius: The radius of curvature for the edges and corners of the box. /// - material: The material of the geometry. convenience init(sideLength: CGFloat, chamferRadius: CGFloat = 0, material: SCNMaterial) { self.init(width: sideLength, height: sideLength, length: sideLength, chamferRadius: chamferRadius) materials = [material] } /// SwifterSwift: Creates a box geometry with the specified width, height, length, chamfer radius, and material color. /// /// - Parameters: /// - width: The width of the box along the x-axis of its local coordinate space. /// - height: The height of the box along the y-axis of its local coordinate space. /// - length: The length of the box along the z-axis of its local coordinate space. /// - chamferRadius: The radius of curvature for the edges and corners of the box. /// - color: The color of the geometry's material. convenience init(width: CGFloat, height: CGFloat, length: CGFloat, chamferRadius: CGFloat = 0, color: Color) { self.init(width: width, height: height, length: length, chamferRadius: chamferRadius) materials = [SCNMaterial(color: color)] } /// SwifterSwift: Creates a cube geometry with the specified side length, chamfer radius, and material color. /// /// - Parameters: /// - sideLength: The width, height, and length of the box in its local coordinate space. /// - chamferRadius: The radius of curvature for the edges and corners of the box. /// - color: The color of the geometry's material. convenience init(sideLength: CGFloat, chamferRadius: CGFloat = 0, color: Color) { self.init(width: sideLength, height: sideLength, length: sideLength, chamferRadius: chamferRadius) materials = [SCNMaterial(color: color)] } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SceneKit/SCNCapsuleExtensions.swift ================================================ // // SCNCapsuleExtensions.swift // SwifterSwift // // Created by Max Härtwig on 06.04.19. // Copyright © 2019 SwifterSwift // #if canImport(SceneKit) import SceneKit // MARK: - Methods public extension SCNCapsule { /// SwifterSwift: Creates a capsule geometry with the specified diameter and height. /// /// - Parameters: /// - capDiameter: The diameter both of the capsule’s cylindrical body and of its hemispherical ends. /// - height: The height of the capsule along the y-axis of its local coordinate space. convenience init(capDiameter: CGFloat, height: CGFloat) { self.init(capRadius: capDiameter / 2, height: height) } /// SwifterSwift: Creates a capsule geometry with the specified radius and height. /// /// - Parameters: /// - capRadius: The radius both of the capsule’s cylindrical body and of its hemispherical ends. /// - height: The height of the capsule along the y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(capRadius: CGFloat, height: CGFloat, material: SCNMaterial) { self.init(capRadius: capRadius, height: height) materials = [material] } /// SwifterSwift: Creates a capsule geometry with the specified diameter and height. /// /// - Parameters: /// - capDiameter: The diameter both of the capsule’s cylindrical body and of its hemispherical ends. /// - height: The height of the capsule along the y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(capDiameter: CGFloat, height: CGFloat, material: SCNMaterial) { self.init(capRadius: capDiameter / 2, height: height) materials = [material] } /// SwifterSwift: Creates a capsule geometry with the specified radius and height. /// /// - Parameters: /// - capRadius: The radius both of the capsule’s cylindrical body and of its hemispherical ends. /// - height: The height of the capsule along the y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(capRadius: CGFloat, height: CGFloat, color: Color) { self.init(capRadius: capRadius, height: height) materials = [SCNMaterial(color: color)] } /// SwifterSwift: Creates a capsule geometry with the specified diameter and height. /// /// - Parameters: /// - capDiameter: The diameter both of the capsule’s cylindrical body and of its hemispherical ends. /// - height: The height of the capsule along the y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(capDiameter: CGFloat, height: CGFloat, color: Color) { self.init(capRadius: capDiameter / 2, height: height) materials = [SCNMaterial(color: color)] } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SceneKit/SCNConeExtensions.swift ================================================ // // SCNConeExtensions.swift // SwifterSwift // // Created by Max Härtwig on 06.04.19. // Copyright © 2019 SwifterSwift // #if canImport(SceneKit) import SceneKit // MARK: - Methods public extension SCNCone { /// SwifterSwift: Creates a cone geometry with the given top diameter, bottom diameter, and height. /// /// - Parameters: /// - topDiameter: The diameter of the cone’s top, forming a circle in the x- and z-axis dimensions of its local coordinate space. /// - bottomDiameter: The diameter of the cone’s base, forming a circle in the x- and z-axis dimensions of its local coordinate space. /// - height: The height of the cone along the y-axis of its local coordinate space. convenience init(topDiameter: CGFloat, bottomDiameter: CGFloat, height: CGFloat) { self.init(topRadius: topDiameter / 2, bottomRadius: bottomDiameter / 2, height: height) } /// SwifterSwift: Creates a cone geometry with the given top radius, bottom radius, height, and material. /// /// - Parameters: /// - topRadius: The radius of the cone’s top, forming a circle in the x- and z-axis dimensions of its local coordinate space. /// - bottomRadius: The radius of the cone’s base, forming a circle in the x- and z-axis dimensions of its local coordinate space. /// - height: The height of the cone along the y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(topRadius: CGFloat, bottomRadius: CGFloat, height: CGFloat, material: SCNMaterial) { self.init(topRadius: topRadius, bottomRadius: bottomRadius, height: height) materials = [material] } /// SwifterSwift: Creates a cone geometry with the given top diameter, bottom diameter, height, and material. /// /// - Parameters: /// - topDiameter: The diameter of the cone’s top, forming a circle in the x- and z-axis dimensions of its local coordinate space. /// - bottomDiameter: The diameter of the cone’s base, forming a circle in the x- and z-axis dimensions of its local coordinate space. /// - height: The height of the cone along the y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(topDiameter: CGFloat, bottomDiameter: CGFloat, height: CGFloat, material: SCNMaterial) { self.init(topRadius: topDiameter / 2, bottomRadius: bottomDiameter / 2, height: height) materials = [material] } /// SwifterSwift: Creates a cone geometry with the given top radius, bottom radius, height, and material. /// /// - Parameters: /// - topRadius: The radius of the cone’s top, forming a circle in the x- and z-axis dimensions of its local coordinate space. /// - bottomRadius: The radius of the cone’s base, forming a circle in the x- and z-axis dimensions of its local coordinate space. /// - height: The height of the cone along the y-axis of its local coordinate space. /// - color: The color of the geometry's material. convenience init(topRadius: CGFloat, bottomRadius: CGFloat, height: CGFloat, color: Color) { self.init(topRadius: topRadius, bottomRadius: bottomRadius, height: height) materials = [SCNMaterial(color: color)] } /// SwifterSwift: Creates a cone geometry with the given top diameter, bottom diameter, height, and material. /// /// - Parameters: /// - topDiameter: The diameter of the cone’s top, forming a circle in the x- and z-axis dimensions of its local coordinate space. /// - bottomDiameter: The diameter of the cone’s base, forming a circle in the x- and z-axis dimensions of its local coordinate space. /// - height: The height of the cone along the y-axis of its local coordinate space. /// - color: The color of the geometry's material. convenience init(topDiameter: CGFloat, bottomDiameter: CGFloat, height: CGFloat, color: Color) { self.init(topRadius: topDiameter / 2, bottomRadius: bottomDiameter / 2, height: height) materials = [SCNMaterial(color: color)] } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SceneKit/SCNCylinderExtensions.swift ================================================ // // SCNCylinderExtensions.swift // SwifterSwift // // Created by Max Härtwig on 06.04.19. // Copyright © 2019 SwifterSwift // #if canImport(SceneKit) import SceneKit // MARK: - Methods public extension SCNCylinder { /// SwifterSwift: Creates a cylinder geometry with the specified diameter and height. /// /// - Parameters: /// - radius: The radius of the cylinder’s circular cross section in the x- and z-axis dimensions of its local coordinate space. /// - height: The height of the cylinder along the y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(diameter: CGFloat, height: CGFloat) { self.init(radius: diameter / 2, height: height) } /// SwifterSwift: Creates a cylinder geometry with the specified radius, height and material. /// /// - Parameters: /// - radius: The radius of the cylinder’s circular cross section in the x- and z-axis dimensions of its local coordinate space. /// - height: The height of the cylinder along the y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(radius: CGFloat, height: CGFloat, material: SCNMaterial) { self.init(radius: radius, height: height) materials = [material] } /// SwifterSwift: Creates a cylinder geometry with the specified diameter, height and material. /// /// - Parameters: /// - radius: The radius of the cylinder’s circular cross section in the x- and z-axis dimensions of its local coordinate space. /// - height: The height of the cylinder along the y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(diameter: CGFloat, height: CGFloat, material: SCNMaterial) { self.init(radius: diameter / 2, height: height) materials = [material] } /// SwifterSwift: Creates a cylinder geometry with the specified radius, height, and material color. /// /// - Parameters: /// - radius: The radius of the cylinder’s circular cross section in the x- and z-axis dimensions of its local coordinate space. /// - height: The height of the cylinder along the y-axis of its local coordinate space. /// - color: The color of the geometry's material. convenience init(radius: CGFloat, height: CGFloat, color: Color) { self.init(radius: radius, height: height) materials = [SCNMaterial(color: color)] } /// SwifterSwift: Creates a cylinder geometry with the specified diameter, height, and material color. /// /// - Parameters: /// - diameter: The diameter of the cylinder’s circular cross section in the x- and z-axis dimensions of its local coordinate space. /// - height: The height of the cylinder along the y-axis of its local coordinate space. /// - color: The color of the geometry's material. convenience init(diameter: CGFloat, height: CGFloat, color: Color) { self.init(radius: diameter / 2, height: height) materials = [SCNMaterial(color: color)] } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SceneKit/SCNGeometryExtensions.swift ================================================ // // SCNGeometryExtensions.swift // SwifterSwift // // Created by Max Härtwig on 06.04.19. // Copyright © 2019 SwifterSwift // #if canImport(SceneKit) import SceneKit // MARK: - Properties public extension SCNGeometry { /// SwifterSwift: Returns the size of the geometry's bounding box. var boundingSize: SCNVector3 { return (boundingBox.max - boundingBox.min).absolute } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SceneKit/SCNMaterialExtensions.swift ================================================ // // SCNMaterialExtensions.swift // SwifterSwift // // Created by Max Härtwig on 06.04.19. // Copyright © 2019 SwifterSwift // #if canImport(SceneKit) import SceneKit // MARK: - Methods public extension SCNMaterial { /// SwifterSwift: Initializes a SCNMaterial with a specific diffuse color /// /// - Parameter color: diffuse color convenience init(color: Color) { self.init() diffuse.contents = color } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SceneKit/SCNPlaneExtensions.swift ================================================ // // SCNPlaneExtensions.swift // SwifterSwift // // Created by Max Härtwig on 06.04.19. // Copyright © 2019 SwifterSwift // #if canImport(SceneKit) import SceneKit // MARK: - Methods public extension SCNPlane { /// SwifterSwift: Creates a square plane geometry with the specified width. /// /// - Parameter width: The width and height of the plane along the x-axis and y-axis of its local coordinate space. convenience init(width: CGFloat) { self.init(width: width, height: width) } /// SwifterSwift: Creates a plane geometry with the specified width, height and material. /// /// - Parameters: /// - width: The width of the plane along the x-axis of its local coordinate space. /// - height: The height of the plane along the y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(width: CGFloat, height: CGFloat, material: SCNMaterial) { self.init(width: width, height: height) materials = [material] } /// SwifterSwift: Creates a square plane geometry with the specified width and material. /// /// - Parameters: /// - width: The width and height of the plane along the x-axis and y-axis of its local coordinate space. /// - material: The material of the geometry. convenience init(width: CGFloat, material: SCNMaterial) { self.init(width: width, height: width) materials = [material] } /// SwifterSwift: Creates a plane geometry with the specified width, height and material color. /// /// - Parameters: /// - width: The width of the plane along the x-axis of its local coordinate space. /// - height: The height of the plane along the y-axis of its local coordinate space. /// - color: The color of the geometry's material. convenience init(width: CGFloat, height: CGFloat, color: Color) { self.init(width: width, height: height) materials = [SCNMaterial(color: color)] } /// SwifterSwift: Creates a square plane geometry with the specified width and material color. /// /// - Parameters: /// - width: The width and height of the plane along the x-axis and y-axis of its local coordinate space. /// - color: The color of the geometry's material. convenience init(width: CGFloat, color: Color) { self.init(width: width, height: width) materials = [SCNMaterial(color: color)] } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SceneKit/SCNShapeExtensions.swift ================================================ // // SCNShapeExtensions.swift // SwifterSwift // // Created by Max Härtwig on 06.04.19. // Copyright © 2019 SwifterSwift // #if canImport(SceneKit) import SceneKit #if canImport(UIKit) import UIKit #endif // MARK: - Methods public extension SCNShape { #if canImport(UIKit) /// SwifterSwift: Creates a shape geometry with the specified path, extrusion depth, and material. /// /// - Parameters: /// - path: The two-dimensional path forming the basis of the shape. /// - extrusionDepth: The thickness of the extruded shape along the z-axis. /// - material: The material of the geometry. convenience init(path: UIBezierPath, extrusionDepth: CGFloat, material: SCNMaterial) { self.init(path: path, extrusionDepth: extrusionDepth) materials = [material] } /// SwifterSwift: Creates a shape geometry with the specified path, extrusion depth, and material. /// /// - Parameters: /// - path: The two-dimensional path forming the basis of the shape. /// - extrusionDepth: The thickness of the extruded shape along the z-axis. /// - color: The color of the geometry's material. convenience init(path: UIBezierPath, extrusionDepth: CGFloat, color: Color) { self.init(path: path, extrusionDepth: extrusionDepth) materials = [SCNMaterial(color: color)] } #endif } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SceneKit/SCNSphereExtensions.swift ================================================ // // SCNSphereExtensions.swift // SwifterSwift // // Created by Max Härtwig on 06.04.19. // Copyright © 2019 SwifterSwift // #if canImport(SceneKit) import SceneKit // MARK: - Methods public extension SCNSphere { /// SwifterSwift: Creates a sphere geometry with the specified diameter. /// /// - Parameter diameter: The diameter of the sphere in its local coordinate space. convenience init(diameter: CGFloat) { self.init(radius: diameter / 2) } /// SwifterSwift: Creates a sphere geometry with the specified radius and material. /// /// - Parameters: /// - radius: The radius of the sphere in its local coordinate space. /// - material: The material of the geometry. convenience init(radius: CGFloat, material: SCNMaterial) { self.init(radius: radius) materials = [material] } /// SwifterSwift: Creates a sphere geometry with the specified radius and material color. /// /// - Parameters: /// - radius: The radius of the sphere in its local coordinate space. /// - color: The color of the geometry's material. convenience init(radius: CGFloat, color: Color) { self.init(radius: radius, material: SCNMaterial(color: color)) } /// SwifterSwift: Creates a sphere geometry with the specified diameter and material. /// /// - Parameters: /// - diameter: The diameter of the sphere in its local coordinate space. /// - material: The material of the geometry. convenience init(diameter: CGFloat, material: SCNMaterial) { self.init(radius: diameter / 2) materials = [material] } /// SwifterSwift: Creates a sphere geometry with the specified diameter and material color. /// /// - Parameters: /// - diameter: The diameter of the sphere in its local coordinate space. /// - color: The color of the geometry's material. convenience init(diameter: CGFloat, color: Color) { self.init(diameter: diameter, material: SCNMaterial(color: color)) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SceneKit/SCNVector3Extensions.swift ================================================ // // SCNVector3Extensions.swift // SwifterSwift // // Created by Max Härtwig on 04.04.19. // Copyright © 2019 SwifterSwift // #if os(OSX) /// SwifterSwift: CGFloat. public typealias SceneKitFloat = CGFloat #else /// SwifterSwift: Float. public typealias SceneKitFloat = Float #endif #if canImport(SceneKit) import SceneKit // MARK: - Methods public extension SCNVector3 { /// SwifterSwift: Returns the absolute values of the vector's components. /// /// SCNVector3(2, -3, -6).abs -> SCNVector3(2, 3, 6) /// var absolute: SCNVector3 { return SCNVector3(abs(x), abs(y), abs(z)) } /// SwifterSwift: Returns the length of the vector. /// /// SCNVector3(2, 3, 6).length -> 7 /// var length: SceneKitFloat { return sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)) } } // MARK: - Operators public extension SCNVector3 { /// SwifterSwift: Add two SCNVector3s. /// /// SCNVector3(10, 10, 10) + SCNVector3(10, 20, -30) -> SCNVector3(20, 30, -20) /// /// - Parameters: /// - lhs: SCNVector3 to add to. /// - rhs: SCNVector3 to add. /// - Returns: result of addition of the two given SCNVector3s. static func + (lhs: SCNVector3, rhs: SCNVector3) -> SCNVector3 { return SCNVector3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z) } /// SwifterSwift: Add a SCNVector3 to self. /// /// SCNVector3(10, 10, 10) += SCNVector3(10, 20, -30) -> SCNVector3(20, 30, -20) /// /// - Parameters: /// - lhs: self /// - rhs: SCNVector3 to add. static func += (lhs: inout SCNVector3, rhs: SCNVector3) { // swiftlint:disable:next shorthand_operator lhs = lhs + rhs } /// SwifterSwift: Subtract two SCNVector3s. /// /// SCNVector3(10, 10, 10) - SCNVector3(10, 20, -30) -> SCNVector3(0, -10, 40) /// /// - Parameters: /// - lhs: SCNVector3 to subtract from. /// - rhs: SCNVector3 to subtract. /// - Returns: result of subtract of the two given SCNVector3s. static func - (lhs: SCNVector3, rhs: SCNVector3) -> SCNVector3 { return SCNVector3(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z) } /// SwifterSwift: Subtract a SCNVector3 from self. /// /// SCNVector3(10, 10, 10) -= SCNVector3(10, 20, -30) -> SCNVector3(0, -10, 40) /// /// - Parameters: /// - lhs: self /// - rhs: SCNVector3 to subtract. static func -= (lhs: inout SCNVector3, rhs: SCNVector3) { // swiftlint:disable:next shorthand_operator lhs = lhs - rhs } /// SwifterSwift: Multiply a SCNVector3 with a scalar /// /// SCNVector3(10, 20, -30) * 3 -> SCNVector3(30, 60, -90) /// /// - Parameters: /// - vector: SCNVector3 to multiply. /// - scalar: scalar value. /// - Returns: result of multiplication of the given SCNVector3 with the scalar. static func * (vector: SCNVector3, scalar: SceneKitFloat) -> SCNVector3 { return SCNVector3(vector.x * scalar, vector.y * scalar, vector.z * scalar) } /// SwifterSwift: Multiply self with a scalar /// /// SCNVector3(10, 20, -30) *= 3 -> SCNVector3(30, 60, -90) /// /// - Parameters: /// - vector: self. /// - scalar: scalar value. /// - Returns: result of multiplication of the given CGPoint with the scalar. static func *= (vector: inout SCNVector3, scalar: SceneKitFloat) { // swiftlint:disable:next shorthand_operator vector = vector * scalar } /// SwifterSwift: Multiply a scalar with a SCNVector3 /// /// 3 * SCNVector3(10, 20, -30) -> SCNVector3(30, 60, -90) /// /// - Parameters: /// - scalar: scalar value. /// - vector: SCNVector3 to multiply. /// - Returns: result of multiplication of the given CGPoint with the scalar. static func * (scalar: SceneKitFloat, vector: SCNVector3) -> SCNVector3 { return SCNVector3(vector.x * scalar, vector.y * scalar, vector.z * scalar) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Shared/ColorExtensions.swift ================================================ // // ColorExtensions.swift // SwifterSwift-iOS // // Created by Omar Albeik on 9/27/17. // Copyright © 2017 SwifterSwift // #if !os(Linux) #if canImport(UIKit) import UIKit /// SwifterSwift: Color public typealias Color = UIColor #endif #if canImport(AppKit) && !targetEnvironment(macCatalyst) import AppKit /// SwifterSwift: Color public typealias Color = NSColor #endif #if !os(watchOS) import CoreImage #endif // MARK: - Properties public extension Color { /// SwifterSwift: Random color. static var random: Color { let red = Int.random(in: 0...255) let green = Int.random(in: 0...255) let blue = Int.random(in: 0...255) return Color(red: red, green: green, blue: blue)! } // swiftlint:disable large_tuple /// SwifterSwift: RGB components for a Color (between 0 and 255). /// /// UIColor.red.rgbComponents.red -> 255 /// NSColor.green.rgbComponents.green -> 255 /// UIColor.blue.rgbComponents.blue -> 255 /// var rgbComponents: (red: Int, green: Int, blue: Int) { var components: [CGFloat] { let comps = cgColor.components! if comps.count == 4 { return comps } return [comps[0], comps[0], comps[0], comps[1]] } let red = components[0] let green = components[1] let blue = components[2] return (red: Int(red * 255.0), green: Int(green * 255.0), blue: Int(blue * 255.0)) } // swiftlint:enable large_tuple // swiftlint:disable large_tuple /// SwifterSwift: RGB components for a Color represented as CGFloat numbers (between 0 and 1) /// /// UIColor.red.rgbComponents.red -> 1.0 /// NSColor.green.rgbComponents.green -> 1.0 /// UIColor.blue.rgbComponents.blue -> 1.0 /// var cgFloatComponents: (red: CGFloat, green: CGFloat, blue: CGFloat) { var components: [CGFloat] { let comps = cgColor.components! if comps.count == 4 { return comps } return [comps[0], comps[0], comps[0], comps[1]] } let red = components[0] let green = components[1] let blue = components[2] return (red: red, green: green, blue: blue) } // swiftlint:enable large_tuple // swiftlint:disable large_tuple /// SwifterSwift: Get components of hue, saturation, and brightness, and alpha (read-only). var hsbaComponents: (hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat) { var hue: CGFloat = 0.0 var saturation: CGFloat = 0.0 var brightness: CGFloat = 0.0 var alpha: CGFloat = 0.0 getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) return (hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) } // swiftlint:enable large_tuple /// SwifterSwift: Hexadecimal value string (read-only). var hexString: String { let components: [Int] = { let comps = cgColor.components! let components = comps.count == 4 ? comps : [comps[0], comps[0], comps[0], comps[1]] return components.map { Int($0 * 255.0) } }() return String(format: "#%02X%02X%02X", components[0], components[1], components[2]) } /// SwifterSwift: Short hexadecimal value string (read-only, if applicable). var shortHexString: String? { let string = hexString.replacingOccurrences(of: "#", with: "") let chrs = Array(string) guard chrs[0] == chrs[1], chrs[2] == chrs[3], chrs[4] == chrs[5] else { return nil } return "#\(chrs[0])\(chrs[2])\(chrs[4])" } /// SwifterSwift: Short hexadecimal value string, or full hexadecimal string if not possible (read-only). var shortHexOrHexString: String { let components: [Int] = { let comps = cgColor.components! let components = comps.count == 4 ? comps : [comps[0], comps[0], comps[0], comps[1]] return components.map { Int($0 * 255.0) } }() let hexString = String(format: "#%02X%02X%02X", components[0], components[1], components[2]) let string = hexString.replacingOccurrences(of: "#", with: "") let chrs = Array(string) guard chrs[0] == chrs[1], chrs[2] == chrs[3], chrs[4] == chrs[5] else { return hexString } return "#\(chrs[0])\(chrs[2])\(chrs[4])" } /// SwifterSwift: Alpha of Color (read-only). var alpha: CGFloat { return cgColor.alpha } #if !os(watchOS) /// SwifterSwift: CoreImage.CIColor (read-only) var coreImageColor: CoreImage.CIColor? { return CoreImage.CIColor(color: self) } #endif /// SwifterSwift: Get UInt representation of a Color (read-only). var uInt: UInt { let comps: [CGFloat] = { let comps = cgColor.components! return comps.count == 4 ? comps : [comps[0], comps[0], comps[0], comps[1]] }() var colorAsUInt32: UInt32 = 0 colorAsUInt32 += UInt32(comps[0] * 255.0) << 16 colorAsUInt32 += UInt32(comps[1] * 255.0) << 8 colorAsUInt32 += UInt32(comps[2] * 255.0) return UInt(colorAsUInt32) } /// SwifterSwift: Get color complementary (read-only, if applicable). var complementary: Color? { let colorSpaceRGB = CGColorSpaceCreateDeviceRGB() let convertColorToRGBSpace: ((_ color: Color) -> Color?) = { color -> Color? in if self.cgColor.colorSpace!.model == CGColorSpaceModel.monochrome { let oldComponents = self.cgColor.components let components: [CGFloat] = [ oldComponents![0], oldComponents![0], oldComponents![0], oldComponents![1]] let colorRef = CGColor(colorSpace: colorSpaceRGB, components: components) let colorOut = Color(cgColor: colorRef!) return colorOut } else { return self } } let color = convertColorToRGBSpace(self) guard let componentColors = color?.cgColor.components else { return nil } let red: CGFloat = sqrt(pow(255.0, 2.0) - pow((componentColors[0]*255), 2.0))/255 let green: CGFloat = sqrt(pow(255.0, 2.0) - pow((componentColors[1]*255), 2.0))/255 let blue: CGFloat = sqrt(pow(255.0, 2.0) - pow((componentColors[2]*255), 2.0))/255 return Color(red: red, green: green, blue: blue, alpha: 1.0) } } // MARK: - Methods public extension Color { /// SwifterSwift: Blend two Colors /// /// - Parameters: /// - color1: first color to blend /// - intensity1: intensity of first color (default is 0.5) /// - color2: second color to blend /// - intensity2: intensity of second color (default is 0.5) /// - Returns: Color created by blending first and seond colors. static func blend(_ color1: Color, intensity1: CGFloat = 0.5, with color2: Color, intensity2: CGFloat = 0.5) -> Color { // http://stackoverflow.com/questions/27342715/blend-uicolors-in-swift let total = intensity1 + intensity2 let level1 = intensity1/total let level2 = intensity2/total guard level1 > 0 else { return color2 } guard level2 > 0 else { return color1 } let components1: [CGFloat] = { let comps = color1.cgColor.components! return comps.count == 4 ? comps : [comps[0], comps[0], comps[0], comps[1]] }() let components2: [CGFloat] = { let comps = color2.cgColor.components! return comps.count == 4 ? comps : [comps[0], comps[0], comps[0], comps[1]] }() let red1 = components1[0] let red2 = components2[0] let green1 = components1[1] let green2 = components2[1] let blue1 = components1[2] let blue2 = components2[2] let alpha1 = color1.cgColor.alpha let alpha2 = color2.cgColor.alpha let red = level1*red1 + level2*red2 let green = level1*green1 + level2*green2 let blue = level1*blue1 + level2*blue2 let alpha = level1*alpha1 + level2*alpha2 return Color(red: red, green: green, blue: blue, alpha: alpha) } /// SwifterSwift: Lighten a color /// /// let color = Color(red: r, green: g, blue: b, alpha: a) /// let lighterColor: Color = color.lighten(by: 0.2) /// /// - Parameter percentage: Percentage by which to lighten the color /// - Returns: A lightened color func lighten(by percentage: CGFloat = 0.2) -> Color { // https://stackoverflow.com/questions/38435308/swift-get-lighter-and-darker-color-variations-for-a-given-uicolor var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 getRed(&red, green: &green, blue: &blue, alpha: &alpha) return Color(red: min(red + percentage, 1.0), green: min(green + percentage, 1.0), blue: min(blue + percentage, 1.0), alpha: alpha) } /// SwifterSwift: Darken a color /// /// let color = Color(red: r, green: g, blue: b, alpha: a) /// let darkerColor: Color = color.darken(by: 0.2) /// /// - Parameter percentage: Percentage by which to darken the color /// - Returns: A darkened color func darken(by percentage: CGFloat = 0.2) -> Color { // https://stackoverflow.com/questions/38435308/swift-get-lighter-and-darker-color-variations-for-a-given-uicolor var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 getRed(&red, green: &green, blue: &blue, alpha: &alpha) return Color(red: max(red - percentage, 0), green: max(green - percentage, 0), blue: max(blue - percentage, 0), alpha: alpha) } } // MARK: - Initializers public extension Color { /// SwifterSwift: Create Color from RGB values with optional transparency. /// /// - Parameters: /// - red: red component. /// - green: green component. /// - blue: blue component. /// - transparency: optional transparency value (default is 1). convenience init?(red: Int, green: Int, blue: Int, transparency: CGFloat = 1) { guard red >= 0 && red <= 255 else { return nil } guard green >= 0 && green <= 255 else { return nil } guard blue >= 0 && blue <= 255 else { return nil } var trans = transparency if trans < 0 { trans = 0 } if trans > 1 { trans = 1 } self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: trans) } /// SwifterSwift: Create Color from hexadecimal value with optional transparency. /// /// - Parameters: /// - hex: hex Int (example: 0xDECEB5). /// - transparency: optional transparency value (default is 1). convenience init?(hex: Int, transparency: CGFloat = 1) { var trans = transparency if trans < 0 { trans = 0 } if trans > 1 { trans = 1 } let red = (hex >> 16) & 0xff let green = (hex >> 8) & 0xff let blue = hex & 0xff self.init(red: red, green: green, blue: blue, transparency: trans) } /// SwifterSwift: Create Color from hexadecimal string with optional transparency (if applicable). /// /// - Parameters: /// - hexString: hexadecimal string (examples: EDE7F6, 0xEDE7F6, #EDE7F6, #0ff, 0xF0F, ..). /// - transparency: optional transparency value (default is 1). convenience init?(hexString: String, transparency: CGFloat = 1) { var string = "" if hexString.lowercased().hasPrefix("0x") { string = hexString.replacingOccurrences(of: "0x", with: "") } else if hexString.hasPrefix("#") { string = hexString.replacingOccurrences(of: "#", with: "") } else { string = hexString } if string.count == 3 { // convert hex to 6 digit format if in short format var str = "" string.forEach { str.append(String(repeating: String($0), count: 2)) } string = str } guard let hexValue = Int(string, radix: 16) else { return nil } var trans = transparency if trans < 0 { trans = 0 } if trans > 1 { trans = 1 } let red = (hexValue >> 16) & 0xff let green = (hexValue >> 8) & 0xff let blue = hexValue & 0xff self.init(red: red, green: green, blue: blue, transparency: trans) } /// SwifterSwift: Create Color from a complementary of a Color (if applicable). /// /// - Parameter color: color of which opposite color is desired. convenience init?(complementaryFor color: Color) { let colorSpaceRGB = CGColorSpaceCreateDeviceRGB() let convertColorToRGBSpace: ((_ color: Color) -> Color?) = { color -> Color? in if color.cgColor.colorSpace!.model == CGColorSpaceModel.monochrome { let oldComponents = color.cgColor.components let components: [CGFloat] = [ oldComponents![0], oldComponents![0], oldComponents![0], oldComponents![1]] let colorRef = CGColor(colorSpace: colorSpaceRGB, components: components) let colorOut = Color(cgColor: colorRef!) return colorOut } else { return color } } let color = convertColorToRGBSpace(color) guard let componentColors = color?.cgColor.components else { return nil } let red: CGFloat = sqrt(pow(255.0, 2.0) - pow((componentColors[0]*255), 2.0))/255 let green: CGFloat = sqrt(pow(255.0, 2.0) - pow((componentColors[1]*255), 2.0))/255 let blue: CGFloat = sqrt(pow(255.0, 2.0) - pow((componentColors[2]*255), 2.0))/255 self.init(red: red, green: green, blue: blue, alpha: 1.0) } } // MARK: - Social public extension Color { /// SwifterSwift: Brand identity color of popular social media platform. struct Social { // https://www.lockedowndesign.com/social-media-colors/ private init() {} /// SwifterSwift: red: 59, green: 89, blue: 152 public static let facebook = Color(red: 59, green: 89, blue: 152)! /// SwifterSwift: red: 0, green: 182, blue: 241 public static let twitter = Color(red: 0, green: 182, blue: 241)! /// SwifterSwift: red: 223, green: 74, blue: 50 public static let googlePlus = Color(red: 223, green: 74, blue: 50)! /// SwifterSwift: red: 0, green: 123, blue: 182 public static let linkedIn = Color(red: 0, green: 123, blue: 182)! /// SwifterSwift: red: 69, green: 187, blue: 255 public static let vimeo = Color(red: 69, green: 187, blue: 255)! /// SwifterSwift: red: 179, green: 18, blue: 23 public static let youtube = Color(red: 179, green: 18, blue: 23)! /// SwifterSwift: red: 195, green: 42, blue: 163 public static let instagram = Color(red: 195, green: 42, blue: 163)! /// SwifterSwift: red: 203, green: 32, blue: 39 public static let pinterest = Color(red: 203, green: 32, blue: 39)! /// SwifterSwift: red: 244, green: 0, blue: 131 public static let flickr = Color(red: 244, green: 0, blue: 131)! /// SwifterSwift: red: 67, green: 2, blue: 151 public static let yahoo = Color(red: 67, green: 2, blue: 151)! /// SwifterSwift: red: 67, green: 2, blue: 151 public static let soundCloud = Color(red: 67, green: 2, blue: 151)! /// SwifterSwift: red: 44, green: 71, blue: 98 public static let tumblr = Color(red: 44, green: 71, blue: 98)! /// SwifterSwift: red: 252, green: 69, blue: 117 public static let foursquare = Color(red: 252, green: 69, blue: 117)! /// SwifterSwift: red: 255, green: 176, blue: 0 public static let swarm = Color(red: 255, green: 176, blue: 0)! /// SwifterSwift: red: 234, green: 76, blue: 137 public static let dribbble = Color(red: 234, green: 76, blue: 137)! /// SwifterSwift: red: 255, green: 87, blue: 0 public static let reddit = Color(red: 255, green: 87, blue: 0)! /// SwifterSwift: red: 74, green: 93, blue: 78 public static let devianArt = Color(red: 74, green: 93, blue: 78)! /// SwifterSwift: red: 238, green: 64, blue: 86 public static let pocket = Color(red: 238, green: 64, blue: 86)! /// SwifterSwift: red: 170, green: 34, blue: 182 public static let quora = Color(red: 170, green: 34, blue: 182)! /// SwifterSwift: red: 247, green: 146, blue: 30 public static let slideShare = Color(red: 247, green: 146, blue: 30)! /// SwifterSwift: red: 0, green: 153, blue: 229 public static let px500 = Color(red: 0, green: 153, blue: 229)! /// SwifterSwift: red: 223, green: 109, blue: 70 public static let listly = Color(red: 223, green: 109, blue: 70)! /// SwifterSwift: red: 0, green: 180, blue: 137 public static let vine = Color(red: 0, green: 180, blue: 137)! /// SwifterSwift: red: 0, green: 175, blue: 240 public static let skype = Color(red: 0, green: 175, blue: 240)! /// SwifterSwift: red: 235, green: 73, blue: 36 public static let stumbleUpon = Color(red: 235, green: 73, blue: 36)! /// SwifterSwift: red: 255, green: 252, blue: 0 public static let snapchat = Color(red: 255, green: 252, blue: 0)! /// SwifterSwift: red: 37, green: 211, blue: 102 public static let whatsApp = Color(red: 37, green: 211, blue: 102)! } } // MARK: - Material colors public extension Color { // swiftlint:disable type_body_length /// SwifterSwift: Google Material design colors palette. struct Material { // https://material.google.com/style/color.html private init() {} /// SwifterSwift: color red500 public static let red = red500 /// SwifterSwift: hex #FFEBEE public static let red50 = Color(hex: 0xFFEBEE)! /// SwifterSwift: hex #FFCDD2 public static let red100 = Color(hex: 0xFFCDD2)! /// SwifterSwift: hex #EF9A9A public static let red200 = Color(hex: 0xEF9A9A)! /// SwifterSwift: hex #E57373 public static let red300 = Color(hex: 0xE57373)! /// SwifterSwift: hex #EF5350 public static let red400 = Color(hex: 0xEF5350)! /// SwifterSwift: hex #F44336 public static let red500 = Color(hex: 0xF44336)! /// SwifterSwift: hex #E53935 public static let red600 = Color(hex: 0xE53935)! /// SwifterSwift: hex #D32F2F public static let red700 = Color(hex: 0xD32F2F)! /// SwifterSwift: hex #C62828 public static let red800 = Color(hex: 0xC62828)! /// SwifterSwift: hex #B71C1C public static let red900 = Color(hex: 0xB71C1C)! /// SwifterSwift: hex #FF8A80 public static let redA100 = Color(hex: 0xFF8A80)! /// SwifterSwift: hex #FF5252 public static let redA200 = Color(hex: 0xFF5252)! /// SwifterSwift: hex #FF1744 public static let redA400 = Color(hex: 0xFF1744)! /// SwifterSwift: hex #D50000 public static let redA700 = Color(hex: 0xD50000)! /// SwifterSwift: color pink500 public static let pink = pink500 /// SwifterSwift: hex #FCE4EC public static let pink50 = Color(hex: 0xFCE4EC)! /// SwifterSwift: hex #F8BBD0 public static let pink100 = Color(hex: 0xF8BBD0)! /// SwifterSwift: hex #F48FB1 public static let pink200 = Color(hex: 0xF48FB1)! /// SwifterSwift: hex #F06292 public static let pink300 = Color(hex: 0xF06292)! /// SwifterSwift: hex #EC407A public static let pink400 = Color(hex: 0xEC407A)! /// SwifterSwift: hex #E91E63 public static let pink500 = Color(hex: 0xE91E63)! /// SwifterSwift: hex #D81B60 public static let pink600 = Color(hex: 0xD81B60)! /// SwifterSwift: hex #C2185B public static let pink700 = Color(hex: 0xC2185B)! /// SwifterSwift: hex #AD1457 public static let pink800 = Color(hex: 0xAD1457)! /// SwifterSwift: hex #880E4F public static let pink900 = Color(hex: 0x880E4F)! /// SwifterSwift: hex #FF80AB public static let pinkA100 = Color(hex: 0xFF80AB)! /// SwifterSwift: hex #FF4081 public static let pinkA200 = Color(hex: 0xFF4081)! /// SwifterSwift: hex #F50057 public static let pinkA400 = Color(hex: 0xF50057)! /// SwifterSwift: hex #C51162 public static let pinkA700 = Color(hex: 0xC51162)! /// SwifterSwift: color purple500 public static let purple = purple500 /// SwifterSwift: hex #F3E5F5 public static let purple50 = Color(hex: 0xF3E5F5)! /// SwifterSwift: hex #E1BEE7 public static let purple100 = Color(hex: 0xE1BEE7)! /// SwifterSwift: hex #CE93D8 public static let purple200 = Color(hex: 0xCE93D8)! /// SwifterSwift: hex #BA68C8 public static let purple300 = Color(hex: 0xBA68C8)! /// SwifterSwift: hex #AB47BC public static let purple400 = Color(hex: 0xAB47BC)! /// SwifterSwift: hex #9C27B0 public static let purple500 = Color(hex: 0x9C27B0)! /// SwifterSwift: hex #8E24AA public static let purple600 = Color(hex: 0x8E24AA)! /// SwifterSwift: hex #7B1FA2 public static let purple700 = Color(hex: 0x7B1FA2)! /// SwifterSwift: hex #6A1B9A public static let purple800 = Color(hex: 0x6A1B9A)! /// SwifterSwift: hex #4A148C public static let purple900 = Color(hex: 0x4A148C)! /// SwifterSwift: hex #EA80FC public static let purpleA100 = Color(hex: 0xEA80FC)! /// SwifterSwift: hex #E040FB public static let purpleA200 = Color(hex: 0xE040FB)! /// SwifterSwift: hex #D500F9 public static let purpleA400 = Color(hex: 0xD500F9)! /// SwifterSwift: hex #AA00FF public static let purpleA700 = Color(hex: 0xAA00FF)! /// SwifterSwift: color deepPurple500 public static let deepPurple = deepPurple500 /// SwifterSwift: hex #EDE7F6 public static let deepPurple50 = Color(hex: 0xEDE7F6)! /// SwifterSwift: hex #D1C4E9 public static let deepPurple100 = Color(hex: 0xD1C4E9)! /// SwifterSwift: hex #B39DDB public static let deepPurple200 = Color(hex: 0xB39DDB)! /// SwifterSwift: hex #9575CD public static let deepPurple300 = Color(hex: 0x9575CD)! /// SwifterSwift: hex #7E57C2 public static let deepPurple400 = Color(hex: 0x7E57C2)! /// SwifterSwift: hex #673AB7 public static let deepPurple500 = Color(hex: 0x673AB7)! /// SwifterSwift: hex #5E35B1 public static let deepPurple600 = Color(hex: 0x5E35B1)! /// SwifterSwift: hex #512DA8 public static let deepPurple700 = Color(hex: 0x512DA8)! /// SwifterSwift: hex #4527A0 public static let deepPurple800 = Color(hex: 0x4527A0)! /// SwifterSwift: hex #311B92 public static let deepPurple900 = Color(hex: 0x311B92)! /// SwifterSwift: hex #B388FF public static let deepPurpleA100 = Color(hex: 0xB388FF)! /// SwifterSwift: hex #7C4DFF public static let deepPurpleA200 = Color(hex: 0x7C4DFF)! /// SwifterSwift: hex #651FFF public static let deepPurpleA400 = Color(hex: 0x651FFF)! /// SwifterSwift: hex #6200EA public static let deepPurpleA700 = Color(hex: 0x6200EA)! /// SwifterSwift: color indigo500 public static let indigo = indigo500 /// SwifterSwift: hex #E8EAF6 public static let indigo50 = Color(hex: 0xE8EAF6)! /// SwifterSwift: hex #C5CAE9 public static let indigo100 = Color(hex: 0xC5CAE9)! /// SwifterSwift: hex #9FA8DA public static let indigo200 = Color(hex: 0x9FA8DA)! /// SwifterSwift: hex #7986CB public static let indigo300 = Color(hex: 0x7986CB)! /// SwifterSwift: hex #5C6BC0 public static let indigo400 = Color(hex: 0x5C6BC0)! /// SwifterSwift: hex #3F51B5 public static let indigo500 = Color(hex: 0x3F51B5)! /// SwifterSwift: hex #3949AB public static let indigo600 = Color(hex: 0x3949AB)! /// SwifterSwift: hex #303F9F public static let indigo700 = Color(hex: 0x303F9F)! /// SwifterSwift: hex #283593 public static let indigo800 = Color(hex: 0x283593)! /// SwifterSwift: hex #1A237E public static let indigo900 = Color(hex: 0x1A237E)! /// SwifterSwift: hex #8C9EFF public static let indigoA100 = Color(hex: 0x8C9EFF)! /// SwifterSwift: hex #536DFE public static let indigoA200 = Color(hex: 0x536DFE)! /// SwifterSwift: hex #3D5AFE public static let indigoA400 = Color(hex: 0x3D5AFE)! /// SwifterSwift: hex #304FFE public static let indigoA700 = Color(hex: 0x304FFE)! /// SwifterSwift: color blue500 public static let blue = blue500 /// SwifterSwift: hex #E3F2FD public static let blue50 = Color(hex: 0xE3F2FD)! /// SwifterSwift: hex #BBDEFB public static let blue100 = Color(hex: 0xBBDEFB)! /// SwifterSwift: hex #90CAF9 public static let blue200 = Color(hex: 0x90CAF9)! /// SwifterSwift: hex #64B5F6 public static let blue300 = Color(hex: 0x64B5F6)! /// SwifterSwift: hex #42A5F5 public static let blue400 = Color(hex: 0x42A5F5)! /// SwifterSwift: hex #2196F3 public static let blue500 = Color(hex: 0x2196F3)! /// SwifterSwift: hex #1E88E5 public static let blue600 = Color(hex: 0x1E88E5)! /// SwifterSwift: hex #1976D2 public static let blue700 = Color(hex: 0x1976D2)! /// SwifterSwift: hex #1565C0 public static let blue800 = Color(hex: 0x1565C0)! /// SwifterSwift: hex #0D47A1 public static let blue900 = Color(hex: 0x0D47A1)! /// SwifterSwift: hex #82B1FF public static let blueA100 = Color(hex: 0x82B1FF)! /// SwifterSwift: hex #448AFF public static let blueA200 = Color(hex: 0x448AFF)! /// SwifterSwift: hex #2979FF public static let blueA400 = Color(hex: 0x2979FF)! /// SwifterSwift: hex #2962FF public static let blueA700 = Color(hex: 0x2962FF)! /// SwifterSwift: color lightBlue500 public static let lightBlue = lightBlue500 /// SwifterSwift: hex #E1F5FE public static let lightBlue50 = Color(hex: 0xE1F5FE)! /// SwifterSwift: hex #B3E5FC public static let lightBlue100 = Color(hex: 0xB3E5FC)! /// SwifterSwift: hex #81D4FA public static let lightBlue200 = Color(hex: 0x81D4FA)! /// SwifterSwift: hex #4FC3F7 public static let lightBlue300 = Color(hex: 0x4FC3F7)! /// SwifterSwift: hex #29B6F6 public static let lightBlue400 = Color(hex: 0x29B6F6)! /// SwifterSwift: hex #03A9F4 public static let lightBlue500 = Color(hex: 0x03A9F4)! /// SwifterSwift: hex #039BE5 public static let lightBlue600 = Color(hex: 0x039BE5)! /// SwifterSwift: hex #0288D1 public static let lightBlue700 = Color(hex: 0x0288D1)! /// SwifterSwift: hex #0277BD public static let lightBlue800 = Color(hex: 0x0277BD)! /// SwifterSwift: hex #01579B public static let lightBlue900 = Color(hex: 0x01579B)! /// SwifterSwift: hex #80D8FF public static let lightBlueA100 = Color(hex: 0x80D8FF)! /// SwifterSwift: hex #40C4FF public static let lightBlueA200 = Color(hex: 0x40C4FF)! /// SwifterSwift: hex #00B0FF public static let lightBlueA400 = Color(hex: 0x00B0FF)! /// SwifterSwift: hex #0091EA public static let lightBlueA700 = Color(hex: 0x0091EA)! /// SwifterSwift: color cyan500 public static let cyan = cyan500 /// SwifterSwift: hex #E0F7FA public static let cyan50 = Color(hex: 0xE0F7FA)! /// SwifterSwift: hex #B2EBF2 public static let cyan100 = Color(hex: 0xB2EBF2)! /// SwifterSwift: hex #80DEEA public static let cyan200 = Color(hex: 0x80DEEA)! /// SwifterSwift: hex #4DD0E1 public static let cyan300 = Color(hex: 0x4DD0E1)! /// SwifterSwift: hex #26C6DA public static let cyan400 = Color(hex: 0x26C6DA)! /// SwifterSwift: hex #00BCD4 public static let cyan500 = Color(hex: 0x00BCD4)! /// SwifterSwift: hex #00ACC1 public static let cyan600 = Color(hex: 0x00ACC1)! /// SwifterSwift: hex #0097A7 public static let cyan700 = Color(hex: 0x0097A7)! /// SwifterSwift: hex #00838F public static let cyan800 = Color(hex: 0x00838F)! /// SwifterSwift: hex #006064 public static let cyan900 = Color(hex: 0x006064)! /// SwifterSwift: hex #84FFFF public static let cyanA100 = Color(hex: 0x84FFFF)! /// SwifterSwift: hex #18FFFF public static let cyanA200 = Color(hex: 0x18FFFF)! /// SwifterSwift: hex #00E5FF public static let cyanA400 = Color(hex: 0x00E5FF)! /// SwifterSwift: hex #00B8D4 public static let cyanA700 = Color(hex: 0x00B8D4)! /// SwifterSwift: color teal500 public static let teal = teal500 /// SwifterSwift: hex #E0F2F1 public static let teal50 = Color(hex: 0xE0F2F1)! /// SwifterSwift: hex #B2DFDB public static let teal100 = Color(hex: 0xB2DFDB)! /// SwifterSwift: hex #80CBC4 public static let teal200 = Color(hex: 0x80CBC4)! /// SwifterSwift: hex #4DB6AC public static let teal300 = Color(hex: 0x4DB6AC)! /// SwifterSwift: hex #26A69A public static let teal400 = Color(hex: 0x26A69A)! /// SwifterSwift: hex #009688 public static let teal500 = Color(hex: 0x009688)! /// SwifterSwift: hex #00897B public static let teal600 = Color(hex: 0x00897B)! /// SwifterSwift: hex #00796B public static let teal700 = Color(hex: 0x00796B)! /// SwifterSwift: hex #00695C public static let teal800 = Color(hex: 0x00695C)! /// SwifterSwift: hex #004D40 public static let teal900 = Color(hex: 0x004D40)! /// SwifterSwift: hex #A7FFEB public static let tealA100 = Color(hex: 0xA7FFEB)! /// SwifterSwift: hex #64FFDA public static let tealA200 = Color(hex: 0x64FFDA)! /// SwifterSwift: hex #1DE9B6 public static let tealA400 = Color(hex: 0x1DE9B6)! /// SwifterSwift: hex #00BFA5 public static let tealA700 = Color(hex: 0x00BFA5)! /// SwifterSwift: color green500 public static let green = green500 /// SwifterSwift: hex #E8F5E9 public static let green50 = Color(hex: 0xE8F5E9)! /// SwifterSwift: hex #C8E6C9 public static let green100 = Color(hex: 0xC8E6C9)! /// SwifterSwift: hex #A5D6A7 public static let green200 = Color(hex: 0xA5D6A7)! /// SwifterSwift: hex #81C784 public static let green300 = Color(hex: 0x81C784)! /// SwifterSwift: hex #66BB6A public static let green400 = Color(hex: 0x66BB6A)! /// SwifterSwift: hex #4CAF50 public static let green500 = Color(hex: 0x4CAF50)! /// SwifterSwift: hex #43A047 public static let green600 = Color(hex: 0x43A047)! /// SwifterSwift: hex #388E3C public static let green700 = Color(hex: 0x388E3C)! /// SwifterSwift: hex #2E7D32 public static let green800 = Color(hex: 0x2E7D32)! /// SwifterSwift: hex #1B5E20 public static let green900 = Color(hex: 0x1B5E20)! /// SwifterSwift: hex #B9F6CA public static let greenA100 = Color(hex: 0xB9F6CA)! /// SwifterSwift: hex #69F0AE public static let greenA200 = Color(hex: 0x69F0AE)! /// SwifterSwift: hex #00E676 public static let greenA400 = Color(hex: 0x00E676)! /// SwifterSwift: hex #00C853 public static let greenA700 = Color(hex: 0x00C853)! /// SwifterSwift: color lightGreen500 public static let lightGreen = lightGreen500 /// SwifterSwift: hex #F1F8E9 public static let lightGreen50 = Color(hex: 0xF1F8E9)! /// SwifterSwift: hex #DCEDC8 public static let lightGreen100 = Color(hex: 0xDCEDC8)! /// SwifterSwift: hex #C5E1A5 public static let lightGreen200 = Color(hex: 0xC5E1A5)! /// SwifterSwift: hex #AED581 public static let lightGreen300 = Color(hex: 0xAED581)! /// SwifterSwift: hex #9CCC65 public static let lightGreen400 = Color(hex: 0x9CCC65)! /// SwifterSwift: hex #8BC34A public static let lightGreen500 = Color(hex: 0x8BC34A)! /// SwifterSwift: hex #7CB342 public static let lightGreen600 = Color(hex: 0x7CB342)! /// SwifterSwift: hex #689F38 public static let lightGreen700 = Color(hex: 0x689F38)! /// SwifterSwift: hex #558B2F public static let lightGreen800 = Color(hex: 0x558B2F)! /// SwifterSwift: hex #33691E public static let lightGreen900 = Color(hex: 0x33691E)! /// SwifterSwift: hex #CCFF90 public static let lightGreenA100 = Color(hex: 0xCCFF90)! /// SwifterSwift: hex #B2FF59 public static let lightGreenA200 = Color(hex: 0xB2FF59)! /// SwifterSwift: hex #76FF03 public static let lightGreenA400 = Color(hex: 0x76FF03)! /// SwifterSwift: hex #64DD17 public static let lightGreenA700 = Color(hex: 0x64DD17)! /// SwifterSwift: color lime500 public static let lime = lime500 /// SwifterSwift: hex #F9FBE7 public static let lime50 = Color(hex: 0xF9FBE7)! /// SwifterSwift: hex #F0F4C3 public static let lime100 = Color(hex: 0xF0F4C3)! /// SwifterSwift: hex #E6EE9C public static let lime200 = Color(hex: 0xE6EE9C)! /// SwifterSwift: hex #DCE775 public static let lime300 = Color(hex: 0xDCE775)! /// SwifterSwift: hex #D4E157 public static let lime400 = Color(hex: 0xD4E157)! /// SwifterSwift: hex #CDDC39 public static let lime500 = Color(hex: 0xCDDC39)! /// SwifterSwift: hex #C0CA33 public static let lime600 = Color(hex: 0xC0CA33)! /// SwifterSwift: hex #AFB42B public static let lime700 = Color(hex: 0xAFB42B)! /// SwifterSwift: hex #9E9D24 public static let lime800 = Color(hex: 0x9E9D24)! /// SwifterSwift: hex #827717 public static let lime900 = Color(hex: 0x827717)! /// SwifterSwift: hex #F4FF81 public static let limeA100 = Color(hex: 0xF4FF81)! /// SwifterSwift: hex #EEFF41 public static let limeA200 = Color(hex: 0xEEFF41)! /// SwifterSwift: hex #C6FF00 public static let limeA400 = Color(hex: 0xC6FF00)! /// SwifterSwift: hex #AEEA00 public static let limeA700 = Color(hex: 0xAEEA00)! /// SwifterSwift: color yellow500 public static let yellow = yellow500 /// SwifterSwift: hex #FFFDE7 public static let yellow50 = Color(hex: 0xFFFDE7)! /// SwifterSwift: hex #FFF9C4 public static let yellow100 = Color(hex: 0xFFF9C4)! /// SwifterSwift: hex #FFF59D public static let yellow200 = Color(hex: 0xFFF59D)! /// SwifterSwift: hex #FFF176 public static let yellow300 = Color(hex: 0xFFF176)! /// SwifterSwift: hex #FFEE58 public static let yellow400 = Color(hex: 0xFFEE58)! /// SwifterSwift: hex #FFEB3B public static let yellow500 = Color(hex: 0xFFEB3B)! /// SwifterSwift: hex #FDD835 public static let yellow600 = Color(hex: 0xFDD835)! /// SwifterSwift: hex #FBC02D public static let yellow700 = Color(hex: 0xFBC02D)! /// SwifterSwift: hex #F9A825 public static let yellow800 = Color(hex: 0xF9A825)! /// SwifterSwift: hex #F57F17 public static let yellow900 = Color(hex: 0xF57F17)! /// SwifterSwift: hex #FFFF8D public static let yellowA100 = Color(hex: 0xFFFF8D)! /// SwifterSwift: hex #FFFF00 public static let yellowA200 = Color(hex: 0xFFFF00)! /// SwifterSwift: hex #FFEA00 public static let yellowA400 = Color(hex: 0xFFEA00)! /// SwifterSwift: hex #FFD600 public static let yellowA700 = Color(hex: 0xFFD600)! /// SwifterSwift: color amber500 public static let amber = amber500 /// SwifterSwift: hex #FFF8E1 public static let amber50 = Color(hex: 0xFFF8E1)! /// SwifterSwift: hex #FFECB3 public static let amber100 = Color(hex: 0xFFECB3)! /// SwifterSwift: hex #FFE082 public static let amber200 = Color(hex: 0xFFE082)! /// SwifterSwift: hex #FFD54F public static let amber300 = Color(hex: 0xFFD54F)! /// SwifterSwift: hex #FFCA28 public static let amber400 = Color(hex: 0xFFCA28)! /// SwifterSwift: hex #FFC107 public static let amber500 = Color(hex: 0xFFC107)! /// SwifterSwift: hex #FFB300 public static let amber600 = Color(hex: 0xFFB300)! /// SwifterSwift: hex #FFA000 public static let amber700 = Color(hex: 0xFFA000)! /// SwifterSwift: hex #FF8F00 public static let amber800 = Color(hex: 0xFF8F00)! /// SwifterSwift: hex #FF6F00 public static let amber900 = Color(hex: 0xFF6F00)! /// SwifterSwift: hex #FFE57F public static let amberA100 = Color(hex: 0xFFE57F)! /// SwifterSwift: hex #FFD740 public static let amberA200 = Color(hex: 0xFFD740)! /// SwifterSwift: hex #FFC400 public static let amberA400 = Color(hex: 0xFFC400)! /// SwifterSwift: hex #FFAB00 public static let amberA700 = Color(hex: 0xFFAB00)! /// SwifterSwift: color orange500 public static let orange = orange500 /// SwifterSwift: hex #FFF3E0 public static let orange50 = Color(hex: 0xFFF3E0)! /// SwifterSwift: hex #FFE0B2 public static let orange100 = Color(hex: 0xFFE0B2)! /// SwifterSwift: hex #FFCC80 public static let orange200 = Color(hex: 0xFFCC80)! /// SwifterSwift: hex #FFB74D public static let orange300 = Color(hex: 0xFFB74D)! /// SwifterSwift: hex #FFA726 public static let orange400 = Color(hex: 0xFFA726)! /// SwifterSwift: hex #FF9800 public static let orange500 = Color(hex: 0xFF9800)! /// SwifterSwift: hex #FB8C00 public static let orange600 = Color(hex: 0xFB8C00)! /// SwifterSwift: hex #F57C00 public static let orange700 = Color(hex: 0xF57C00)! /// SwifterSwift: hex #EF6C00 public static let orange800 = Color(hex: 0xEF6C00)! /// SwifterSwift: hex #E65100 public static let orange900 = Color(hex: 0xE65100)! /// SwifterSwift: hex #FFD180 public static let orangeA100 = Color(hex: 0xFFD180)! /// SwifterSwift: hex #FFAB40 public static let orangeA200 = Color(hex: 0xFFAB40)! /// SwifterSwift: hex #FF9100 public static let orangeA400 = Color(hex: 0xFF9100)! /// SwifterSwift: hex #FF6D00 public static let orangeA700 = Color(hex: 0xFF6D00)! /// SwifterSwift: color deepOrange500 public static let deepOrange = deepOrange500 /// SwifterSwift: hex #FBE9E7 public static let deepOrange50 = Color(hex: 0xFBE9E7)! /// SwifterSwift: hex #FFCCBC public static let deepOrange100 = Color(hex: 0xFFCCBC)! /// SwifterSwift: hex #FFAB91 public static let deepOrange200 = Color(hex: 0xFFAB91)! /// SwifterSwift: hex #FF8A65 public static let deepOrange300 = Color(hex: 0xFF8A65)! /// SwifterSwift: hex #FF7043 public static let deepOrange400 = Color(hex: 0xFF7043)! /// SwifterSwift: hex #FF5722 public static let deepOrange500 = Color(hex: 0xFF5722)! /// SwifterSwift: hex #F4511E public static let deepOrange600 = Color(hex: 0xF4511E)! /// SwifterSwift: hex #E64A19 public static let deepOrange700 = Color(hex: 0xE64A19)! /// SwifterSwift: hex #D84315 public static let deepOrange800 = Color(hex: 0xD84315)! /// SwifterSwift: hex #BF360C public static let deepOrange900 = Color(hex: 0xBF360C)! /// SwifterSwift: hex #FF9E80 public static let deepOrangeA100 = Color(hex: 0xFF9E80)! /// SwifterSwift: hex #FF6E40 public static let deepOrangeA200 = Color(hex: 0xFF6E40)! /// SwifterSwift: hex #FF3D00 public static let deepOrangeA400 = Color(hex: 0xFF3D00)! /// SwifterSwift: hex #DD2C00 public static let deepOrangeA700 = Color(hex: 0xDD2C00)! /// SwifterSwift: color brown500 public static let brown = brown500 /// SwifterSwift: hex #EFEBE9 public static let brown50 = Color(hex: 0xEFEBE9)! /// SwifterSwift: hex #D7CCC8 public static let brown100 = Color(hex: 0xD7CCC8)! /// SwifterSwift: hex #BCAAA4 public static let brown200 = Color(hex: 0xBCAAA4)! /// SwifterSwift: hex #A1887F public static let brown300 = Color(hex: 0xA1887F)! /// SwifterSwift: hex #8D6E63 public static let brown400 = Color(hex: 0x8D6E63)! /// SwifterSwift: hex #795548 public static let brown500 = Color(hex: 0x795548)! /// SwifterSwift: hex #6D4C41 public static let brown600 = Color(hex: 0x6D4C41)! /// SwifterSwift: hex #5D4037 public static let brown700 = Color(hex: 0x5D4037)! /// SwifterSwift: hex #4E342E public static let brown800 = Color(hex: 0x4E342E)! /// SwifterSwift: hex #3E2723 public static let brown900 = Color(hex: 0x3E2723)! /// SwifterSwift: color grey500 public static let grey = grey500 /// SwifterSwift: hex #FAFAFA public static let grey50 = Color(hex: 0xFAFAFA)! /// SwifterSwift: hex #F5F5F5 public static let grey100 = Color(hex: 0xF5F5F5)! /// SwifterSwift: hex #EEEEEE public static let grey200 = Color(hex: 0xEEEEEE)! /// SwifterSwift: hex #E0E0E0 public static let grey300 = Color(hex: 0xE0E0E0)! /// SwifterSwift: hex #BDBDBD public static let grey400 = Color(hex: 0xBDBDBD)! /// SwifterSwift: hex #9E9E9E public static let grey500 = Color(hex: 0x9E9E9E)! /// SwifterSwift: hex #757575 public static let grey600 = Color(hex: 0x757575)! /// SwifterSwift: hex #616161 public static let grey700 = Color(hex: 0x616161)! /// SwifterSwift: hex #424242 public static let grey800 = Color(hex: 0x424242)! /// SwifterSwift: hex #212121 public static let grey900 = Color(hex: 0x212121)! /// SwifterSwift: color blueGrey500 public static let blueGrey = blueGrey500 /// SwifterSwift: hex #ECEFF1 public static let blueGrey50 = Color(hex: 0xECEFF1)! /// SwifterSwift: hex #CFD8DC public static let blueGrey100 = Color(hex: 0xCFD8DC)! /// SwifterSwift: hex #B0BEC5 public static let blueGrey200 = Color(hex: 0xB0BEC5)! /// SwifterSwift: hex #90A4AE public static let blueGrey300 = Color(hex: 0x90A4AE)! /// SwifterSwift: hex #78909C public static let blueGrey400 = Color(hex: 0x78909C)! /// SwifterSwift: hex #607D8B public static let blueGrey500 = Color(hex: 0x607D8B)! /// SwifterSwift: hex #546E7A public static let blueGrey600 = Color(hex: 0x546E7A)! /// SwifterSwift: hex #455A64 public static let blueGrey700 = Color(hex: 0x455A64)! /// SwifterSwift: hex #37474F public static let blueGrey800 = Color(hex: 0x37474F)! /// SwifterSwift: hex #263238 public static let blueGrey900 = Color(hex: 0x263238)! /// SwifterSwift: hex #000000 public static let black = Color(hex: 0x000000)! /// SwifterSwift: hex #FFFFFF public static let white = Color(hex: 0xFFFFFF)! } } // MARK: - CSS colors public extension Color { /// SwifterSwift: CSS colors. struct CSS { // http://www.w3schools.com/colors/colors_names.asp private init() {} /// SwifterSwift: hex #F0F8FF public static let aliceBlue = Color(hex: 0xF0F8FF)! /// SwifterSwift: hex #FAEBD7 public static let antiqueWhite = Color(hex: 0xFAEBD7)! /// SwifterSwift: hex #00FFFF public static let aqua = Color(hex: 0x00FFFF)! /// SwifterSwift: hex #7FFFD4 public static let aquamarine = Color(hex: 0x7FFFD4)! /// SwifterSwift: hex #F0FFFF public static let azure = Color(hex: 0xF0FFFF)! /// SwifterSwift: hex #F5F5DC public static let beige = Color(hex: 0xF5F5DC)! /// SwifterSwift: hex #FFE4C4 public static let bisque = Color(hex: 0xFFE4C4)! /// SwifterSwift: hex #000000 public static let black = Color(hex: 0x000000)! /// SwifterSwift: hex #FFEBCD public static let blanchedAlmond = Color(hex: 0xFFEBCD)! /// SwifterSwift: hex #0000FF public static let blue = Color(hex: 0x0000FF)! /// SwifterSwift: hex #8A2BE2 public static let blueViolet = Color(hex: 0x8A2BE2)! /// SwifterSwift: hex #A52A2A public static let brown = Color(hex: 0xA52A2A)! /// SwifterSwift: hex #DEB887 public static let burlyWood = Color(hex: 0xDEB887)! /// SwifterSwift: hex #5F9EA0 public static let cadetBlue = Color(hex: 0x5F9EA0)! /// SwifterSwift: hex #7FFF00 public static let chartreuse = Color(hex: 0x7FFF00)! /// SwifterSwift: hex #D2691E public static let chocolate = Color(hex: 0xD2691E)! /// SwifterSwift: hex #FF7F50 public static let coral = Color(hex: 0xFF7F50)! /// SwifterSwift: hex #6495ED public static let cornflowerBlue = Color(hex: 0x6495ED)! /// SwifterSwift: hex #FFF8DC public static let cornsilk = Color(hex: 0xFFF8DC)! /// SwifterSwift: hex #DC143C public static let crimson = Color(hex: 0xDC143C)! /// SwifterSwift: hex #00FFFF public static let cyan = Color(hex: 0x00FFFF)! /// SwifterSwift: hex #00008B public static let darkBlue = Color(hex: 0x00008B)! /// SwifterSwift: hex #008B8B public static let darkCyan = Color(hex: 0x008B8B)! /// SwifterSwift: hex #B8860B public static let darkGoldenRod = Color(hex: 0xB8860B)! /// SwifterSwift: hex #A9A9A9 public static let darkGray = Color(hex: 0xA9A9A9)! /// SwifterSwift: hex #A9A9A9 public static let darkGrey = Color(hex: 0xA9A9A9)! /// SwifterSwift: hex #006400 public static let darkGreen = Color(hex: 0x006400)! /// SwifterSwift: hex #BDB76B public static let darkKhaki = Color(hex: 0xBDB76B)! /// SwifterSwift: hex #8B008B public static let darkMagenta = Color(hex: 0x8B008B)! /// SwifterSwift: hex #556B2F public static let darkOliveGreen = Color(hex: 0x556B2F)! /// SwifterSwift: hex #FF8C00 public static let darkOrange = Color(hex: 0xFF8C00)! /// SwifterSwift: hex #9932CC public static let darkOrchid = Color(hex: 0x9932CC)! /// SwifterSwift: hex #8B0000 public static let darkRed = Color(hex: 0x8B0000)! /// SwifterSwift: hex #E9967A public static let darkSalmon = Color(hex: 0xE9967A)! /// SwifterSwift: hex #8FBC8F public static let darkSeaGreen = Color(hex: 0x8FBC8F)! /// SwifterSwift: hex #483D8B public static let darkSlateBlue = Color(hex: 0x483D8B)! /// SwifterSwift: hex #2F4F4F public static let darkSlateGray = Color(hex: 0x2F4F4F)! /// SwifterSwift: hex #2F4F4F public static let darkSlateGrey = Color(hex: 0x2F4F4F)! /// SwifterSwift: hex #00CED1 public static let darkTurquoise = Color(hex: 0x00CED1)! /// SwifterSwift: hex #9400D3 public static let darkViolet = Color(hex: 0x9400D3)! /// SwifterSwift: hex #FF1493 public static let deepPink = Color(hex: 0xFF1493)! /// SwifterSwift: hex #00BFFF public static let deepSkyBlue = Color(hex: 0x00BFFF)! /// SwifterSwift: hex #696969 public static let dimGray = Color(hex: 0x696969)! /// SwifterSwift: hex #696969 public static let dimGrey = Color(hex: 0x696969)! /// SwifterSwift: hex #1E90FF public static let dodgerBlue = Color(hex: 0x1E90FF)! /// SwifterSwift: hex #B22222 public static let fireBrick = Color(hex: 0xB22222)! /// SwifterSwift: hex #FFFAF0 public static let floralWhite = Color(hex: 0xFFFAF0)! /// SwifterSwift: hex #228B22 public static let forestGreen = Color(hex: 0x228B22)! /// SwifterSwift: hex #FF00FF public static let fuchsia = Color(hex: 0xFF00FF)! /// SwifterSwift: hex #DCDCDC public static let gainsboro = Color(hex: 0xDCDCDC)! /// SwifterSwift: hex #F8F8FF public static let ghostWhite = Color(hex: 0xF8F8FF)! /// SwifterSwift: hex #FFD700 public static let gold = Color(hex: 0xFFD700)! /// SwifterSwift: hex #DAA520 public static let goldenRod = Color(hex: 0xDAA520)! /// SwifterSwift: hex #808080 public static let gray = Color(hex: 0x808080)! /// SwifterSwift: hex #808080 public static let grey = Color(hex: 0x808080)! /// SwifterSwift: hex #008000 public static let green = Color(hex: 0x008000)! /// SwifterSwift: hex #ADFF2F public static let greenYellow = Color(hex: 0xADFF2F)! /// SwifterSwift: hex #F0FFF0 public static let honeyDew = Color(hex: 0xF0FFF0)! /// SwifterSwift: hex #FF69B4 public static let hotPink = Color(hex: 0xFF69B4)! /// SwifterSwift: hex #CD5C5C public static let indianRed = Color(hex: 0xCD5C5C)! /// SwifterSwift: hex #4B0082 public static let indigo = Color(hex: 0x4B0082)! /// SwifterSwift: hex #FFFFF0 public static let ivory = Color(hex: 0xFFFFF0)! /// SwifterSwift: hex #F0E68C public static let khaki = Color(hex: 0xF0E68C)! /// SwifterSwift: hex #E6E6FA public static let lavender = Color(hex: 0xE6E6FA)! /// SwifterSwift: hex #FFF0F5 public static let lavenderBlush = Color(hex: 0xFFF0F5)! /// SwifterSwift: hex #7CFC00 public static let lawnGreen = Color(hex: 0x7CFC00)! /// SwifterSwift: hex #FFFACD public static let lemonChiffon = Color(hex: 0xFFFACD)! /// SwifterSwift: hex #ADD8E6 public static let lightBlue = Color(hex: 0xADD8E6)! /// SwifterSwift: hex #F08080 public static let lightCoral = Color(hex: 0xF08080)! /// SwifterSwift: hex #E0FFFF public static let lightCyan = Color(hex: 0xE0FFFF)! /// SwifterSwift: hex #FAFAD2 public static let lightGoldenRodYellow = Color(hex: 0xFAFAD2)! /// SwifterSwift: hex #D3D3D3 public static let lightGray = Color(hex: 0xD3D3D3)! /// SwifterSwift: hex #D3D3D3 public static let lightGrey = Color(hex: 0xD3D3D3)! /// SwifterSwift: hex #90EE90 public static let lightGreen = Color(hex: 0x90EE90)! /// SwifterSwift: hex #FFB6C1 public static let lightPink = Color(hex: 0xFFB6C1)! /// SwifterSwift: hex #FFA07A public static let lightSalmon = Color(hex: 0xFFA07A)! /// SwifterSwift: hex #20B2AA public static let lightSeaGreen = Color(hex: 0x20B2AA)! /// SwifterSwift: hex #87CEFA public static let lightSkyBlue = Color(hex: 0x87CEFA)! /// SwifterSwift: hex #778899 public static let lightSlateGray = Color(hex: 0x778899)! /// SwifterSwift: hex #778899 public static let lightSlateGrey = Color(hex: 0x778899)! /// SwifterSwift: hex #B0C4DE public static let lightSteelBlue = Color(hex: 0xB0C4DE)! /// SwifterSwift: hex #FFFFE0 public static let lightYellow = Color(hex: 0xFFFFE0)! /// SwifterSwift: hex #00FF00 public static let lime = Color(hex: 0x00FF00)! /// SwifterSwift: hex #32CD32 public static let limeGreen = Color(hex: 0x32CD32)! /// SwifterSwift: hex #FAF0E6 public static let linen = Color(hex: 0xFAF0E6)! /// SwifterSwift: hex #FF00FF public static let magenta = Color(hex: 0xFF00FF)! /// SwifterSwift: hex #800000 public static let maroon = Color(hex: 0x800000)! /// SwifterSwift: hex #66CDAA public static let mediumAquaMarine = Color(hex: 0x66CDAA)! /// SwifterSwift: hex #0000CD public static let mediumBlue = Color(hex: 0x0000CD)! /// SwifterSwift: hex #BA55D3 public static let mediumOrchid = Color(hex: 0xBA55D3)! /// SwifterSwift: hex #9370DB public static let mediumPurple = Color(hex: 0x9370DB)! /// SwifterSwift: hex #3CB371 public static let mediumSeaGreen = Color(hex: 0x3CB371)! /// SwifterSwift: hex #7B68EE public static let mediumSlateBlue = Color(hex: 0x7B68EE)! /// SwifterSwift: hex #00FA9A public static let mediumSpringGreen = Color(hex: 0x00FA9A)! /// SwifterSwift: hex #48D1CC public static let mediumTurquoise = Color(hex: 0x48D1CC)! /// SwifterSwift: hex #C71585 public static let mediumVioletRed = Color(hex: 0xC71585)! /// SwifterSwift: hex #191970 public static let midnightBlue = Color(hex: 0x191970)! /// SwifterSwift: hex #F5FFFA public static let mintCream = Color(hex: 0xF5FFFA)! /// SwifterSwift: hex #FFE4E1 public static let mistyRose = Color(hex: 0xFFE4E1)! /// SwifterSwift: hex #FFE4B5 public static let moccasin = Color(hex: 0xFFE4B5)! /// SwifterSwift: hex #FFDEAD public static let navajoWhite = Color(hex: 0xFFDEAD)! /// SwifterSwift: hex #000080 public static let navy = Color(hex: 0x000080)! /// SwifterSwift: hex #FDF5E6 public static let oldLace = Color(hex: 0xFDF5E6)! /// SwifterSwift: hex #808000 public static let olive = Color(hex: 0x808000)! /// SwifterSwift: hex #6B8E23 public static let oliveDrab = Color(hex: 0x6B8E23)! /// SwifterSwift: hex #FFA500 public static let orange = Color(hex: 0xFFA500)! /// SwifterSwift: hex #FF4500 public static let orangeRed = Color(hex: 0xFF4500)! /// SwifterSwift: hex #DA70D6 public static let orchid = Color(hex: 0xDA70D6)! /// SwifterSwift: hex #EEE8AA public static let paleGoldenRod = Color(hex: 0xEEE8AA)! /// SwifterSwift: hex #98FB98 public static let paleGreen = Color(hex: 0x98FB98)! /// SwifterSwift: hex #AFEEEE public static let paleTurquoise = Color(hex: 0xAFEEEE)! /// SwifterSwift: hex #DB7093 public static let paleVioletRed = Color(hex: 0xDB7093)! /// SwifterSwift: hex #FFEFD5 public static let papayaWhip = Color(hex: 0xFFEFD5)! /// SwifterSwift: hex #FFDAB9 public static let peachPuff = Color(hex: 0xFFDAB9)! /// SwifterSwift: hex #CD853F public static let peru = Color(hex: 0xCD853F)! /// SwifterSwift: hex #FFC0CB public static let pink = Color(hex: 0xFFC0CB)! /// SwifterSwift: hex #DDA0DD public static let plum = Color(hex: 0xDDA0DD)! /// SwifterSwift: hex #B0E0E6 public static let powderBlue = Color(hex: 0xB0E0E6)! /// SwifterSwift: hex #800080 public static let purple = Color(hex: 0x800080)! /// SwifterSwift: hex #663399 public static let rebeccaPurple = Color(hex: 0x663399)! /// SwifterSwift: hex #FF0000 public static let red = Color(hex: 0xFF0000)! /// SwifterSwift: hex #BC8F8F public static let rosyBrown = Color(hex: 0xBC8F8F)! /// SwifterSwift: hex #4169E1 public static let royalBlue = Color(hex: 0x4169E1)! /// SwifterSwift: hex #8B4513 public static let saddleBrown = Color(hex: 0x8B4513)! /// SwifterSwift: hex #FA8072 public static let salmon = Color(hex: 0xFA8072)! /// SwifterSwift: hex #F4A460 public static let sandyBrown = Color(hex: 0xF4A460)! /// SwifterSwift: hex #2E8B57 public static let seaGreen = Color(hex: 0x2E8B57)! /// SwifterSwift: hex #FFF5EE public static let seaShell = Color(hex: 0xFFF5EE)! /// SwifterSwift: hex #A0522D public static let sienna = Color(hex: 0xA0522D)! /// SwifterSwift: hex #C0C0C0 public static let silver = Color(hex: 0xC0C0C0)! /// SwifterSwift: hex #87CEEB public static let skyBlue = Color(hex: 0x87CEEB)! /// SwifterSwift: hex #6A5ACD public static let slateBlue = Color(hex: 0x6A5ACD)! /// SwifterSwift: hex #708090 public static let slateGray = Color(hex: 0x708090)! /// SwifterSwift: hex #708090 public static let slateGrey = Color(hex: 0x708090)! /// SwifterSwift: hex #FFFAFA public static let snow = Color(hex: 0xFFFAFA)! /// SwifterSwift: hex #00FF7F public static let springGreen = Color(hex: 0x00FF7F)! /// SwifterSwift: hex #4682B4 public static let steelBlue = Color(hex: 0x4682B4)! /// SwifterSwift: hex #D2B48C public static let tan = Color(hex: 0xD2B48C)! /// SwifterSwift: hex #008080 public static let teal = Color(hex: 0x008080)! /// SwifterSwift: hex #D8BFD8 public static let thistle = Color(hex: 0xD8BFD8)! /// SwifterSwift: hex #FF6347 public static let tomato = Color(hex: 0xFF6347)! /// SwifterSwift: hex #40E0D0 public static let turquoise = Color(hex: 0x40E0D0)! /// SwifterSwift: hex #EE82EE public static let violet = Color(hex: 0xEE82EE)! /// SwifterSwift: hex #F5DEB3 public static let wheat = Color(hex: 0xF5DEB3)! /// SwifterSwift: hex #FFFFFF public static let white = Color(hex: 0xFFFFFF)! /// SwifterSwift: hex #F5F5F5 public static let whiteSmoke = Color(hex: 0xF5F5F5)! /// SwifterSwift: hex #FFFF00 public static let yellow = Color(hex: 0xFFFF00)! /// SwifterSwift: hex #9ACD32 public static let yellowGreen = Color(hex: 0x9ACD32)! } } // MARK: - Flat UI colors public extension Color { /// SwifterSwift: Flat UI colors struct FlatUI { // http://flatuicolors.com. /// SwifterSwift: hex #1ABC9C public static let turquoise = Color(hex: 0x1abc9c)! /// SwifterSwift: hex #16A085 public static let greenSea = Color(hex: 0x16a085)! /// SwifterSwift: hex #2ECC71 public static let emerald = Color(hex: 0x2ecc71)! /// SwifterSwift: hex #27AE60 public static let nephritis = Color(hex: 0x27ae60)! /// SwifterSwift: hex #3498DB public static let peterRiver = Color(hex: 0x3498db)! /// SwifterSwift: hex #2980B9 public static let belizeHole = Color(hex: 0x2980b9)! /// SwifterSwift: hex #9B59B6 public static let amethyst = Color(hex: 0x9b59b6)! /// SwifterSwift: hex #8E44AD public static let wisteria = Color(hex: 0x8e44ad)! /// SwifterSwift: hex #34495E public static let wetAsphalt = Color(hex: 0x34495e)! /// SwifterSwift: hex #2C3E50 public static let midnightBlue = Color(hex: 0x2c3e50)! /// SwifterSwift: hex #F1C40F public static let sunFlower = Color(hex: 0xf1c40f)! /// SwifterSwift: hex #F39C12 public static let flatOrange = Color(hex: 0xf39c12)! /// SwifterSwift: hex #E67E22 public static let carrot = Color(hex: 0xe67e22)! /// SwifterSwift: hex #D35400 public static let pumkin = Color(hex: 0xd35400)! /// SwifterSwift: hex #E74C3C public static let alizarin = Color(hex: 0xe74c3c)! /// SwifterSwift: hex #C0392B public static let pomegranate = Color(hex: 0xc0392b)! /// SwifterSwift: hex #ECF0F1 public static let clouds = Color(hex: 0xecf0f1)! /// SwifterSwift: hex #BDC3C7 public static let silver = Color(hex: 0xbdc3c7)! /// SwifterSwift: hex #7F8C8D public static let asbestos = Color(hex: 0x7f8c8d)! /// SwifterSwift: hex #95A5A6 public static let concerte = Color(hex: 0x95a5a6)! } // swiftlint:enable type_body_length } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/Shared/EdgeInsetsExtensions.swift ================================================ // // EdgeInsetsExtensions.swift // SwifterSwift // // Created by Guy Kogus on 03/01/2020. // Copyright © 2020 SwifterSwift // #if os(iOS) || os(tvOS) || os(watchOS) import UIKit /// SwifterSwift: EdgeInsets public typealias EdgeInsets = UIEdgeInsets #elseif os(macOS) import Foundation /// SwifterSwift: EdgeInsets public typealias EdgeInsets = NSEdgeInsets public extension NSEdgeInsets { /// SwifterSwift: An edge insets struct whose top, left, bottom, and right fields are all set to 0. static let zero = NSEdgeInsets() } // swiftlint:disable missing_swifterswift_prefix extension NSEdgeInsets: Equatable { /// Returns a Boolean value indicating whether two values are equal. /// /// Equality is the inverse of inequality. For any values `a` and `b`, /// `a == b` implies that `a != b` is `false`. /// /// - Parameters: /// - lhs: A value to compare. /// - rhs: Another value to compare. public static func == (lhs: NSEdgeInsets, rhs: NSEdgeInsets) -> Bool { return lhs.top == rhs.top && lhs.left == rhs.left && lhs.bottom == rhs.bottom && lhs.right == rhs.right } } // swiftlint:enable missing_swifterswift_prefix #endif #if os(iOS) || os(tvOS) || os(watchOS) || os(macOS) // MARK: - Properties public extension EdgeInsets { /// SwifterSwift: Return the vertical insets. The vertical insets is composed by top + bottom. /// var vertical: CGFloat { // Source: https://github.com/MessageKit/MessageKit/blob/master/Sources/SwifterSwift/EdgeInsets%2BExtensions.swift return top + bottom } /// SwifterSwift: Return the horizontal insets. The horizontal insets is composed by left + right. /// var horizontal: CGFloat { // Source: https://github.com/MessageKit/MessageKit/blob/master/Sources/SwifterSwift/EdgeInsets%2BExtensions.swift return left + right } } // MARK: - Methods public extension EdgeInsets { /// SwifterSwift: Creates an `EdgeInsets` with the inset value applied to all (top, bottom, right, left) /// /// - Parameter inset: Inset to be applied in all the edges. init(inset: CGFloat) { self.init(top: inset, left: inset, bottom: inset, right: inset) } /// SwifterSwift: Creates an `EdgeInsets` with the horizontal value equally divided and applied to right and left. /// And the vertical value equally divided and applied to top and bottom. /// /// /// - Parameter horizontal: Inset to be applied to right and left. /// - Parameter vertical: Inset to be applied to top and bottom. init(horizontal: CGFloat, vertical: CGFloat) { self.init(top: vertical / 2, left: horizontal / 2, bottom: vertical / 2, right: horizontal / 2) } /// SwifterSwift: Creates an `EdgeInsets` based on current value and top offset. /// /// - Parameters: /// - top: Offset to be applied in to the top edge. /// - Returns: EdgeInsets offset with given offset. func insetBy(top: CGFloat) -> EdgeInsets { return EdgeInsets(top: self.top + top, left: left, bottom: bottom, right: right) } /// SwifterSwift: Creates an `EdgeInsets` based on current value and left offset. /// /// - Parameters: /// - left: Offset to be applied in to the left edge. /// - Returns: EdgeInsets offset with given offset. func insetBy(left: CGFloat) -> EdgeInsets { return EdgeInsets(top: top, left: self.left + left, bottom: bottom, right: right) } /// SwifterSwift: Creates an `EdgeInsets` based on current value and bottom offset. /// /// - Parameters: /// - bottom: Offset to be applied in to the bottom edge. /// - Returns: EdgeInsets offset with given offset. func insetBy(bottom: CGFloat) -> EdgeInsets { return EdgeInsets(top: top, left: left, bottom: self.bottom + bottom, right: right) } /// SwifterSwift: Creates an `EdgeInsets` based on current value and right offset. /// /// - Parameters: /// - right: Offset to be applied in to the right edge. /// - Returns: EdgeInsets offset with given offset. func insetBy(right: CGFloat) -> EdgeInsets { return EdgeInsets(top: top, left: left, bottom: bottom, right: self.right + right) } /// SwifterSwift: Creates an `EdgeInsets` based on current value and horizontal value equally divided and applied to right offset and left offset. /// /// - Parameters: /// - horizontal: Offset to be applied to right and left. /// - Returns: EdgeInsets offset with given offset. func insetBy(horizontal: CGFloat) -> EdgeInsets { return EdgeInsets(top: top, left: left + horizontal / 2, bottom: bottom, right: right + horizontal / 2) } /// SwifterSwift: Creates an `EdgeInsets` based on current value and vertical value equally divided and applied to top and bottom. /// /// - Parameters: /// - vertical: Offset to be applied to top and bottom. /// - Returns: EdgeInsets offset with given offset. func insetBy(vertical: CGFloat) -> EdgeInsets { return EdgeInsets(top: top + vertical / 2, left: left, bottom: bottom + vertical / 2, right: right) } } // MARK: - Operators public extension EdgeInsets { /// SwifterSwift: Add all the properties of two `EdgeInsets` to create their addition. /// /// - Parameters: /// - lhs: The left-hand expression /// - rhs: The right-hand expression /// - Returns: A new `EdgeInsets` instance where the values of `lhs` and `rhs` are added together. static func + (_ lhs: EdgeInsets, _ rhs: EdgeInsets) -> EdgeInsets { return EdgeInsets(top: lhs.top + rhs.top, left: lhs.left + rhs.left, bottom: lhs.bottom + rhs.bottom, right: lhs.right + rhs.right) } /// SwifterSwift: Add all the properties of two `EdgeInsets` to the left-hand instance. /// /// - Parameters: /// - lhs: The left-hand expression to be mutated /// - rhs: The right-hand expression static func += (_ lhs: inout EdgeInsets, _ rhs: EdgeInsets) { lhs.top += rhs.top lhs.left += rhs.left lhs.bottom += rhs.bottom lhs.right += rhs.right } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SpriteKit/SKNodeExtensions.swift ================================================ // // SKNodeExtensions.swift // SwifterSwift // // Created by Olivia Brown on 5/28/18. // Copyright © 2018 SwifterSwift // #if canImport(SpriteKit) import SpriteKit // MARK: - Methods public extension SKNode { /// SwifterSwift: Return an array of all SKNode descendants /// /// mySKNode.descendants() -> [childNodeOne, childNodeTwo] /// func descendants() -> [SKNode] { return children + children.reduce(into: [SKNode]()) { $0 += $1.descendants() } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/StoreKit/SKProductExtensions.swift ================================================ // // SKProductExtensions.swift // SwifterSwift // // Created by Batuhan Saka on 26.12.2019. // Copyright © 2019 SwifterSwift // #if canImport(StoreKit) import StoreKit @available(watchOS 6.2, *) public extension SKProduct { private static let priceFormatter: NumberFormatter = { let priceFormatter = NumberFormatter() priceFormatter.numberStyle = .currency return priceFormatter }() /// SwifterSwift: Localized price of SKProduct var localizedPrice: String? { let formatter = SKProduct.priceFormatter formatter.locale = priceLocale return formatter.string(from: price) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/ArrayExtensions.swift ================================================ // // ArrayExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/5/16. // Copyright © 2016 SwifterSwift // // MARK: - Methods public extension Array { /// SwifterSwift: Insert an element at the beginning of array. /// /// [2, 3, 4, 5].prepend(1) -> [1, 2, 3, 4, 5] /// ["e", "l", "l", "o"].prepend("h") -> ["h", "e", "l", "l", "o"] /// /// - Parameter newElement: element to insert. mutating func prepend(_ newElement: Element) { insert(newElement, at: 0) } /// SwifterSwift: Safely swap values at given index positions. /// /// [1, 2, 3, 4, 5].safeSwap(from: 3, to: 0) -> [4, 2, 3, 1, 5] /// ["h", "e", "l", "l", "o"].safeSwap(from: 1, to: 0) -> ["e", "h", "l", "l", "o"] /// /// - Parameters: /// - index: index of first element. /// - otherIndex: index of other element. mutating func safeSwap(from index: Index, to otherIndex: Index) { guard index != otherIndex else { return } guard startIndex.. [MyStruct(x: 1), MyStruct(x: 2), MyStruct(x: 3)] /// /// - Parameters: /// - otherArray: array containing elements in the desired order. /// - keyPath: keyPath indiciating the property that the array should be sorted by /// - Returns: sorted array. func sorted(like otherArray: [T], keyPath: KeyPath) -> [Element] { let dict = otherArray.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } return sorted { guard let thisIndex = dict[$0[keyPath: keyPath]] else { return false } guard let otherIndex = dict[$1[keyPath: keyPath]] else { return true } return thisIndex < otherIndex } } } // MARK: - Methods (Equatable) public extension Array where Element: Equatable { /// SwifterSwift: Remove all instances of an item from array. /// /// [1, 2, 2, 3, 4, 5].removeAll(2) -> [1, 3, 4, 5] /// ["h", "e", "l", "l", "o"].removeAll("l") -> ["h", "e", "o"] /// /// - Parameter item: item to remove. /// - Returns: self after removing all instances of item. @discardableResult mutating func removeAll(_ item: Element) -> [Element] { removeAll(where: { $0 == item }) return self } /// SwifterSwift: Remove all instances contained in items parameter from array. /// /// [1, 2, 2, 3, 4, 5].removeAll([2,5]) -> [1, 3, 4] /// ["h", "e", "l", "l", "o"].removeAll(["l", "h"]) -> ["e", "o"] /// /// - Parameter items: items to remove. /// - Returns: self after removing all instances of all items in given array. @discardableResult mutating func removeAll(_ items: [Element]) -> [Element] { guard !items.isEmpty else { return self } removeAll(where: { items.contains($0) }) return self } /// SwifterSwift: Remove all duplicate elements from Array. /// /// [1, 2, 2, 3, 4, 5].removeDuplicates() -> [1, 2, 3, 4, 5] /// ["h", "e", "l", "l", "o"]. removeDuplicates() -> ["h", "e", "l", "o"] /// /// - Returns: Return array with all duplicate elements removed. @discardableResult mutating func removeDuplicates() -> [Element] { // Thanks to https://github.com/sairamkotha for improving the method self = reduce(into: [Element]()) { if !$0.contains($1) { $0.append($1) } } return self } /// SwifterSwift: Return array with all duplicate elements removed. /// /// [1, 1, 2, 2, 3, 3, 3, 4, 5].withoutDuplicates() -> [1, 2, 3, 4, 5]) /// ["h", "e", "l", "l", "o"].withoutDuplicates() -> ["h", "e", "l", "o"]) /// /// - Returns: an array of unique elements. /// func withoutDuplicates() -> [Element] { // Thanks to https://github.com/sairamkotha for improving the method return reduce(into: [Element]()) { if !$0.contains($1) { $0.append($1) } } } /// SwifterSwift: Returns an array with all duplicate elements removed using KeyPath to compare. /// /// - Parameter path: Key path to compare, the value must be Equatable. /// - Returns: an array of unique elements. func withoutDuplicates(keyPath path: KeyPath) -> [Element] { return reduce(into: [Element]()) { (result, element) in if !result.contains(where: { $0[keyPath: path] == element[keyPath: path] }) { result.append(element) } } } /// SwifterSwift: Returns an array with all duplicate elements removed using KeyPath to compare. /// /// - Parameter path: Key path to compare, the value must be Hashable. /// - Returns: an array of unique elements. func withoutDuplicates(keyPath path: KeyPath) -> [Element] { var set = Set() return filter { set.insert($0[keyPath: path]).inserted } } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/BidirectionalCollectionExtensions.swift ================================================ // // BidirectionalCollectionExtensions.swift // SwifterSwift // // Created by Quentin Jin on 2018/10/13. // Copyright © 2018 SwifterSwift // // MARK: - Methods public extension BidirectionalCollection { /// SwifterSwift: Returns the element at the specified position. If offset is negative, the `n`th element from the end will be returned where `n` is the result of `abs(distance)`. /// /// let arr = [1, 2, 3, 4, 5] /// arr[offset: 1] -> 2 /// arr[offset: -2] -> 4 /// /// - Parameter distance: The distance to offset. subscript(offset distance: Int) -> Element { let index = distance >= 0 ? startIndex : endIndex return self[indices.index(index, offsetBy: distance)] } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/BinaryFloatingPointExtensions.swift ================================================ // // BinaryFloatingPointExtensions.swift // SwifterSwift // // Created by Quentin Jin on 2018/10/13. // Copyright © 2018 SwifterSwift // #if canImport(Foundation) import Foundation // MARK: - Methods public extension BinaryFloatingPoint { #if canImport(Foundation) /// SwifterSwift: Returns a rounded value with the specified number of decimal places and rounding rule. If `numberOfDecimalPlaces` is negative, `0` will be used. /// /// let num = 3.1415927 /// num.rounded(numberOfDecimalPlaces: 3, rule: .up) -> 3.142 /// num.rounded(numberOfDecimalPlaces: 3, rule: .down) -> 3.141 /// num.rounded(numberOfDecimalPlaces: 2, rule: .awayFromZero) -> 3.15 /// num.rounded(numberOfDecimalPlaces: 4, rule: .towardZero) -> 3.1415 /// num.rounded(numberOfDecimalPlaces: -1, rule: .toNearestOrEven) -> 3 /// /// - Parameters: /// - numberOfDecimalPlaces: The expected number of decimal places. /// - rule: The rounding rule to use. /// - Returns: The rounded value. func rounded(numberOfDecimalPlaces: Int, rule: FloatingPointRoundingRule) -> Self { let factor = Self(pow(10.0, Double(max(0, numberOfDecimalPlaces)))) return (self * factor).rounded(rule) / factor } #endif } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/BoolExtensions.swift ================================================ // // BoolExtensions.swift // SwifterSwift // // Created by Omar Albeik on 07/12/2016. // Copyright © 2016 SwifterSwift // // MARK: - Properties public extension Bool { /// SwifterSwift: Return 1 if true, or 0 if false. /// /// false.int -> 0 /// true.int -> 1 /// var int: Int { return self ? 1 : 0 } /// SwifterSwift: Return "true" if true, or "false" if false. /// /// false.string -> "false" /// true.string -> "true" /// var string: String { return self ? "true" : "false" } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/CharacterExtensions.swift ================================================ // // CharacterExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/8/16. // Copyright © 2016 SwifterSwift // // MARK: - Properties public extension Character { /// SwifterSwift: Check if character is emoji. /// /// Character("😀").isEmoji -> true /// var isEmoji: Bool { // http://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji let scalarValue = String(self).unicodeScalars.first!.value switch scalarValue { case 0x1F600...0x1F64F, // Emoticons 0x1F300...0x1F5FF, // Misc Symbols and Pictographs 0x1F680...0x1F6FF, // Transport and Map 0x1F1E6...0x1F1FF, // Regional country flags 0x2600...0x26FF, // Misc symbols 0x2700...0x27BF, // Dingbats 0xE0020...0xE007F, // Tags 0xFE00...0xFE0F, // Variation Selectors 0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs 127000...127600, // Various asian characters 65024...65039, // Variation selector 9100...9300, // Misc items 8400...8447: // Combining Diacritical Marks for Symbols return true default: return false } } /// SwifterSwift: Integer from character (if applicable). /// /// Character("1").int -> 1 /// Character("A").int -> nil /// var int: Int? { return Int(String(self)) } /// SwifterSwift: String from character. /// /// Character("a").string -> "a" /// var string: String { return String(self) } /// SwifterSwift: Return the character lowercased. /// /// Character("A").lowercased -> Character("a") /// var lowercased: Character { return String(self).lowercased().first! } /// SwifterSwift: Return the character uppercased. /// /// Character("a").uppercased -> Character("A") /// var uppercased: Character { return String(self).uppercased().first! } } // MARK: - Methods public extension Character { /// SwifterSwift: Random character. /// /// Character.random() -> k /// /// - Returns: A random character. static func randomAlphanumeric() -> Character { return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".randomElement()! } } // MARK: - Operators public extension Character { /// SwifterSwift: Repeat character multiple times. /// /// Character("-") * 10 -> "----------" /// /// - Parameters: /// - lhs: character to repeat. /// - rhs: number of times to repeat character. /// - Returns: string with character repeated n times. static func * (lhs: Character, rhs: Int) -> String { guard rhs > 0 else { return "" } return String(repeating: String(lhs), count: rhs) } /// SwifterSwift: Repeat character multiple times. /// /// 10 * Character("-") -> "----------" /// /// - Parameters: /// - lhs: number of times to repeat character. /// - rhs: character to repeat. /// - Returns: string with character repeated n times. static func * (lhs: Int, rhs: Character) -> String { guard lhs > 0 else { return "" } return String(repeating: String(rhs), count: lhs) } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/CollectionExtensions.swift ================================================ // // CollectionExtensions.swift // SwifterSwift // // Created by Sergey Fedortsov on 19.12.16. // Copyright © 2016 SwifterSwift // #if canImport(Dispatch) import Dispatch #endif // MARK: - Methods public extension Collection { #if canImport(Dispatch) /// SwifterSwift: Performs `each` closure for each element of collection in parallel. /// /// array.forEachInParallel { item in /// print(item) /// } /// /// - Parameter each: closure to run for each element. func forEachInParallel(_ each: (Self.Element) -> Void) { let indicesArray = Array(indices) DispatchQueue.concurrentPerform(iterations: indicesArray.count) { (index) in let elementIndex = indicesArray[index] each(self[elementIndex]) } } #endif /// SwifterSwift: Safe protects the array from out of bounds by use of optional. /// /// let arr = [1, 2, 3, 4, 5] /// arr[safe: 1] -> 2 /// arr[safe: 10] -> nil /// /// - Parameter index: index of element to access element. subscript(safe index: Index) -> Element? { return indices.contains(index) ? self[index] : nil } /// SwifterSwift: Returns an array of slices of length "size" from the array. If array can't be split evenly, the final slice will be the remaining elements. /// /// [0, 2, 4, 7].group(by: 2) -> [[0, 2], [4, 7]] /// [0, 2, 4, 7, 6].group(by: 2) -> [[0, 2], [4, 7], [6]] /// /// - Parameter size: The size of the slices to be returned. /// - Returns: grouped self. func group(by size: Int) -> [[Element]]? { // Inspired by: https://lodash.com/docs/4.17.4#chunk guard size > 0, !isEmpty else { return nil } var start = startIndex var slices = [[Element]]() while start != endIndex { let end = index(start, offsetBy: size, limitedBy: endIndex) ?? endIndex slices.append(Array(self[start.. [0, 2, 5] /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: all indices where the specified condition evaluates to true. (optional) func indices(where condition: (Element) throws -> Bool) rethrows -> [Index]? { var indicies: [Index] = [] for (index, value) in lazy.enumerated() where try condition(value) { indicies.append(index) } return indicies.isEmpty ? nil : indicies } /// SwifterSwift: Calls the given closure with an array of size of the parameter slice. /// /// [0, 2, 4, 7].forEach(slice: 2) { print($0) } -> // print: [0, 2], [4, 7] /// [0, 2, 4, 7, 6].forEach(slice: 2) { print($0) } -> // print: [0, 2], [4, 7], [6] /// /// - Parameters: /// - slice: size of array in each interation. /// - body: a closure that takes an array of slice size as a parameter. func forEach(slice: Int, body: ([Element]) throws -> Void) rethrows { guard slice > 0, !isEmpty else { return } var value: Int = 0 while value < count { try body(Array(self[Swift.max(value, startIndex).. Double { // http://stackoverflow.com/questions/28288148/making-my-function-calculate-average-of-array-swift return isEmpty ? 0 : Double(reduce(0, +)) / Double(count) } } // MARK: - Methods (FloatingPoint) public extension Collection where Element: FloatingPoint { /// SwifterSwift: Average of all elements in array. /// /// [1.2, 2.3, 4.5, 3.4, 4.5].average() = 3.18 /// /// - Returns: average of the array's elements. func average() -> Element { guard !isEmpty else { return 0 } return reduce(0, {$0 + $1}) / Element(count) } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/ComparableExtensions.swift ================================================ // // ComparableExtensions.swift // SwifterSwift // // Created by Shai Mishali on 5/4/18. // Copyright © 2018 SwifterSwift // // MARK: - Methods public extension Comparable { /// SwifterSwift: Returns true if value is in the provided range. /// /// 1.isBetween(5...7) // false /// 7.isBetween(6...12) // true /// date.isBetween(date1...date2) /// "c".isBetween(a...d) // true /// 0.32.isBetween(0.31...0.33) // true /// /// - parameter min: Minimum comparable value. /// - parameter max: Maximum comparable value. /// /// - returns: `true` if value is between `min` and `max`, `false` otherwise. func isBetween(_ range: ClosedRange) -> Bool { return range ~= self } /// SwifterSwift: Returns value limited within the provided range. /// /// 1.clamped(to: 3...8) // 3 /// 4.clamped(to: 3...7) // 4 /// "c".clamped(to: "e"..."g") // "e" /// 0.32.clamped(to: 0.1...0.29) // 0.29 /// /// - parameter min: Lower bound to limit the value to. /// - parameter max: Upper bound to limit the value to. /// /// - returns: A value limited to the range between `min` and `max`. func clamped(to range: ClosedRange) -> Self { return max(range.lowerBound, min(self, range.upperBound)) } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/DecodableExtensions.swift ================================================ // // DecodableExtensions.swift // SwifterSwift // // Created by Mustafa GUNES on 16.01.2020. // Copyright © 2020 SwifterSwift // #if canImport(Foundation) import Foundation #endif public extension Decodable { #if canImport(Foundation) /// SwifterSwift: Parsing the model in Decodable type /// - Parameters: /// - data: Data. /// - decoder: JSONDecoder. Initialized by default init?(from data: Data, using decoder: JSONDecoder = .init()) { guard let parsed = try? decoder.decode(Self.self, from: data) else { return nil } self = parsed } #endif } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/Deprecated/StdlibDeprecated.swift ================================================ // // StdlibDeprecated.swift // SwifterSwift // // Created by Luciano Almeida on 21/09/19. // Copyright © 2019 SwifterSwift // private func optionalCompareAscending(path1: T?, path2: T?) -> Bool { guard let path1 = path1, let path2 = path2 else { return false } return path1 < path2 } private func optionalCompareDescending(path1: T?, path2: T?) -> Bool { guard let path1 = path1, let path2 = path2 else { return false } return path1 > path2 } public extension Array { /// SwifterSwift: Returns a sorted array based on an optional keypath. /// /// - Parameter path: Key path to sort. The key path type must be Comparable. /// - Parameter ascending: If order must be ascending. /// - Returns: Sorted array based on keyPath. @available(*, deprecated, message: "Use sorted(by:with:) instead.") func sorted(by path: KeyPath, ascending: Bool) -> [Element] { if ascending { return sorted(by: path, with: optionalCompareAscending) } return sorted(by: path, with: optionalCompareDescending) } /// SwifterSwift: Returns a sorted array based on a keypath. /// /// - Parameter path: Key path to sort. The key path type must be Comparable. /// - Parameter ascending: If order must be ascending. /// - Returns: Sorted array based on keyPath. @available(*, deprecated, message: "Use sorted(by:with:) instead.") func sorted(by path: KeyPath, ascending: Bool) -> [Element] { if ascending { return sorted(by: path, with: <) } return sorted(by: path, with: >) } /// SwifterSwift: Sort the array based on an optional keypath. /// /// - Parameters: /// - path: Key path to sort, must be Comparable. /// - ascending: whether order is ascending or not. /// - Returns: self after sorting. @available(*, deprecated, message: "Use sort(by:with:) instead.") @discardableResult mutating func sort(by path: KeyPath, ascending: Bool) -> [Element] { if ascending { sort(by: path, with: optionalCompareAscending) } else { sort(by: path, with: optionalCompareDescending) } return self } /// SwifterSwift: Sort the array based on a keypath. /// /// - Parameters: /// - path: Key path to sort, must be Comparable. /// - ascending: whether order is ascending or not. /// - Returns: self after sorting. @available(*, deprecated, message: "Use sort(by:with:) instead.") @discardableResult mutating func sort(by path: KeyPath, ascending: Bool) -> [Element] { if ascending { sort(by: path, with: <) } else { sort(by: path, with: >) } return self } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/DictionaryExtensions.swift ================================================ // // DictionaryExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/24/16. // Copyright © 2016 SwifterSwift // #if canImport(Foundation) import Foundation #endif // MARK: - Methods public extension Dictionary { /// SwifterSwift: Creates a Dictionary from a given sequence grouped by a given key path. /// /// - Parameters: /// - sequence: Sequence being grouped /// - keypath: The key path to group by. init(grouping sequence: S, by keyPath: KeyPath) where Value == [S.Element] { self.init(grouping: sequence, by: { $0[keyPath: keyPath] }) } /// SwifterSwift: Check if key exists in dictionary. /// /// let dict: [String: Any] = ["testKey": "testValue", "testArrayKey": [1, 2, 3, 4, 5]] /// dict.has(key: "testKey") -> true /// dict.has(key: "anotherKey") -> false /// /// - Parameter key: key to search for /// - Returns: true if key exists in dictionary. func has(key: Key) -> Bool { return index(forKey: key) != nil } /// SwifterSwift: Remove all keys contained in the keys parameter from the dictionary. /// /// var dict : [String: String] = ["key1" : "value1", "key2" : "value2", "key3" : "value3"] /// dict.removeAll(keys: ["key1", "key2"]) /// dict.keys.contains("key3") -> true /// dict.keys.contains("key1") -> false /// dict.keys.contains("key2") -> false /// /// - Parameter keys: keys to be removed mutating func removeAll(keys: S) where S.Element == Key { keys.forEach { removeValue(forKey: $0) } } /// SwifterSwift: Remove a value for a random key from the dictionary. @discardableResult mutating func removeValueForRandomKey() -> Value? { guard let randomKey = keys.randomElement() else { return nil } return removeValue(forKey: randomKey) } #if canImport(Foundation) /// SwifterSwift: JSON Data from dictionary. /// /// - Parameter prettify: set true to prettify data (default is false). /// - Returns: optional JSON Data (if applicable). func jsonData(prettify: Bool = false) -> Data? { guard JSONSerialization.isValidJSONObject(self) else { return nil } let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization.WritingOptions() return try? JSONSerialization.data(withJSONObject: self, options: options) } #endif #if canImport(Foundation) /// SwifterSwift: JSON String from dictionary. /// /// dict.jsonString() -> "{"testKey":"testValue","testArrayKey":[1,2,3,4,5]}" /// /// dict.jsonString(prettify: true) /// /* /// returns the following string: /// /// "{ /// "testKey" : "testValue", /// "testArrayKey" : [ /// 1, /// 2, /// 3, /// 4, /// 5 /// ] /// }" /// /// */ /// /// - Parameter prettify: set true to prettify string (default is false). /// - Returns: optional JSON String (if applicable). func jsonString(prettify: Bool = false) -> String? { guard JSONSerialization.isValidJSONObject(self) else { return nil } let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization.WritingOptions() guard let jsonData = try? JSONSerialization.data(withJSONObject: self, options: options) else { return nil } return String(data: jsonData, encoding: .utf8) } #endif /// SwifterSwift: Returns a dictionary containing the results of mapping the given closure over the sequence’s elements. /// - Parameter transform: A mapping closure. `transform` accepts an element of this sequence as its parameter and returns a transformed value of the same or of a different type. /// - Returns: A dictionary containing the transformed elements of this sequence. func mapKeysAndValues(_ transform: ((key: Key, value: Value)) throws -> (K, V)) rethrows -> [K: V] { return [K: V](uniqueKeysWithValues: try map(transform)) } /// SwifterSwift: Returns a dictionary containing the non-`nil` results of calling the given transformation with each element of this sequence. /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value. /// - Returns: A dictionary of the non-`nil` results of calling `transform` with each element of the sequence. /// - Complexity: *O(m + n)*, where _m_ is the length of this sequence and _n_ is the length of the result. func compactMapKeysAndValues(_ transform: ((key: Key, value: Value)) throws -> (K, V)?) rethrows -> [K: V] { return [K: V](uniqueKeysWithValues: try compactMap(transform)) } } // MARK: - Methods (Value: Equatable) public extension Dictionary where Value: Equatable { /// SwifterSwift: Returns an array of all keys that have the given value in dictionary. /// /// let dict = ["key1": "value1", "key2": "value1", "key3": "value2"] /// dict.keys(forValue: "value1") -> ["key1", "key2"] /// dict.keys(forValue: "value2") -> ["key3"] /// dict.keys(forValue: "value3") -> [] /// /// - Parameter value: Value for which keys are to be fetched. /// - Returns: An array containing keys that have the given value. func keys(forValue value: Value) -> [Key] { return keys.filter { self[$0] == value } } } // MARK: - Methods (ExpressibleByStringLiteral) public extension Dictionary where Key: StringProtocol { /// SwifterSwift: Lowercase all keys in dictionary. /// /// var dict = ["tEstKeY": "value"] /// dict.lowercaseAllKeys() /// print(dict) // prints "["testkey": "value"]" /// mutating func lowercaseAllKeys() { // http://stackoverflow.com/questions/33180028/extend-dictionary-where-key-is-of-type-string for key in keys { if let lowercaseKey = String(describing: key).lowercased() as? Key { self[lowercaseKey] = removeValue(forKey: key) } } } } // MARK: - Subscripts public extension Dictionary { /// SwifterSwift: Deep fetch or set a value from nested dictionaries. /// /// var dict = ["key": ["key1": ["key2": "value"]]] /// dict[path: ["key", "key1", "key2"]] = "newValue" /// dict[path: ["key", "key1", "key2"]] -> "newValue" /// /// - Note: Value fetching is iterative, while setting is recursive. /// /// - Complexity: O(N), _N_ being the length of the path passed in. /// /// - Parameter path: An array of keys to the desired value. /// /// - Returns: The value for the key-path passed in. `nil` if no value is found. subscript(path path: [Key]) -> Any? { get { guard !path.isEmpty else { return nil } var result: Any? = self for key in path { if let element = (result as? [Key: Any])?[key] { result = element } else { return nil } } return result } set { if let first = path.first { if path.count == 1, let new = newValue as? Value { return self[first] = new } if var nested = self[first] as? [Key: Any] { nested[path: Array(path.dropFirst())] = newValue return self[first] = nested as? Value } } } } } // MARK: - Operators public extension Dictionary { /// SwifterSwift: Merge the keys/values of two dictionaries. /// /// let dict: [String: String] = ["key1": "value1"] /// let dict2: [String: String] = ["key2": "value2"] /// let result = dict + dict2 /// result["key1"] -> "value1" /// result["key2"] -> "value2" /// /// - Parameters: /// - lhs: dictionary /// - rhs: dictionary /// - Returns: An dictionary with keys and values from both. static func + (lhs: [Key: Value], rhs: [Key: Value]) -> [Key: Value] { var result = lhs rhs.forEach { result[$0] = $1 } return result } // MARK: - Operators /// SwifterSwift: Append the keys and values from the second dictionary into the first one. /// /// var dict: [String: String] = ["key1": "value1"] /// let dict2: [String: String] = ["key2": "value2"] /// dict += dict2 /// dict["key1"] -> "value1" /// dict["key2"] -> "value2" /// /// - Parameters: /// - lhs: dictionary /// - rhs: dictionary static func += (lhs: inout [Key: Value], rhs: [Key: Value]) { rhs.forEach { lhs[$0] = $1} } /// SwifterSwift: Remove keys contained in the sequence from the dictionary /// /// let dict: [String: String] = ["key1": "value1", "key2": "value2", "key3": "value3"] /// let result = dict-["key1", "key2"] /// result.keys.contains("key3") -> true /// result.keys.contains("key1") -> false /// result.keys.contains("key2") -> false /// /// - Parameters: /// - lhs: dictionary /// - rhs: array with the keys to be removed. /// - Returns: a new dictionary with keys removed. static func - (lhs: [Key: Value], keys: S) -> [Key: Value] where S.Element == Key { var result = lhs result.removeAll(keys: keys) return result } /// SwifterSwift: Remove keys contained in the sequence from the dictionary /// /// var dict: [String: String] = ["key1": "value1", "key2": "value2", "key3": "value3"] /// dict-=["key1", "key2"] /// dict.keys.contains("key3") -> true /// dict.keys.contains("key1") -> false /// dict.keys.contains("key2") -> false /// /// - Parameters: /// - lhs: dictionary /// - rhs: array with the keys to be removed. static func -= (lhs: inout [Key: Value], keys: S) where S.Element == Key { lhs.removeAll(keys: keys) } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/DoubleExtensions.swift ================================================ // // DoubleExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/6/16. // Copyright © 2016 SwifterSwift // #if canImport(CoreGraphics) import CoreGraphics #endif #if os(macOS) || os(iOS) import Darwin #elseif os(Linux) import Glibc #endif // MARK: - Properties public extension Double { /// SwifterSwift: Int. var int: Int { return Int(self) } /// SwifterSwift: Float. var float: Float { return Float(self) } #if canImport(CoreGraphics) /// SwifterSwift: CGFloat. var cgFloat: CGFloat { return CGFloat(self) } #endif } // MARK: - Operators precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence } infix operator ** : PowerPrecedence /// SwifterSwift: Value of exponentiation. /// /// - Parameters: /// - lhs: base double. /// - rhs: exponent double. /// - Returns: exponentiation result (example: 4.4 ** 0.5 = 2.0976176963). func ** (lhs: Double, rhs: Double) -> Double { // http://nshipster.com/swift-operators/ return pow(lhs, rhs) } // swiftlint:disable identifier_name prefix operator √ /// SwifterSwift: Square root of double. /// /// - Parameter double: double value to find square root for. /// - Returns: square root of given double. public prefix func √ (double: Double) -> Double { // http://nshipster.com/swift-operators/ return sqrt(double) } // swiftlint:enable identifier_name ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/FloatExtensions.swift ================================================ // // FloatExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/8/16. // Copyright © 2016 SwifterSwift // #if canImport(CoreGraphics) import CoreGraphics #endif #if os(macOS) || os(iOS) import Darwin #elseif os(Linux) import Glibc #endif // MARK: - Properties public extension Float { /// SwifterSwift: Int. var int: Int { return Int(self) } /// SwifterSwift: Double. var double: Double { return Double(self) } #if canImport(CoreGraphics) /// SwifterSwift: CGFloat. var cgFloat: CGFloat { return CGFloat(self) } #endif } // MARK: - Operators precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence } infix operator ** : PowerPrecedence /// SwifterSwift: Value of exponentiation. /// /// - Parameters: /// - lhs: base float. /// - rhs: exponent float. /// - Returns: exponentiation result (4.4 ** 0.5 = 2.0976176963). func ** (lhs: Float, rhs: Float) -> Float { // http://nshipster.com/swift-operators/ return pow(lhs, rhs) } // swiftlint:disable identifier_name prefix operator √ /// SwifterSwift: Square root of float. /// /// - Parameter float: float value to find square root for /// - Returns: square root of given float. public prefix func √ (float: Float) -> Float { // http://nshipster.com/swift-operators/ return sqrt(float) } // swiftlint:enable identifier_name ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/FloatingPointExtensions.swift ================================================ // // FloatingPointExtensions.swift // SwifterSwift // // Created by Omar Albeik on 7/23/17. // Copyright © 2017 SwifterSwift // #if canImport(Foundation) import Foundation #endif // MARK: - Properties public extension FloatingPoint { /// SwifterSwift: Absolute value of number. var abs: Self { return Swift.abs(self) } /// SwifterSwift: Check if number is positive. var isPositive: Bool { return self > 0 } /// SwifterSwift: Check if number is negative. var isNegative: Bool { return self < 0 } #if canImport(Foundation) /// SwifterSwift: Ceil of number. var ceil: Self { return Foundation.ceil(self) } #endif /// SwifterSwift: Radian value of degree input. var degreesToRadians: Self { return Self.pi * self / Self(180) } #if canImport(Foundation) /// SwifterSwift: Floor of number. var floor: Self { return Foundation.floor(self) } #endif /// SwifterSwift: Degree value of radian input. var radiansToDegrees: Self { return self * Self(180) / Self.pi } } // MARK: - Operators // swiftlint:disable identifier_name infix operator ± /// SwifterSwift: Tuple of plus-minus operation. /// /// - Parameters: /// - lhs: number /// - rhs: number /// - Returns: tuple of plus-minus operation ( 2.5 ± 1.5 -> (4, 1)). func ± (lhs: T, rhs: T) -> (T, T) { // http://nshipster.com/swift-operators/ return (lhs + rhs, lhs - rhs) } // swiftlint:enable identifier_name // swiftlint:disable identifier_name prefix operator ± /// SwifterSwift: Tuple of plus-minus operation. /// /// - Parameter int: number /// - Returns: tuple of plus-minus operation (± 2.5 -> (2.5, -2.5)). public prefix func ± (number: T) -> (T, T) { // http://nshipster.com/swift-operators/ return 0 ± number } // swiftlint:enable identifier_name ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/IntExtensions.swift ================================================ // // IntExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/6/16. // Copyright © 2016 SwifterSwift // #if canImport(CoreGraphics) import CoreGraphics #endif #if os(macOS) || os(iOS) import Darwin #elseif os(Linux) import Glibc #endif // MARK: - Properties public extension Int { /// SwifterSwift: CountableRange 0.. { return 0..= 0 ? "" : "-" } let abs = Swift.abs(self) if abs == 0 { return "0k" } else if abs >= 0 && abs < 1000 { return "0k" } else if abs >= 1000 && abs < 1000000 { return String(format: "\(sign)%ik", abs / 1000) } return String(format: "\(sign)%ikk", abs / 100000) } /// SwifterSwift: Array of digits of integer value. var digits: [Int] { guard self != 0 else { return [0] } var digits = [Int]() var number = abs while number != 0 { let xNumber = number % 10 digits.append(xNumber) number /= 10 } digits.reverse() return digits } /// SwifterSwift: Number of digits of integer value. var digitsCount: Int { guard self != 0 else { return 1 } let number = Double(abs) return Int(log10(number) + 1) } } // MARK: - Methods public extension Int { /// SwifterSwift: check if given integer prime or not. Warning: Using big numbers can be computationally expensive! /// - Returns: true or false depending on prime-ness func isPrime() -> Bool { // To improve speed on latter loop :) if self == 2 { return true } guard self > 1 && self % 2 != 0 else { return false } // Explanation: It is enough to check numbers until // the square root of that number. If you go up from N by one, // other multiplier will go 1 down to get similar result // (integer-wise operation) such way increases speed of operation let base = Int(sqrt(Double(self))) for int in Swift.stride(from: 3, through: base, by: 2) where self % int == 0 { return false } return true } /// SwifterSwift: Roman numeral string from integer (if applicable). /// /// 10.romanNumeral() -> "X" /// /// - Returns: The roman numeral string. func romanNumeral() -> String? { // https://gist.github.com/kumo/a8e1cb1f4b7cff1548c7 guard self > 0 else { // there is no roman numeral for 0 or negative numbers return nil } let romanValues = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"] let arabicValues = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] var romanValue = "" var startingValue = self for (index, romanChar) in romanValues.enumerated() { let arabicValue = arabicValues[index] let div = startingValue / arabicValue if div > 0 { for _ in 0..
Int { return number == 0 ? self : Int(round(Double(self) / Double(number))) * number } } // MARK: - Operators precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence } infix operator ** : PowerPrecedence /// SwifterSwift: Value of exponentiation. /// /// - Parameters: /// - lhs: base integer. /// - rhs: exponent integer. /// - Returns: exponentiation result (example: 2 ** 3 = 8). func ** (lhs: Int, rhs: Int) -> Double { // http://nshipster.com/swift-operators/ return pow(Double(lhs), Double(rhs)) } // swiftlint:disable identifier_name prefix operator √ /// SwifterSwift: Square root of integer. /// /// - Parameter int: integer value to find square root for /// - Returns: square root of given integer. public prefix func √ (int: Int) -> Double { // http://nshipster.com/swift-operators/ return sqrt(Double(int)) } // swiftlint:enable identifier_name // swiftlint:disable identifier_name infix operator ± /// SwifterSwift: Tuple of plus-minus operation. /// /// - Parameters: /// - lhs: integer number. /// - rhs: integer number. /// - Returns: tuple of plus-minus operation (example: 2 ± 3 -> (5, -1)). func ± (lhs: Int, rhs: Int) -> (Int, Int) { // http://nshipster.com/swift-operators/ return (lhs + rhs, lhs - rhs) } // swiftlint:enable identifier_name // swiftlint:disable identifier_name prefix operator ± /// SwifterSwift: Tuple of plus-minus operation. /// /// - Parameter int: integer number /// - Returns: tuple of plus-minus operation (example: ± 2 -> (2, -2)). public prefix func ± (int: Int) -> (Int, Int) { // http://nshipster.com/swift-operators/ return 0 ± int } // swiftlint:enable identifier_name ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/KeyedDecodingContainerExtensions.swift ================================================ // // KeyedDecodingContainer.swift // SwifterSwift // // Created by Francesco Deliro on 23/10/2019. // Copyright © 2019 SwifterSwift // #if canImport(Foundation) import Foundation #endif public extension KeyedDecodingContainer { #if canImport(Foundation) /// SwifterSwift: Try to decode a Bool as Int then String before decoding as Bool. /// /// - Parameter key: Key. /// - Returns: Decoded Bool value. /// - Throws: Decoding error. func decodeBoolAsIntOrString(forKey key: Key) throws -> Bool { if let intValue = try? decode(Int.self, forKey: key) { return (intValue as NSNumber).boolValue } else if let stringValue = try? decode(String.self, forKey: key) { return (stringValue as NSString).boolValue } else { return try decode(Bool.self, forKey: key) } } #endif #if canImport(Foundation) /// SwifterSwift: Try to decode a Bool as Int then String before decoding as Bool if present. /// /// - Parameter key: Key. /// - Returns: Decoded Bool value. /// - Throws: Decoding error. func decodeBoolAsIntOrStringIfPresent(forKey key: Key) throws -> Bool? { if let intValue = try? decodeIfPresent(Int.self, forKey: key) { return (intValue as NSNumber).boolValue } else if let stringValue = try? decodeIfPresent(String.self, forKey: key) { return (stringValue as NSString).boolValue } else { return try decodeIfPresent(Bool.self, forKey: key) } } #endif } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/MutableCollectionExtensions.swift ================================================ // // MutableCollectionExtensions.swift // SwifterSwift // // Created by Luciano Almeida on 21/09/19. // Copyright © 2019 SwifterSwift // public extension MutableCollection where Self: RandomAccessCollection { /// SwifterSwift: Sort the collection based on a keypath and a compare function. /// /// - Parameter keyPath: Key path to sort by. The key path type must be Comparable. /// - Parameter compare: Comparation function that will determine the ordering. mutating func sort(by keyPath: KeyPath, with compare: (T, T) -> Bool) { sort { compare($0[keyPath: keyPath], $1[keyPath: keyPath]) } } /// SwifterSwift: Sort the collection based on a keypath. /// /// - Parameter keyPath: Key path to sort by. The key path type must be Comparable. mutating func sort(by keyPath: KeyPath) { sort { $0[keyPath: keyPath] < $1[keyPath: keyPath] } } /// SwifterSwift: Sort the collection based on two key paths. The second one will be used in case the values of the first one match. /// /// - Parameters: /// - keyPath1: Key path to sort by. Must be Comparable. /// - keyPath2: Key path to sort by in case the values of `keyPath1` match. Must be Comparable. mutating func sort(by keyPath1: KeyPath, and keyPath2: KeyPath) { sort { if $0[keyPath: keyPath1] != $1[keyPath: keyPath1] { return $0[keyPath: keyPath1] < $1[keyPath: keyPath1] } return $0[keyPath: keyPath2] < $1[keyPath: keyPath2] } } /// SwifterSwift: Sort the collection based on three key paths. Whenever the values of one key path match, the next one will be used. /// /// - Parameters: /// - keyPath1: Key path to sort by. Must be Comparable. /// - keyPath2: Key path to sort by in case the values of `keyPath1` match. Must be Comparable. /// - keyPath3: Key path to sort by in case the values of `keyPath1` and `keyPath2` match. Must be Comparable. mutating func sort(by keyPath1: KeyPath, and keyPath2: KeyPath, and keyPath3: KeyPath) { sort { if $0[keyPath: keyPath1] != $1[keyPath: keyPath1] { return $0[keyPath: keyPath1] < $1[keyPath: keyPath1] } if $0[keyPath: keyPath2] != $1[keyPath: keyPath2] { return $0[keyPath: keyPath2] < $1[keyPath: keyPath2] } return $0[keyPath: keyPath3] < $1[keyPath: keyPath3] } } } public extension MutableCollection { /// SwifterSwift: Assign a given value to a field `keyPath` of all elements in the collection. /// /// - Parameter value: The new value of the field /// - Parameter keyPath: The actual field of the element mutating func assignToAll(value: Value, by keyPath: WritableKeyPath) { guard !isEmpty else { return } var idx = startIndex while idx != endIndex { self[idx][keyPath: keyPath] = value idx = index(after: idx) } } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/OptionalExtensions.swift ================================================ // // OptionalExtensions.swift // SwifterSwift // // Created by Omar Albeik on 3/3/17. // Copyright © 2017 SwifterSwift // // MARK: - Methods public extension Optional { /// SwifterSwift: Get self of default value (if self is nil). /// /// let foo: String? = nil /// print(foo.unwrapped(or: "bar")) -> "bar" /// /// let bar: String? = "bar" /// print(bar.unwrapped(or: "foo")) -> "bar" /// /// - Parameter defaultValue: default value to return if self is nil. /// - Returns: self if not nil or default value if nil. func unwrapped(or defaultValue: Wrapped) -> Wrapped { // http://www.russbishop.net/improving-optionals return self ?? defaultValue } /// SwifterSwift: Gets the wrapped value of an optional. If the optional is `nil`, throw a custom error. /// /// let foo: String? = nil /// try print(foo.unwrapped(or: MyError.notFound)) -> error: MyError.notFound /// /// let bar: String? = "bar" /// try print(bar.unwrapped(or: MyError.notFound)) -> "bar" /// /// - Parameter error: The error to throw if the optional is `nil`. /// - Returns: The value wrapped by the optional. /// - Throws: The error passed in. func unwrapped(or error: Error) throws -> Wrapped { guard let wrapped = self else { throw error } return wrapped } /// SwifterSwift: Runs a block to Wrapped if not nil /// /// let foo: String? = nil /// foo.run { unwrappedFoo in /// // block will never run sice foo is nill /// print(unwrappedFoo) /// } /// /// let bar: String? = "bar" /// bar.run { unwrappedBar in /// // block will run sice bar is not nill /// print(unwrappedBar) -> "bar" /// } /// /// - Parameter block: a block to run if self is not nil. func run(_ block: (Wrapped) -> Void) { // http://www.russbishop.net/improving-optionals _ = map(block) } /// SwifterSwift: Assign an optional value to a variable only if the value is not nil. /// /// let someParameter: String? = nil /// let parameters = [String: Any]() // Some parameters to be attached to a GET request /// parameters[someKey] ??= someParameter // It won't be added to the parameters dict /// /// - Parameters: /// - lhs: Any? /// - rhs: Any? static func ??= (lhs: inout Optional, rhs: Optional) { guard let rhs = rhs else { return } lhs = rhs } /// SwifterSwift: Assign an optional value to a variable only if the variable is nil. /// /// var someText: String? = nil /// let newText = "Foo" /// let defaultText = "Bar" /// someText ?= newText // someText is now "Foo" because it was nil before /// someText ?= defaultText // someText doesn't change its value because it's not nil /// /// - Parameters: /// - lhs: Any? /// - rhs: Any? static func ?= (lhs: inout Optional, rhs: @autoclosure () -> Optional) { if lhs == nil { lhs = rhs() } } } // MARK: - Methods (Collection) public extension Optional where Wrapped: Collection { /// SwifterSwift: Check if optional is nil or empty collection. var isNilOrEmpty: Bool { guard let collection = self else { return true } return collection.isEmpty } /// SwifterSwift: Returns the collection only if it is not nill and not empty. var nonEmpty: Wrapped? { guard let collection = self else { return nil } guard !collection.isEmpty else { return nil } return collection } } // MARK: - Methods (RawRepresentable, RawValue: Equatable) public extension Optional where Wrapped: RawRepresentable, Wrapped.RawValue: Equatable { // swiftlint:disable missing_swifterswift_prefix /// Returns a Boolean value indicating whether two values are equal. /// /// Equality is the inverse of inequality. For any values `a` and `b`, /// `a == b` implies that `a != b` is `false`. /// /// - Parameters: /// - lhs: A value to compare. /// - rhs: Another value to compare. @inlinable static func == (lhs: Optional, rhs: Wrapped.RawValue?) -> Bool { return lhs?.rawValue == rhs } /// Returns a Boolean value indicating whether two values are equal. /// /// Equality is the inverse of inequality. For any values `a` and `b`, /// `a == b` implies that `a != b` is `false`. /// /// - Parameters: /// - lhs: A value to compare. /// - rhs: Another value to compare. @inlinable static func == (lhs: Wrapped.RawValue?, rhs: Optional) -> Bool { return lhs == rhs?.rawValue } /// Returns a Boolean value indicating whether two values are not equal. /// /// Inequality is the inverse of equality. For any values `a` and `b`, /// `a != b` implies that `a == b` is `false`. /// /// - Parameters: /// - lhs: A value to compare. /// - rhs: Another value to compare. @inlinable static func != (lhs: Optional, rhs: Wrapped.RawValue?) -> Bool { return lhs?.rawValue != rhs } /// Returns a Boolean value indicating whether two values are not equal. /// /// Inequality is the inverse of equality. For any values `a` and `b`, /// `a != b` implies that `a == b` is `false`. /// /// - Parameters: /// - lhs: A value to compare. /// - rhs: Another value to compare. @inlinable static func != (lhs: Wrapped.RawValue?, rhs: Optional) -> Bool { return lhs != rhs?.rawValue } // swiftlint:enable missing_swifterswift_prefix } // MARK: - Operators infix operator ??= : AssignmentPrecedence infix operator ?= : AssignmentPrecedence ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/RandomAccessCollectionExtensions.swift ================================================ // // RandomAccessCollectionExtensions.swift // SwifterSwift // // Created by Luciano Almeida on 7/13/18. // Copyright © 2018 SwifterSwift // public extension RandomAccessCollection where Element: Equatable { /// SwifterSwift: All indices of specified item. /// /// [1, 2, 2, 3, 4, 2, 5].indices(of 2) -> [1, 2, 5] /// [1.2, 2.3, 4.5, 3.4, 4.5].indices(of 2.3) -> [1] /// ["h", "e", "l", "l", "o"].indices(of "l") -> [2, 3] /// /// - Parameter item: item to check. /// - Returns: an array with all indices of the given item. func indices(of item: Element) -> [Index] { var indices: [Index] = [] var idx = startIndex while idx < endIndex { if self[idx] == item { indices.append(idx) } formIndex(after: &idx) } return indices } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/RangeReplaceableCollectionExtensions.swift ================================================ // // RangeReplaceableCollectionExtensions.swift // SwifterSwift // // Created by Luciano Almeida on 7/2/18. // Copyright © 2018 SwifterSwift // // MARK: - Initializers public extension RangeReplaceableCollection { /// SwifterSwift: Creates a new collection of a given size where for each position of the collection the value will be the result of a call of the given expression. /// /// let values = Array(expression: "Value", count: 3) /// print(values) /// // Prints "["Value", "Value", "Value"]" /// /// - Parameters: /// - expression: The expression to execute for each position of the collection. /// - count: The count of the collection. init(expression: @autoclosure () throws -> Element, count: Int) rethrows { self.init() // swiftlint:disable:next empty_count if count > 0 { reserveCapacity(count) while self.count < count { append(try expression()) } } } } // MARK: - Methods public extension RangeReplaceableCollection { /// SwifterSwift: Returns a new rotated collection by the given places. /// /// [1, 2, 3, 4].rotated(by: 1) -> [4,1,2,3] /// [1, 2, 3, 4].rotated(by: 3) -> [2,3,4,1] /// [1, 2, 3, 4].rotated(by: -1) -> [2,3,4,1] /// /// - Parameter places: Number of places that the array be rotated. If the value is positive the end becomes the start, if it negative it's that start becom the end. /// - Returns: The new rotated collection. func rotated(by places: Int) -> Self { // Inspired by: https://ruby-doc.org/core-2.2.0/Array.html#method-i-rotate var copy = self return copy.rotate(by: places) } /// SwifterSwift: Rotate the collection by the given places. /// /// [1, 2, 3, 4].rotate(by: 1) -> [4,1,2,3] /// [1, 2, 3, 4].rotate(by: 3) -> [2,3,4,1] /// [1, 2, 3, 4].rotated(by: -1) -> [2,3,4,1] /// /// - Parameter places: The number of places that the array should be rotated. If the value is positive the end becomes the start, if it negative it's that start become the end. /// - Returns: self after rotating. @discardableResult mutating func rotate(by places: Int) -> Self { guard places != 0 else { return self } let placesToMove = places%count if placesToMove > 0 { let range = index(endIndex, offsetBy: -placesToMove)... let slice = self[range] removeSubrange(range) insert(contentsOf: slice, at: startIndex) } else { let range = startIndex.. [1, 2, 3, 4, 2, 5] /// ["h", "e", "l", "l", "o"].removeFirst { $0 == "e" } -> ["h", "l", "l", "o"] /// /// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match. /// - Returns: The first element for which predicate returns true, after removing it. If no elements in the collection satisfy the given predicate, returns `nil`. @discardableResult mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows -> Element? { guard let index = try firstIndex(where: predicate) else { return nil } return remove(at: index) } /// SwifterSwift: Remove a random value from the collection. @discardableResult mutating func removeRandomElement() -> Element? { guard let randomIndex = indices.randomElement() else { return nil } return remove(at: randomIndex) } /// SwifterSwift: Keep elements of Array while condition is true. /// /// [0, 2, 4, 7].keep(while: { $0 % 2 == 0 }) -> [0, 2, 4] /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: self after applying provided condition. /// - Throws: provided condition exception. @discardableResult mutating func keep(while condition: (Element) throws -> Bool) rethrows -> Self { if let idx = try firstIndex(where: { try !condition($0) }) { removeSubrange(idx...) } return self } /// SwifterSwift: Take element of Array while condition is true. /// /// [0, 2, 4, 7, 6, 8].take( where: {$0 % 2 == 0}) -> [0, 2, 4] /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: All elements up until condition evaluates to false. func take(while condition: (Element) throws -> Bool) rethrows -> Self { return Self(try prefix(while: condition)) } /// SwifterSwift: Skip elements of Array while condition is true. /// /// [0, 2, 4, 7, 6, 8].skip( where: {$0 % 2 == 0}) -> [6, 8] /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: All elements after the condition evaluates to false. func skip(while condition: (Element) throws-> Bool) rethrows -> Self { guard let idx = try firstIndex(where: { try !condition($0) }) else { return Self() } return Self(self[idx...]) } /// SwifterSwift: Remove all duplicate elements using KeyPath to compare. /// /// - Parameter path: Key path to compare, the value must be Equatable. mutating func removeDuplicates(keyPath path: KeyPath) { var items = [Element]() removeAll { element -> Bool in guard items.contains(where: { $0[keyPath: path] == element[keyPath: path] }) else { items.append(element) return false } return true } } /// SwifterSwift: Remove all duplicate elements using KeyPath to compare. /// /// - Parameter path: Key path to compare, the value must be Hashable. mutating func removeDuplicates(keyPath path: KeyPath) { var set = Set() removeAll { !set.insert($0[keyPath: path]).inserted } } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/SequenceExtensions.swift ================================================ // // SequenceExtensions.swift // SwifterSwift // // Created by Anton Novoselov on 04/04/2018. // Copyright © 2018 SwifterSwift // public extension Sequence { /// SwifterSwift: Check if all elements in collection match a conditon. /// /// [2, 2, 4].all(matching: {$0 % 2 == 0}) -> true /// [1,2, 2, 4].all(matching: {$0 % 2 == 0}) -> false /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: true when all elements in the array match the specified condition. func all(matching condition: (Element) throws -> Bool) rethrows -> Bool { return try !contains { try !condition($0) } } /// SwifterSwift: Check if no elements in collection match a conditon. /// /// [2, 2, 4].none(matching: {$0 % 2 == 0}) -> false /// [1, 3, 5, 7].none(matching: {$0 % 2 == 0}) -> true /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: true when no elements in the array match the specified condition. func none(matching condition: (Element) throws -> Bool) rethrows -> Bool { return try !contains { try condition($0) } } /// SwifterSwift: Check if any element in collection match a conditon. /// /// [2, 2, 4].any(matching: {$0 % 2 == 0}) -> false /// [1, 3, 5, 7].any(matching: {$0 % 2 == 0}) -> true /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: true when no elements in the array match the specified condition. func any(matching condition: (Element) throws -> Bool) rethrows -> Bool { return try contains { try condition($0) } } /// SwifterSwift: Get last element that satisfies a conditon. /// /// [2, 2, 4, 7].last(where: {$0 % 2 == 0}) -> 4 /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: the last element in the array matching the specified condition. (optional) func last(where condition: (Element) throws -> Bool) rethrows -> Element? { for element in reversed() { if try condition(element) { return element } } return nil } /// SwifterSwift: Filter elements based on a rejection condition. /// /// [2, 2, 4, 7].reject(where: {$0 % 2 == 0}) -> [7] /// /// - Parameter condition: to evaluate the exclusion of an element from the array. /// - Returns: the array with rejected values filtered from it. func reject(where condition: (Element) throws -> Bool) rethrows -> [Element] { return try filter { return try !condition($0) } } /// SwifterSwift: Get element count based on condition. /// /// [2, 2, 4, 7].count(where: {$0 % 2 == 0}) -> 3 /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: number of times the condition evaluated to true. func count(where condition: (Element) throws -> Bool) rethrows -> Int { var count = 0 for element in self where try condition(element) { count += 1 } return count } /// SwifterSwift: Iterate over a collection in reverse order. (right to left) /// /// [0, 2, 4, 7].forEachReversed({ print($0)}) -> // Order of print: 7,4,2,0 /// /// - Parameter body: a closure that takes an element of the array as a parameter. func forEachReversed(_ body: (Element) throws -> Void) rethrows { try reversed().forEach(body) } /// SwifterSwift: Calls the given closure with each element where condition is true. /// /// [0, 2, 4, 7].forEach(where: {$0 % 2 == 0}, body: { print($0)}) -> // print: 0, 2, 4 /// /// - Parameters: /// - condition: condition to evaluate each element against. /// - body: a closure that takes an element of the array as a parameter. func forEach(where condition: (Element) throws -> Bool, body: (Element) throws -> Void) rethrows { for element in self where try condition(element) { try body(element) } } /// SwifterSwift: Reduces an array while returning each interim combination. /// /// [1, 2, 3].accumulate(initial: 0, next: +) -> [1, 3, 6] /// /// - Parameters: /// - initial: initial value. /// - next: closure that combines the accumulating value and next element of the array. /// - Returns: an array of the final accumulated value and each interim combination. func accumulate(initial: U, next: (U, Element) throws -> U) rethrows -> [U] { var runningTotal = initial return try map { element in runningTotal = try next(runningTotal, element) return runningTotal } } /// SwifterSwift: Filtered and map in a single operation. /// /// [1,2,3,4,5].filtered({ $0 % 2 == 0 }, map: { $0.string }) -> ["2", "4"] /// /// - Parameters: /// - isIncluded: condition of inclusion to evaluate each element against. /// - transform: transform element function to evaluate every element. /// - Returns: Return an filtered and mapped array. func filtered(_ isIncluded: (Element) throws -> Bool, map transform: (Element) throws -> T) rethrows -> [T] { return try compactMap({ if try isIncluded($0) { return try transform($0) } return nil }) } /// SwifterSwift: Get the only element based on a condition. /// /// [].single(where: {_ in true}) -> nil /// [4].single(where: {_ in true}) -> 4 /// [1, 4, 7].single(where: {$0 % 2 == 0}) -> 4 /// [2, 2, 4, 7].single(where: {$0 % 2 == 0}) -> nil /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: The only element in the array matching the specified condition. If there are more matching elements, nil is returned. (optional) func single(where condition: ((Element) throws -> Bool)) rethrows -> Element? { var singleElement: Element? for element in self where try condition(element) { guard singleElement == nil else { singleElement = nil break } singleElement = element } return singleElement } /// SwifterSwift: Remove duplicate elements based on condition. /// /// [1, 2, 1, 3, 2].withoutDuplicates { $0 } -> [1, 2, 3] /// [(1, 4), (2, 2), (1, 3), (3, 2), (2, 1)].withoutDuplicates { $0.0 } -> [(1, 4), (2, 2), (3, 2)] /// /// - Parameter transform: A closure that should return the value to be evaluated for repeating elements. /// - Returns: Sequence without repeating elements /// - Complexity: O(*n*), where *n* is the length of the sequence. func withoutDuplicates(transform: (Element) throws -> T) rethrows -> [Element] { var set = Set() return try filter { set.insert(try transform($0)).inserted } } /// SwifterSwift: Separates all items into 2 lists based on a given predicate. The first list contains all items for which the specified condition evaluates to true. The second list contains those that don't. /// /// let (even, odd) = [0, 1, 2, 3, 4, 5].divided { $0 % 2 == 0 } /// let (minors, adults) = people.divided { $0.age < 18 } /// /// - Parameter condition: condition to evaluate each element against. /// - Returns: A tuple of matched and non-matched items func divided(by condition: (Element) throws -> Bool) rethrows -> (matching: [Element], nonMatching: [Element]) { // Inspired by: http://ruby-doc.org/core-2.5.0/Enumerable.html#method-i-partition var matching = ContiguousArray() var nonMatching = ContiguousArray() var iterator = self.makeIterator() while let element = iterator.next() { try condition(element) ? matching.append(element) : nonMatching.append(element) } return (Array(matching), Array(nonMatching)) } /// SwifterSwift: Return a sorted array based on a key path and a compare function. /// /// - Parameter keyPath: Key path to sort by. /// - Parameter compare: Comparation function that will determine the ordering. /// - Returns: The sorted array. func sorted(by keyPath: KeyPath, with compare: (T, T) -> Bool) -> [Element] { return sorted { compare($0[keyPath: keyPath], $1[keyPath: keyPath]) } } /// SwifterSwift: Return a sorted array based on a key path. /// /// - Parameter keyPath: Key path to sort by. The key path type must be Comparable. /// - Returns: The sorted array. func sorted(by keyPath: KeyPath) -> [Element] { return sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] } } /// SwifterSwift: Returns a sorted sequence based on two key paths. The second one will be used in case the values of the first one match. /// /// - Parameters: /// - keyPath1: Key path to sort by. Must be Comparable. /// - keyPath2: Key path to sort by in case the values of `keyPath1` match. Must be Comparable. func sorted(by keyPath1: KeyPath, and keyPath2: KeyPath) -> [Element] { return sorted { if $0[keyPath: keyPath1] != $1[keyPath: keyPath1] { return $0[keyPath: keyPath1] < $1[keyPath: keyPath1] } return $0[keyPath: keyPath2] < $1[keyPath: keyPath2] } } /// SwifterSwift: Returns a sorted sequence based on three key paths. Whenever the values of one key path match, the next one will be used. /// /// - Parameters: /// - keyPath1: Key path to sort by. Must be Comparable. /// - keyPath2: Key path to sort by in case the values of `keyPath1` match. Must be Comparable. /// - keyPath3: Key path to sort by in case the values of `keyPath1` and `keyPath2` match. Must be Comparable. func sorted(by keyPath1: KeyPath, and keyPath2: KeyPath, and keyPath3: KeyPath) -> [Element] { return sorted { if $0[keyPath: keyPath1] != $1[keyPath: keyPath1] { return $0[keyPath: keyPath1] < $1[keyPath: keyPath1] } if $0[keyPath: keyPath2] != $1[keyPath: keyPath2] { return $0[keyPath: keyPath2] < $1[keyPath: keyPath2] } return $0[keyPath: keyPath3] < $1[keyPath: keyPath3] } } /// SwifterSwift: Sum of a `AdditiveArithmetic` property of each `Element` in a `Sequence`. /// /// ["James", "Wade", "Bryant"].sum(for: \.count) -> 15 /// /// - Parameter keyPath: Key path of the `AdditiveArithmetic` property. /// - Returns: The sum of the `AdditiveArithmetic` propertys at `keyPath`. func sum(for keyPath: KeyPath) -> T { // Inspired by: https://swiftbysundell.com/articles/reducers-in-swift/ return reduce(.zero) { $0 + $1[keyPath: keyPath] } } /// SwifterSwift: Returns an array containing the results of mapping the given key path over the sequence’s elements. /// /// - Parameter keyPath: Key path to map. /// - Returns: An array containing the results of mapping. func map(by keyPath: KeyPath) -> [T] { return map { $0[keyPath: keyPath] } } /// SwifterSwift: Returns an array containing the non-nil results of mapping the given key path over the sequence’s elements. /// /// - Parameter keyPath: Key path to map. /// - Returns: An array containing the non-nil results of mapping. func compactMap(by keyPath: KeyPath) -> [T] { return compactMap { $0[keyPath: keyPath] } } /// SwifterSwift: Returns an array containing the results of filtering the sequence’s elements by a boolean key path. /// /// - Parameter keyPath: Boolean key path. If it's value is `true` the element will be added to result. /// - Returns: An array containing filtered elements. func filter(by keyPath: KeyPath) -> [Element] { return filter { $0[keyPath: keyPath] } } } public extension Sequence where Element: Equatable { /// SwifterSwift: Check if array contains an array of elements. /// /// [1, 2, 3, 4, 5].contains([1, 2]) -> true /// [1.2, 2.3, 4.5, 3.4, 4.5].contains([2, 6]) -> false /// ["h", "e", "l", "l", "o"].contains(["l", "o"]) -> true /// /// - Parameter elements: array of elements to check. /// - Returns: true if array contains all given items. func contains(_ elements: [Element]) -> Bool { guard !elements.isEmpty else { return true } for element in elements { if !contains(element) { return false } } return true } } public extension Sequence where Element: Hashable { /// SwifterSwift: Check whether a sequence contains duplicates. /// /// - Returns: true if the receiver contains duplicates. func containsDuplicates() -> Bool { var set = Set() for element in self { if !set.insert(element).inserted { return true } } return false } /// SwifterSwift: Getting the duplicated elements in a sequence. /// /// [1, 1, 2, 2, 3, 3, 3, 4, 5].duplicates().sorted() -> [1, 2, 3]) /// ["h", "e", "l", "l", "o"].duplicates().sorted() -> ["l"]) /// /// - Returns: An array of duplicated elements. /// func duplicates() -> [Element] { var set = Set() var duplicates = Set() forEach { if !set.insert($0).inserted { duplicates.insert($0) } } return Array(duplicates) } } // MARK: - Methods (Numeric) public extension Sequence where Element: Numeric { /// SwifterSwift: Sum of all elements in array. /// /// [1, 2, 3, 4, 5].sum() -> 15 /// /// - Returns: sum of the array's elements. func sum() -> Element { return reduce(into: 0, +=) } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/SignedIntegerExtensions.swift ================================================ // // SignedIntegerExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/15/17. // Copyright © 2017 SwifterSwift // #if canImport(Foundation) import Foundation #endif // MARK: - Properties public extension SignedInteger { /// SwifterSwift: Absolute value of integer number. var abs: Self { return Swift.abs(self) } /// SwifterSwift: Check if integer is positive. var isPositive: Bool { return self > 0 } /// SwifterSwift: Check if integer is negative. var isNegative: Bool { return self < 0 } /// SwifterSwift: Check if integer is even. var isEven: Bool { return (self % 2) == 0 } /// SwifterSwift: Check if integer is odd. var isOdd: Bool { return (self % 2) != 0 } /// SwifterSwift: String of format (XXh XXm) from seconds Int. var timeString: String { guard self > 0 else { return "0 sec" } if self < 60 { return "\(self) sec" } if self < 3600 { return "\(self / 60) min" } let hours = self / 3600 let mins = (self % 3600) / 60 if hours != 0 && mins == 0 { return "\(hours)h" } return "\(hours)h \(mins)m" } } // MARK: - Methods public extension SignedInteger { /// SwifterSwift: Greatest common divisor of integer value and n. /// /// - Parameter number: integer value to find gcd with. /// - Returns: greatest common divisor of self and n. func gcd(of number: Self) -> Self { return number == 0 ? self : number.gcd(of: self % number) } /// SwifterSwift: Least common multiple of integer and n. /// /// - Parameter number: integer value to find lcm with. /// - Returns: least common multiple of self and n. func lcm(of number: Self) -> Self { return (self * number).abs / gcd(of: number) } #if canImport(Foundation) /// SwifterSwift: Ordinal representation of an integer. /// /// print((12).ordinalString()) // prints "12th" /// /// - Parameter locale: locale, default is .current. /// - Returns: string ordinal representation of number in specified locale language. E.g. input 92, output in "en": "92nd". @available(macOS 10.11, *) func ordinalString(locale: Locale = .current) -> String? { let formatter = NumberFormatter() formatter.locale = locale formatter.numberStyle = .ordinal guard let number = self as? NSNumber else { return nil } return formatter.string(from: number) } #endif } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/SignedNumericExtensions.swift ================================================ // // SignedNumberExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/15/17. // Copyright © 2017 SwifterSwift // #if canImport(Foundation) import Foundation #endif // MARK: - Properties public extension SignedNumeric { /// SwifterSwift: String. var string: String { return String(describing: self) } #if canImport(Foundation) /// SwifterSwift: String with number and current locale currency. var asLocaleCurrency: String? { let formatter = NumberFormatter() formatter.numberStyle = .currency formatter.locale = Locale.current // swiftlint:disable:next force_cast return formatter.string(from: self as! NSNumber) } #endif } // MARK: - Methods public extension SignedNumeric { #if canImport(Foundation) /// SwifterSwift: Spelled out representation of a number. /// /// print((12.32).spelledOutString()) // prints "twelve point three two" /// /// - Parameter locale: Locale, default is .current. /// - Returns: String representation of number spelled in specified locale language. E.g. input 92, output in "en": "ninety-two" func spelledOutString(locale: Locale = .current) -> String? { let formatter = NumberFormatter() formatter.locale = locale formatter.numberStyle = .spellOut guard let number = self as? NSNumber else { return nil } return formatter.string(from: number) } #endif } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/StringExtensions.swift ================================================ // // StringExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/5/16. // Copyright © 2016 SwifterSwift // #if canImport(Foundation) import Foundation #endif #if canImport(UIKit) import UIKit #endif #if canImport(AppKit) import AppKit #endif #if canImport(CoreGraphics) import CoreGraphics #endif // MARK: - Properties public extension String { #if canImport(Foundation) /// SwifterSwift: String decoded from base64 (if applicable). /// /// "SGVsbG8gV29ybGQh".base64Decoded = Optional("Hello World!") /// var base64Decoded: String? { let remainder = count % 4 var padding = "" if remainder > 0 { padding = String(repeating: "=", count: 4 - remainder) } guard let data = Data(base64Encoded: self + padding, options: .ignoreUnknownCharacters) else { return nil } return String(data: data, encoding: .utf8) } #endif #if canImport(Foundation) /// SwifterSwift: String encoded in base64 (if applicable). /// /// "Hello World!".base64Encoded -> Optional("SGVsbG8gV29ybGQh") /// var base64Encoded: String? { // https://github.com/Reza-Rg/Base64-Swift-Extension/blob/master/Base64.swift let plainData = data(using: .utf8) return plainData?.base64EncodedString() } #endif /// SwifterSwift: Array of characters of a string. var charactersArray: [Character] { return Array(self) } #if canImport(Foundation) /// SwifterSwift: CamelCase of string. /// /// "sOme vAriable naMe".camelCased -> "someVariableName" /// var camelCased: String { let source = lowercased() let first = source[.. true /// var containEmoji: Bool { // http://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji for scalar in unicodeScalars { switch scalar.value { case 0x1F600...0x1F64F, // Emoticons 0x1F300...0x1F5FF, // Misc Symbols and Pictographs 0x1F680...0x1F6FF, // Transport and Map 0x1F1E6...0x1F1FF, // Regional country flags 0x2600...0x26FF, // Misc symbols 0x2700...0x27BF, // Dingbats 0xE0020...0xE007F, // Tags 0xFE00...0xFE0F, // Variation Selectors 0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs 127000...127600, // Various asian characters 65024...65039, // Variation selector 9100...9300, // Misc items 8400...8447: // Combining Diacritical Marks for Symbols return true default: continue } } return false } /// SwifterSwift: First character of string (if applicable). /// /// "Hello".firstCharacterAsString -> Optional("H") /// "".firstCharacterAsString -> nil /// var firstCharacterAsString: String? { guard let first = first else { return nil } return String(first) } /// SwifterSwift: Check if string contains one or more letters. /// /// "123abc".hasLetters -> true /// "123".hasLetters -> false /// var hasLetters: Bool { return rangeOfCharacter(from: .letters, options: .numeric, range: nil) != nil } /// SwifterSwift: Check if string contains one or more numbers. /// /// "abcd".hasNumbers -> false /// "123abc".hasNumbers -> true /// var hasNumbers: Bool { return rangeOfCharacter(from: .decimalDigits, options: .literal, range: nil) != nil } /// SwifterSwift: Check if string contains only letters. /// /// "abc".isAlphabetic -> true /// "123abc".isAlphabetic -> false /// var isAlphabetic: Bool { let hasLetters = rangeOfCharacter(from: .letters, options: .numeric, range: nil) != nil let hasNumbers = rangeOfCharacter(from: .decimalDigits, options: .literal, range: nil) != nil return hasLetters && !hasNumbers } /// SwifterSwift: Check if string contains at least one letter and one number. /// /// // useful for passwords /// "123abc".isAlphaNumeric -> true /// "abc".isAlphaNumeric -> false /// var isAlphaNumeric: Bool { let hasLetters = rangeOfCharacter(from: .letters, options: .numeric, range: nil) != nil let hasNumbers = rangeOfCharacter(from: .decimalDigits, options: .literal, range: nil) != nil let comps = components(separatedBy: .alphanumerics) return comps.joined(separator: "").count == 0 && hasLetters && hasNumbers } /// SwifterSwift: Check if string is palindrome. /// /// "abcdcba".isPalindrome -> true /// "Mom".isPalindrome -> true /// "A man a plan a canal, Panama!".isPalindrome -> true /// "Mama".isPalindrome -> false /// var isPalindrome: Bool { let letters = filter { $0.isLetter } guard !letters.isEmpty else { return false } let midIndex = letters.index(letters.startIndex, offsetBy: letters.count / 2) let firstHalf = letters[letters.startIndex.. true /// var isValidEmail: Bool { // http://emailregex.com/ let regex = "^(?:[\\p{L}0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[\\p{L}0-9!#$%\\&'*+/=?\\^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\p{L}0-9](?:[a-z0-9-]*[\\p{L}0-9])?\\.)+[\\p{L}0-9](?:[\\p{L}0-9-]*[\\p{L}0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[\\p{L}0-9-]*[\\p{L}0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])$" return range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil } #endif #if canImport(Foundation) /// SwifterSwift: Check if string is a valid URL. /// /// "https://google.com".isValidUrl -> true /// var isValidUrl: Bool { return URL(string: self) != nil } #endif #if canImport(Foundation) /// SwifterSwift: Check if string is a valid schemed URL. /// /// "https://google.com".isValidSchemedUrl -> true /// "google.com".isValidSchemedUrl -> false /// var isValidSchemedUrl: Bool { guard let url = URL(string: self) else { return false } return url.scheme != nil } #endif #if canImport(Foundation) /// SwifterSwift: Check if string is a valid https URL. /// /// "https://google.com".isValidHttpsUrl -> true /// var isValidHttpsUrl: Bool { guard let url = URL(string: self) else { return false } return url.scheme == "https" } #endif #if canImport(Foundation) /// SwifterSwift: Check if string is a valid http URL. /// /// "http://google.com".isValidHttpUrl -> true /// var isValidHttpUrl: Bool { guard let url = URL(string: self) else { return false } return url.scheme == "http" } #endif #if canImport(Foundation) /// SwifterSwift: Check if string is a valid file URL. /// /// "file://Documents/file.txt".isValidFileUrl -> true /// var isValidFileUrl: Bool { return URL(string: self)?.isFileURL ?? false } #endif #if canImport(Foundation) /// SwifterSwift: Check if string is a valid Swift number. Note: In North America, "." is the decimal separator, while in many parts of Europe "," is used, /// /// "123".isNumeric -> true /// "1.3".isNumeric -> true (en_US) /// "1,3".isNumeric -> true (fr_FR) /// "abc".isNumeric -> false /// var isNumeric: Bool { let scanner = Scanner(string: self) scanner.locale = NSLocale.current #if os(Linux) || targetEnvironment(macCatalyst) return scanner.scanDecimal() != nil && scanner.isAtEnd #else return scanner.scanDecimal(nil) && scanner.isAtEnd #endif } #endif #if canImport(Foundation) /// SwifterSwift: Check if string only contains digits. /// /// "123".isDigits -> true /// "1.3".isDigits -> false /// "abc".isDigits -> false /// var isDigits: Bool { return CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: self)) } #endif /// SwifterSwift: Last character of string (if applicable). /// /// "Hello".lastCharacterAsString -> Optional("o") /// "".lastCharacterAsString -> nil /// var lastCharacterAsString: String? { guard let last = last else { return nil } return String(last) } #if canImport(Foundation) /// SwifterSwift: Latinized string. /// /// "Hèllö Wórld!".latinized -> "Hello World!" /// var latinized: String { return folding(options: .diacriticInsensitive, locale: Locale.current) } #endif #if canImport(Foundation) /// SwifterSwift: Bool value from string (if applicable). /// /// "1".bool -> true /// "False".bool -> false /// "Hello".bool = nil /// var bool: Bool? { let selfLowercased = trimmingCharacters(in: .whitespacesAndNewlines).lowercased() switch selfLowercased { case "true", "yes", "1": return true case "false", "no", "0": return false default: return nil } } #endif #if canImport(Foundation) /// SwifterSwift: Date object from "yyyy-MM-dd" formatted string. /// /// "2007-06-29".date -> Optional(Date) /// var date: Date? { let selfLowercased = trimmingCharacters(in: .whitespacesAndNewlines).lowercased() let formatter = DateFormatter() formatter.timeZone = TimeZone.current formatter.dateFormat = "yyyy-MM-dd" return formatter.date(from: selfLowercased) } #endif #if canImport(Foundation) /// SwifterSwift: Date object from "yyyy-MM-dd HH:mm:ss" formatted string. /// /// "2007-06-29 14:23:09".dateTime -> Optional(Date) /// var dateTime: Date? { let selfLowercased = trimmingCharacters(in: .whitespacesAndNewlines).lowercased() let formatter = DateFormatter() formatter.timeZone = TimeZone.current formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" return formatter.date(from: selfLowercased) } #endif /// SwifterSwift: Integer value from string (if applicable). /// /// "101".int -> 101 /// var int: Int? { return Int(self) } /// SwifterSwift: Lorem ipsum string of given length. /// /// - Parameter length: number of characters to limit lorem ipsum to (default is 445 - full lorem ipsum). /// - Returns: Lorem ipsum dolor sit amet... string. static func loremIpsum(ofLength length: Int = 445) -> String { guard length > 0 else { return "" } // https://www.lipsum.com/ let loremIpsum = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. """ if loremIpsum.count > length { return String(loremIpsum[loremIpsum.startIndex.. URL(string: "https://google.com") /// "not url".url -> nil /// var url: URL? { return URL(string: self) } #endif #if canImport(Foundation) /// SwifterSwift: String with no spaces or new lines in beginning and end. /// /// " hello \n".trimmed -> "hello" /// var trimmed: String { return trimmingCharacters(in: .whitespacesAndNewlines) } #endif #if canImport(Foundation) /// SwifterSwift: Readable string from a URL string. /// /// "it's%20easy%20to%20decode%20strings".urlDecoded -> "it's easy to decode strings" /// var urlDecoded: String { return removingPercentEncoding ?? self } #endif #if canImport(Foundation) /// SwifterSwift: URL escaped string. /// /// "it's easy to encode strings".urlEncoded -> "it's%20easy%20to%20encode%20strings" /// var urlEncoded: String { return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)! } #endif #if canImport(Foundation) /// SwifterSwift: String without spaces and new lines. /// /// " \n Swifter \n Swift ".withoutSpacesAndNewLines -> "SwifterSwift" /// var withoutSpacesAndNewLines: String { return replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "\n", with: "") } #endif #if canImport(Foundation) /// SwifterSwift: Check if the given string contains only white spaces var isWhitespace: Bool { return trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } #endif #if os(iOS) || os(tvOS) /// SwifterSwift: Check if the given string spelled correctly var isSpelledCorrectly: Bool { let checker = UITextChecker() let range = NSRange(location: 0, length: utf16.count) let misspelledRange = checker.rangeOfMisspelledWord(in: self, range: range, startingAt: 0, wrap: false, language: Locale.preferredLanguages.first ?? "en") return misspelledRange.location == NSNotFound } #endif } // MARK: - Methods public extension String { #if canImport(Foundation) /// SwifterSwift: Float value from string (if applicable). /// /// - Parameter locale: Locale (default is Locale.current) /// - Returns: Optional Float value from given string. func float(locale: Locale = .current) -> Float? { let formatter = NumberFormatter() formatter.locale = locale formatter.allowsFloats = true return formatter.number(from: self)?.floatValue } #endif #if canImport(Foundation) /// SwifterSwift: Double value from string (if applicable). /// /// - Parameter locale: Locale (default is Locale.current) /// - Returns: Optional Double value from given string. func double(locale: Locale = .current) -> Double? { let formatter = NumberFormatter() formatter.locale = locale formatter.allowsFloats = true return formatter.number(from: self)?.doubleValue } #endif #if canImport(CoreGraphics) && canImport(Foundation) /// SwifterSwift: CGFloat value from string (if applicable). /// /// - Parameter locale: Locale (default is Locale.current) /// - Returns: Optional CGFloat value from given string. func cgFloat(locale: Locale = .current) -> CGFloat? { let formatter = NumberFormatter() formatter.locale = locale formatter.allowsFloats = true return formatter.number(from: self) as? CGFloat } #endif #if canImport(Foundation) /// SwifterSwift: Array of strings separated by new lines. /// /// "Hello\ntest".lines() -> ["Hello", "test"] /// /// - Returns: Strings separated by new lines. func lines() -> [String] { var result = [String]() enumerateLines { line, _ in result.append(line) } return result } #endif #if canImport(Foundation) /// SwifterSwift: Returns a localized string, with an optional comment for translators. /// /// "Hello world".localized -> Hallo Welt /// func localized(comment: String = "") -> String { return NSLocalizedString(self, comment: comment) } #endif /// SwifterSwift: The most common character in string. /// /// "This is a test, since e is appearing everywhere e should be the common character".mostCommonCharacter() -> "e" /// /// - Returns: The most common character. func mostCommonCharacter() -> Character? { let mostCommon = withoutSpacesAndNewLines.reduce(into: [Character: Int]()) { let count = $0[$1] ?? 0 $0[$1] = count + 1 }.max { $0.1 < $1.1 }?.key return mostCommon } /// SwifterSwift: Array with unicodes for all characters in a string. /// /// "SwifterSwift".unicodeArray() -> [83, 119, 105, 102, 116, 101, 114, 83, 119, 105, 102, 116] /// /// - Returns: The unicodes for all characters in a string. func unicodeArray() -> [Int] { return unicodeScalars.map { Int($0.value) } } #if canImport(Foundation) /// SwifterSwift: an array of all words in a string /// /// "Swift is amazing".words() -> ["Swift", "is", "amazing"] /// /// - Returns: The words contained in a string. func words() -> [String] { // https://stackoverflow.com/questions/42822838 let chararacterSet = CharacterSet.whitespacesAndNewlines.union(.punctuationCharacters) let comps = components(separatedBy: chararacterSet) return comps.filter { !$0.isEmpty } } #endif #if canImport(Foundation) /// SwifterSwift: Count of words in a string. /// /// "Swift is amazing".wordsCount() -> 3 /// /// - Returns: The count of words contained in a string. func wordCount() -> Int { // https://stackoverflow.com/questions/42822838 let chararacterSet = CharacterSet.whitespacesAndNewlines.union(.punctuationCharacters) let comps = components(separatedBy: chararacterSet) let words = comps.filter { !$0.isEmpty } return words.count } #endif #if canImport(Foundation) /// SwifterSwift: Transforms the string into a slug string. /// /// "Swift is amazing".toSlug() -> "swift-is-amazing" /// /// - Returns: The string in slug format. func toSlug() -> String { let lowercased = self.lowercased() let latinized = lowercased.folding(options: .diacriticInsensitive, locale: Locale.current) let withDashes = latinized.replacingOccurrences(of: " ", with: "-") let alphanumerics = NSCharacterSet.alphanumerics var filtered = withDashes.filter { guard String($0) != "-" else { return true } guard String($0) != "&" else { return true } return String($0).rangeOfCharacter(from: alphanumerics) != nil } while filtered.lastCharacterAsString == "-" { filtered = String(filtered.dropLast()) } while filtered.firstCharacterAsString == "-" { filtered = String(filtered.dropFirst()) } return filtered.replacingOccurrences(of: "--", with: "-") } #endif /// SwifterSwift: Safely subscript string with index. /// /// "Hello World!"[safe: 3] -> "l" /// "Hello World!"[safe: 20] -> nil /// /// - Parameter index: index. subscript(safe index: Int) -> Character? { guard index >= 0 && index < count else { return nil } return self[self.index(startIndex, offsetBy: index)] } /// SwifterSwift: Safely subscript string within a given range. /// /// "Hello World!"[safe: 6..<11] -> "World" /// "Hello World!"[safe: 21..<110] -> nil /// /// "Hello World!"[safe: 6...11] -> "World!" /// "Hello World!"[safe: 21...110] -> nil /// /// - Parameter range: Range expression. subscript(safe range: R) -> String? where R: RangeExpression, R.Bound == Int { let range = range.relative(to: Int.min..= 0, let lowerIndex = index(startIndex, offsetBy: range.lowerBound, limitedBy: endIndex), let upperIndex = index(startIndex, offsetBy: range.upperBound, limitedBy: endIndex) else { return nil } return String(self[lowerIndex.. String { let source = lowercased() let first = source[.. "Hello world" /// "".firstCharacterUppercased() -> "" /// mutating func firstCharacterUppercased() { guard let first = first else { return } self = String(first).uppercased() + dropFirst() } /// SwifterSwift: Check if string contains only unique characters. /// func hasUniqueCharacters() -> Bool { guard count > 0 else { return false } var uniqueChars = Set() for char in self { if uniqueChars.contains(String(char)) { return false } uniqueChars.insert(String(char)) } return true } #if canImport(Foundation) /// SwifterSwift: Check if string contains one or more instance of substring. /// /// "Hello World!".contain("O") -> false /// "Hello World!".contain("o", caseSensitive: false) -> true /// /// - Parameters: /// - string: substring to search for. /// - caseSensitive: set true for case sensitive search (default is true). /// - Returns: true if string contains one or more instance of substring. func contains(_ string: String, caseSensitive: Bool = true) -> Bool { if !caseSensitive { return range(of: string, options: .caseInsensitive) != nil } return range(of: string) != nil } #endif #if canImport(Foundation) /// SwifterSwift: Count of substring in string. /// /// "Hello World!".count(of: "o") -> 2 /// "Hello World!".count(of: "L", caseSensitive: false) -> 3 /// /// - Parameters: /// - string: substring to search for. /// - caseSensitive: set true for case sensitive search (default is true). /// - Returns: count of appearance of substring in string. func count(of string: String, caseSensitive: Bool = true) -> Int { if !caseSensitive { return lowercased().components(separatedBy: string.lowercased()).count - 1 } return components(separatedBy: string).count - 1 } #endif /// SwifterSwift: Check if string ends with substring. /// /// "Hello World!".ends(with: "!") -> true /// "Hello World!".ends(with: "WoRld!", caseSensitive: false) -> true /// /// - Parameters: /// - suffix: substring to search if string ends with. /// - caseSensitive: set true for case sensitive search (default is true). /// - Returns: true if string ends with substring. func ends(with suffix: String, caseSensitive: Bool = true) -> Bool { if !caseSensitive { return lowercased().hasSuffix(suffix.lowercased()) } return hasSuffix(suffix) } #if canImport(Foundation) /// SwifterSwift: Latinize string. /// /// var str = "Hèllö Wórld!" /// str.latinize() /// print(str) // prints "Hello World!" /// @discardableResult mutating func latinize() -> String { self = folding(options: .diacriticInsensitive, locale: Locale.current) return self } #endif /// SwifterSwift: Random string of given length. /// /// String.random(ofLength: 18) -> "u7MMZYvGo9obcOcPj8" /// /// - Parameter length: number of characters in string. /// - Returns: random string of given length. static func random(ofLength length: Int) -> String { guard length > 0 else { return "" } let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" var randomString = "" for _ in 1...length { randomString.append(base.randomElement()!) } return randomString } /// SwifterSwift: Reverse string. @discardableResult mutating func reverse() -> String { let chars: [Character] = reversed() self = String(chars) return self } /// SwifterSwift: Sliced string from a start index with length. /// /// "Hello World".slicing(from: 6, length: 5) -> "World" /// /// - Parameters: /// - index: string index the slicing should start from. /// - length: amount of characters to be sliced after given index. /// - Returns: sliced substring of length number of characters (if applicable) (example: "Hello World".slicing(from: 6, length: 5) -> "World") func slicing(from index: Int, length: Int) -> String? { guard length >= 0, index >= 0, index < count else { return nil } guard index.advanced(by: length) <= count else { return self[safe: index.. 0 else { return "" } return self[safe: index.. String { if let str = slicing(from: index, length: length) { self = String(str) } return self } /// SwifterSwift: Slice given string from a start index to an end index (if applicable). /// /// var str = "Hello World" /// str.slice(from: 6, to: 11) /// print(str) // prints "World" /// /// - Parameters: /// - start: string index the slicing should start from. /// - end: string index the slicing should end at. @discardableResult mutating func slice(from start: Int, to end: Int) -> String { guard end >= start else { return self } if let str = self[safe: start.. String { guard index < count else { return self } if let str = self[safe: index.. true /// "hello World".starts(with: "H", caseSensitive: false) -> true /// /// - Parameters: /// - suffix: substring to search if string starts with. /// - caseSensitive: set true for case sensitive search (default is true). /// - Returns: true if string starts with substring. func starts(with prefix: String, caseSensitive: Bool = true) -> Bool { if !caseSensitive { return lowercased().hasPrefix(prefix.lowercased()) } return hasPrefix(prefix) } #if canImport(Foundation) /// SwifterSwift: Date object from string of date format. /// /// "2017-01-15".date(withFormat: "yyyy-MM-dd") -> Date set to Jan 15, 2017 /// "not date string".date(withFormat: "yyyy-MM-dd") -> nil /// /// - Parameter format: date format. /// - Returns: Date object from string (if applicable). func date(withFormat format: String) -> Date? { let dateFormatter = DateFormatter() dateFormatter.dateFormat = format return dateFormatter.date(from: self) } #endif #if canImport(Foundation) /// SwifterSwift: Removes spaces and new lines in beginning and end of string. /// /// var str = " \n Hello World \n\n\n" /// str.trim() /// print(str) // prints "Hello World" /// @discardableResult mutating func trim() -> String { self = trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) return self } #endif /// SwifterSwift: Truncate string (cut it to a given number of characters). /// /// var str = "This is a very long sentence" /// str.truncate(toLength: 14) /// print(str) // prints "This is a very..." /// /// - Parameters: /// - toLength: maximum number of characters before cutting. /// - trailing: string to add at the end of truncated string (default is "..."). @discardableResult mutating func truncate(toLength length: Int, trailing: String? = "...") -> String { guard length > 0 else { return self } if count > length { self = self[startIndex.. "This is a very..." /// "Short sentence".truncated(toLength: 14) -> "Short sentence" /// /// - Parameters: /// - toLength: maximum number of characters before cutting. /// - trailing: string to add at the end of truncated string. /// - Returns: truncated string (this is an extr...). func truncated(toLength length: Int, trailing: String? = "...") -> String { guard 1.. String { if let decoded = removingPercentEncoding { self = decoded } return self } #endif #if canImport(Foundation) /// SwifterSwift: Escape string. /// /// var str = "it's easy to encode strings" /// str.urlEncode() /// print(str) // prints "it's%20easy%20to%20encode%20strings" /// @discardableResult mutating func urlEncode() -> String { if let encoded = addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) { self = encoded } return self } #endif #if canImport(Foundation) /// SwifterSwift: Verify if string matches the regex pattern. /// /// - Parameter pattern: Pattern to verify. /// - Returns: true if string matches the pattern. func matches(pattern: String) -> Bool { return range(of: pattern, options: .regularExpression, range: nil, locale: nil) != nil } #endif /// SwifterSwift: Pad string to fit the length parameter size with another string in the start. /// /// "hue".padStart(10) -> " hue" /// "hue".padStart(10, with: "br") -> "brbrbrbhue" /// /// - Parameter length: The target length to pad. /// - Parameter string: Pad string. Default is " ". @discardableResult mutating func padStart(_ length: Int, with string: String = " ") -> String { self = paddingStart(length, with: string) return self } /// SwifterSwift: Returns a string by padding to fit the length parameter size with another string in the start. /// /// "hue".paddingStart(10) -> " hue" /// "hue".paddingStart(10, with: "br") -> "brbrbrbhue" /// /// - Parameter length: The target length to pad. /// - Parameter string: Pad string. Default is " ". /// - Returns: The string with the padding on the start. func paddingStart(_ length: Int, with string: String = " ") -> String { guard count < length else { return self } let padLength = length - count if padLength < string.count { return string[string.startIndex.. "hue " /// "hue".padEnd(10, with: "br") -> "huebrbrbrb" /// /// - Parameter length: The target length to pad. /// - Parameter string: Pad string. Default is " ". @discardableResult mutating func padEnd(_ length: Int, with string: String = " ") -> String { self = paddingEnd(length, with: string) return self } /// SwifterSwift: Returns a string by padding to fit the length parameter size with another string in the end. /// /// "hue".paddingEnd(10) -> "hue " /// "hue".paddingEnd(10, with: "br") -> "huebrbrbrb" /// /// - Parameter length: The target length to pad. /// - Parameter string: Pad string. Default is " ". /// - Returns: The string with the padding on the end. func paddingEnd(_ length: Int, with string: String = " ") -> String { guard count < length else { return self } let padLength = length - count if padLength < string.count { return self + string[string.startIndex.. "World!" /// /// - Parameter prefix: Prefix to remove from the string. /// - Returns: The string after prefix removing. func removingPrefix(_ prefix: String) -> String { guard hasPrefix(prefix) else { return self } return String(dropFirst(prefix.count)) } /// SwifterSwift: Removes given suffix from the string. /// /// "Hello, World!".removingSuffix(", World!") -> "Hello" /// /// - Parameter suffix: Suffix to remove from the string. /// - Returns: The string after suffix removing. func removingSuffix(_ suffix: String) -> String { guard hasSuffix(suffix) else { return self } return String(dropLast(suffix.count)) } /// SwifterSwift: Adds prefix to the string. /// /// "www.apple.com".withPrefix("https://") -> "https://www.apple.com" /// /// - Parameter prefix: Prefix to add to the string. /// - Returns: The string with the prefix prepended. func withPrefix(_ prefix: String) -> String { // https://www.hackingwithswift.com/articles/141/8-useful-swift-extensions guard !hasPrefix(prefix) else { return self } return prefix + self } } // MARK: - Initializers public extension String { #if canImport(Foundation) /// SwifterSwift: Create a new string from a base64 string (if applicable). /// /// String(base64: "SGVsbG8gV29ybGQh") = "Hello World!" /// String(base64: "hello") = nil /// /// - Parameter base64: base64 string. init?(base64: String) { guard let decodedData = Data(base64Encoded: base64) else { return nil } guard let str = String(data: decodedData, encoding: .utf8) else { return nil } self.init(str) } #endif /// SwifterSwift: Create a new random string of given length. /// /// String(randomOfLength: 10) -> "gY8r3MHvlQ" /// /// - Parameter length: number of characters in string. init(randomOfLength length: Int) { guard length > 0 else { self.init() return } let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" var randomString = "" for _ in 1...length { randomString.append(base.randomElement()!) } self = randomString } } #if !os(Linux) // MARK: - NSAttributedString public extension String { #if canImport(UIKit) private typealias Font = UIFont #endif #if canImport(AppKit) && !targetEnvironment(macCatalyst) private typealias Font = NSFont #endif #if os(iOS) || os(macOS) /// SwifterSwift: Bold string. var bold: NSAttributedString { return NSMutableAttributedString(string: self, attributes: [.font: Font.boldSystemFont(ofSize: Font.systemFontSize)]) } #endif #if canImport(Foundation) /// SwifterSwift: Underlined string var underline: NSAttributedString { return NSAttributedString(string: self, attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue]) } #endif #if canImport(Foundation) /// SwifterSwift: Strikethrough string. var strikethrough: NSAttributedString { return NSAttributedString(string: self, attributes: [.strikethroughStyle: NSNumber(value: NSUnderlineStyle.single.rawValue as Int)]) } #endif #if os(iOS) /// SwifterSwift: Italic string. var italic: NSAttributedString { return NSMutableAttributedString(string: self, attributes: [.font: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)]) } #endif #if canImport(AppKit) || canImport(UIKit) /// SwifterSwift: Add color to string. /// /// - Parameter color: text color. /// - Returns: a NSAttributedString versions of string colored with given color. func colored(with color: Color) -> NSAttributedString { return NSMutableAttributedString(string: self, attributes: [.foregroundColor: color]) } #endif } #endif // MARK: - Operators public extension String { /// SwifterSwift: Repeat string multiple times. /// /// 'bar' * 3 -> "barbarbar" /// /// - Parameters: /// - lhs: string to repeat. /// - rhs: number of times to repeat character. /// - Returns: new string with given string repeated n times. static func * (lhs: String, rhs: Int) -> String { guard rhs > 0 else { return "" } return String(repeating: lhs, count: rhs) } /// SwifterSwift: Repeat string multiple times. /// /// 3 * 'bar' -> "barbarbar" /// /// - Parameters: /// - lhs: number of times to repeat character. /// - rhs: string to repeat. /// - Returns: new string with given string repeated n times. static func * (lhs: Int, rhs: String) -> String { guard lhs > 0 else { return "" } return String(repeating: rhs, count: lhs) } } #if canImport(Foundation) // MARK: - NSString extensions public extension String { /// SwifterSwift: NSString from a string. var nsString: NSString { return NSString(string: self) } /// SwifterSwift: NSString lastPathComponent. var lastPathComponent: String { return (self as NSString).lastPathComponent } /// SwifterSwift: NSString pathExtension. var pathExtension: String { return (self as NSString).pathExtension } /// SwifterSwift: NSString deletingLastPathComponent. var deletingLastPathComponent: String { return (self as NSString).deletingLastPathComponent } /// SwifterSwift: NSString deletingPathExtension. var deletingPathExtension: String { return (self as NSString).deletingPathExtension } /// SwifterSwift: NSString pathComponents. var pathComponents: [String] { return (self as NSString).pathComponents } /// SwifterSwift: NSString appendingPathComponent(str: String) /// /// - Note: This method only works with file paths (not, for example, string representations of URLs. /// See NSString [appendingPathComponent(_:)](https://developer.apple.com/documentation/foundation/nsstring/1417069-appendingpathcomponent) /// - Parameter str: the path component to append to the receiver. /// - Returns: a new string made by appending aString to the receiver, preceded if necessary by a path separator. func appendingPathComponent(_ str: String) -> String { return (self as NSString).appendingPathComponent(str) } /// SwifterSwift: NSString appendingPathExtension(str: String) /// /// - Parameter str: The extension to append to the receiver. /// - Returns: a new string made by appending to the receiver an extension separator followed by ext (if applicable). func appendingPathExtension(_ str: String) -> String? { return (self as NSString).appendingPathExtension(str) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/SwiftStdlib/StringProtocolExtensions.swift ================================================ // // StringProtocolExtensions.swift // SwifterSwift // // Created by Max Härtwig on 11/26/17. // Copyright © 2017 SwifterSwift // import Foundation public extension StringProtocol { /// SwifterSwift: The longest common suffix. /// /// "Hello world!".commonSuffix(with: "It's cold!") = "ld!" /// /// - Parameters: /// - Parameter aString: The string with which to compare the receiver. /// - Parameter options: Options for the comparison. /// - Returns: The longest common suffix of the receiver and the given String func commonSuffix(with aString: T, options: String.CompareOptions = []) -> String { guard !isEmpty && !aString.isEmpty else { return "" } var idx = endIndex var strIdx = aString.endIndex repeat { formIndex(before: &idx) aString.formIndex(before: &strIdx) guard String(self[idx]).compare(String(aString[strIdx]), options: options) == .orderedSame else { formIndex(after: &idx) break } } while idx > startIndex && strIdx > aString.startIndex return String(self[idx...]) } } ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIActivityExtensions.swift ================================================ // // UIActivityExtensions.swift // SwifterSwift // // Created by Hannes Staffler on 07.10.18. // Copyright © 2018 SwifterSwift // #if canImport(UIKit) && os(iOS) import UIKit // MARK: - ActivityType public extension UIActivity.ActivityType { /// SwifterSwift: AddToiCloudDrive static let addToiCloudDrive = UIActivity.ActivityType("com.apple.CloudDocsUI.AddToiCloudDrive") /// SwifterSwift: WhatsApp share extension static let postToWhatsApp = UIActivity.ActivityType("net.whatsapp.WhatsApp.ShareExtension") /// SwifterSwift: LinkedIn share extension static let postToLinkedIn = UIActivity.ActivityType("com.linkedin.LinkedIn.ShareExtension") /// SwifterSwift: XING share extension static let postToXing = UIActivity.ActivityType("com.xing.XING.Xing-Share") } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIAlertControllerExtensions.swift ================================================ // // UIAlertControllerExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/23/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit #if canImport(AudioToolbox) import AudioToolbox #endif // MARK: - Methods public extension UIAlertController { /// SwifterSwift: Present alert view controller in the current view controller. /// /// - Parameters: /// - animated: set true to animate presentation of alert controller (default is true). /// - vibrate: set true to vibrate the device while presenting the alert (default is false). /// - completion: an optional completion handler to be called after presenting alert controller (default is nil). func show(animated: Bool = true, vibrate: Bool = false, completion: (() -> Void)? = nil) { #if targetEnvironment(macCatalyst) let window = UIApplication.shared.windows.last #else let window = UIApplication.shared.keyWindow #endif window?.rootViewController?.present(self, animated: animated, completion: completion) if vibrate { #if canImport(AudioToolbox) AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) #endif } } /// SwifterSwift: Add an action to Alert /// /// - Parameters: /// - title: action title /// - style: action style (default is UIAlertActionStyle.default) /// - isEnabled: isEnabled status for action (default is true) /// - handler: optional action handler to be called when button is tapped (default is nil) /// - Returns: action created by this method @discardableResult func addAction(title: String, style: UIAlertAction.Style = .default, isEnabled: Bool = true, handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction { let action = UIAlertAction(title: title, style: style, handler: handler) action.isEnabled = isEnabled addAction(action) return action } /// SwifterSwift: Add a text field to Alert /// /// - Parameters: /// - text: text field text (default is nil) /// - placeholder: text field placeholder text (default is nil) /// - editingChangedTarget: an optional target for text field's editingChanged /// - editingChangedSelector: an optional selector for text field's editingChanged func addTextField(text: String? = nil, placeholder: String? = nil, editingChangedTarget: Any?, editingChangedSelector: Selector?) { addTextField { textField in textField.text = text textField.placeholder = placeholder if let target = editingChangedTarget, let selector = editingChangedSelector { textField.addTarget(target, action: selector, for: .editingChanged) } } } } // MARK: - Initializers public extension UIAlertController { /// SwifterSwift: Create new alert view controller with default OK action. /// /// - Parameters: /// - title: alert controller's title. /// - message: alert controller's message (default is nil). /// - defaultActionButtonTitle: default action button title (default is "OK") /// - tintColor: alert controller's tint color (default is nil) convenience init(title: String, message: String? = nil, defaultActionButtonTitle: String = "OK", tintColor: UIColor? = nil) { self.init(title: title, message: message, preferredStyle: .alert) let defaultAction = UIAlertAction(title: defaultActionButtonTitle, style: .default, handler: nil) addAction(defaultAction) if let color = tintColor { view.tintColor = color } } /// SwifterSwift: Create new error alert view controller from Error with default OK action. /// /// - Parameters: /// - title: alert controller's title (default is "Error"). /// - error: error to set alert controller's message to it's localizedDescription. /// - defaultActionButtonTitle: default action button title (default is "OK") /// - tintColor: alert controller's tint color (default is nil) convenience init(title: String = "Error", error: Error, defaultActionButtonTitle: String = "OK", preferredStyle: UIAlertController.Style = .alert, tintColor: UIColor? = nil) { self.init(title: title, message: error.localizedDescription, preferredStyle: preferredStyle) let defaultAction = UIAlertAction(title: defaultActionButtonTitle, style: .default, handler: nil) addAction(defaultAction) if let color = tintColor { view.tintColor = color } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIApplicationExtensions.swift ================================================ // // UIApplicationExtensions.swift // SwifterSwift // // Created by Omar Albeik on 3/30/19. // Copyright © 2019 SwifterSwift // #if canImport(UIKit) import UIKit #if os(iOS) || os(tvOS) public extension UIApplication { /// SwifterSwift: Application running environment. /// /// - debug: Application is running in debug mode. /// - testFlight: Application is installed from Test Flight. /// - appStore: Application is installed from the App Store. enum Environment { /// SwifterSwift: Application is running in debug mode. case debug /// SwifterSwift: Application is installed from Test Flight. case testFlight /// SwifterSwift: Application is installed from the App Store. case appStore } /// SwifterSwift: Current inferred app environment. var inferredEnvironment: Environment { #if DEBUG return .debug #elseif targetEnvironment(simulator) return .debug #else if Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") != nil { return .testFlight } guard let appStoreReceiptUrl = Bundle.main.appStoreReceiptURL else { return .debug } if appStoreReceiptUrl.lastPathComponent.lowercased() == "sandboxreceipt" { return .testFlight } if appStoreReceiptUrl.path.lowercased().contains("simulator") { return .debug } return .appStore #endif } /// SwifterSwift: Application name (if applicable). var displayName: String? { return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String } /// SwifterSwift: App current build number (if applicable). var buildNumber: String? { return Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String } /// SwifterSwift: App's current version number (if applicable). var version: String? { return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String } } #endif #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIBarButtonItemExtensions.swift ================================================ // // UIBarButtonItemExtensions.swift // SwifterSwift // // Created by Omar Albeik on 08/12/2016. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Properties public extension UIBarButtonItem { /// SwifterSwift: Creates a flexible space UIBarButtonItem static var flexibleSpace: UIBarButtonItem { return UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) } } // MARK: - Methods public extension UIBarButtonItem { /// SwifterSwift: Add Target to UIBarButtonItem /// /// - Parameters: /// - target: target. /// - action: selector to run when button is tapped. func addTargetForAction(_ target: AnyObject, action: Selector) { self.target = target self.action = action } /// SwifterSwift: Creates a fixed space UIBarButtonItem with a specific width /// /// - Parameter width: Width of the UIBarButtonItem static func fixedSpace(width: CGFloat) -> UIBarButtonItem { let barButtonItem = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) barButtonItem.width = width return barButtonItem } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIBezierPathExtensions.swift ================================================ // // UIBezierPathExtensions.swift // SwifterSwift // // Created by Max Härtwig on 04.04.19. // Copyright © 2019 SwifterSwift // #if canImport(UIKit) import UIKit // MARK: - Initializers public extension UIBezierPath { /// SwifterSwift: Initializes a UIBezierPath with a line from a CGPoint to another CGPoint. /// /// - Parameters: /// - from: The point from which to path should start. /// - to: The point where the path should end. convenience init(from: CGPoint, to otherPoint: CGPoint) { self.init() move(to: from) addLine(to: otherPoint) } /// SwifterSwift: Initializes a UIBezierPath connecting the given CGPoints with straight lines. /// /// - Parameter points: The points of which the path should consist. convenience init(points: [CGPoint]) { self.init() if !points.isEmpty { move(to: points[0]) for point in points[1...] { addLine(to: point) } } } /// SwifterSwift: Initializes a polygonal UIBezierPath with the given CGPoints. At least 3 points must be given. /// /// - Parameter points: The points of the polygon which the path should form. convenience init?(polygonWithPoints points: [CGPoint]) { guard points.count > 2 else {return nil} self.init() move(to: points[0]) for point in points[1...] { addLine(to: point) } close() } /// SwifterSwift: Initializes a UIBezierPath with an oval path of given size. /// /// - Parameters: /// - size: The width and height of the oval. /// - centered: Whether the oval should be centered in its coordinate space. convenience init(ovalOf size: CGSize, centered: Bool) { let origin = centered ? CGPoint(x: -size.width / 2, y: -size.height / 2) : .zero self.init(ovalIn: CGRect(origin: origin, size: size)) } /// SwifterSwift: Initializes a UIBezierPath with a rectangular path of given size. /// /// - Parameters: /// - size: The width and height of the rect. /// - centered: Whether the oval should be centered in its coordinate space. convenience init(rectOf size: CGSize, centered: Bool) { let origin = centered ? CGPoint(x: -size.width / 2, y: -size.height / 2) : .zero self.init(rect: CGRect(origin: origin, size: size)) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIButtonExtensions.swift ================================================ // // UIButtonExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/22/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Properties public extension UIButton { /// SwifterSwift: Image of disabled state for button; also inspectable from Storyboard. @IBInspectable var imageForDisabled: UIImage? { get { return image(for: .disabled) } set { setImage(newValue, for: .disabled) } } /// SwifterSwift: Image of highlighted state for button; also inspectable from Storyboard. @IBInspectable var imageForHighlighted: UIImage? { get { return image(for: .highlighted) } set { setImage(newValue, for: .highlighted) } } /// SwifterSwift: Image of normal state for button; also inspectable from Storyboard. @IBInspectable var imageForNormal: UIImage? { get { return image(for: .normal) } set { setImage(newValue, for: .normal) } } /// SwifterSwift: Image of selected state for button; also inspectable from Storyboard. @IBInspectable var imageForSelected: UIImage? { get { return image(for: .selected) } set { setImage(newValue, for: .selected) } } /// SwifterSwift: Title color of disabled state for button; also inspectable from Storyboard. @IBInspectable var titleColorForDisabled: UIColor? { get { return titleColor(for: .disabled) } set { setTitleColor(newValue, for: .disabled) } } /// SwifterSwift: Title color of highlighted state for button; also inspectable from Storyboard. @IBInspectable var titleColorForHighlighted: UIColor? { get { return titleColor(for: .highlighted) } set { setTitleColor(newValue, for: .highlighted) } } /// SwifterSwift: Title color of normal state for button; also inspectable from Storyboard. @IBInspectable var titleColorForNormal: UIColor? { get { return titleColor(for: .normal) } set { setTitleColor(newValue, for: .normal) } } /// SwifterSwift: Title color of selected state for button; also inspectable from Storyboard. @IBInspectable var titleColorForSelected: UIColor? { get { return titleColor(for: .selected) } set { setTitleColor(newValue, for: .selected) } } /// SwifterSwift: Title of disabled state for button; also inspectable from Storyboard. @IBInspectable var titleForDisabled: String? { get { return title(for: .disabled) } set { setTitle(newValue, for: .disabled) } } /// SwifterSwift: Title of highlighted state for button; also inspectable from Storyboard. @IBInspectable var titleForHighlighted: String? { get { return title(for: .highlighted) } set { setTitle(newValue, for: .highlighted) } } /// SwifterSwift: Title of normal state for button; also inspectable from Storyboard. @IBInspectable var titleForNormal: String? { get { return title(for: .normal) } set { setTitle(newValue, for: .normal) } } /// SwifterSwift: Title of selected state for button; also inspectable from Storyboard. @IBInspectable var titleForSelected: String? { get { return title(for: .selected) } set { setTitle(newValue, for: .selected) } } } // MARK: - Methods public extension UIButton { private var states: [UIControl.State] { return [.normal, .selected, .highlighted, .disabled] } /// SwifterSwift: Set image for all states. /// /// - Parameter image: UIImage. func setImageForAllStates(_ image: UIImage) { states.forEach { setImage(image, for: $0) } } /// SwifterSwift: Set title color for all states. /// /// - Parameter color: UIColor. func setTitleColorForAllStates(_ color: UIColor) { states.forEach { setTitleColor(color, for: $0) } } /// SwifterSwift: Set title for all states. /// /// - Parameter title: title string. func setTitleForAllStates(_ title: String) { states.forEach { setTitle(title, for: $0) } } /// SwifterSwift: Center align title text and image /// - Parameters: /// - imageAboveText: set true to make image above title text, default is false, image on left of text /// - spacing: spacing between title text and image. func centerTextAndImage(imageAboveText: Bool = false, spacing: CGFloat) { if imageAboveText { // https://stackoverflow.com/questions/2451223/#7199529 guard let imageSize = imageView?.image?.size, let text = titleLabel?.text, let font = titleLabel?.font else { return } let titleSize = text.size(withAttributes: [.font: font]) let titleOffset = -(imageSize.height + spacing) titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageSize.width, bottom: titleOffset, right: 0.0) let imageOffset = -(titleSize.height + spacing) imageEdgeInsets = UIEdgeInsets(top: imageOffset, left: 0.0, bottom: 0.0, right: -titleSize.width) let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0 contentEdgeInsets = UIEdgeInsets(top: edgeOffset, left: 0.0, bottom: edgeOffset, right: 0.0) } else { let insetAmount = spacing / 2 imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: insetAmount) titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: -insetAmount) contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount) } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UICollectionViewExtensions.swift ================================================ // // UICollectionViewExtensions.swift // SwifterSwift // // Created by Omar Albeik on 11/12/2016. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Properties public extension UICollectionView { /// SwifterSwift: Index path of last item in collectionView. var indexPathForLastItem: IndexPath? { return indexPathForLastItem(inSection: lastSection) } /// SwifterSwift: Index of last section in collectionView. var lastSection: Int { return numberOfSections > 0 ? numberOfSections - 1 : 0 } } // MARK: - Methods public extension UICollectionView { /// SwifterSwift: Number of all items in all sections of collectionView. /// /// - Returns: The count of all rows in the collectionView. func numberOfItems() -> Int { var section = 0 var itemsCount = 0 while section < numberOfSections { itemsCount += numberOfItems(inSection: section) section += 1 } return itemsCount } /// SwifterSwift: IndexPath for last item in section. /// /// - Parameter section: section to get last item in. /// - Returns: optional last indexPath for last item in section (if applicable). func indexPathForLastItem(inSection section: Int) -> IndexPath? { guard section >= 0 else { return nil } guard section < numberOfSections else { return nil } guard numberOfItems(inSection: section) > 0 else { return IndexPath(item: 0, section: section) } return IndexPath(item: numberOfItems(inSection: section) - 1, section: section) } /// SwifterSwift: Reload data with a completion handler. /// /// - Parameter completion: completion handler to run after reloadData finishes. func reloadData(_ completion: @escaping () -> Void) { UIView.animate(withDuration: 0, animations: { self.reloadData() }, completion: { _ in completion() }) } /// SwifterSwift: Dequeue reusable UICollectionViewCell using class name. /// /// - Parameters: /// - name: UICollectionViewCell type. /// - indexPath: location of cell in collectionView. /// - Returns: UICollectionViewCell object with associated class name. func dequeueReusableCell(withClass name: T.Type, for indexPath: IndexPath) -> T { guard let cell = dequeueReusableCell(withReuseIdentifier: String(describing: name), for: indexPath) as? T else { fatalError("Couldn't find UICollectionViewCell for \(String(describing: name)), make sure the cell is registered with collection view") } return cell } /// SwifterSwift: Dequeue reusable UICollectionReusableView using class name. /// /// - Parameters: /// - kind: the kind of supplementary view to retrieve. This value is defined by the layout object. /// - name: UICollectionReusableView type. /// - indexPath: location of cell in collectionView. /// - Returns: UICollectionReusableView object with associated class name. func dequeueReusableSupplementaryView(ofKind kind: String, withClass name: T.Type, for indexPath: IndexPath) -> T { guard let cell = dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: String(describing: name), for: indexPath) as? T else { fatalError("Couldn't find UICollectionReusableView for \(String(describing: name)), make sure the view is registered with collection view") } return cell } /// SwifterSwift: Register UICollectionReusableView using class name. /// /// - Parameters: /// - kind: the kind of supplementary view to retrieve. This value is defined by the layout object. /// - name: UICollectionReusableView type. func register(supplementaryViewOfKind kind: String, withClass name: T.Type) { register(T.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: String(describing: name)) } /// SwifterSwift: Register UICollectionViewCell using class name. /// /// - Parameters: /// - nib: Nib file used to create the collectionView cell. /// - name: UICollectionViewCell type. func register(nib: UINib?, forCellWithClass name: T.Type) { register(nib, forCellWithReuseIdentifier: String(describing: name)) } /// SwifterSwift: Register UICollectionViewCell using class name. /// /// - Parameter name: UICollectionViewCell type. func register(cellWithClass name: T.Type) { register(T.self, forCellWithReuseIdentifier: String(describing: name)) } /// SwifterSwift: Register UICollectionReusableView using class name. /// /// - Parameters: /// - nib: Nib file used to create the reusable view. /// - kind: the kind of supplementary view to retrieve. This value is defined by the layout object. /// - name: UICollectionReusableView type. func register(nib: UINib?, forSupplementaryViewOfKind kind: String, withClass name: T.Type) { register(nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: String(describing: name)) } /// SwifterSwift: Register UICollectionViewCell with .xib file using only its corresponding class. /// Assumes that the .xib filename and cell class has the same name. /// /// - Parameters: /// - name: UICollectionViewCell type. /// - bundleClass: Class in which the Bundle instance will be based on. func register(nibWithCellClass name: T.Type, at bundleClass: AnyClass? = nil) { let identifier = String(describing: name) var bundle: Bundle? if let bundleName = bundleClass { bundle = Bundle(for: bundleName) } register(UINib(nibName: identifier, bundle: bundle), forCellWithReuseIdentifier: identifier) } /// SwifterSwift: Safely scroll to possibly invalid IndexPath /// /// - Parameters: /// - indexPath: Target IndexPath to scroll to /// - scrollPosition: Scroll position /// - animated: Whether to animate or not func safeScrollToItem(at indexPath: IndexPath, at scrollPosition: UICollectionView.ScrollPosition, animated: Bool) { guard indexPath.item >= 0 && indexPath.section >= 0 && indexPath.section < numberOfSections && indexPath.item < numberOfItems(inSection: indexPath.section) else { return } scrollToItem(at: indexPath, at: scrollPosition, animated: animated) } /// SwifterSwift: Check whether IndexPath is valid within the CollectionView /// /// - Parameter indexPath: An IndexPath to check /// - Returns: Boolean value for valid or invalid IndexPath func isValidIndexPath(_ indexPath: IndexPath) -> Bool { return indexPath.section >= 0 && indexPath.item >= 0 && indexPath.section < numberOfSections && indexPath.item < numberOfItems(inSection: indexPath.section) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIColorExtensions.swift ================================================ // // UIColorExtensions.swift // SwifterSwift // // Created by Max Haertwig on 10/06/19. // Copyright © 2019 SwifterSwift // #if canImport(UIKit) import UIKit public extension UIColor { #if !os(watchOS) /// SwifterSwift: Create a UIColor with different colors for light and dark mode. /// /// - Parameters: /// - light: Color to use in light/unspecified mode. /// - dark: Color to use in dark mode. @available(iOS 13.0, tvOS 13.0, *) convenience init(light: UIColor, dark: UIColor) { self.init(dynamicProvider: { $0.userInterfaceStyle == .dark ? dark : light }) } #endif } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIDatePickerExtensions.swift ================================================ // // UIDatePickerExtensions.swift // SwifterSwift // // Created by Omar Albeik on 12/9/17. // Copyright © 2017 SwifterSwift // #if canImport(UIKit) && os(iOS) import UIKit // MARK: - Properties public extension UIDatePicker { /// SwifterSwift: Text color of UIDatePicker. var textColor: UIColor? { set { setValue(newValue, forKeyPath: "textColor") } get { return value(forKeyPath: "textColor") as? UIColor } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIFontExtensions.swift ================================================ // // UIFontExtensions.swift // SwifterSwift // // Created by Benjamin Meyer on 9/16/17. // Copyright © 2017 SwifterSwift // #if canImport(UIKit) import UIKit // MARK: - Properties public extension UIFont { /// SwifterSwift: Font as bold font var bold: UIFont { return UIFont(descriptor: fontDescriptor.withSymbolicTraits(.traitBold)!, size: 0) } /// SwifterSwift: Font as italic font var italic: UIFont { return UIFont(descriptor: fontDescriptor.withSymbolicTraits(.traitItalic)!, size: 0) } /// SwifterSwift: Font as monospaced font /// /// UIFont.preferredFont(forTextStyle: .body).monospaced /// var monospaced: UIFont { let settings = [[UIFontDescriptor.FeatureKey.featureIdentifier: kNumberSpacingType, UIFontDescriptor.FeatureKey.typeIdentifier: kMonospacedNumbersSelector]] let attributes = [UIFontDescriptor.AttributeName.featureSettings: settings] let newDescriptor = fontDescriptor.addingAttributes(attributes) return UIFont(descriptor: newDescriptor, size: 0) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIGestureRecognizerExtensions.swift ================================================ // // UIGestureRecognizerExtensions.swift // SwifterSwift // // Created by Morgan Dock on 4/21/18. // Copyright © 2018 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Methods public extension UIGestureRecognizer { /// SwifterSwift: Remove Gesture Recognizer from its view. func removeFromView() { view?.removeGestureRecognizer(self) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIImageExtensions.swift ================================================ // // UIImageExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/6/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) import UIKit // MARK: - Properties public extension UIImage { /// SwifterSwift: Size in bytes of UIImage var bytesSize: Int { return jpegData(compressionQuality: 1)?.count ?? 0 } /// SwifterSwift: Size in kilo bytes of UIImage var kilobytesSize: Int { return (jpegData(compressionQuality: 1)?.count ?? 0) / 1024 } /// SwifterSwift: UIImage with .alwaysOriginal rendering mode. var original: UIImage { return withRenderingMode(.alwaysOriginal) } /// SwifterSwift: UIImage with .alwaysTemplate rendering mode. var template: UIImage { return withRenderingMode(.alwaysTemplate) } } // MARK: - Methods public extension UIImage { /// SwifterSwift: Compressed UIImage from original UIImage. /// /// - Parameter quality: The quality of the resulting JPEG image, expressed as a value from 0.0 to 1.0. The value 0.0 represents the maximum compression (or lowest quality) while the value 1.0 represents the least compression (or best quality), (default is 0.5). /// - Returns: optional UIImage (if applicable). func compressed(quality: CGFloat = 0.5) -> UIImage? { guard let data = jpegData(compressionQuality: quality) else { return nil } return UIImage(data: data) } /// SwifterSwift: Compressed UIImage data from original UIImage. /// /// - Parameter quality: The quality of the resulting JPEG image, expressed as a value from 0.0 to 1.0. The value 0.0 represents the maximum compression (or lowest quality) while the value 1.0 represents the least compression (or best quality), (default is 0.5). /// - Returns: optional Data (if applicable). func compressedData(quality: CGFloat = 0.5) -> Data? { return jpegData(compressionQuality: quality) } /// SwifterSwift: UIImage Cropped to CGRect. /// /// - Parameter rect: CGRect to crop UIImage to. /// - Returns: cropped UIImage func cropped(to rect: CGRect) -> UIImage { guard rect.size.width <= size.width && rect.size.height <= size.height else { return self } let scaledRect = rect.applying(CGAffineTransform(scaleX: scale, y: scale)) guard let image = cgImage?.cropping(to: scaledRect) else { return self } return UIImage(cgImage: image, scale: scale, orientation: imageOrientation) } /// SwifterSwift: UIImage scaled to height with respect to aspect ratio. /// /// - Parameters: /// - toHeight: new height. /// - opaque: flag indicating whether the bitmap is opaque. /// - Returns: optional scaled UIImage (if applicable). func scaled(toHeight: CGFloat, opaque: Bool = false) -> UIImage? { let scale = toHeight / size.height let newWidth = size.width * scale UIGraphicsBeginImageContextWithOptions(CGSize(width: newWidth, height: toHeight), opaque, self.scale) draw(in: CGRect(x: 0, y: 0, width: newWidth, height: toHeight)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } /// SwifterSwift: UIImage scaled to width with respect to aspect ratio. /// /// - Parameters: /// - toWidth: new width. /// - opaque: flag indicating whether the bitmap is opaque. /// - Returns: optional scaled UIImage (if applicable). func scaled(toWidth: CGFloat, opaque: Bool = false) -> UIImage? { let scale = toWidth / size.width let newHeight = size.height * scale UIGraphicsBeginImageContextWithOptions(CGSize(width: toWidth, height: newHeight), opaque, self.scale) draw(in: CGRect(x: 0, y: 0, width: toWidth, height: newHeight)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } /// SwifterSwift: Creates a copy of the receiver rotated by the given angle. /// /// // Rotate the image by 180° /// image.rotated(by: Measurement(value: 180, unit: .degrees)) /// /// - Parameter angle: The angle measurement by which to rotate the image. /// - Returns: A new image rotated by the given angle. @available(tvOS 10.0, watchOS 3.0, *) func rotated(by angle: Measurement) -> UIImage? { let radians = CGFloat(angle.converted(to: .radians).value) let destRect = CGRect(origin: .zero, size: size) .applying(CGAffineTransform(rotationAngle: radians)) let roundedDestRect = CGRect(x: destRect.origin.x.rounded(), y: destRect.origin.y.rounded(), width: destRect.width.rounded(), height: destRect.height.rounded()) UIGraphicsBeginImageContext(roundedDestRect.size) guard let contextRef = UIGraphicsGetCurrentContext() else { return nil } contextRef.translateBy(x: roundedDestRect.width / 2, y: roundedDestRect.height / 2) contextRef.rotate(by: radians) draw(in: CGRect(origin: CGPoint(x: -size.width / 2, y: -size.height / 2), size: size)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } /// SwifterSwift: Creates a copy of the receiver rotated by the given angle (in radians). /// /// // Rotate the image by 180° /// image.rotated(by: .pi) /// /// - Parameter radians: The angle, in radians, by which to rotate the image. /// - Returns: A new image rotated by the given angle. func rotated(by radians: CGFloat) -> UIImage? { let destRect = CGRect(origin: .zero, size: size) .applying(CGAffineTransform(rotationAngle: radians)) let roundedDestRect = CGRect(x: destRect.origin.x.rounded(), y: destRect.origin.y.rounded(), width: destRect.width.rounded(), height: destRect.height.rounded()) UIGraphicsBeginImageContext(roundedDestRect.size) guard let contextRef = UIGraphicsGetCurrentContext() else { return nil } contextRef.translateBy(x: roundedDestRect.width / 2, y: roundedDestRect.height / 2) contextRef.rotate(by: radians) draw(in: CGRect(origin: CGPoint(x: -size.width / 2, y: -size.height / 2), size: size)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } /// SwifterSwift: UIImage filled with color /// /// - Parameter color: color to fill image with. /// - Returns: UIImage filled with given color. func filled(withColor color: UIColor) -> UIImage { #if !os(watchOS) if #available(tvOS 10.0, *) { let format = UIGraphicsImageRendererFormat() format.scale = scale let renderer = UIGraphicsImageRenderer(size: size, format: format) return renderer.image { context in color.setFill() context.fill(CGRect(origin: .zero, size: size)) } } #endif UIGraphicsBeginImageContextWithOptions(size, false, scale) color.setFill() guard let context = UIGraphicsGetCurrentContext() else { return self } context.translateBy(x: 0, y: size.height) context.scaleBy(x: 1.0, y: -1.0) context.setBlendMode(CGBlendMode.normal) let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) guard let mask = cgImage else { return self } context.clip(to: rect, mask: mask) context.fill(rect) let newImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return newImage } /// SwifterSwift: UIImage tinted with color /// /// - Parameters: /// - color: color to tint image with. /// - blendMode: how to blend the tint /// - Returns: UIImage tinted with given color. func tint(_ color: UIColor, blendMode: CGBlendMode, alpha: CGFloat = 1.0) -> UIImage { let drawRect = CGRect(origin: .zero, size: size) #if !os(watchOS) if #available(tvOS 10.0, *) { let format = UIGraphicsImageRendererFormat() format.scale = scale return UIGraphicsImageRenderer(size: size, format: format).image { context in color.setFill() context.fill(drawRect) draw(in: drawRect, blendMode: blendMode, alpha: alpha) } } #endif UIGraphicsBeginImageContextWithOptions(size, false, scale) defer { UIGraphicsEndImageContext() } let context = UIGraphicsGetCurrentContext() color.setFill() context?.fill(drawRect) draw(in: drawRect, blendMode: blendMode, alpha: alpha) return UIGraphicsGetImageFromCurrentImageContext()! } /// SwifterSwift: UImage with background color /// /// - Parameters: /// - backgroundColor: Color to use as background color /// - Returns: UIImage with a background color that is visible where alpha < 1 func withBackgroundColor(_ backgroundColor: UIColor) -> UIImage { #if !os(watchOS) if #available(tvOS 10.0, *) { let format = UIGraphicsImageRendererFormat() format.scale = scale return UIGraphicsImageRenderer(size: size, format: format).image { context in backgroundColor.setFill() context.fill(context.format.bounds) draw(at: .zero) } } #endif UIGraphicsBeginImageContextWithOptions(size, false, scale) defer { UIGraphicsEndImageContext() } backgroundColor.setFill() UIRectFill(CGRect(origin: .zero, size: size)) draw(at: .zero) return UIGraphicsGetImageFromCurrentImageContext()! } /// SwifterSwift: UIImage with rounded corners /// /// - Parameters: /// - radius: corner radius (optional), resulting image will be round if unspecified /// - Returns: UIImage with all corners rounded func withRoundedCorners(radius: CGFloat? = nil) -> UIImage? { let maxRadius = min(size.width, size.height) / 2 let cornerRadius: CGFloat if let radius = radius, radius > 0 && radius <= maxRadius { cornerRadius = radius } else { cornerRadius = maxRadius } UIGraphicsBeginImageContextWithOptions(size, false, scale) let rect = CGRect(origin: .zero, size: size) UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip() draw(in: rect) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } /// SwifterSwift: Base 64 encoded PNG data of the image. /// /// - returns: Base 64 encoded PNG data of the image as a String. func pngBase64String() -> String? { return pngData()?.base64EncodedString() } /// SwifterSwift: Base 64 encoded JPEG data of the image. /// /// - parameter compressionQuality: The quality of the resulting JPEG image, expressed as a value from 0.0 to 1.0. The value 0.0 represents the maximum compression (or lowest quality) while the value 1.0 represents the least compression (or best quality). /// - returns: Base 64 encoded JPEG data of the image as a String. func jpegBase64String(compressionQuality: CGFloat) -> String? { return jpegData(compressionQuality: compressionQuality)?.base64EncodedString() } } // MARK: - Initializers public extension UIImage { /// SwifterSwift: Create UIImage from color and size. /// /// - Parameters: /// - color: image fill color. /// - size: image size. convenience init(color: UIColor, size: CGSize) { UIGraphicsBeginImageContextWithOptions(size, false, 1) defer { UIGraphicsEndImageContext() } color.setFill() UIRectFill(CGRect(origin: .zero, size: size)) guard let aCgImage = UIGraphicsGetImageFromCurrentImageContext()?.cgImage else { self.init() return } self.init(cgImage: aCgImage) } /// SwifterSwift: Create a new image from a base 64 string. /// /// - Parameters: /// - base64String: a base-64 `String`, representing the image /// - scale: The scale factor to assume when interpreting the image data created from the base-64 string. Applying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the `size` property. convenience init?(base64String: String, scale: CGFloat = 1.0) { guard let data = Data(base64Encoded: base64String) else { return nil } self.init(data: data, scale: scale) } /// SwifterSwift: Create a new image from a URL /// /// - Important: /// Use this method to convert data:// URLs to UIImage objects. /// Don't use this synchronous initializer to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated. /// Instead, for non-file URLs, consider using this in an asynchronous way, using `dataTask(with:completionHandler:)` method of the URLSession class or a library such as `AlamofireImage`, `Kingfisher`, `SDWebImage`, or others to perform asynchronous network image loading. /// - Parameters: /// - url: a `URL`, representing the image location /// - scale: The scale factor to assume when interpreting the image data created from the URL. Applying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the `size` property. convenience init?(url: URL, scale: CGFloat = 1.0) throws { let data = try Data(contentsOf: url) self.init(data: data, scale: scale) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIImageViewExtensions.swift ================================================ // // UIImageViewExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/25/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Methods public extension UIImageView { /// SwifterSwift: Set image from a URL. /// /// - Parameters: /// - url: URL of image. /// - contentMode: imageView content mode (default is .scaleAspectFit). /// - placeHolder: optional placeholder image /// - completionHandler: optional completion handler to run when download finishs (default is nil). func download( from url: URL, contentMode: UIView.ContentMode = .scaleAspectFit, placeholder: UIImage? = nil, completionHandler: ((UIImage?) -> Void)? = nil) { image = placeholder self.contentMode = contentMode URLSession.shared.dataTask(with: url) { (data, response, _) in guard let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200, let mimeType = response?.mimeType, mimeType.hasPrefix("image"), let data = data, let image = UIImage(data: data) else { completionHandler?(nil) return } DispatchQueue.main.async { [unowned self] in self.image = image completionHandler?(image) } }.resume() } /// SwifterSwift: Make image view blurry /// /// - Parameter style: UIBlurEffectStyle (default is .light). func blur(withStyle style: UIBlurEffect.Style = .light) { let blurEffect = UIBlurEffect(style: style) let blurEffectView = UIVisualEffectView(effect: blurEffect) blurEffectView.frame = bounds blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] // for supporting device rotation addSubview(blurEffectView) clipsToBounds = true } /// SwifterSwift: Blurred version of an image view /// /// - Parameter style: UIBlurEffectStyle (default is .light). /// - Returns: blurred version of self. func blurred(withStyle style: UIBlurEffect.Style = .light) -> UIImageView { let imgView = self imgView.blur(withStyle: style) return imgView } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UILabelExtensions.swift ================================================ // // UILabelExtensions.swift // SwifterSwift // // Created by Omar Albeik on 9/23/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Methods public extension UILabel { /// SwifterSwift: Initialize a UILabel with text convenience init(text: String?) { self.init() self.text = text } /// SwifterSwift: Initialize a UILabel with a text and font style. /// /// - Parameters: /// - text: the label's text. /// - style: the text style of the label, used to determine which font should be used. convenience init(text: String, style: UIFont.TextStyle) { self.init() font = UIFont.preferredFont(forTextStyle: style) self.text = text } /// SwifterSwift: Required height for a label var requiredHeight: CGFloat { let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude)) label.numberOfLines = 0 label.lineBreakMode = NSLineBreakMode.byWordWrapping label.font = font label.text = text label.attributedText = attributedText label.sizeToFit() return label.frame.height } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UILayoutPriorityExtensions.swift ================================================ // // UILayoutPriorityExtensions.swift // SwifterSwift // // Created by diamantidis on 8/19/18. // Copyright © 2018 SwifterSwift. All rights reserved. // #if os(iOS) || os(tvOS) import UIKit extension UILayoutPriority: ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral { // MARK: - Initializers /// SwifterSwift: Initialize `UILayoutPriority` with a float literal /// /// constraint.priority = 0.5 /// /// - Parameter value: The float value of the constraint public init(floatLiteral value: Float) { self.init(rawValue: value) } /// SwifterSwift: Initialize `UILayoutPriority` with an integer literal /// /// constraint.priority = 5 /// /// - Parameter value: The integer value of the constraint public init(integerLiteral value: Int) { self.init(rawValue: Float(value)) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UINavigationBarExtensions.swift ================================================ // // UINavigationBarExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/22/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Methods public extension UINavigationBar { /// SwifterSwift: Set Navigation Bar title, title color and font. /// /// - Parameters: /// - font: title font /// - color: title text color (default is .black). func setTitleFont(_ font: UIFont, color: UIColor = .black) { var attrs = [NSAttributedString.Key: Any]() attrs[.font] = font attrs[.foregroundColor] = color titleTextAttributes = attrs } /// SwifterSwift: Make navigation bar transparent. /// /// - Parameter tint: tint color (default is .white). func makeTransparent(withTint tint: UIColor = .white) { isTranslucent = true backgroundColor = .clear barTintColor = .clear setBackgroundImage(UIImage(), for: .default) tintColor = tint titleTextAttributes = [.foregroundColor: tint] shadowImage = UIImage() } /// SwifterSwift: Set navigationBar background and text colors /// /// - Parameters: /// - background: backgound color /// - text: text color func setColors(background: UIColor, text: UIColor) { isTranslucent = false backgroundColor = background barTintColor = background setBackgroundImage(UIImage(), for: .default) tintColor = text titleTextAttributes = [.foregroundColor: text] } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UINavigationControllerExtensions.swift ================================================ // // UINavigationControllerExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/6/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Methods public extension UINavigationController { /// SwifterSwift: Pop ViewController with completion handler. /// /// - Parameters: /// - animated: Set this value to true to animate the transition (default is true). /// - completion: optional completion handler (default is nil). func popViewController(animated: Bool = true, _ completion: (() -> Void)? = nil) { // https://github.com/cotkjaer/UserInterface/blob/master/UserInterface/UIViewController.swift CATransaction.begin() CATransaction.setCompletionBlock(completion) popViewController(animated: animated) CATransaction.commit() } /// SwifterSwift: Push ViewController with completion handler. /// /// - Parameters: /// - viewController: viewController to push. /// - completion: optional completion handler (default is nil). func pushViewController(_ viewController: UIViewController, completion: (() -> Void)? = nil) { // https://github.com/cotkjaer/UserInterface/blob/master/UserInterface/UIViewController.swift CATransaction.begin() CATransaction.setCompletionBlock(completion) pushViewController(viewController, animated: true) CATransaction.commit() } /// SwifterSwift: Make navigation controller's navigation bar transparent. /// /// - Parameter tint: tint color (default is .white). func makeTransparent(withTint tint: UIColor = .white) { navigationBar.setBackgroundImage(UIImage(), for: .default) navigationBar.shadowImage = UIImage() navigationBar.isTranslucent = true navigationBar.tintColor = tint navigationBar.titleTextAttributes = [.foregroundColor: tint] } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UINavigationItemExtensions.swift ================================================ // // UINavigationItemExtensions.swift // SwifterSwift // // Created by Omar Albeik on 9/28/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Methods public extension UINavigationItem { /// SwifterSwift: Replace title label with an image in navigation item. /// /// - Parameter image: UIImage to replace title with. func replaceTitle(with image: UIImage) { let logoImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 30)) logoImageView.contentMode = .scaleAspectFit logoImageView.image = image titleView = logoImageView } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIRefreshControlExtensions.swift ================================================ // // UIRefreshControlExtensions.swift // SwifterSwift // // Created by ratul sharker on 7/24/18. // Copyright © 2018 SwifterSwift // #if os(iOS) import UIKit // MARK: - Methods public extension UIRefreshControl { /// SwifterSwift: Programatically begin refresh control inside of UITableView. /// /// - Parameters: /// - tableView: UITableView instance, inside which the refresh control is contained. /// - animated: Boolean, indicates that is the content offset changing should be animated or not. /// - sendAction: Boolean, indicates that should it fire sendActions method for valueChanged UIControlEvents func beginRefreshing(in tableView: UITableView, animated: Bool, sendAction: Bool = false) { // https://stackoverflow.com/questions/14718850/14719658#14719658 assert(superview == tableView, "Refresh control does not belong to the receiving table view") beginRefreshing() let offsetPoint = CGPoint(x: 0, y: -frame.height) tableView.setContentOffset(offsetPoint, animated: animated) if sendAction { sendActions(for: .valueChanged) } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIScrollViewExtensions.swift ================================================ // // UIScrollViewExtensions.swift // SwifterSwift // // Created by camila oliveira on 22/04/18. // Copyright © 2018 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Methods public extension UIScrollView { /// SwifterSwift: Takes a snapshot of an entire ScrollView /// /// AnySubclassOfUIScroolView().snapshot /// UITableView().snapshot /// /// - Returns: Snapshot as UIimage for rendered ScrollView var snapshot: UIImage? { // Original Source: https://gist.github.com/thestoics/1204051 UIGraphicsBeginImageContextWithOptions(contentSize, false, 0) defer { UIGraphicsEndImageContext() } guard let context = UIGraphicsGetCurrentContext() else { return nil } let previousFrame = frame frame = CGRect(origin: frame.origin, size: contentSize) layer.render(in: context) frame = previousFrame return UIGraphicsGetImageFromCurrentImageContext() } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UISearchBarExtensions.swift ================================================ // // UISearchBarExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/23/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && os(iOS) import UIKit // MARK: - Properties public extension UISearchBar { /// SwifterSwift: Text field inside search bar (if applicable). var textField: UITextField? { if #available(iOS 13.0, *) { return searchTextField } let subViews = subviews.flatMap { $0.subviews } guard let textField = (subViews.filter { $0 is UITextField }).first as? UITextField else { return nil } return textField } /// SwifterSwift: Text with no spaces or new lines in beginning and end (if applicable). var trimmedText: String? { return text?.trimmingCharacters(in: .whitespacesAndNewlines) } } // MARK: - Methods public extension UISearchBar { /// SwifterSwift: Clear text. func clear() { text = "" } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UISegmentedControlExtensions.swift ================================================ // // UISegmentedControlExtensions.swift // SwifterSwift // // Created by Omar Albeik on 9/28/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Properties public extension UISegmentedControl { /// SwifterSwift: Segments titles. var segmentTitles: [String] { get { let range = 0.. Void)? = nil) { if animated { UIView.animate(withDuration: duration, animations: { self.setValue(value, animated: true) }, completion: { _ in completion?() }) } else { setValue(value, animated: false) completion?() } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIStackViewExtensions.swift ================================================ // // UIStackViewExtensions.swift // SwifterSwift-iOS // // Created by Benjamin Meyer on 2/18/18. // Copyright © 2018 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Initializers public extension UIStackView { /// SwifterSwift: Initialize an UIStackView with an array of UIView and common parameters. /// /// let stackView = UIStackView(arrangedSubviews: [UIView(), UIView()], axis: .vertical) /// /// - Parameters: /// - arrangedSubviews: The UIViews to add to the stack. /// - axis: The axis along which the arranged views are laid out. /// - spacing: The distance in points between the adjacent edges of the stack view’s arranged views.(default: 0.0) /// - alignment: The alignment of the arranged subviews perpendicular to the stack view’s axis. (default: .fill) /// - distribution: The distribution of the arranged views along the stack view’s axis.(default: .fill) convenience init( arrangedSubviews: [UIView], axis: NSLayoutConstraint.Axis, spacing: CGFloat = 0.0, alignment: UIStackView.Alignment = .fill, distribution: UIStackView.Distribution = .fill) { self.init(arrangedSubviews: arrangedSubviews) self.axis = axis self.spacing = spacing self.alignment = alignment self.distribution = distribution } /// SwifterSwift: Adds array of views to the end of the arrangedSubviews array. /// /// - Parameter views: views array. func addArrangedSubviews(_ views: [UIView]) { for view in views { addArrangedSubview(view) } } /// SwifterSwift: Removes all views in stack’s array of arranged subviews. func removeArrangedSubviews() { for view in arrangedSubviews { removeArrangedSubview(view) } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIStoryboardExtensions.swift ================================================ // // UIStoryboardExtensions.swift // SwifterSwift // // Created by Steven on 2/6/17. // Copyright © 2017 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Methods public extension UIStoryboard { /// SwifterSwift: Get main storyboard for application static var main: UIStoryboard? { let bundle = Bundle.main guard let name = bundle.object(forInfoDictionaryKey: "UIMainStoryboardFile") as? String else { return nil } return UIStoryboard(name: name, bundle: bundle) } /// SwifterSwift: Instantiate a UIViewController using its class name /// /// - Parameter name: UIViewController type /// - Returns: The view controller corresponding to specified class name func instantiateViewController(withClass name: T.Type) -> T? { return instantiateViewController(withIdentifier: String(describing: name)) as? T } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UISwitchExtensions.swift ================================================ // // UISwitchExtensions.swift // SwifterSwift // // Created by Omar Albeik on 08/12/2016. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && os(iOS) import UIKit // MARK: - Methods public extension UISwitch { /// SwifterSwift: Toggle a UISwitch /// /// - Parameter animated: set true to animate the change (default is true) func toggle(animated: Bool = true) { setOn(!isOn, animated: animated) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UITabBarExtensions.swift ================================================ // // UITabBarExtensions.swift // SwifterSwift // // Created by Omar Albeik on 9/28/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Methods public extension UITabBar { /// SwifterSwift: Set tabBar colors. /// /// - Parameters: /// - background: background color. /// - selectedBackground: background color for selected tab. /// - item: icon tint color for items. /// - selectedItem: icon tint color for item. func setColors( background: UIColor? = nil, selectedBackground: UIColor? = nil, item: UIColor? = nil, selectedItem: UIColor? = nil) { // background barTintColor = background ?? barTintColor // selectedItem tintColor = selectedItem ?? tintColor // shadowImage = UIImage() backgroundImage = UIImage() isTranslucent = false // selectedBackgoundColor guard let barItems = items else { return } if let selectedbg = selectedBackground { let rect = CGSize(width: frame.width/CGFloat(barItems.count), height: frame.height) selectionIndicatorImage = { (color: UIColor, size: CGSize) -> UIImage in UIGraphicsBeginImageContextWithOptions(size, false, 1) color.setFill() UIRectFill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) guard let image = UIGraphicsGetImageFromCurrentImageContext() else { return UIImage() } UIGraphicsEndImageContext() guard let aCgImage = image.cgImage else { return UIImage() } return UIImage(cgImage: aCgImage) }(selectedbg, rect) } if let itemColor = item { for barItem in barItems as [UITabBarItem] { // item guard let image = barItem.image else { continue } barItem.image = { (image: UIImage, color: UIColor) -> UIImage in UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale) color.setFill() guard let context = UIGraphicsGetCurrentContext() else { return image } context.translateBy(x: 0, y: image.size.height) context.scaleBy(x: 1.0, y: -1.0) context.setBlendMode(CGBlendMode.normal) let rect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height) guard let mask = image.cgImage else { return image } context.clip(to: rect, mask: mask) context.fill(rect) let newImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return newImage }(image, itemColor).withRenderingMode(.alwaysOriginal) barItem.setTitleTextAttributes([.foregroundColor: itemColor], for: .normal) if let selected = selectedItem { barItem.setTitleTextAttributes([.foregroundColor: selected], for: .selected) } } } } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UITableViewExtensions.swift ================================================ // // UITableViewExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/22/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Properties public extension UITableView { /// SwifterSwift: Index path of last row in tableView. var indexPathForLastRow: IndexPath? { guard let lastSection = lastSection else { return nil } return indexPathForLastRow(inSection: lastSection) } /// SwifterSwift: Index of last section in tableView. var lastSection: Int? { return numberOfSections > 0 ? numberOfSections - 1 : nil } } // MARK: - Methods public extension UITableView { /// SwifterSwift: Number of all rows in all sections of tableView. /// /// - Returns: The count of all rows in the tableView. func numberOfRows() -> Int { var section = 0 var rowCount = 0 while section < numberOfSections { rowCount += numberOfRows(inSection: section) section += 1 } return rowCount } /// SwifterSwift: IndexPath for last row in section. /// /// - Parameter section: section to get last row in. /// - Returns: optional last indexPath for last row in section (if applicable). func indexPathForLastRow(inSection section: Int) -> IndexPath? { guard numberOfSections > 0, section >= 0 else { return nil } guard numberOfRows(inSection: section) > 0 else { return IndexPath(row: 0, section: section) } return IndexPath(row: numberOfRows(inSection: section) - 1, section: section) } /// SwifterSwift: Reload data with a completion handler. /// /// - Parameter completion: completion handler to run after reloadData finishes. func reloadData(_ completion: @escaping () -> Void) { UIView.animate(withDuration: 0, animations: { self.reloadData() }, completion: { _ in completion() }) } /// SwifterSwift: Remove TableFooterView. func removeTableFooterView() { tableFooterView = nil } /// SwifterSwift: Remove TableHeaderView. func removeTableHeaderView() { tableHeaderView = nil } /// SwifterSwift: Scroll to bottom of TableView. /// /// - Parameter animated: set true to animate scroll (default is true). func scrollToBottom(animated: Bool = true) { let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height) setContentOffset(bottomOffset, animated: animated) } /// SwifterSwift: Scroll to top of TableView. /// /// - Parameter animated: set true to animate scroll (default is true). func scrollToTop(animated: Bool = true) { setContentOffset(CGPoint.zero, animated: animated) } /// SwifterSwift: Dequeue reusable UITableViewCell using class name /// /// - Parameter name: UITableViewCell type /// - Returns: UITableViewCell object with associated class name. func dequeueReusableCell(withClass name: T.Type) -> T { guard let cell = dequeueReusableCell(withIdentifier: String(describing: name)) as? T else { fatalError("Couldn't find UITableViewCell for \(String(describing: name)), make sure the cell is registered with table view") } return cell } /// SwifterSwift: Dequeue reusable UITableViewCell using class name for indexPath /// /// - Parameters: /// - name: UITableViewCell type. /// - indexPath: location of cell in tableView. /// - Returns: UITableViewCell object with associated class name. func dequeueReusableCell(withClass name: T.Type, for indexPath: IndexPath) -> T { guard let cell = dequeueReusableCell(withIdentifier: String(describing: name), for: indexPath) as? T else { fatalError("Couldn't find UITableViewCell for \(String(describing: name)), make sure the cell is registered with table view") } return cell } /// SwifterSwift: Dequeue reusable UITableViewHeaderFooterView using class name /// /// - Parameter name: UITableViewHeaderFooterView type /// - Returns: UITableViewHeaderFooterView object with associated class name. func dequeueReusableHeaderFooterView(withClass name: T.Type) -> T { guard let headerFooterView = dequeueReusableHeaderFooterView(withIdentifier: String(describing: name)) as? T else { fatalError("Couldn't find UITableViewHeaderFooterView for \(String(describing: name)), make sure the view is registered with table view") } return headerFooterView } /// SwifterSwift: Register UITableViewHeaderFooterView using class name /// /// - Parameters: /// - nib: Nib file used to create the header or footer view. /// - name: UITableViewHeaderFooterView type. func register(nib: UINib?, withHeaderFooterViewClass name: T.Type) { register(nib, forHeaderFooterViewReuseIdentifier: String(describing: name)) } /// SwifterSwift: Register UITableViewHeaderFooterView using class name /// /// - Parameter name: UITableViewHeaderFooterView type func register(headerFooterViewClassWith name: T.Type) { register(T.self, forHeaderFooterViewReuseIdentifier: String(describing: name)) } /// SwifterSwift: Register UITableViewCell using class name /// /// - Parameter name: UITableViewCell type func register(cellWithClass name: T.Type) { register(T.self, forCellReuseIdentifier: String(describing: name)) } /// SwifterSwift: Register UITableViewCell using class name /// /// - Parameters: /// - nib: Nib file used to create the tableView cell. /// - name: UITableViewCell type. func register(nib: UINib?, withCellClass name: T.Type) { register(nib, forCellReuseIdentifier: String(describing: name)) } /// SwifterSwift: Register UITableViewCell with .xib file using only its corresponding class. /// Assumes that the .xib filename and cell class has the same name. /// /// - Parameters: /// - name: UITableViewCell type. /// - bundleClass: Class in which the Bundle instance will be based on. func register(nibWithCellClass name: T.Type, at bundleClass: AnyClass? = nil) { let identifier = String(describing: name) var bundle: Bundle? if let bundleName = bundleClass { bundle = Bundle(for: bundleName) } register(UINib(nibName: identifier, bundle: bundle), forCellReuseIdentifier: identifier) } /// SwifterSwift: Check whether IndexPath is valid within the tableView /// /// - Parameter indexPath: An IndexPath to check /// - Returns: Boolean value for valid or invalid IndexPath func isValidIndexPath(_ indexPath: IndexPath) -> Bool { return indexPath.section >= 0 && indexPath.row >= 0 && indexPath.section < numberOfSections && indexPath.row < numberOfRows(inSection: indexPath.section) } /// SwifterSwift: Safely scroll to possibly invalid IndexPath /// /// - Parameters: /// - indexPath: Target IndexPath to scroll to /// - scrollPosition: Scroll position /// - animated: Whether to animate or not func safeScrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) { guard indexPath.section < numberOfSections else { return } guard indexPath.row < numberOfRows(inSection: indexPath.section) else { return } scrollToRow(at: indexPath, at: scrollPosition, animated: animated) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UITextFieldExtensions.swift ================================================ // // UITextFieldExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/5/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Enums public extension UITextField { /// SwifterSwift: UITextField text type. /// /// - emailAddress: UITextField is used to enter email addresses. /// - password: UITextField is used to enter passwords. /// - generic: UITextField is used to enter generic text. enum TextType { /// SwifterSwift: UITextField is used to enter email addresses. case emailAddress /// SwifterSwift: UITextField is used to enter passwords. case password /// SwifterSwift: UITextField is used to enter generic text. case generic } } // MARK: - Properties public extension UITextField { /// SwifterSwift: Set textField for common text types. var textType: TextType { get { if keyboardType == .emailAddress { return .emailAddress } else if isSecureTextEntry { return .password } return .generic } set { switch newValue { case .emailAddress: keyboardType = .emailAddress autocorrectionType = .no autocapitalizationType = .none isSecureTextEntry = false placeholder = "Email Address" case .password: keyboardType = .asciiCapable autocorrectionType = .no autocapitalizationType = .none isSecureTextEntry = true placeholder = "Password" case .generic: isSecureTextEntry = false } } } /// SwifterSwift: Check if text field is empty. var isEmpty: Bool { return text?.isEmpty == true } /// SwifterSwift: Return text with no spaces or new lines in beginning and end. var trimmedText: String? { return text?.trimmingCharacters(in: .whitespacesAndNewlines) } /// SwifterSwift: Check if textFields text is a valid email format. /// /// textField.text = "john@doe.com" /// textField.hasValidEmail -> true /// /// textField.text = "swifterswift" /// textField.hasValidEmail -> false /// var hasValidEmail: Bool { // http://stackoverflow.com/questions/25471114/how-to-validate-an-e-mail-address-in-swift return text!.range(of: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}", options: String.CompareOptions.regularExpression, range: nil, locale: nil) != nil } /// SwifterSwift: Left view tint color. @IBInspectable var leftViewTintColor: UIColor? { get { guard let iconView = leftView as? UIImageView else { return nil } return iconView.tintColor } set { guard let iconView = leftView as? UIImageView else { return } iconView.image = iconView.image?.withRenderingMode(.alwaysTemplate) iconView.tintColor = newValue } } /// SwifterSwift: Right view tint color. @IBInspectable var rightViewTintColor: UIColor? { get { guard let iconView = rightView as? UIImageView else { return nil } return iconView.tintColor } set { guard let iconView = rightView as? UIImageView else { return } iconView.image = iconView.image?.withRenderingMode(.alwaysTemplate) iconView.tintColor = newValue } } } // MARK: - Methods public extension UITextField { /// SwifterSwift: Clear text. func clear() { text = "" attributedText = NSAttributedString(string: "") } /// SwifterSwift: Set placeholder text color. /// /// - Parameter color: placeholder text color. func setPlaceHolderTextColor(_ color: UIColor) { guard let holder = placeholder, !holder.isEmpty else { return } attributedPlaceholder = NSAttributedString(string: holder, attributes: [.foregroundColor: color]) } /// SwifterSwift: Add padding to the left of the textfield rect. /// /// - Parameter padding: amount of padding to apply to the left of the textfield rect. func addPaddingLeft(_ padding: CGFloat) { let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: padding, height: frame.height)) leftView = paddingView leftViewMode = .always } /// SwifterSwift: Add padding to the left of the textfield rect. /// /// - Parameters: /// - image: left image /// - padding: amount of padding between icon and the left of textfield func addPaddingLeftIcon(_ image: UIImage, padding: CGFloat) { let imageView = UIImageView(image: image) imageView.contentMode = .center leftView = imageView leftView?.frame.size = CGSize(width: image.size.width + padding, height: image.size.height) leftViewMode = .always } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UITextViewExtensions.swift ================================================ // // UITextViewExtensions.swift // SwifterSwift // // Created by Omar Albeik on 9/28/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Methods public extension UITextView { /// SwifterSwift: Clear text. func clear() { text = "" attributedText = NSAttributedString(string: "") } /// SwifterSwift: Scroll to the bottom of text view func scrollToBottom() { // swiftlint:disable:next legacy_constructor let range = NSMakeRange((text as NSString).length - 1, 1) scrollRangeToVisible(range) } /// SwifterSwift: Scroll to the top of text view func scrollToTop() { // swiftlint:disable:next legacy_constructor let range = NSMakeRange(0, 1) scrollRangeToVisible(range) } /// SwifterSwift: Wrap to the content (Text / Attributed Text). func wrapToContent() { contentInset = .zero scrollIndicatorInsets = .zero contentOffset = .zero textContainerInset = .zero textContainer.lineFragmentPadding = 0 sizeToFit() } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIViewControllerExtensions.swift ================================================ // // UIViewControllerExtensions.swift // SwifterSwift // // Created by Emirhan Erdogan on 07/08/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - Properties public extension UIViewController { /// SwifterSwift: Check if ViewController is onscreen and not hidden. var isVisible: Bool { // http://stackoverflow.com/questions/2777438/how-to-tell-if-uiviewcontrollers-view-is-visible return isViewLoaded && view.window != nil } } // MARK: - Methods public extension UIViewController { /// SwifterSwift: Assign as listener to notification. /// /// - Parameters: /// - name: notification name. /// - selector: selector to run with notified. func addNotificationObserver(name: Notification.Name, selector: Selector) { NotificationCenter.default.addObserver(self, selector: selector, name: name, object: nil) } /// SwifterSwift: Unassign as listener to notification. /// /// - Parameter name: notification name. func removeNotificationObserver(name: Notification.Name) { NotificationCenter.default.removeObserver(self, name: name, object: nil) } /// SwifterSwift: Unassign as listener from all notifications. func removeNotificationsObserver() { NotificationCenter.default.removeObserver(self) } /// SwifterSwift: Helper method to display an alert on any UIViewController subclass. Uses UIAlertController to show an alert /// /// - Parameters: /// - title: title of the alert /// - message: message/body of the alert /// - buttonTitles: (Optional)list of button titles for the alert. Default button i.e "OK" will be shown if this paramter is nil /// - highlightedButtonIndex: (Optional) index of the button from buttonTitles that should be highlighted. If this parameter is nil no button will be highlighted /// - completion: (Optional) completion block to be invoked when any one of the buttons is tapped. It passes the index of the tapped button as an argument /// - Returns: UIAlertController object (discardable). @discardableResult func showAlert(title: String?, message: String?, buttonTitles: [String]? = nil, highlightedButtonIndex: Int? = nil, completion: ((Int) -> Void)? = nil) -> UIAlertController { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) var allButtons = buttonTitles ?? [String]() if allButtons.count == 0 { allButtons.append("OK") } for index in 0.. Void)? = nil) { popoverContent.modalPresentationStyle = .popover if let size = size { popoverContent.preferredContentSize = size } if let popoverPresentationVC = popoverContent.popoverPresentationController { popoverPresentationVC.sourceView = view popoverPresentationVC.sourceRect = CGRect(origin: sourcePoint, size: .zero) popoverPresentationVC.delegate = delegate } present(popoverContent, animated: animated, completion: completion) } #endif } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIViewExtensions.swift ================================================ // // UIViewExtensions.swift // SwifterSwift // // Created by Omar Albeik on 8/5/16. // Copyright © 2016 SwifterSwift // #if canImport(UIKit) && !os(watchOS) import UIKit // MARK: - enums public extension UIView { /// SwifterSwift: Shake directions of a view. /// /// - horizontal: Shake left and right. /// - vertical: Shake up and down. enum ShakeDirection { /// SwifterSwift: Shake left and right. case horizontal /// SwifterSwift: Shake up and down. case vertical } /// SwifterSwift: Angle units. /// /// - degrees: degrees. /// - radians: radians. enum AngleUnit { /// SwifterSwift: degrees. case degrees /// SwifterSwift: radians. case radians } /// SwifterSwift: Shake animations types. /// /// - linear: linear animation. /// - easeIn: easeIn animation. /// - easeOut: easeOut animation. /// - easeInOut: easeInOut animation. enum ShakeAnimationType { /// SwifterSwift: linear animation. case linear /// SwifterSwift: easeIn animation. case easeIn /// SwifterSwift: easeOut animation. case easeOut /// SwifterSwift: easeInOut animation. case easeInOut } } // MARK: - Properties public extension UIView { /// SwifterSwift: Border color of view; also inspectable from Storyboard. @IBInspectable var borderColor: UIColor? { get { guard let color = layer.borderColor else { return nil } return UIColor(cgColor: color) } set { guard let color = newValue else { layer.borderColor = nil return } // Fix React-Native conflict issue guard String(describing: type(of: color)) != "__NSCFType" else { return } layer.borderColor = color.cgColor } } /// SwifterSwift: Border width of view; also inspectable from Storyboard. @IBInspectable var borderWidth: CGFloat { get { return layer.borderWidth } set { layer.borderWidth = newValue } } /// SwifterSwift: Corner radius of view; also inspectable from Storyboard. @IBInspectable var cornerRadius: CGFloat { get { return layer.cornerRadius } set { layer.masksToBounds = true layer.cornerRadius = abs(CGFloat(Int(newValue * 100)) / 100) } } /// SwifterSwift: Height of view. var height: CGFloat { get { return frame.size.height } set { frame.size.height = newValue } } /// SwifterSwift: Check if view is in RTL format. var isRightToLeft: Bool { if #available(tvOS 10.0, *) { return effectiveUserInterfaceLayoutDirection == .rightToLeft } else { return false } } /// SwifterSwift: Take screenshot of view (if applicable). var screenshot: UIImage? { UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, 0) defer { UIGraphicsEndImageContext() } guard let context = UIGraphicsGetCurrentContext() else { return nil } layer.render(in: context) return UIGraphicsGetImageFromCurrentImageContext() } /// SwifterSwift: Shadow color of view; also inspectable from Storyboard. @IBInspectable var shadowColor: UIColor? { get { guard let color = layer.shadowColor else { return nil } return UIColor(cgColor: color) } set { layer.shadowColor = newValue?.cgColor } } /// SwifterSwift: Shadow offset of view; also inspectable from Storyboard. @IBInspectable var shadowOffset: CGSize { get { return layer.shadowOffset } set { layer.shadowOffset = newValue } } /// SwifterSwift: Shadow opacity of view; also inspectable from Storyboard. @IBInspectable var shadowOpacity: Float { get { return layer.shadowOpacity } set { layer.shadowOpacity = newValue } } /// SwifterSwift: Shadow radius of view; also inspectable from Storyboard. @IBInspectable var shadowRadius: CGFloat { get { return layer.shadowRadius } set { layer.shadowRadius = newValue } } /// SwifterSwift: Size of view. var size: CGSize { get { return frame.size } set { width = newValue.width height = newValue.height } } /// SwifterSwift: Get view's parent view controller var parentViewController: UIViewController? { weak var parentResponder: UIResponder? = self while parentResponder != nil { parentResponder = parentResponder!.next if let viewController = parentResponder as? UIViewController { return viewController } } return nil } /// SwifterSwift: Width of view. var width: CGFloat { get { return frame.size.width } set { frame.size.width = newValue } } /// SwifterSwift: x origin of view. var x: CGFloat { get { return frame.origin.x } set { frame.origin.x = newValue } } /// SwifterSwift: y origin of view. var y: CGFloat { get { return frame.origin.y } set { frame.origin.y = newValue } } } // MARK: - Methods public extension UIView { /// SwifterSwift: Recursively find the first responder. func firstResponder() -> UIView? { var views = [UIView](arrayLiteral: self) var index = 0 repeat { let view = views[index] if view.isFirstResponder { return view } views.append(contentsOf: view.subviews) index += 1 } while index < views.count return nil } /// SwifterSwift: Set some or all corners radiuses of view. /// /// - Parameters: /// - corners: array of corners to change (example: [.bottomLeft, .topRight]). /// - radius: radius for selected corners. func roundCorners(_ corners: UIRectCorner, radius: CGFloat) { let maskPath = UIBezierPath( roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) let shape = CAShapeLayer() shape.path = maskPath.cgPath layer.mask = shape } /// SwifterSwift: Add shadow to view. /// /// - Parameters: /// - color: shadow color (default is #137992). /// - radius: shadow radius (default is 3). /// - offset: shadow offset (default is .zero). /// - opacity: shadow opacity (default is 0.5). func addShadow(ofColor color: UIColor = UIColor(red: 0.07, green: 0.47, blue: 0.57, alpha: 1.0), radius: CGFloat = 3, offset: CGSize = .zero, opacity: Float = 0.5) { layer.shadowColor = color.cgColor layer.shadowOffset = offset layer.shadowRadius = radius layer.shadowOpacity = opacity layer.masksToBounds = false } /// SwifterSwift: Add array of subviews to view. /// /// - Parameter subviews: array of subviews to add to self. func addSubviews(_ subviews: [UIView]) { subviews.forEach { addSubview($0) } } /// SwifterSwift: Fade in view. /// /// - Parameters: /// - duration: animation duration in seconds (default is 1 second). /// - completion: optional completion handler to run with animation finishes (default is nil) func fadeIn(duration: TimeInterval = 1, completion: ((Bool) -> Void)? = nil) { if isHidden { isHidden = false } UIView.animate(withDuration: duration, animations: { self.alpha = 1 }, completion: completion) } /// SwifterSwift: Fade out view. /// /// - Parameters: /// - duration: animation duration in seconds (default is 1 second). /// - completion: optional completion handler to run with animation finishes (default is nil) func fadeOut(duration: TimeInterval = 1, completion: ((Bool) -> Void)? = nil) { if isHidden { isHidden = false } UIView.animate(withDuration: duration, animations: { self.alpha = 0 }, completion: completion) } /// SwifterSwift: Load view from nib. /// /// - Parameters: /// - name: nib name. /// - bundle: bundle of nib (default is nil). /// - Returns: optional UIView (if applicable). class func loadFromNib(named name: String, bundle: Bundle? = nil) -> UIView? { return UINib(nibName: name, bundle: bundle).instantiate(withOwner: nil, options: nil)[0] as? UIView } /// SwifterSwift: Remove all subviews in view. func removeSubviews() { subviews.forEach({ $0.removeFromSuperview() }) } /// SwifterSwift: Remove all gesture recognizers from view. func removeGestureRecognizers() { gestureRecognizers?.forEach(removeGestureRecognizer) } /// SwifterSwift: Attaches gesture recognizers to the view. Attaching gesture recognizers to a view defines the scope of the represented gesture, causing it to receive touches hit-tested to that view and all of its subviews. The view establishes a strong reference to the gesture recognizers. /// /// - Parameter gestureRecognizers: The array of gesture recognizers to be added to the view. func addGestureRecognizers(_ gestureRecognizers: [UIGestureRecognizer]) { for recognizer in gestureRecognizers { addGestureRecognizer(recognizer) } } /// SwifterSwift: Detaches gesture recognizers from the receiving view. This method releases gestureRecognizers in addition to detaching them from the view. /// /// - Parameter gestureRecognizers: The array of gesture recognizers to be removed from the view. func removeGestureRecognizers(_ gestureRecognizers: [UIGestureRecognizer]) { for recognizer in gestureRecognizers { removeGestureRecognizer(recognizer) } } /// SwifterSwift: Rotate view by angle on relative axis. /// /// - Parameters: /// - angle: angle to rotate view by. /// - type: type of the rotation angle. /// - animated: set true to animate rotation (default is true). /// - duration: animation duration in seconds (default is 1 second). /// - completion: optional completion handler to run with animation finishes (default is nil). func rotate(byAngle angle: CGFloat, ofType type: AngleUnit, animated: Bool = false, duration: TimeInterval = 1, completion: ((Bool) -> Void)? = nil) { let angleWithType = (type == .degrees) ? .pi * angle / 180.0 : angle let aDuration = animated ? duration : 0 UIView.animate(withDuration: aDuration, delay: 0, options: .curveLinear, animations: { () -> Void in self.transform = self.transform.rotated(by: angleWithType) }, completion: completion) } /// SwifterSwift: Rotate view to angle on fixed axis. /// /// - Parameters: /// - angle: angle to rotate view to. /// - type: type of the rotation angle. /// - animated: set true to animate rotation (default is false). /// - duration: animation duration in seconds (default is 1 second). /// - completion: optional completion handler to run with animation finishes (default is nil). func rotate(toAngle angle: CGFloat, ofType type: AngleUnit, animated: Bool = false, duration: TimeInterval = 1, completion: ((Bool) -> Void)? = nil) { let angleWithType = (type == .degrees) ? .pi * angle / 180.0 : angle let aDuration = animated ? duration : 0 UIView.animate(withDuration: aDuration, animations: { self.transform = self.transform.concatenating(CGAffineTransform(rotationAngle: angleWithType)) }, completion: completion) } /// SwifterSwift: Scale view by offset. /// /// - Parameters: /// - offset: scale offset /// - animated: set true to animate scaling (default is false). /// - duration: animation duration in seconds (default is 1 second). /// - completion: optional completion handler to run with animation finishes (default is nil). func scale(by offset: CGPoint, animated: Bool = false, duration: TimeInterval = 1, completion: ((Bool) -> Void)? = nil) { if animated { UIView.animate(withDuration: duration, delay: 0, options: .curveLinear, animations: { () -> Void in self.transform = self.transform.scaledBy(x: offset.x, y: offset.y) }, completion: completion) } else { transform = transform.scaledBy(x: offset.x, y: offset.y) completion?(true) } } /// SwifterSwift: Shake view. /// /// - Parameters: /// - direction: shake direction (horizontal or vertical), (default is .horizontal) /// - duration: animation duration in seconds (default is 1 second). /// - animationType: shake animation type (default is .easeOut). /// - completion: optional completion handler to run with animation finishes (default is nil). func shake(direction: ShakeDirection = .horizontal, duration: TimeInterval = 1, animationType: ShakeAnimationType = .easeOut, completion:(() -> Void)? = nil) { CATransaction.begin() let animation: CAKeyframeAnimation switch direction { case .horizontal: animation = CAKeyframeAnimation(keyPath: "transform.translation.x") case .vertical: animation = CAKeyframeAnimation(keyPath: "transform.translation.y") } switch animationType { case .linear: animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) case .easeIn: animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) case .easeOut: animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) case .easeInOut: animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) } CATransaction.setCompletionBlock(completion) animation.duration = duration animation.values = [-20.0, 20.0, -20.0, 20.0, -10.0, 10.0, -5.0, 5.0, 0.0 ] layer.add(animation, forKey: "shake") CATransaction.commit() } /// SwifterSwift: Add Visual Format constraints. /// /// - Parameters: /// - withFormat: visual Format language /// - views: array of views which will be accessed starting with index 0 (example: [v0], [v1], [v2]..) @available(iOS 9, *) func addConstraints(withFormat: String, views: UIView...) { // https://videos.letsbuildthatapp.com/ var viewsDictionary: [String: UIView] = [:] for (index, view) in views.enumerated() { let key = "v\(index)" view.translatesAutoresizingMaskIntoConstraints = false viewsDictionary[key] = view } addConstraints(NSLayoutConstraint.constraints(withVisualFormat: withFormat, options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: viewsDictionary)) } /// SwifterSwift: Anchor all sides of the view into it's superview. @available(iOS 9, *) func fillToSuperview() { // https://videos.letsbuildthatapp.com/ translatesAutoresizingMaskIntoConstraints = false if let superview = superview { let left = leftAnchor.constraint(equalTo: superview.leftAnchor) let right = rightAnchor.constraint(equalTo: superview.rightAnchor) let top = topAnchor.constraint(equalTo: superview.topAnchor) let bottom = bottomAnchor.constraint(equalTo: superview.bottomAnchor) NSLayoutConstraint.activate([left, right, top, bottom]) } } /// SwifterSwift: Add anchors from any side of the current view into the specified anchors and returns the newly added constraints. /// /// - Parameters: /// - top: current view's top anchor will be anchored into the specified anchor /// - left: current view's left anchor will be anchored into the specified anchor /// - bottom: current view's bottom anchor will be anchored into the specified anchor /// - right: current view's right anchor will be anchored into the specified anchor /// - topConstant: current view's top anchor margin /// - leftConstant: current view's left anchor margin /// - bottomConstant: current view's bottom anchor margin /// - rightConstant: current view's right anchor margin /// - widthConstant: current view's width /// - heightConstant: current view's height /// - Returns: array of newly added constraints (if applicable). @available(iOS 9, *) @discardableResult func anchor( top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] { // https://videos.letsbuildthatapp.com/ translatesAutoresizingMaskIntoConstraints = false var anchors = [NSLayoutConstraint]() if let top = top { anchors.append(topAnchor.constraint(equalTo: top, constant: topConstant)) } if let left = left { anchors.append(leftAnchor.constraint(equalTo: left, constant: leftConstant)) } if let bottom = bottom { anchors.append(bottomAnchor.constraint(equalTo: bottom, constant: -bottomConstant)) } if let right = right { anchors.append(rightAnchor.constraint(equalTo: right, constant: -rightConstant)) } if widthConstant > 0 { anchors.append(widthAnchor.constraint(equalToConstant: widthConstant)) } if heightConstant > 0 { anchors.append(heightAnchor.constraint(equalToConstant: heightConstant)) } anchors.forEach({$0.isActive = true}) return anchors } /// SwifterSwift: Anchor center X into current view's superview with a constant margin value. /// /// - Parameter constant: constant of the anchor constraint (default is 0). @available(iOS 9, *) func anchorCenterXToSuperview(constant: CGFloat = 0) { // https://videos.letsbuildthatapp.com/ translatesAutoresizingMaskIntoConstraints = false if let anchor = superview?.centerXAnchor { centerXAnchor.constraint(equalTo: anchor, constant: constant).isActive = true } } /// SwifterSwift: Anchor center Y into current view's superview with a constant margin value. /// /// - Parameter withConstant: constant of the anchor constraint (default is 0). @available(iOS 9, *) func anchorCenterYToSuperview(constant: CGFloat = 0) { // https://videos.letsbuildthatapp.com/ translatesAutoresizingMaskIntoConstraints = false if let anchor = superview?.centerYAnchor { centerYAnchor.constraint(equalTo: anchor, constant: constant).isActive = true } } /// SwifterSwift: Anchor center X and Y into current view's superview @available(iOS 9, *) func anchorCenterSuperview() { // https://videos.letsbuildthatapp.com/ anchorCenterXToSuperview() anchorCenterYToSuperview() } /// SwifterSwift: Search all superviews until a view with the condition is found. /// /// - Parameter predicate: predicate to evaluate on superviews. func ancestorView(where predicate: (UIView?) -> Bool) -> UIView? { if predicate(superview) { return superview } return superview?.ancestorView(where: predicate) } /// SwifterSwift: Search all superviews until a view with this class is found. /// /// - Parameter name: class of the view to search. func ancestorView(withClass name: T.Type) -> T? { return ancestorView(where: { $0 is T }) as? T } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift/Sources/SwifterSwift/UIKit/UIWindowExtensions.swift ================================================ // // UIWindowExtensions.swift // SwifterSwift // // Created by Omar Albeik on 6/2/18. // Copyright © 2018 SwifterSwift // #if canImport(UIKit) && os(iOS) import UIKit // MARK: - Methods public extension UIWindow { /// SwifterSwift: Switch current root view controller with a new view controller. /// /// - Parameters: /// - viewController: new view controller. /// - animated: set to true to animate view controller change (default is true). /// - duration: animation duration in seconds (default is 0.5). /// - options: animation options (default is .transitionFlipFromRight). /// - completion: optional completion handler called after view controller is changed. func switchRootViewController( to viewController: UIViewController, animated: Bool = true, duration: TimeInterval = 0.5, options: UIView.AnimationOptions = .transitionFlipFromRight, _ completion: (() -> Void)? = nil) { guard animated else { rootViewController = viewController completion?() return } UIView.transition(with: self, duration: duration, options: options, animations: { let oldState = UIView.areAnimationsEnabled UIView.setAnimationsEnabled(false) self.rootViewController = viewController UIView.setAnimationsEnabled(oldState) }, completion: { _ in completion?() }) } } #endif ================================================ FILE: JetChat/Pods/SwifterSwift.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 03EB7307E9C51C4BF3DA3E4D0C348544 /* URLRequestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 178FD5068418D4694DA165CE74F05D9E /* URLRequestExtensions.swift */; }; 07EA34BA5BE9B97AEFC21F035C491D55 /* LocaleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D119E1C346654B51D3847EDB3DBAF55 /* LocaleExtensions.swift */; }; 0AAB0088F0554CFBED8D391FDF3C3539 /* SCNCapsuleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954D39B5E8A6C530E265B4E062DF36BE /* SCNCapsuleExtensions.swift */; }; 0CBDCAC389F00121CC337E375C2AF667 /* SCNGeometryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218323CA0B825E681597113058220A9B /* SCNGeometryExtensions.swift */; }; 0D922CC19608EB07EE21A4F253D100E3 /* UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9871738DE63EB144588651D23C7D18FE /* UIImageExtensions.swift */; }; 0F62DFAF753D55EDDD354DF4E109524D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB30716EFACBE1CB02F2790B95774EF3 /* CollectionExtensions.swift */; }; 111A72E7C6FDF2180B004F6ED44AF41F /* MKPolylineExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3D274871F89572C27EE3BEC87374B2 /* MKPolylineExtensions.swift */; }; 13499163560520F8B4050FFEF84C0E84 /* CGSizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606283D802087028C59738E507B0B015 /* CGSizeExtensions.swift */; }; 14F2F38BA898987A3C9BB42C91E2760B /* IntExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0996DEE0A4BC2D8B66C05D71F445C55 /* IntExtensions.swift */; }; 1656946797E7AAD38BDAACEA7AC8EA1F /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DFE65F71A64AD8E6B36330FA08CFF21 /* UIColorExtensions.swift */; }; 175DB1053FA9CE2BD9FE0D0BD0254DC3 /* CLVisitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20127FF74C4A327C951325642D4944E /* CLVisitExtensions.swift */; }; 1A34A8781F259CD72C4DA9315064868C /* UIStoryboardExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682389727407FCDF01C01A67F93B02A0 /* UIStoryboardExtensions.swift */; }; 1AE8E1AD45DDCB97CEE21FFAB7D28345 /* BidirectionalCollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56EE9919F12F60AF1834DF30F4A6650B /* BidirectionalCollectionExtensions.swift */; }; 1C6B4D930A70308B15BB15415B06EA7F /* UIWindowExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC21ABB1AA407AA5668FC6A22835E7E /* UIWindowExtensions.swift */; }; 218E5326F4787C9D54B26B79D5B83F48 /* CGColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB908C282231C49C6C783D879EB7435 /* CGColorExtensions.swift */; }; 22AC24E6DA3D9330E2166AEB446E5707 /* UILabelExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D342AD86260B6E9550F40CEB3302ADD /* UILabelExtensions.swift */; }; 2321CE61C077A5A701CACCA6203F8CE3 /* CGPointExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771250677063121F977F8409A6E40843 /* CGPointExtensions.swift */; }; 24068E23BB105AA1E2E1B1D0D38C2F05 /* ColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B578E54F4D46DD9F46F8B9643B2583 /* ColorExtensions.swift */; }; 2918EE1385CEC0C8CA3951A35718AD4D /* BoolExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BBDA997029AC80DD983E7C192CA28F /* BoolExtensions.swift */; }; 2EDFF7BCC44E324E761D16DAB43E450E /* SwifterSwift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4082B1964B4057753713F19AA9DA5E68 /* SwifterSwift-dummy.m */; }; 2FBDC589C66A74D1A8B250E9E17405A4 /* SCNMaterialExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04043BCF44622F6539E023AAB29BDCB7 /* SCNMaterialExtensions.swift */; }; 3039712B6792D9194A6A707B1C997762 /* UIStackViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A0FA82E8964599F1944D8C74AA6C575 /* UIStackViewExtensions.swift */; }; 3420881E860560553A7C4D1D830E9EFB /* DispatchQueueExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BCCE0DB59E83F57F2523A9ED4BB422 /* DispatchQueueExtensions.swift */; }; 3DF3BE43A9F5B0A759A819E0168AD786 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76DE2450EE709CAED21FC210C09FD96B /* Foundation.framework */; }; 4100B04283FBD77405F68111C2F6BCD1 /* CGAffineTransformExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59B2A9D78BF486A8FECF592CD6019F5 /* CGAffineTransformExtensions.swift */; }; 432E981674837E39A577E90B9F394EC9 /* UIGestureRecognizerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A263F147BA9226700C552AB3E64A51BD /* UIGestureRecognizerExtensions.swift */; }; 439531DBBD2CD9AC1084835C1AB9D863 /* CAGradientLayerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50260D1481E33470EC6E3110DBCEB733 /* CAGradientLayerExtensions.swift */; }; 4613FD2CF78609995491E56C211B68F0 /* UISwitchExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE97146B83CC335037CBFD82946B9A2 /* UISwitchExtensions.swift */; }; 4AC9316A34881CDA256F5ED70C5E4802 /* UIAlertControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB77ABD38654DABAD05287F11ED8938B /* UIAlertControllerExtensions.swift */; }; 4F179B540566B805F45D879113E97A56 /* UINavigationControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BAAF1A7A3BF13F8CD868518074B48A1 /* UINavigationControllerExtensions.swift */; }; 517A98D6A26F35FBB8E5212F1F0B30DE /* DecodableExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E9571F4D88103DC85CAE6A8CC71042F /* DecodableExtensions.swift */; }; 5239AEFA621DA2C574DCA9C5C79669C7 /* NSViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE9C83A8C75D96235B8D5DF8701C845 /* NSViewExtensions.swift */; }; 552673953BD6FC91987BF1395578A0F0 /* NSRegularExpressionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D685340B50903935B27A6C9651E97E3 /* NSRegularExpressionExtensions.swift */; }; 562C37E302E7B276FEDDE4E12B8E2529 /* UIBezierPathExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14E1A3B41AB81795D86F65C1FB3E2E3 /* UIBezierPathExtensions.swift */; }; 5B05CE019FDA65D780C88EEF1F7ADA40 /* EdgeInsetsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72CC46D0B75E1017579AEACB13C73ED /* EdgeInsetsExtensions.swift */; }; 5BC092B1771A2008C13B17F0094A84EC /* UIButtonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305578AB855FB1E373BBF7A01ECB7215 /* UIButtonExtensions.swift */; }; 5E9DD323AE7FFDC4E4F8901DE7F6BD6F /* CLLocationArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBEFCB1090C9AA1F9F33EC64EE5D417F /* CLLocationArrayExtensions.swift */; }; 5EBC1F1F8DBECBB3EC1AB9956B50ED8E /* CLLocationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2BC9A218385F4217AC3BFC7BCE7557 /* CLLocationExtensions.swift */; }; 62DFCE85125759A95E824E2B7930800C /* UITabBarExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDBC1BA1C5FFE410D416BCAEEE0617D9 /* UITabBarExtensions.swift */; }; 6A31FF4F9A55302C61EF75EEFD35C92A /* CGFloatExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57BDB6938C15EC7B124293A48338F85 /* CGFloatExtensions.swift */; }; 6BBCCADE90A67FAA3A894A43A2C71B63 /* UILayoutPriorityExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D451642680D2098A7C8B329798692AAE /* UILayoutPriorityExtensions.swift */; }; 6C4AA88C1F2E94125A694DB9FD432F96 /* UIImageViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23539B9C5507B6E35B4681B217B78D90 /* UIImageViewExtensions.swift */; }; 6CCCD46A77D45150BB045DAE0403F5D0 /* UICollectionViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCDC5D0A2730DD39273DD6A5EFB7FE2 /* UICollectionViewExtensions.swift */; }; 6D556BE5CD6A49E57E62B2701F46B8BB /* SCNVector3Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FC1CCAB354220891F760C13AB15E7C /* SCNVector3Extensions.swift */; }; 70243DF7F77AAEE4CF7C408643BEAD21 /* UINavigationItemExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59851519DBB8F426780D2D371E8C634 /* UINavigationItemExtensions.swift */; }; 7178911787E3EC7497CF75265D735626 /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73BFEC95832D0F042982EBFE4B0D633B /* DoubleExtensions.swift */; }; 71E2302F9518855C23FB787A2F276CA7 /* DateExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B70CBF4D3BBAAB253AE240E3BD1D2669 /* DateExtensions.swift */; }; 72D14DC232192E1F1CC5753A27392D6B /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22EE35C81179AFB2C1F3A5963C6B6DB6 /* ArrayExtensions.swift */; }; 75C339D908FCA80DEB948BC41D3B9621 /* CGRectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72E62B259520D37285F70EB3E188330D /* CGRectExtensions.swift */; }; 775826A7565BF8E0DB96E8C92B94F871 /* CalendarExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F8BB10F976CFAAAB4684F53551FAB4 /* CalendarExtensions.swift */; }; 77FE0D8763867E669155617F0B93E297 /* SKProductExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB8536E3EB8CD95F15D4DC5E74AFB79 /* SKProductExtensions.swift */; }; 782D8FD93A4793B4B54A48A36A25E5C7 /* RandomAccessCollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DD624CBAFB637E9FBA4D442D34070B6 /* RandomAccessCollectionExtensions.swift */; }; 786B4EA5A038B57A5102F8D430985D52 /* StdlibDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 928840F843E8E53A543FAEA32BC083DC /* StdlibDeprecated.swift */; }; 795FFA64B92824C1F216D0E44C28EBF5 /* MKMapViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57CB559FBC6AE7C0E24A71D65D47F26 /* MKMapViewExtensions.swift */; }; 79D687E344534317A3D139ECAA46302D /* SCNPlaneExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8A3EDC80884D9624A0D6CA831BB547 /* SCNPlaneExtensions.swift */; }; 7BD7208E1E33605F855D79CCAF064BDF /* NSPredicateExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AF4B051BA7E66BE7825AC5BCA78C1F /* NSPredicateExtensions.swift */; }; 7D47491F730D6EBF3E321AB4460959D8 /* UserDefaultsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5FFC47499975D687F87DE19D36C6CF /* UserDefaultsExtensions.swift */; }; 7E8B7D216EAA941BDC5B5DD85B33CFE4 /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC77D85B8CBC7DC219B7C9CA14D03CD3 /* URLExtensions.swift */; }; 8121F09E6B03CF7E6F312C06A6EC748D /* SCNShapeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DBF309A1D49A9644FC5789B8820183A /* SCNShapeExtensions.swift */; }; 825ABFBDE1C9DA71D6D121B5371843F8 /* CharacterExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92F06CD5382CDC1E1D370150DACA1B85 /* CharacterExtensions.swift */; }; 83490435DF0BFE10E5C91885EF8AF568 /* SignedIntegerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA9EC86E7703705BCAAB378942DE5C0A /* SignedIntegerExtensions.swift */; }; 8D557A151174CA1C5E5E6093CA8754C0 /* StringProtocolExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B71480FF4BFAB3D75E034EF541134FB1 /* StringProtocolExtensions.swift */; }; 8FEC6BD040A6B6A4B94041B2AA83FF48 /* NotificationCenterExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 905DB01192D878FCDBE7F1D91F4597AE /* NotificationCenterExtensions.swift */; }; 92F7E733B76493867F3D07D90CA4F3DC /* SKNodeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358D2D0ECB435DB809D2616D7D4517C1 /* SKNodeExtensions.swift */; }; 932D82DEC397AF2C3581D2F3B7D2ABA3 /* SequenceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6F81758DE2D7A5A39A6A3EFA9541E5 /* SequenceExtensions.swift */; }; 93E5D4AFCF456D239A1C4D93252425E8 /* UIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945F243DA0454AF69346579BC6424E3D /* UIViewExtensions.swift */; }; 9491F6FC214E6BF6264DEE0D895A9E32 /* BinaryFloatingPointExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A6BB50E92B8522E6DEAADC99BC1A115 /* BinaryFloatingPointExtensions.swift */; }; 95766F1D9480E13E3FDB3E1C7F06B902 /* CATransform3DExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA7F3F31756C370D5B14E1EEE0AE5BE4 /* CATransform3DExtensions.swift */; }; 96DEF1684B8029C6EDDBAD71F7DCD6EC /* UISegmentedControlExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4424FCC7DC1EE80F2693173EB78938FA /* UISegmentedControlExtensions.swift */; }; 97250638352ACDCD8D1A061BCF67461D /* NSColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 139AACB589F770DA888264EE9B8D9169 /* NSColorExtensions.swift */; }; 9872F9B4D19118084E21FBCF6822E97E /* UIFontExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1281D19029AB6C4DF1B47DC937885D43 /* UIFontExtensions.swift */; }; 9C91D78BD8565C4C667DBD8321860470 /* UITextViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A03B2F4D91E274E8EF0582570038AF /* UITextViewExtensions.swift */; }; A22BA32DFC8A4CAC3ABC62B529D73488 /* RangeReplaceableCollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB68D0BE860C3B820CF39C982C1EAD5 /* RangeReplaceableCollectionExtensions.swift */; }; A2786EEF1693912AE814A6785AD9EDBA /* ComparableExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC731D9D9765D6830B6BBFB0F63F651B /* ComparableExtensions.swift */; }; A866E7A41110C36E678B6E7E7F519BE2 /* UISliderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA503D5F851D0024A91C386B9B189F43 /* UISliderExtensions.swift */; }; AEF83650746F38895C6F7B21FF325CFB /* CGVectorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FA355E92810C7AF1733C20C6446796 /* CGVectorExtensions.swift */; }; BACECD65B0600451C280C42D23B21FB0 /* UITextFieldExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C67EE97F9A1E69CFCC678E30E14AF0C /* UITextFieldExtensions.swift */; }; BCFBA55AC384A970A2061406DE0E0A54 /* NSAttributedStringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E2A317F359EA8EB151B9E5456D3366 /* NSAttributedStringExtensions.swift */; }; C82E1F635BE16DB13EE84170111D5B43 /* MutableCollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABF94317EADAC29122E24AE86612F3E /* MutableCollectionExtensions.swift */; }; C9B88453E903714E2E97A2D9E7E47D4E /* UIViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 447AD35780D46F45EC19687011A1F899 /* UIViewControllerExtensions.swift */; }; CC984A78A132F0334BC30997B0877385 /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D184D60B91C0BA9113DB447A038586B /* DataExtensions.swift */; }; CF4B40ACF2D0EBABF294E857405A8731 /* UISearchBarExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FFE4DE50CDEE9B8D76ECB9A46E340B /* UISearchBarExtensions.swift */; }; D121D7E529242400ABFD6779967B71B7 /* UIRefreshControlExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BFF4E919039710300FEF4082055F84 /* UIRefreshControlExtensions.swift */; }; D6D24DA1A68A850F427E829C9DAF6DB7 /* NSImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2CAE688482AC51AB4C1A2AFEA783AE /* NSImageExtensions.swift */; }; D9B29DBA992652124F22B77E88BB8175 /* UIBarButtonItemExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACA77963E017C39A2D8303722AE6F47E /* UIBarButtonItemExtensions.swift */; }; DC89265A7CEFBA540B83A9EE80743444 /* FileManagerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35EFC972F3D7BF43CDE63E2D6A0F5986 /* FileManagerExtensions.swift */; }; DD014204D6653020B552CF7040B457D1 /* UIDatePickerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C295D93943620A4B6A0961EBE32B3439 /* UIDatePickerExtensions.swift */; }; DFB980CF78B1FA065F3A5CE8D967FFB6 /* UIActivityExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE09214E89FD76E9563FA768262C6F44 /* UIActivityExtensions.swift */; }; E7BA717E905FB466C590F82D2678DC3F /* KeyedDecodingContainerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0394A8255308C91839463AA740E741FB /* KeyedDecodingContainerExtensions.swift */; }; E7BAFD365B0E10E46AF1FBC048EBE0AB /* UINavigationBarExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ECC2F58C574422B4C30C996557B7A21 /* UINavigationBarExtensions.swift */; }; E9A49B82523887E554F2C53B60C98DFE /* DictionaryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EACA88525495A4E05DE1E1CFD4DBD3C /* DictionaryExtensions.swift */; }; EAB799EB9FFE5102852925AB8940564D /* SCNConeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E23DB1DEF15AE7C320CCEE6F95E16C /* SCNConeExtensions.swift */; }; EAF800C24C4325406BED1BB76A706557 /* OptionalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3EA480D615827C74BB57E9B7186DB7 /* OptionalExtensions.swift */; }; EC37FDF83867FF75E6B2F8E1A3BC0F7E /* FloatingPointExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94DFECA9BE3583ACDFBB9EE17AF16D44 /* FloatingPointExtensions.swift */; }; EEFA37D89A0EDDC560C9C85925F6CA46 /* UIScrollViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 901E737E1A60E866DB4E989D257EE4AD /* UIScrollViewExtensions.swift */; }; F1CD4C012EE244450A25885BB6960831 /* SignedNumericExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44756B39FE8C6002C2664696E734634B /* SignedNumericExtensions.swift */; }; F4801477E956ED32EAF2B2F10FCB2CC4 /* UIApplicationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A25FC51E77F378B5FF632FC91519413D /* UIApplicationExtensions.swift */; }; F518834379772EE5069D62B8EB881F00 /* SCNBoxExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29131DA8B572A41696F2D440BF6FCCA7 /* SCNBoxExtensions.swift */; }; F683F35D64B7E7BE7910DE75644F94B0 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC29232BA21F2FD5D0313E4B5A55C720 /* StringExtensions.swift */; }; F6F3B08650F9E5DA1D1A905B2BF42518 /* SCNSphereExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABBC474C3E93D1E93CA97492CBD53B3 /* SCNSphereExtensions.swift */; }; F7E6489CD2AA97160BBF965D7FCE9EAE /* FloatExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF7618364A9C6BCB072DA07D966DF34 /* FloatExtensions.swift */; }; FAB79DE5A9A0DDA6110AEFE4DAC792E3 /* SwifterSwift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = BE2BF4C7CC79826758773F2D7AC81C82 /* SwifterSwift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; FBC9A1A33320B9914F951B23B2E23498 /* UITableViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7797400C38392F5276C74DC40868A2 /* UITableViewExtensions.swift */; }; FD8F1D2703C5DB87EA4A27F031EEF82E /* SCNCylinderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE7F962148CC4465A6AD4E2F84FF403D /* SCNCylinderExtensions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0394A8255308C91839463AA740E741FB /* KeyedDecodingContainerExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KeyedDecodingContainerExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/KeyedDecodingContainerExtensions.swift; sourceTree = ""; }; 04043BCF44622F6539E023AAB29BDCB7 /* SCNMaterialExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SCNMaterialExtensions.swift; path = Sources/SwifterSwift/SceneKit/SCNMaterialExtensions.swift; sourceTree = ""; }; 0BAAF1A7A3BF13F8CD868518074B48A1 /* UINavigationControllerExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UINavigationControllerExtensions.swift; path = Sources/SwifterSwift/UIKit/UINavigationControllerExtensions.swift; sourceTree = ""; }; 0CE97146B83CC335037CBFD82946B9A2 /* UISwitchExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UISwitchExtensions.swift; path = Sources/SwifterSwift/UIKit/UISwitchExtensions.swift; sourceTree = ""; }; 0EACA88525495A4E05DE1E1CFD4DBD3C /* DictionaryExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DictionaryExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/DictionaryExtensions.swift; sourceTree = ""; }; 1281D19029AB6C4DF1B47DC937885D43 /* UIFontExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIFontExtensions.swift; path = Sources/SwifterSwift/UIKit/UIFontExtensions.swift; sourceTree = ""; }; 139AACB589F770DA888264EE9B8D9169 /* NSColorExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSColorExtensions.swift; path = Sources/SwifterSwift/AppKit/NSColorExtensions.swift; sourceTree = ""; }; 178FD5068418D4694DA165CE74F05D9E /* URLRequestExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = URLRequestExtensions.swift; path = Sources/SwifterSwift/Foundation/URLRequestExtensions.swift; sourceTree = ""; }; 1900A0055F0FD7FC2A2F0CD15D34DC84 /* SwifterSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwifterSwift.release.xcconfig; sourceTree = ""; }; 1D342AD86260B6E9550F40CEB3302ADD /* UILabelExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UILabelExtensions.swift; path = Sources/SwifterSwift/UIKit/UILabelExtensions.swift; sourceTree = ""; }; 1F2CAE688482AC51AB4C1A2AFEA783AE /* NSImageExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSImageExtensions.swift; path = Sources/SwifterSwift/AppKit/NSImageExtensions.swift; sourceTree = ""; }; 1F7797400C38392F5276C74DC40868A2 /* UITableViewExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UITableViewExtensions.swift; path = Sources/SwifterSwift/UIKit/UITableViewExtensions.swift; sourceTree = ""; }; 218323CA0B825E681597113058220A9B /* SCNGeometryExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SCNGeometryExtensions.swift; path = Sources/SwifterSwift/SceneKit/SCNGeometryExtensions.swift; sourceTree = ""; }; 22EE35C81179AFB2C1F3A5963C6B6DB6 /* ArrayExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ArrayExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/ArrayExtensions.swift; sourceTree = ""; }; 23539B9C5507B6E35B4681B217B78D90 /* UIImageViewExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIImageViewExtensions.swift; path = Sources/SwifterSwift/UIKit/UIImageViewExtensions.swift; sourceTree = ""; }; 26F8BB10F976CFAAAB4684F53551FAB4 /* CalendarExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CalendarExtensions.swift; path = Sources/SwifterSwift/Foundation/CalendarExtensions.swift; sourceTree = ""; }; 29131DA8B572A41696F2D440BF6FCCA7 /* SCNBoxExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SCNBoxExtensions.swift; path = Sources/SwifterSwift/SceneKit/SCNBoxExtensions.swift; sourceTree = ""; }; 2AB8536E3EB8CD95F15D4DC5E74AFB79 /* SKProductExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SKProductExtensions.swift; path = Sources/SwifterSwift/StoreKit/SKProductExtensions.swift; sourceTree = ""; }; 2D8A3EDC80884D9624A0D6CA831BB547 /* SCNPlaneExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SCNPlaneExtensions.swift; path = Sources/SwifterSwift/SceneKit/SCNPlaneExtensions.swift; sourceTree = ""; }; 305578AB855FB1E373BBF7A01ECB7215 /* UIButtonExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIButtonExtensions.swift; path = Sources/SwifterSwift/UIKit/UIButtonExtensions.swift; sourceTree = ""; }; 30E2A317F359EA8EB151B9E5456D3366 /* NSAttributedStringExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSAttributedStringExtensions.swift; path = Sources/SwifterSwift/Foundation/NSAttributedStringExtensions.swift; sourceTree = ""; }; 358D2D0ECB435DB809D2616D7D4517C1 /* SKNodeExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SKNodeExtensions.swift; path = Sources/SwifterSwift/SpriteKit/SKNodeExtensions.swift; sourceTree = ""; }; 35EFC972F3D7BF43CDE63E2D6A0F5986 /* FileManagerExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FileManagerExtensions.swift; path = Sources/SwifterSwift/Foundation/FileManagerExtensions.swift; sourceTree = ""; }; 372F4FBE84F0525B54F5E1289553BC79 /* SwifterSwift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwifterSwift-prefix.pch"; sourceTree = ""; }; 3DB68D0BE860C3B820CF39C982C1EAD5 /* RangeReplaceableCollectionExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RangeReplaceableCollectionExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/RangeReplaceableCollectionExtensions.swift; sourceTree = ""; }; 4082B1964B4057753713F19AA9DA5E68 /* SwifterSwift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SwifterSwift-dummy.m"; sourceTree = ""; }; 4424FCC7DC1EE80F2693173EB78938FA /* UISegmentedControlExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UISegmentedControlExtensions.swift; path = Sources/SwifterSwift/UIKit/UISegmentedControlExtensions.swift; sourceTree = ""; }; 44756B39FE8C6002C2664696E734634B /* SignedNumericExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SignedNumericExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/SignedNumericExtensions.swift; sourceTree = ""; }; 447AD35780D46F45EC19687011A1F899 /* UIViewControllerExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIViewControllerExtensions.swift; path = Sources/SwifterSwift/UIKit/UIViewControllerExtensions.swift; sourceTree = ""; }; 4ABBC474C3E93D1E93CA97492CBD53B3 /* SCNSphereExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SCNSphereExtensions.swift; path = Sources/SwifterSwift/SceneKit/SCNSphereExtensions.swift; sourceTree = ""; }; 4AF7618364A9C6BCB072DA07D966DF34 /* FloatExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FloatExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/FloatExtensions.swift; sourceTree = ""; }; 4D119E1C346654B51D3847EDB3DBAF55 /* LocaleExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LocaleExtensions.swift; path = Sources/SwifterSwift/Foundation/LocaleExtensions.swift; sourceTree = ""; }; 4DD624CBAFB637E9FBA4D442D34070B6 /* RandomAccessCollectionExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RandomAccessCollectionExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/RandomAccessCollectionExtensions.swift; sourceTree = ""; }; 50260D1481E33470EC6E3110DBCEB733 /* CAGradientLayerExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CAGradientLayerExtensions.swift; path = Sources/SwifterSwift/CoreAnimation/CAGradientLayerExtensions.swift; sourceTree = ""; }; 56EE9919F12F60AF1834DF30F4A6650B /* BidirectionalCollectionExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BidirectionalCollectionExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/BidirectionalCollectionExtensions.swift; sourceTree = ""; }; 5A0FA82E8964599F1944D8C74AA6C575 /* UIStackViewExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIStackViewExtensions.swift; path = Sources/SwifterSwift/UIKit/UIStackViewExtensions.swift; sourceTree = ""; }; 5C67EE97F9A1E69CFCC678E30E14AF0C /* UITextFieldExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UITextFieldExtensions.swift; path = Sources/SwifterSwift/UIKit/UITextFieldExtensions.swift; sourceTree = ""; }; 5DCDC5D0A2730DD39273DD6A5EFB7FE2 /* UICollectionViewExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UICollectionViewExtensions.swift; path = Sources/SwifterSwift/UIKit/UICollectionViewExtensions.swift; sourceTree = ""; }; 5ECC2F58C574422B4C30C996557B7A21 /* UINavigationBarExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UINavigationBarExtensions.swift; path = Sources/SwifterSwift/UIKit/UINavigationBarExtensions.swift; sourceTree = ""; }; 606283D802087028C59738E507B0B015 /* CGSizeExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CGSizeExtensions.swift; path = Sources/SwifterSwift/CoreGraphics/CGSizeExtensions.swift; sourceTree = ""; }; 60A03B2F4D91E274E8EF0582570038AF /* UITextViewExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UITextViewExtensions.swift; path = Sources/SwifterSwift/UIKit/UITextViewExtensions.swift; sourceTree = ""; }; 682389727407FCDF01C01A67F93B02A0 /* UIStoryboardExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIStoryboardExtensions.swift; path = Sources/SwifterSwift/UIKit/UIStoryboardExtensions.swift; sourceTree = ""; }; 6A3D274871F89572C27EE3BEC87374B2 /* MKPolylineExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MKPolylineExtensions.swift; path = Sources/SwifterSwift/MapKit/MKPolylineExtensions.swift; sourceTree = ""; }; 6AB908C282231C49C6C783D879EB7435 /* CGColorExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CGColorExtensions.swift; path = Sources/SwifterSwift/CoreGraphics/CGColorExtensions.swift; sourceTree = ""; }; 6DBF309A1D49A9644FC5789B8820183A /* SCNShapeExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SCNShapeExtensions.swift; path = Sources/SwifterSwift/SceneKit/SCNShapeExtensions.swift; sourceTree = ""; }; 6EE9C83A8C75D96235B8D5DF8701C845 /* NSViewExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSViewExtensions.swift; path = Sources/SwifterSwift/AppKit/NSViewExtensions.swift; sourceTree = ""; }; 6F2EFB40A354412DEB57740124966124 /* SwifterSwift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SwifterSwift-Info.plist"; sourceTree = ""; }; 72E62B259520D37285F70EB3E188330D /* CGRectExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CGRectExtensions.swift; path = Sources/SwifterSwift/CoreGraphics/CGRectExtensions.swift; sourceTree = ""; }; 72FFE4DE50CDEE9B8D76ECB9A46E340B /* UISearchBarExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UISearchBarExtensions.swift; path = Sources/SwifterSwift/UIKit/UISearchBarExtensions.swift; sourceTree = ""; }; 73BFEC95832D0F042982EBFE4B0D633B /* DoubleExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DoubleExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/DoubleExtensions.swift; sourceTree = ""; }; 74FA355E92810C7AF1733C20C6446796 /* CGVectorExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CGVectorExtensions.swift; path = Sources/SwifterSwift/CoreGraphics/CGVectorExtensions.swift; sourceTree = ""; }; 76DE2450EE709CAED21FC210C09FD96B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 771250677063121F977F8409A6E40843 /* CGPointExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CGPointExtensions.swift; path = Sources/SwifterSwift/CoreGraphics/CGPointExtensions.swift; sourceTree = ""; }; 78BBDA997029AC80DD983E7C192CA28F /* BoolExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BoolExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/BoolExtensions.swift; sourceTree = ""; }; 7CC21ABB1AA407AA5668FC6A22835E7E /* UIWindowExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIWindowExtensions.swift; path = Sources/SwifterSwift/UIKit/UIWindowExtensions.swift; sourceTree = ""; }; 7D184D60B91C0BA9113DB447A038586B /* DataExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DataExtensions.swift; path = Sources/SwifterSwift/Foundation/DataExtensions.swift; sourceTree = ""; }; 7F16F0AFDC2F153DB390FA481C6CA674 /* SwifterSwift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SwifterSwift.modulemap; sourceTree = ""; }; 8A6BB50E92B8522E6DEAADC99BC1A115 /* BinaryFloatingPointExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BinaryFloatingPointExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/BinaryFloatingPointExtensions.swift; sourceTree = ""; }; 8DFE65F71A64AD8E6B36330FA08CFF21 /* UIColorExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIColorExtensions.swift; path = Sources/SwifterSwift/UIKit/UIColorExtensions.swift; sourceTree = ""; }; 8E9571F4D88103DC85CAE6A8CC71042F /* DecodableExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DecodableExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/DecodableExtensions.swift; sourceTree = ""; }; 901E737E1A60E866DB4E989D257EE4AD /* UIScrollViewExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIScrollViewExtensions.swift; path = Sources/SwifterSwift/UIKit/UIScrollViewExtensions.swift; sourceTree = ""; }; 905DB01192D878FCDBE7F1D91F4597AE /* NotificationCenterExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NotificationCenterExtensions.swift; path = Sources/SwifterSwift/Foundation/NotificationCenterExtensions.swift; sourceTree = ""; }; 928840F843E8E53A543FAEA32BC083DC /* StdlibDeprecated.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StdlibDeprecated.swift; path = Sources/SwifterSwift/SwiftStdlib/Deprecated/StdlibDeprecated.swift; sourceTree = ""; }; 92F06CD5382CDC1E1D370150DACA1B85 /* CharacterExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CharacterExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/CharacterExtensions.swift; sourceTree = ""; }; 945F243DA0454AF69346579BC6424E3D /* UIViewExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIViewExtensions.swift; path = Sources/SwifterSwift/UIKit/UIViewExtensions.swift; sourceTree = ""; }; 94DFECA9BE3583ACDFBB9EE17AF16D44 /* FloatingPointExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FloatingPointExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/FloatingPointExtensions.swift; sourceTree = ""; }; 954D39B5E8A6C530E265B4E062DF36BE /* SCNCapsuleExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SCNCapsuleExtensions.swift; path = Sources/SwifterSwift/SceneKit/SCNCapsuleExtensions.swift; sourceTree = ""; }; 97D8DA6CB02ED5125505335D2CCA262A /* SwifterSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwifterSwift.debug.xcconfig; sourceTree = ""; }; 9871738DE63EB144588651D23C7D18FE /* UIImageExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIImageExtensions.swift; path = Sources/SwifterSwift/UIKit/UIImageExtensions.swift; sourceTree = ""; }; 98B578E54F4D46DD9F46F8B9643B2583 /* ColorExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorExtensions.swift; path = Sources/SwifterSwift/Shared/ColorExtensions.swift; sourceTree = ""; }; 9D685340B50903935B27A6C9651E97E3 /* NSRegularExpressionExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSRegularExpressionExtensions.swift; path = Sources/SwifterSwift/Foundation/NSRegularExpressionExtensions.swift; sourceTree = ""; }; A25FC51E77F378B5FF632FC91519413D /* UIApplicationExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIApplicationExtensions.swift; path = Sources/SwifterSwift/UIKit/UIApplicationExtensions.swift; sourceTree = ""; }; A263F147BA9226700C552AB3E64A51BD /* UIGestureRecognizerExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIGestureRecognizerExtensions.swift; path = Sources/SwifterSwift/UIKit/UIGestureRecognizerExtensions.swift; sourceTree = ""; }; AC29232BA21F2FD5D0313E4B5A55C720 /* StringExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StringExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/StringExtensions.swift; sourceTree = ""; }; AC3EA480D615827C74BB57E9B7186DB7 /* OptionalExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OptionalExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/OptionalExtensions.swift; sourceTree = ""; }; ACA77963E017C39A2D8303722AE6F47E /* UIBarButtonItemExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIBarButtonItemExtensions.swift; path = Sources/SwifterSwift/UIKit/UIBarButtonItemExtensions.swift; sourceTree = ""; }; B1BFF4E919039710300FEF4082055F84 /* UIRefreshControlExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIRefreshControlExtensions.swift; path = Sources/SwifterSwift/UIKit/UIRefreshControlExtensions.swift; sourceTree = ""; }; B57BDB6938C15EC7B124293A48338F85 /* CGFloatExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CGFloatExtensions.swift; path = Sources/SwifterSwift/CoreGraphics/CGFloatExtensions.swift; sourceTree = ""; }; B57CB559FBC6AE7C0E24A71D65D47F26 /* MKMapViewExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MKMapViewExtensions.swift; path = Sources/SwifterSwift/MapKit/MKMapViewExtensions.swift; sourceTree = ""; }; B70CBF4D3BBAAB253AE240E3BD1D2669 /* DateExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DateExtensions.swift; path = Sources/SwifterSwift/Foundation/DateExtensions.swift; sourceTree = ""; }; B71480FF4BFAB3D75E034EF541134FB1 /* StringProtocolExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StringProtocolExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/StringProtocolExtensions.swift; sourceTree = ""; }; BABF94317EADAC29122E24AE86612F3E /* MutableCollectionExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MutableCollectionExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/MutableCollectionExtensions.swift; sourceTree = ""; }; BB77ABD38654DABAD05287F11ED8938B /* UIAlertControllerExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIAlertControllerExtensions.swift; path = Sources/SwifterSwift/UIKit/UIAlertControllerExtensions.swift; sourceTree = ""; }; BC77D85B8CBC7DC219B7C9CA14D03CD3 /* URLExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = URLExtensions.swift; path = Sources/SwifterSwift/Foundation/URLExtensions.swift; sourceTree = ""; }; BD6F81758DE2D7A5A39A6A3EFA9541E5 /* SequenceExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SequenceExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/SequenceExtensions.swift; sourceTree = ""; }; BE2BF4C7CC79826758773F2D7AC81C82 /* SwifterSwift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwifterSwift-umbrella.h"; sourceTree = ""; }; BE5FFC47499975D687F87DE19D36C6CF /* UserDefaultsExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UserDefaultsExtensions.swift; path = Sources/SwifterSwift/Foundation/UserDefaultsExtensions.swift; sourceTree = ""; }; C14E1A3B41AB81795D86F65C1FB3E2E3 /* UIBezierPathExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIBezierPathExtensions.swift; path = Sources/SwifterSwift/UIKit/UIBezierPathExtensions.swift; sourceTree = ""; }; C295D93943620A4B6A0961EBE32B3439 /* UIDatePickerExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIDatePickerExtensions.swift; path = Sources/SwifterSwift/UIKit/UIDatePickerExtensions.swift; sourceTree = ""; }; C2E23DB1DEF15AE7C320CCEE6F95E16C /* SCNConeExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SCNConeExtensions.swift; path = Sources/SwifterSwift/SceneKit/SCNConeExtensions.swift; sourceTree = ""; }; C3E7BFD79922D2545166916876E56FD5 /* SwifterSwift */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SwifterSwift; path = SwifterSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C72CC46D0B75E1017579AEACB13C73ED /* EdgeInsetsExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EdgeInsetsExtensions.swift; path = Sources/SwifterSwift/Shared/EdgeInsetsExtensions.swift; sourceTree = ""; }; CA503D5F851D0024A91C386B9B189F43 /* UISliderExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UISliderExtensions.swift; path = Sources/SwifterSwift/UIKit/UISliderExtensions.swift; sourceTree = ""; }; D20127FF74C4A327C951325642D4944E /* CLVisitExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CLVisitExtensions.swift; path = Sources/SwifterSwift/CoreLocation/CLVisitExtensions.swift; sourceTree = ""; }; D451642680D2098A7C8B329798692AAE /* UILayoutPriorityExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UILayoutPriorityExtensions.swift; path = Sources/SwifterSwift/UIKit/UILayoutPriorityExtensions.swift; sourceTree = ""; }; D4AF4B051BA7E66BE7825AC5BCA78C1F /* NSPredicateExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSPredicateExtensions.swift; path = Sources/SwifterSwift/Foundation/NSPredicateExtensions.swift; sourceTree = ""; }; D59851519DBB8F426780D2D371E8C634 /* UINavigationItemExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UINavigationItemExtensions.swift; path = Sources/SwifterSwift/UIKit/UINavigationItemExtensions.swift; sourceTree = ""; }; DA7F3F31756C370D5B14E1EEE0AE5BE4 /* CATransform3DExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CATransform3DExtensions.swift; path = Sources/SwifterSwift/CoreAnimation/CATransform3DExtensions.swift; sourceTree = ""; }; DBEFCB1090C9AA1F9F33EC64EE5D417F /* CLLocationArrayExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CLLocationArrayExtensions.swift; path = Sources/SwifterSwift/CoreLocation/CLLocationArrayExtensions.swift; sourceTree = ""; }; DD2BC9A218385F4217AC3BFC7BCE7557 /* CLLocationExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CLLocationExtensions.swift; path = Sources/SwifterSwift/CoreLocation/CLLocationExtensions.swift; sourceTree = ""; }; DE09214E89FD76E9563FA768262C6F44 /* UIActivityExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIActivityExtensions.swift; path = Sources/SwifterSwift/UIKit/UIActivityExtensions.swift; sourceTree = ""; }; DE7F962148CC4465A6AD4E2F84FF403D /* SCNCylinderExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SCNCylinderExtensions.swift; path = Sources/SwifterSwift/SceneKit/SCNCylinderExtensions.swift; sourceTree = ""; }; EDBC1BA1C5FFE410D416BCAEEE0617D9 /* UITabBarExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UITabBarExtensions.swift; path = Sources/SwifterSwift/UIKit/UITabBarExtensions.swift; sourceTree = ""; }; F0996DEE0A4BC2D8B66C05D71F445C55 /* IntExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IntExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/IntExtensions.swift; sourceTree = ""; }; F1BCCE0DB59E83F57F2523A9ED4BB422 /* DispatchQueueExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DispatchQueueExtensions.swift; path = Sources/SwifterSwift/Dispatch/DispatchQueueExtensions.swift; sourceTree = ""; }; F1FC1CCAB354220891F760C13AB15E7C /* SCNVector3Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SCNVector3Extensions.swift; path = Sources/SwifterSwift/SceneKit/SCNVector3Extensions.swift; sourceTree = ""; }; F59B2A9D78BF486A8FECF592CD6019F5 /* CGAffineTransformExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CGAffineTransformExtensions.swift; path = Sources/SwifterSwift/CoreGraphics/CGAffineTransformExtensions.swift; sourceTree = ""; }; FA9EC86E7703705BCAAB378942DE5C0A /* SignedIntegerExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SignedIntegerExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/SignedIntegerExtensions.swift; sourceTree = ""; }; FB30716EFACBE1CB02F2790B95774EF3 /* CollectionExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CollectionExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/CollectionExtensions.swift; sourceTree = ""; }; FC731D9D9765D6830B6BBFB0F63F651B /* ComparableExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ComparableExtensions.swift; path = Sources/SwifterSwift/SwiftStdlib/ComparableExtensions.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 1C2CB0681C86445FB40D30DACAD4C258 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3DF3BE43A9F5B0A759A819E0168AD786 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 09D45F274C0BCF1F0EAF5A62E85870EE /* Dispatch */ = { isa = PBXGroup; children = ( ); name = Dispatch; sourceTree = ""; }; 1EA955DCC84A3AA272E92497F65A2943 = { isa = PBXGroup; children = ( 21637DC1033BB54640EABA698E19FD3C /* Frameworks */, 4050E7EE49EB8280C661940FCC028127 /* Products */, 7E3539F3C93C33894DA02FF1B97102FF /* SwifterSwift */, ); sourceTree = ""; }; 21637DC1033BB54640EABA698E19FD3C /* Frameworks */ = { isa = PBXGroup; children = ( 2352CDDAA1A179B84038FD5F68E65CC5 /* iOS */, ); name = Frameworks; sourceTree = ""; }; 2352CDDAA1A179B84038FD5F68E65CC5 /* iOS */ = { isa = PBXGroup; children = ( 76DE2450EE709CAED21FC210C09FD96B /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; 252184C9937BF04C8C175F48E142DC16 /* SwiftStdlib */ = { isa = PBXGroup; children = ( ); name = SwiftStdlib; sourceTree = ""; }; 4050E7EE49EB8280C661940FCC028127 /* Products */ = { isa = PBXGroup; children = ( C3E7BFD79922D2545166916876E56FD5 /* SwifterSwift */, ); name = Products; sourceTree = ""; }; 4CDA0020315339C3E2E44135A0C38D20 /* UIKit */ = { isa = PBXGroup; children = ( ); name = UIKit; sourceTree = ""; }; 6FF750DE262983C50022B14283BEDC43 /* CoreAnimation */ = { isa = PBXGroup; children = ( ); name = CoreAnimation; sourceTree = ""; }; 7E3539F3C93C33894DA02FF1B97102FF /* SwifterSwift */ = { isa = PBXGroup; children = ( 22EE35C81179AFB2C1F3A5963C6B6DB6 /* ArrayExtensions.swift */, 56EE9919F12F60AF1834DF30F4A6650B /* BidirectionalCollectionExtensions.swift */, 8A6BB50E92B8522E6DEAADC99BC1A115 /* BinaryFloatingPointExtensions.swift */, 78BBDA997029AC80DD983E7C192CA28F /* BoolExtensions.swift */, 50260D1481E33470EC6E3110DBCEB733 /* CAGradientLayerExtensions.swift */, 26F8BB10F976CFAAAB4684F53551FAB4 /* CalendarExtensions.swift */, DA7F3F31756C370D5B14E1EEE0AE5BE4 /* CATransform3DExtensions.swift */, F59B2A9D78BF486A8FECF592CD6019F5 /* CGAffineTransformExtensions.swift */, 6AB908C282231C49C6C783D879EB7435 /* CGColorExtensions.swift */, B57BDB6938C15EC7B124293A48338F85 /* CGFloatExtensions.swift */, 771250677063121F977F8409A6E40843 /* CGPointExtensions.swift */, 72E62B259520D37285F70EB3E188330D /* CGRectExtensions.swift */, 606283D802087028C59738E507B0B015 /* CGSizeExtensions.swift */, 74FA355E92810C7AF1733C20C6446796 /* CGVectorExtensions.swift */, 92F06CD5382CDC1E1D370150DACA1B85 /* CharacterExtensions.swift */, DBEFCB1090C9AA1F9F33EC64EE5D417F /* CLLocationArrayExtensions.swift */, DD2BC9A218385F4217AC3BFC7BCE7557 /* CLLocationExtensions.swift */, D20127FF74C4A327C951325642D4944E /* CLVisitExtensions.swift */, FB30716EFACBE1CB02F2790B95774EF3 /* CollectionExtensions.swift */, 98B578E54F4D46DD9F46F8B9643B2583 /* ColorExtensions.swift */, FC731D9D9765D6830B6BBFB0F63F651B /* ComparableExtensions.swift */, 7D184D60B91C0BA9113DB447A038586B /* DataExtensions.swift */, B70CBF4D3BBAAB253AE240E3BD1D2669 /* DateExtensions.swift */, 8E9571F4D88103DC85CAE6A8CC71042F /* DecodableExtensions.swift */, 0EACA88525495A4E05DE1E1CFD4DBD3C /* DictionaryExtensions.swift */, F1BCCE0DB59E83F57F2523A9ED4BB422 /* DispatchQueueExtensions.swift */, 73BFEC95832D0F042982EBFE4B0D633B /* DoubleExtensions.swift */, C72CC46D0B75E1017579AEACB13C73ED /* EdgeInsetsExtensions.swift */, 35EFC972F3D7BF43CDE63E2D6A0F5986 /* FileManagerExtensions.swift */, 4AF7618364A9C6BCB072DA07D966DF34 /* FloatExtensions.swift */, 94DFECA9BE3583ACDFBB9EE17AF16D44 /* FloatingPointExtensions.swift */, F0996DEE0A4BC2D8B66C05D71F445C55 /* IntExtensions.swift */, 0394A8255308C91839463AA740E741FB /* KeyedDecodingContainerExtensions.swift */, 4D119E1C346654B51D3847EDB3DBAF55 /* LocaleExtensions.swift */, B57CB559FBC6AE7C0E24A71D65D47F26 /* MKMapViewExtensions.swift */, 6A3D274871F89572C27EE3BEC87374B2 /* MKPolylineExtensions.swift */, BABF94317EADAC29122E24AE86612F3E /* MutableCollectionExtensions.swift */, 905DB01192D878FCDBE7F1D91F4597AE /* NotificationCenterExtensions.swift */, 30E2A317F359EA8EB151B9E5456D3366 /* NSAttributedStringExtensions.swift */, 139AACB589F770DA888264EE9B8D9169 /* NSColorExtensions.swift */, 1F2CAE688482AC51AB4C1A2AFEA783AE /* NSImageExtensions.swift */, D4AF4B051BA7E66BE7825AC5BCA78C1F /* NSPredicateExtensions.swift */, 9D685340B50903935B27A6C9651E97E3 /* NSRegularExpressionExtensions.swift */, 6EE9C83A8C75D96235B8D5DF8701C845 /* NSViewExtensions.swift */, AC3EA480D615827C74BB57E9B7186DB7 /* OptionalExtensions.swift */, 4DD624CBAFB637E9FBA4D442D34070B6 /* RandomAccessCollectionExtensions.swift */, 3DB68D0BE860C3B820CF39C982C1EAD5 /* RangeReplaceableCollectionExtensions.swift */, 29131DA8B572A41696F2D440BF6FCCA7 /* SCNBoxExtensions.swift */, 954D39B5E8A6C530E265B4E062DF36BE /* SCNCapsuleExtensions.swift */, C2E23DB1DEF15AE7C320CCEE6F95E16C /* SCNConeExtensions.swift */, DE7F962148CC4465A6AD4E2F84FF403D /* SCNCylinderExtensions.swift */, 218323CA0B825E681597113058220A9B /* SCNGeometryExtensions.swift */, 04043BCF44622F6539E023AAB29BDCB7 /* SCNMaterialExtensions.swift */, 2D8A3EDC80884D9624A0D6CA831BB547 /* SCNPlaneExtensions.swift */, 6DBF309A1D49A9644FC5789B8820183A /* SCNShapeExtensions.swift */, 4ABBC474C3E93D1E93CA97492CBD53B3 /* SCNSphereExtensions.swift */, F1FC1CCAB354220891F760C13AB15E7C /* SCNVector3Extensions.swift */, BD6F81758DE2D7A5A39A6A3EFA9541E5 /* SequenceExtensions.swift */, FA9EC86E7703705BCAAB378942DE5C0A /* SignedIntegerExtensions.swift */, 44756B39FE8C6002C2664696E734634B /* SignedNumericExtensions.swift */, 358D2D0ECB435DB809D2616D7D4517C1 /* SKNodeExtensions.swift */, 2AB8536E3EB8CD95F15D4DC5E74AFB79 /* SKProductExtensions.swift */, 928840F843E8E53A543FAEA32BC083DC /* StdlibDeprecated.swift */, AC29232BA21F2FD5D0313E4B5A55C720 /* StringExtensions.swift */, B71480FF4BFAB3D75E034EF541134FB1 /* StringProtocolExtensions.swift */, DE09214E89FD76E9563FA768262C6F44 /* UIActivityExtensions.swift */, BB77ABD38654DABAD05287F11ED8938B /* UIAlertControllerExtensions.swift */, A25FC51E77F378B5FF632FC91519413D /* UIApplicationExtensions.swift */, ACA77963E017C39A2D8303722AE6F47E /* UIBarButtonItemExtensions.swift */, C14E1A3B41AB81795D86F65C1FB3E2E3 /* UIBezierPathExtensions.swift */, 305578AB855FB1E373BBF7A01ECB7215 /* UIButtonExtensions.swift */, 5DCDC5D0A2730DD39273DD6A5EFB7FE2 /* UICollectionViewExtensions.swift */, 8DFE65F71A64AD8E6B36330FA08CFF21 /* UIColorExtensions.swift */, C295D93943620A4B6A0961EBE32B3439 /* UIDatePickerExtensions.swift */, 1281D19029AB6C4DF1B47DC937885D43 /* UIFontExtensions.swift */, A263F147BA9226700C552AB3E64A51BD /* UIGestureRecognizerExtensions.swift */, 9871738DE63EB144588651D23C7D18FE /* UIImageExtensions.swift */, 23539B9C5507B6E35B4681B217B78D90 /* UIImageViewExtensions.swift */, 1D342AD86260B6E9550F40CEB3302ADD /* UILabelExtensions.swift */, D451642680D2098A7C8B329798692AAE /* UILayoutPriorityExtensions.swift */, 5ECC2F58C574422B4C30C996557B7A21 /* UINavigationBarExtensions.swift */, 0BAAF1A7A3BF13F8CD868518074B48A1 /* UINavigationControllerExtensions.swift */, D59851519DBB8F426780D2D371E8C634 /* UINavigationItemExtensions.swift */, B1BFF4E919039710300FEF4082055F84 /* UIRefreshControlExtensions.swift */, 901E737E1A60E866DB4E989D257EE4AD /* UIScrollViewExtensions.swift */, 72FFE4DE50CDEE9B8D76ECB9A46E340B /* UISearchBarExtensions.swift */, 4424FCC7DC1EE80F2693173EB78938FA /* UISegmentedControlExtensions.swift */, CA503D5F851D0024A91C386B9B189F43 /* UISliderExtensions.swift */, 5A0FA82E8964599F1944D8C74AA6C575 /* UIStackViewExtensions.swift */, 682389727407FCDF01C01A67F93B02A0 /* UIStoryboardExtensions.swift */, 0CE97146B83CC335037CBFD82946B9A2 /* UISwitchExtensions.swift */, EDBC1BA1C5FFE410D416BCAEEE0617D9 /* UITabBarExtensions.swift */, 1F7797400C38392F5276C74DC40868A2 /* UITableViewExtensions.swift */, 5C67EE97F9A1E69CFCC678E30E14AF0C /* UITextFieldExtensions.swift */, 60A03B2F4D91E274E8EF0582570038AF /* UITextViewExtensions.swift */, 447AD35780D46F45EC19687011A1F899 /* UIViewControllerExtensions.swift */, 945F243DA0454AF69346579BC6424E3D /* UIViewExtensions.swift */, 7CC21ABB1AA407AA5668FC6A22835E7E /* UIWindowExtensions.swift */, BC77D85B8CBC7DC219B7C9CA14D03CD3 /* URLExtensions.swift */, 178FD5068418D4694DA165CE74F05D9E /* URLRequestExtensions.swift */, BE5FFC47499975D687F87DE19D36C6CF /* UserDefaultsExtensions.swift */, DA2D30645119FB37069C6C54A99A4265 /* AppKit */, 6FF750DE262983C50022B14283BEDC43 /* CoreAnimation */, D3ECEEF3CC51BAA80DB99EC017BA9385 /* CoreGraphics */, FF241B031FB76F0B33B95D8B48C704D7 /* CoreLocation */, 09D45F274C0BCF1F0EAF5A62E85870EE /* Dispatch */, 8949DD494B05BAFA1D8F0866B7C5E251 /* Foundation */, DC43686BA92B7EE9623B7D88377172DB /* MapKit */, FB5F0B32326AC2E3D365B7F98F0B4781 /* SceneKit */, E3F89D354A8A79220B9DD9B9A74BCCBD /* SpriteKit */, 8536424DC0FC8CCB595CC6F8400A41A4 /* StoreKit */, B6EA7C28688A8F621072B79EF1F7EBF7 /* Support Files */, 252184C9937BF04C8C175F48E142DC16 /* SwiftStdlib */, 4CDA0020315339C3E2E44135A0C38D20 /* UIKit */, ); name = SwifterSwift; path = SwifterSwift; sourceTree = ""; }; 8536424DC0FC8CCB595CC6F8400A41A4 /* StoreKit */ = { isa = PBXGroup; children = ( ); name = StoreKit; sourceTree = ""; }; 8949DD494B05BAFA1D8F0866B7C5E251 /* Foundation */ = { isa = PBXGroup; children = ( ); name = Foundation; sourceTree = ""; }; B6EA7C28688A8F621072B79EF1F7EBF7 /* Support Files */ = { isa = PBXGroup; children = ( 7F16F0AFDC2F153DB390FA481C6CA674 /* SwifterSwift.modulemap */, 4082B1964B4057753713F19AA9DA5E68 /* SwifterSwift-dummy.m */, 6F2EFB40A354412DEB57740124966124 /* SwifterSwift-Info.plist */, 372F4FBE84F0525B54F5E1289553BC79 /* SwifterSwift-prefix.pch */, BE2BF4C7CC79826758773F2D7AC81C82 /* SwifterSwift-umbrella.h */, 97D8DA6CB02ED5125505335D2CCA262A /* SwifterSwift.debug.xcconfig */, 1900A0055F0FD7FC2A2F0CD15D34DC84 /* SwifterSwift.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/SwifterSwift"; sourceTree = ""; }; D3ECEEF3CC51BAA80DB99EC017BA9385 /* CoreGraphics */ = { isa = PBXGroup; children = ( ); name = CoreGraphics; sourceTree = ""; }; DA2D30645119FB37069C6C54A99A4265 /* AppKit */ = { isa = PBXGroup; children = ( ); name = AppKit; sourceTree = ""; }; DC43686BA92B7EE9623B7D88377172DB /* MapKit */ = { isa = PBXGroup; children = ( ); name = MapKit; sourceTree = ""; }; E3F89D354A8A79220B9DD9B9A74BCCBD /* SpriteKit */ = { isa = PBXGroup; children = ( ); name = SpriteKit; sourceTree = ""; }; FB5F0B32326AC2E3D365B7F98F0B4781 /* SceneKit */ = { isa = PBXGroup; children = ( ); name = SceneKit; sourceTree = ""; }; FF241B031FB76F0B33B95D8B48C704D7 /* CoreLocation */ = { isa = PBXGroup; children = ( ); name = CoreLocation; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 0AF2C65D674CF7CF72C0A619932EC405 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( FAB79DE5A9A0DDA6110AEFE4DAC792E3 /* SwifterSwift-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 480FE5A632FE83D1C34E3D143792B43C /* SwifterSwift */ = { isa = PBXNativeTarget; buildConfigurationList = BCE753E9843F3AB9722F2FB8CF09021E /* Build configuration list for PBXNativeTarget "SwifterSwift" */; buildPhases = ( 0AF2C65D674CF7CF72C0A619932EC405 /* Headers */, 24D6BAA63EF8681DF6B37FCF19B5C97B /* Sources */, 1C2CB0681C86445FB40D30DACAD4C258 /* Frameworks */, 8D3C53FAD5927D1BE6CDCE9C32EAA6A8 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = SwifterSwift; productName = SwifterSwift; productReference = C3E7BFD79922D2545166916876E56FD5 /* SwifterSwift */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 51965990A9A124C29BB3ED953766EFC7 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = B035FDBAFC404E09999CF952AEF4D8D6 /* Build configuration list for PBXProject "SwifterSwift" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 1EA955DCC84A3AA272E92497F65A2943; productRefGroup = 4050E7EE49EB8280C661940FCC028127 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 480FE5A632FE83D1C34E3D143792B43C /* SwifterSwift */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 8D3C53FAD5927D1BE6CDCE9C32EAA6A8 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 24D6BAA63EF8681DF6B37FCF19B5C97B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 72D14DC232192E1F1CC5753A27392D6B /* ArrayExtensions.swift in Sources */, 1AE8E1AD45DDCB97CEE21FFAB7D28345 /* BidirectionalCollectionExtensions.swift in Sources */, 9491F6FC214E6BF6264DEE0D895A9E32 /* BinaryFloatingPointExtensions.swift in Sources */, 2918EE1385CEC0C8CA3951A35718AD4D /* BoolExtensions.swift in Sources */, 439531DBBD2CD9AC1084835C1AB9D863 /* CAGradientLayerExtensions.swift in Sources */, 775826A7565BF8E0DB96E8C92B94F871 /* CalendarExtensions.swift in Sources */, 95766F1D9480E13E3FDB3E1C7F06B902 /* CATransform3DExtensions.swift in Sources */, 4100B04283FBD77405F68111C2F6BCD1 /* CGAffineTransformExtensions.swift in Sources */, 218E5326F4787C9D54B26B79D5B83F48 /* CGColorExtensions.swift in Sources */, 6A31FF4F9A55302C61EF75EEFD35C92A /* CGFloatExtensions.swift in Sources */, 2321CE61C077A5A701CACCA6203F8CE3 /* CGPointExtensions.swift in Sources */, 75C339D908FCA80DEB948BC41D3B9621 /* CGRectExtensions.swift in Sources */, 13499163560520F8B4050FFEF84C0E84 /* CGSizeExtensions.swift in Sources */, AEF83650746F38895C6F7B21FF325CFB /* CGVectorExtensions.swift in Sources */, 825ABFBDE1C9DA71D6D121B5371843F8 /* CharacterExtensions.swift in Sources */, 5E9DD323AE7FFDC4E4F8901DE7F6BD6F /* CLLocationArrayExtensions.swift in Sources */, 5EBC1F1F8DBECBB3EC1AB9956B50ED8E /* CLLocationExtensions.swift in Sources */, 175DB1053FA9CE2BD9FE0D0BD0254DC3 /* CLVisitExtensions.swift in Sources */, 0F62DFAF753D55EDDD354DF4E109524D /* CollectionExtensions.swift in Sources */, 24068E23BB105AA1E2E1B1D0D38C2F05 /* ColorExtensions.swift in Sources */, A2786EEF1693912AE814A6785AD9EDBA /* ComparableExtensions.swift in Sources */, CC984A78A132F0334BC30997B0877385 /* DataExtensions.swift in Sources */, 71E2302F9518855C23FB787A2F276CA7 /* DateExtensions.swift in Sources */, 517A98D6A26F35FBB8E5212F1F0B30DE /* DecodableExtensions.swift in Sources */, E9A49B82523887E554F2C53B60C98DFE /* DictionaryExtensions.swift in Sources */, 3420881E860560553A7C4D1D830E9EFB /* DispatchQueueExtensions.swift in Sources */, 7178911787E3EC7497CF75265D735626 /* DoubleExtensions.swift in Sources */, 5B05CE019FDA65D780C88EEF1F7ADA40 /* EdgeInsetsExtensions.swift in Sources */, DC89265A7CEFBA540B83A9EE80743444 /* FileManagerExtensions.swift in Sources */, F7E6489CD2AA97160BBF965D7FCE9EAE /* FloatExtensions.swift in Sources */, EC37FDF83867FF75E6B2F8E1A3BC0F7E /* FloatingPointExtensions.swift in Sources */, 14F2F38BA898987A3C9BB42C91E2760B /* IntExtensions.swift in Sources */, E7BA717E905FB466C590F82D2678DC3F /* KeyedDecodingContainerExtensions.swift in Sources */, 07EA34BA5BE9B97AEFC21F035C491D55 /* LocaleExtensions.swift in Sources */, 795FFA64B92824C1F216D0E44C28EBF5 /* MKMapViewExtensions.swift in Sources */, 111A72E7C6FDF2180B004F6ED44AF41F /* MKPolylineExtensions.swift in Sources */, C82E1F635BE16DB13EE84170111D5B43 /* MutableCollectionExtensions.swift in Sources */, 8FEC6BD040A6B6A4B94041B2AA83FF48 /* NotificationCenterExtensions.swift in Sources */, BCFBA55AC384A970A2061406DE0E0A54 /* NSAttributedStringExtensions.swift in Sources */, 97250638352ACDCD8D1A061BCF67461D /* NSColorExtensions.swift in Sources */, D6D24DA1A68A850F427E829C9DAF6DB7 /* NSImageExtensions.swift in Sources */, 7BD7208E1E33605F855D79CCAF064BDF /* NSPredicateExtensions.swift in Sources */, 552673953BD6FC91987BF1395578A0F0 /* NSRegularExpressionExtensions.swift in Sources */, 5239AEFA621DA2C574DCA9C5C79669C7 /* NSViewExtensions.swift in Sources */, EAF800C24C4325406BED1BB76A706557 /* OptionalExtensions.swift in Sources */, 782D8FD93A4793B4B54A48A36A25E5C7 /* RandomAccessCollectionExtensions.swift in Sources */, A22BA32DFC8A4CAC3ABC62B529D73488 /* RangeReplaceableCollectionExtensions.swift in Sources */, F518834379772EE5069D62B8EB881F00 /* SCNBoxExtensions.swift in Sources */, 0AAB0088F0554CFBED8D391FDF3C3539 /* SCNCapsuleExtensions.swift in Sources */, EAB799EB9FFE5102852925AB8940564D /* SCNConeExtensions.swift in Sources */, FD8F1D2703C5DB87EA4A27F031EEF82E /* SCNCylinderExtensions.swift in Sources */, 0CBDCAC389F00121CC337E375C2AF667 /* SCNGeometryExtensions.swift in Sources */, 2FBDC589C66A74D1A8B250E9E17405A4 /* SCNMaterialExtensions.swift in Sources */, 79D687E344534317A3D139ECAA46302D /* SCNPlaneExtensions.swift in Sources */, 8121F09E6B03CF7E6F312C06A6EC748D /* SCNShapeExtensions.swift in Sources */, F6F3B08650F9E5DA1D1A905B2BF42518 /* SCNSphereExtensions.swift in Sources */, 6D556BE5CD6A49E57E62B2701F46B8BB /* SCNVector3Extensions.swift in Sources */, 932D82DEC397AF2C3581D2F3B7D2ABA3 /* SequenceExtensions.swift in Sources */, 83490435DF0BFE10E5C91885EF8AF568 /* SignedIntegerExtensions.swift in Sources */, F1CD4C012EE244450A25885BB6960831 /* SignedNumericExtensions.swift in Sources */, 92F7E733B76493867F3D07D90CA4F3DC /* SKNodeExtensions.swift in Sources */, 77FE0D8763867E669155617F0B93E297 /* SKProductExtensions.swift in Sources */, 786B4EA5A038B57A5102F8D430985D52 /* StdlibDeprecated.swift in Sources */, F683F35D64B7E7BE7910DE75644F94B0 /* StringExtensions.swift in Sources */, 8D557A151174CA1C5E5E6093CA8754C0 /* StringProtocolExtensions.swift in Sources */, 2EDFF7BCC44E324E761D16DAB43E450E /* SwifterSwift-dummy.m in Sources */, DFB980CF78B1FA065F3A5CE8D967FFB6 /* UIActivityExtensions.swift in Sources */, 4AC9316A34881CDA256F5ED70C5E4802 /* UIAlertControllerExtensions.swift in Sources */, F4801477E956ED32EAF2B2F10FCB2CC4 /* UIApplicationExtensions.swift in Sources */, D9B29DBA992652124F22B77E88BB8175 /* UIBarButtonItemExtensions.swift in Sources */, 562C37E302E7B276FEDDE4E12B8E2529 /* UIBezierPathExtensions.swift in Sources */, 5BC092B1771A2008C13B17F0094A84EC /* UIButtonExtensions.swift in Sources */, 6CCCD46A77D45150BB045DAE0403F5D0 /* UICollectionViewExtensions.swift in Sources */, 1656946797E7AAD38BDAACEA7AC8EA1F /* UIColorExtensions.swift in Sources */, DD014204D6653020B552CF7040B457D1 /* UIDatePickerExtensions.swift in Sources */, 9872F9B4D19118084E21FBCF6822E97E /* UIFontExtensions.swift in Sources */, 432E981674837E39A577E90B9F394EC9 /* UIGestureRecognizerExtensions.swift in Sources */, 0D922CC19608EB07EE21A4F253D100E3 /* UIImageExtensions.swift in Sources */, 6C4AA88C1F2E94125A694DB9FD432F96 /* UIImageViewExtensions.swift in Sources */, 22AC24E6DA3D9330E2166AEB446E5707 /* UILabelExtensions.swift in Sources */, 6BBCCADE90A67FAA3A894A43A2C71B63 /* UILayoutPriorityExtensions.swift in Sources */, E7BAFD365B0E10E46AF1FBC048EBE0AB /* UINavigationBarExtensions.swift in Sources */, 4F179B540566B805F45D879113E97A56 /* UINavigationControllerExtensions.swift in Sources */, 70243DF7F77AAEE4CF7C408643BEAD21 /* UINavigationItemExtensions.swift in Sources */, D121D7E529242400ABFD6779967B71B7 /* UIRefreshControlExtensions.swift in Sources */, EEFA37D89A0EDDC560C9C85925F6CA46 /* UIScrollViewExtensions.swift in Sources */, CF4B40ACF2D0EBABF294E857405A8731 /* UISearchBarExtensions.swift in Sources */, 96DEF1684B8029C6EDDBAD71F7DCD6EC /* UISegmentedControlExtensions.swift in Sources */, A866E7A41110C36E678B6E7E7F519BE2 /* UISliderExtensions.swift in Sources */, 3039712B6792D9194A6A707B1C997762 /* UIStackViewExtensions.swift in Sources */, 1A34A8781F259CD72C4DA9315064868C /* UIStoryboardExtensions.swift in Sources */, 4613FD2CF78609995491E56C211B68F0 /* UISwitchExtensions.swift in Sources */, 62DFCE85125759A95E824E2B7930800C /* UITabBarExtensions.swift in Sources */, FBC9A1A33320B9914F951B23B2E23498 /* UITableViewExtensions.swift in Sources */, BACECD65B0600451C280C42D23B21FB0 /* UITextFieldExtensions.swift in Sources */, 9C91D78BD8565C4C667DBD8321860470 /* UITextViewExtensions.swift in Sources */, C9B88453E903714E2E97A2D9E7E47D4E /* UIViewControllerExtensions.swift in Sources */, 93E5D4AFCF456D239A1C4D93252425E8 /* UIViewExtensions.swift in Sources */, 1C6B4D930A70308B15BB15415B06EA7F /* UIWindowExtensions.swift in Sources */, 7E8B7D216EAA941BDC5B5DD85B33CFE4 /* URLExtensions.swift in Sources */, 03EB7307E9C51C4BF3DA3E4D0C348544 /* URLRequestExtensions.swift in Sources */, 7D47491F730D6EBF3E321AB4460959D8 /* UserDefaultsExtensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 029D75D552967FAC945A9BFA2050AF98 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 97D8DA6CB02ED5125505335D2CCA262A /* SwifterSwift.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/SwifterSwift/SwifterSwift-prefix.pch"; INFOPLIST_FILE = "Target Support Files/SwifterSwift/SwifterSwift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/SwifterSwift/SwifterSwift.modulemap"; PRODUCT_MODULE_NAME = SwifterSwift; PRODUCT_NAME = SwifterSwift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.1; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 7057D08AABFD809FD326576FA97E9730 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; A497C7C53459B4B15C644F70ED20805E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; F17B13D65CDA6DB28BDAB85852B2C9AB /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 1900A0055F0FD7FC2A2F0CD15D34DC84 /* SwifterSwift.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/SwifterSwift/SwifterSwift-prefix.pch"; INFOPLIST_FILE = "Target Support Files/SwifterSwift/SwifterSwift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/SwifterSwift/SwifterSwift.modulemap"; PRODUCT_MODULE_NAME = SwifterSwift; PRODUCT_NAME = SwifterSwift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.1; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ B035FDBAFC404E09999CF952AEF4D8D6 /* Build configuration list for PBXProject "SwifterSwift" */ = { isa = XCConfigurationList; buildConfigurations = ( A497C7C53459B4B15C644F70ED20805E /* Debug */, 7057D08AABFD809FD326576FA97E9730 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BCE753E9843F3AB9722F2FB8CF09021E /* Build configuration list for PBXNativeTarget "SwifterSwift" */ = { isa = XCConfigurationList; buildConfigurations = ( 029D75D552967FAC945A9BFA2050AF98 /* Debug */, F17B13D65CDA6DB28BDAB85852B2C9AB /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 51965990A9A124C29BB3ED953766EFC7 /* Project object */; } ================================================ FILE: JetChat/Pods/SwiftyJSON/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2017 Ruoyu Fu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/SwiftyJSON/README.md ================================================ # SwiftyJSON [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) ![CocoaPods](https://img.shields.io/cocoapods/v/SwiftyJSON.svg) ![Platform](https://img.shields.io/badge/platforms-iOS%208.0%20%7C%20macOS%2010.10%20%7C%20tvOS%209.0%20%7C%20watchOS%203.0-F28D00.svg) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) SwiftyJSON makes it easy to deal with JSON data in Swift. Platform | Build Status ---------| --------------| *OS | [![Travis CI](https://travis-ci.org/SwiftyJSON/SwiftyJSON.svg?branch=master)](https://travis-ci.org/SwiftyJSON/SwiftyJSON) | [Linux](https://github.com/IBM-Swift/SwiftyJSON) | [![Build Status](https://travis-ci.org/IBM-Swift/SwiftyJSON.svg?branch=master)](https://travis-ci.org/IBM-Swift/SwiftyJSON) | 1. [Why is the typical JSON handling in Swift NOT good](#why-is-the-typical-json-handling-in-swift-not-good) 2. [Requirements](#requirements) 3. [Integration](#integration) 4. [Usage](#usage) - [Initialization](#initialization) - [Subscript](#subscript) - [Loop](#loop) - [Error](#error) - [Optional getter](#optional-getter) - [Non-optional getter](#non-optional-getter) - [Setter](#setter) - [Raw object](#raw-object) - [Literal convertibles](#literal-convertibles) - [Merging](#merging) 5. [Work with Alamofire](#work-with-alamofire) 6. [Work with Moya](#work-with-moya) 7. [SwiftyJSON Model Generator](#swiftyjson-model-generator) ## Why is the typical JSON handling in Swift NOT good? Swift is very strict about types. But although explicit typing is good for saving us from mistakes, it becomes painful when dealing with JSON and other areas that are, by nature, implicit about types. Take the Twitter API for example. Say we want to retrieve a user's "name" value of some tweet in Swift (according to [Twitter's API](https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline)). The code would look like this: ```swift if let statusesArray = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]], let user = statusesArray[0]["user"] as? [String: Any], let username = user["name"] as? String { // Finally we got the username } ``` It's not good. Even if we use optional chaining, it would be messy: ```swift if let JSONObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]], let username = (JSONObject[0]["user"] as? [String: Any])?["name"] as? String { // There's our username } ``` An unreadable mess--for something that should really be simple! With SwiftyJSON all you have to do is: ```swift let json = JSON(data: dataFromNetworking) if let userName = json[0]["user"]["name"].string { //Now you got your value } ``` And don't worry about the Optional Wrapping thing. It's done for you automatically. ```swift let json = JSON(data: dataFromNetworking) let result = json[999999]["wrong_key"]["wrong_name"] if let userName = result.string { //Calm down, take it easy, the ".string" property still produces the correct Optional String type with safety } else { //Print the error print(result.error) } ``` ## Requirements - iOS 8.0+ | macOS 10.10+ | tvOS 9.0+ | watchOS 2.0+ - Xcode 8 ## Integration #### CocoaPods (iOS 8+, OS X 10.9+) You can use [CocoaPods](http://cocoapods.org/) to install `SwiftyJSON` by adding it to your `Podfile`: ```ruby platform :ios, '8.0' use_frameworks! target 'MyApp' do pod 'SwiftyJSON', '~> 4.0' end ``` #### Carthage (iOS 8+, OS X 10.9+) You can use [Carthage](https://github.com/Carthage/Carthage) to install `SwiftyJSON` by adding it to your `Cartfile`: ``` github "SwiftyJSON/SwiftyJSON" ~> 4.0 ``` If you use Carthage to build your dependencies, make sure you have added `SwiftyJSON.framework` to the "Linked Frameworks and Libraries" section of your target, and have included them in your Carthage framework copying build phase. #### Swift Package Manager You can use [The Swift Package Manager](https://swift.org/package-manager) to install `SwiftyJSON` by adding the proper description to your `Package.swift` file: ```swift // swift-tools-version:4.0 import PackageDescription let package = Package( name: "YOUR_PROJECT_NAME", dependencies: [ .package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "4.0.0"), ] ) ``` Then run `swift build` whenever you get prepared. #### Manually (iOS 7+, OS X 10.9+) To use this library in your project manually you may: 1. for Projects, just drag SwiftyJSON.swift to the project tree 2. for Workspaces, include the whole SwiftyJSON.xcodeproj ## Usage #### Initialization ```swift import SwiftyJSON ``` ```swift let json = JSON(data: dataFromNetworking) ``` Or ```swift let json = JSON(jsonObject) ``` Or ```swift if let dataFromString = jsonString.data(using: .utf8, allowLossyConversion: false) { let json = JSON(data: dataFromString) } ``` #### Subscript ```swift // Getting a double from a JSON Array let name = json[0].double ``` ```swift // Getting an array of string from a JSON Array let arrayNames = json["users"].arrayValue.map {$0["name"].stringValue} ``` ```swift // Getting a string from a JSON Dictionary let name = json["name"].stringValue ``` ```swift // Getting a string using a path to the element let path: [JSONSubscriptType] = [1,"list",2,"name"] let name = json[path].string // Just the same let name = json[1]["list"][2]["name"].string // Alternatively let name = json[1,"list",2,"name"].string ``` ```swift // With a hard way let name = json[].string ``` ```swift // With a custom way let keys:[JSONSubscriptType] = [1,"list",2,"name"] let name = json[keys].string ``` #### Loop ```swift // If json is .Dictionary for (key,subJson):(String, JSON) in json { // Do something you want } ``` *The first element is always a String, even if the JSON is an Array* ```swift // If json is .Array // The `index` is 0.. = json["list"].arrayValue ``` ```swift // If not a Dictionary or nil, return [:] let user: Dictionary = json["user"].dictionaryValue ``` #### Setter ```swift json["name"] = JSON("new-name") json[0] = JSON(1) ``` ```swift json["id"].int = 1234567890 json["coordinate"].double = 8766.766 json["name"].string = "Jack" json.arrayObject = [1,2,3,4] json.dictionaryObject = ["name":"Jack", "age":25] ``` #### Raw object ```swift let rawObject: Any = json.object ``` ```swift let rawValue: Any = json.rawValue ``` ```swift //convert the JSON to raw NSData do { let rawData = try json.rawData() //Do something you want } catch { print("Error \(error)") } ``` ```swift //convert the JSON to a raw String if let rawString = json.rawString() { //Do something you want } else { print("json.rawString is nil") } ``` #### Existence ```swift // shows you whether value specified in JSON or not if json["name"].exists() ``` #### Literal convertibles For more info about literal convertibles: [Swift Literal Convertibles](http://nshipster.com/swift-literal-convertible/) ```swift // StringLiteralConvertible let json: JSON = "I'm a json" ``` ```swift / /IntegerLiteralConvertible let json: JSON = 12345 ``` ```swift // BooleanLiteralConvertible let json: JSON = true ``` ```swift // FloatLiteralConvertible let json: JSON = 2.8765 ``` ```swift // DictionaryLiteralConvertible let json: JSON = ["I":"am", "a":"json"] ``` ```swift // ArrayLiteralConvertible let json: JSON = ["I", "am", "a", "json"] ``` ```swift // With subscript in array var json: JSON = [1,2,3] json[0] = 100 json[1] = 200 json[2] = 300 json[999] = 300 // Don't worry, nothing will happen ``` ```swift // With subscript in dictionary var json: JSON = ["name": "Jack", "age": 25] json["name"] = "Mike" json["age"] = "25" // It's OK to set String json["address"] = "L.A." // Add the "address": "L.A." in json ``` ```swift // Array & Dictionary var json: JSON = ["name": "Jack", "age": 25, "list": ["a", "b", "c", ["what": "this"]]] json["list"][3]["what"] = "that" json["list",3,"what"] = "that" let path: [JSONSubscriptType] = ["list",3,"what"] json[path] = "that" ``` ```swift // With other JSON objects let user: JSON = ["username" : "Steve", "password": "supersecurepassword"] let auth: JSON = [ "user": user.object, // use user.object instead of just user "apikey": "supersecretapitoken" ] ``` #### Merging It is possible to merge one JSON into another JSON. Merging a JSON into another JSON adds all non existing values to the original JSON which are only present in the `other` JSON. If both JSONs contain a value for the same key, _mostly_ this value gets overwritten in the original JSON, but there are two cases where it provides some special treatment: - In case of both values being a `JSON.Type.array` the values form the array found in the `other` JSON getting appended to the original JSON's array value. - In case of both values being a `JSON.Type.dictionary` both JSON-values are getting merged the same way the encapsulating JSON is merged. In a case where two fields in a JSON have different types, the value will get always overwritten. There are two different fashions for merging: `merge` modifies the original JSON, whereas `merged` works non-destructively on a copy. ```swift let original: JSON = [ "first_name": "John", "age": 20, "skills": ["Coding", "Reading"], "address": [ "street": "Front St", "zip": "12345", ] ] let update: JSON = [ "last_name": "Doe", "age": 21, "skills": ["Writing"], "address": [ "zip": "12342", "city": "New York City" ] ] let updated = original.merge(with: update) // [ // "first_name": "John", // "last_name": "Doe", // "age": 21, // "skills": ["Coding", "Reading", "Writing"], // "address": [ // "street": "Front St", // "zip": "12342", // "city": "New York City" // ] // ] ``` ## String representation There are two options available: - use the default Swift one - use a custom one that will handle optionals well and represent `nil` as `"null"`: ```swift let dict = ["1":2, "2":"two", "3": nil] as [String: Any?] let json = JSON(dict) let representation = json.rawString(options: [.castNilToNSNull: true]) // representation is "{\"1\":2,\"2\":\"two\",\"3\":null}", which represents {"1":2,"2":"two","3":null} ``` ## Work with [Alamofire](https://github.com/Alamofire/Alamofire) SwiftyJSON nicely wraps the result of the Alamofire JSON response handler: ```swift Alamofire.request(url, method: .get).validate().responseJSON { response in switch response.result { case .success(let value): let json = JSON(value) print("JSON: \(json)") case .failure(let error): print(error) } } ``` We also provide an extension of Alamofire for serializing NSData to SwiftyJSON's JSON. See: [Alamofire-SwiftyJSON](https://github.com/SwiftyJSON/Alamofire-SwiftyJSON) ## Work with [Moya](https://github.com/Moya/Moya) SwiftyJSON parse data to JSON: ```swift let provider = MoyaProvider() provider.request(.showProducts) { result in switch result { case let .success(moyaResponse): let data = moyaResponse.data let json = JSON(data: data) // convert network data to json print(json) case let .failure(error): print("error: \(error)") } } ``` ## SwiftyJSON Model Generator Tools to generate SwiftyJSON Models * [JSON Cafe](http://www.jsoncafe.com/) * [JSON Export](https://github.com/Ahmed-Ali/JSONExport) ================================================ FILE: JetChat/Pods/SwiftyJSON/Source/SwiftyJSON/SwiftyJSON.swift ================================================ // SwiftyJSON.swift // // Copyright (c) 2014 - 2017 Ruoyu Fu, Pinglin Tang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation // MARK: - Error // swiftlint:disable line_length public enum SwiftyJSONError: Int, Swift.Error { case unsupportedType = 999 case indexOutOfBounds = 900 case elementTooDeep = 902 case wrongType = 901 case notExist = 500 case invalidJSON = 490 } extension SwiftyJSONError: CustomNSError { /// return the error domain of SwiftyJSONError public static var errorDomain: String { return "com.swiftyjson.SwiftyJSON" } /// return the error code of SwiftyJSONError public var errorCode: Int { return self.rawValue } /// return the userInfo of SwiftyJSONError public var errorUserInfo: [String: Any] { switch self { case .unsupportedType: return [NSLocalizedDescriptionKey: "It is an unsupported type."] case .indexOutOfBounds: return [NSLocalizedDescriptionKey: "Array Index is out of bounds."] case .wrongType: return [NSLocalizedDescriptionKey: "Couldn't merge, because the JSONs differ in type on top level."] case .notExist: return [NSLocalizedDescriptionKey: "Dictionary key does not exist."] case .invalidJSON: return [NSLocalizedDescriptionKey: "JSON is invalid."] case .elementTooDeep: return [NSLocalizedDescriptionKey: "Element too deep. Increase maxObjectDepth and make sure there is no reference loop."] } } } // MARK: - JSON Type /** JSON's type definitions. See http://www.json.org */ public enum Type: Int { case number case string case bool case array case dictionary case null case unknown } // MARK: - JSON Base public struct JSON { /** Creates a JSON using the data. - parameter data: The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary - parameter opt: The JSON serialization reading options. `[]` by default. - returns: The created JSON */ public init(data: Data, options opt: JSONSerialization.ReadingOptions = []) throws { let object: Any = try JSONSerialization.jsonObject(with: data, options: opt) self.init(jsonObject: object) } /** Creates a JSON object - note: this does not parse a `String` into JSON, instead use `init(parseJSON: String)` - parameter object: the object - returns: the created JSON object */ public init(_ object: Any) { switch object { case let object as Data: do { try self.init(data: object) } catch { self.init(jsonObject: NSNull()) } default: self.init(jsonObject: object) } } /** Parses the JSON string into a JSON object - parameter json: the JSON string - returns: the created JSON object */ public init(parseJSON jsonString: String) { if let data = jsonString.data(using: .utf8) { self.init(data) } else { self.init(NSNull()) } } /** Creates a JSON using the object. - parameter jsonObject: The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity. - returns: The created JSON */ fileprivate init(jsonObject: Any) { object = jsonObject } /** Merges another JSON into this JSON, whereas primitive values which are not present in this JSON are getting added, present values getting overwritten, array values getting appended and nested JSONs getting merged the same way. - parameter other: The JSON which gets merged into this JSON - throws `ErrorWrongType` if the other JSONs differs in type on the top level. */ public mutating func merge(with other: JSON) throws { try self.merge(with: other, typecheck: true) } /** Merges another JSON into this JSON and returns a new JSON, whereas primitive values which are not present in this JSON are getting added, present values getting overwritten, array values getting appended and nested JSONS getting merged the same way. - parameter other: The JSON which gets merged into this JSON - throws `ErrorWrongType` if the other JSONs differs in type on the top level. - returns: New merged JSON */ public func merged(with other: JSON) throws -> JSON { var merged = self try merged.merge(with: other, typecheck: true) return merged } /** Private woker function which does the actual merging Typecheck is set to true for the first recursion level to prevent total override of the source JSON */ fileprivate mutating func merge(with other: JSON, typecheck: Bool) throws { if type == other.type { switch type { case .dictionary: for (key, _) in other { try self[key].merge(with: other[key], typecheck: false) } case .array: self = JSON(arrayValue + other.arrayValue) default: self = other } } else { if typecheck { throw SwiftyJSONError.wrongType } else { self = other } } } /// Private object fileprivate var rawArray: [Any] = [] fileprivate var rawDictionary: [String: Any] = [:] fileprivate var rawString: String = "" fileprivate var rawNumber: NSNumber = 0 fileprivate var rawNull: NSNull = NSNull() fileprivate var rawBool: Bool = false /// JSON type, fileprivate setter public fileprivate(set) var type: Type = .null /// Error in JSON, fileprivate setter public fileprivate(set) var error: SwiftyJSONError? /// Object in JSON public var object: Any { get { switch type { case .array: return rawArray case .dictionary: return rawDictionary case .string: return rawString case .number: return rawNumber case .bool: return rawBool default: return rawNull } } set { error = nil switch unwrap(newValue) { case let number as NSNumber: if number.isBool { type = .bool rawBool = number.boolValue } else { type = .number rawNumber = number } case let string as String: type = .string rawString = string case _ as NSNull: type = .null case nil: type = .null case let array as [Any]: type = .array rawArray = array case let dictionary as [String: Any]: type = .dictionary rawDictionary = dictionary default: type = .unknown error = SwiftyJSONError.unsupportedType } } } /// The static null JSON @available(*, unavailable, renamed:"null") public static var nullJSON: JSON { return null } public static var null: JSON { return JSON(NSNull()) } } /// Private method to unwarp an object recursively private func unwrap(_ object: Any) -> Any { switch object { case let json as JSON: return unwrap(json.object) case let array as [Any]: return array.map(unwrap) case let dictionary as [String: Any]: var d = dictionary dictionary.forEach { pair in d[pair.key] = unwrap(pair.value) } return d default: return object } } public enum Index: Comparable { case array(Int) case dictionary(DictionaryIndex) case null static public func == (lhs: Index, rhs: Index) -> Bool { switch (lhs, rhs) { case (.array(let left), .array(let right)): return left == right case (.dictionary(let left), .dictionary(let right)): return left == right case (.null, .null): return true default: return false } } static public func < (lhs: Index, rhs: Index) -> Bool { switch (lhs, rhs) { case (.array(let left), .array(let right)): return left < right case (.dictionary(let left), .dictionary(let right)): return left < right default: return false } } } public typealias JSONIndex = Index public typealias JSONRawIndex = Index extension JSON: Swift.Collection { public typealias Index = JSONRawIndex public var startIndex: Index { switch type { case .array: return .array(rawArray.startIndex) case .dictionary: return .dictionary(rawDictionary.startIndex) default: return .null } } public var endIndex: Index { switch type { case .array: return .array(rawArray.endIndex) case .dictionary: return .dictionary(rawDictionary.endIndex) default: return .null } } public func index(after i: Index) -> Index { switch i { case .array(let idx): return .array(rawArray.index(after: idx)) case .dictionary(let idx): return .dictionary(rawDictionary.index(after: idx)) default: return .null } } public subscript (position: Index) -> (String, JSON) { switch position { case .array(let idx): return (String(idx), JSON(rawArray[idx])) case .dictionary(let idx): return (rawDictionary[idx].key, JSON(rawDictionary[idx].value)) default: return ("", JSON.null) } } } // MARK: - Subscript /** * To mark both String and Int can be used in subscript. */ public enum JSONKey { case index(Int) case key(String) } public protocol JSONSubscriptType { var jsonKey: JSONKey { get } } extension Int: JSONSubscriptType { public var jsonKey: JSONKey { return JSONKey.index(self) } } extension String: JSONSubscriptType { public var jsonKey: JSONKey { return JSONKey.key(self) } } extension JSON { /// If `type` is `.array`, return json whose object is `array[index]`, otherwise return null json with error. fileprivate subscript(index index: Int) -> JSON { get { if type != .array { var r = JSON.null r.error = self.error ?? SwiftyJSONError.wrongType return r } else if rawArray.indices.contains(index) { return JSON(rawArray[index]) } else { var r = JSON.null r.error = SwiftyJSONError.indexOutOfBounds return r } } set { if type == .array && rawArray.indices.contains(index) && newValue.error == nil { rawArray[index] = newValue.object } } } /// If `type` is `.dictionary`, return json whose object is `dictionary[key]` , otherwise return null json with error. fileprivate subscript(key key: String) -> JSON { get { var r = JSON.null if type == .dictionary { if let o = rawDictionary[key] { r = JSON(o) } else { r.error = SwiftyJSONError.notExist } } else { r.error = self.error ?? SwiftyJSONError.wrongType } return r } set { if type == .dictionary && newValue.error == nil { rawDictionary[key] = newValue.object } } } /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`, return `subscript(key:)`. fileprivate subscript(sub sub: JSONSubscriptType) -> JSON { get { switch sub.jsonKey { case .index(let index): return self[index: index] case .key(let key): return self[key: key] } } set { switch sub.jsonKey { case .index(let index): self[index: index] = newValue case .key(let key): self[key: key] = newValue } } } /** Find a json in the complex data structures by using array of Int and/or String as path. Example: ``` let json = JSON[data] let path = [9,"list","person","name"] let name = json[path] ``` The same as: let name = json[9]["list"]["person"]["name"] - parameter path: The target json's path. - returns: Return a json found by the path or a null json with error */ public subscript(path: [JSONSubscriptType]) -> JSON { get { return path.reduce(self) { $0[sub: $1] } } set { switch path.count { case 0: return case 1: self[sub:path[0]].object = newValue.object default: var aPath = path aPath.remove(at: 0) var nextJSON = self[sub: path[0]] nextJSON[aPath] = newValue self[sub: path[0]] = nextJSON } } } /** Find a json in the complex data structures by using array of Int and/or String as path. - parameter path: The target json's path. Example: let name = json[9,"list","person","name"] The same as: let name = json[9]["list"]["person"]["name"] - returns: Return a json found by the path or a null json with error */ public subscript(path: JSONSubscriptType...) -> JSON { get { return self[path] } set { self[path] = newValue } } } // MARK: - LiteralConvertible extension JSON: Swift.ExpressibleByStringLiteral { public init(stringLiteral value: StringLiteralType) { self.init(value) } public init(extendedGraphemeClusterLiteral value: StringLiteralType) { self.init(value) } public init(unicodeScalarLiteral value: StringLiteralType) { self.init(value) } } extension JSON: Swift.ExpressibleByIntegerLiteral { public init(integerLiteral value: IntegerLiteralType) { self.init(value) } } extension JSON: Swift.ExpressibleByBooleanLiteral { public init(booleanLiteral value: BooleanLiteralType) { self.init(value) } } extension JSON: Swift.ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { self.init(value) } } extension JSON: Swift.ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (String, Any)...) { let dictionary = elements.reduce(into: [String: Any](), { $0[$1.0] = $1.1}) self.init(dictionary) } } extension JSON: Swift.ExpressibleByArrayLiteral { public init(arrayLiteral elements: Any...) { self.init(elements) } } // MARK: - Raw extension JSON: Swift.RawRepresentable { public init?(rawValue: Any) { if JSON(rawValue).type == .unknown { return nil } else { self.init(rawValue) } } public var rawValue: Any { return object } public func rawData(options opt: JSONSerialization.WritingOptions = JSONSerialization.WritingOptions(rawValue: 0)) throws -> Data { guard JSONSerialization.isValidJSONObject(object) else { throw SwiftyJSONError.invalidJSON } return try JSONSerialization.data(withJSONObject: object, options: opt) } public func rawString(_ encoding: String.Encoding = .utf8, options opt: JSONSerialization.WritingOptions = .prettyPrinted) -> String? { do { return try _rawString(encoding, options: [.jsonSerialization: opt]) } catch { print("Could not serialize object to JSON because:", error.localizedDescription) return nil } } public func rawString(_ options: [writingOptionsKeys: Any]) -> String? { let encoding = options[.encoding] as? String.Encoding ?? String.Encoding.utf8 let maxObjectDepth = options[.maxObjextDepth] as? Int ?? 10 do { return try _rawString(encoding, options: options, maxObjectDepth: maxObjectDepth) } catch { print("Could not serialize object to JSON because:", error.localizedDescription) return nil } } fileprivate func _rawString(_ encoding: String.Encoding = .utf8, options: [writingOptionsKeys: Any], maxObjectDepth: Int = 10) throws -> String? { guard maxObjectDepth > 0 else { throw SwiftyJSONError.invalidJSON } switch type { case .dictionary: do { if !(options[.castNilToNSNull] as? Bool ?? false) { let jsonOption = options[.jsonSerialization] as? JSONSerialization.WritingOptions ?? JSONSerialization.WritingOptions.prettyPrinted let data = try rawData(options: jsonOption) return String(data: data, encoding: encoding) } guard let dict = object as? [String: Any?] else { return nil } let body = try dict.keys.map { key throws -> String in guard let value = dict[key] else { return "\"\(key)\": null" } guard let unwrappedValue = value else { return "\"\(key)\": null" } let nestedValue = JSON(unwrappedValue) guard let nestedString = try nestedValue._rawString(encoding, options: options, maxObjectDepth: maxObjectDepth - 1) else { throw SwiftyJSONError.elementTooDeep } if nestedValue.type == .string { return "\"\(key)\": \"\(nestedString.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\""))\"" } else { return "\"\(key)\": \(nestedString)" } } return "{\(body.joined(separator: ","))}" } catch _ { return nil } case .array: do { if !(options[.castNilToNSNull] as? Bool ?? false) { let jsonOption = options[.jsonSerialization] as? JSONSerialization.WritingOptions ?? JSONSerialization.WritingOptions.prettyPrinted let data = try rawData(options: jsonOption) return String(data: data, encoding: encoding) } guard let array = object as? [Any?] else { return nil } let body = try array.map { value throws -> String in guard let unwrappedValue = value else { return "null" } let nestedValue = JSON(unwrappedValue) guard let nestedString = try nestedValue._rawString(encoding, options: options, maxObjectDepth: maxObjectDepth - 1) else { throw SwiftyJSONError.invalidJSON } if nestedValue.type == .string { return "\"\(nestedString.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\""))\"" } else { return nestedString } } return "[\(body.joined(separator: ","))]" } catch _ { return nil } case .string: return rawString case .number: return rawNumber.stringValue case .bool: return rawBool.description case .null: return "null" default: return nil } } } // MARK: - Printable, DebugPrintable extension JSON: Swift.CustomStringConvertible, Swift.CustomDebugStringConvertible { public var description: String { return rawString(options: .prettyPrinted) ?? "unknown" } public var debugDescription: String { return description } } // MARK: - Array extension JSON { //Optional [JSON] public var array: [JSON]? { return type == .array ? rawArray.map { JSON($0) } : nil } //Non-optional [JSON] public var arrayValue: [JSON] { return self.array ?? [] } //Optional [Any] public var arrayObject: [Any]? { get { switch type { case .array: return rawArray default: return nil } } set { self.object = newValue ?? NSNull() } } } // MARK: - Dictionary extension JSON { //Optional [String : JSON] public var dictionary: [String: JSON]? { if type == .dictionary { var d = [String: JSON](minimumCapacity: rawDictionary.count) rawDictionary.forEach { pair in d[pair.key] = JSON(pair.value) } return d } else { return nil } } //Non-optional [String : JSON] public var dictionaryValue: [String: JSON] { return dictionary ?? [:] } //Optional [String : Any] public var dictionaryObject: [String: Any]? { get { switch type { case .dictionary: return rawDictionary default: return nil } } set { object = newValue ?? NSNull() } } } // MARK: - Bool extension JSON { // : Swift.Bool //Optional bool public var bool: Bool? { get { switch type { case .bool: return rawBool default: return nil } } set { object = newValue ?? NSNull() } } //Non-optional bool public var boolValue: Bool { get { switch type { case .bool: return rawBool case .number: return rawNumber.boolValue case .string: return ["true", "y", "t", "yes", "1"].contains { rawString.caseInsensitiveCompare($0) == .orderedSame } default: return false } } set { object = newValue } } } // MARK: - String extension JSON { //Optional string public var string: String? { get { switch type { case .string: return object as? String default: return nil } } set { object = newValue ?? NSNull() } } //Non-optional string public var stringValue: String { get { switch type { case .string: return object as? String ?? "" case .number: return rawNumber.stringValue case .bool: return (object as? Bool).map { String($0) } ?? "" default: return "" } } set { object = newValue } } } // MARK: - Number extension JSON { //Optional number public var number: NSNumber? { get { switch type { case .number: return rawNumber case .bool: return NSNumber(value: rawBool ? 1 : 0) default: return nil } } set { object = newValue ?? NSNull() } } //Non-optional number public var numberValue: NSNumber { get { switch type { case .string: let decimal = NSDecimalNumber(string: object as? String) return decimal == .notANumber ? .zero : decimal case .number: return object as? NSNumber ?? NSNumber(value: 0) case .bool: return NSNumber(value: rawBool ? 1 : 0) default: return NSNumber(value: 0.0) } } set { object = newValue } } } // MARK: - Null extension JSON { public var null: NSNull? { set { object = NSNull() } get { switch type { case .null: return rawNull default: return nil } } } public func exists() -> Bool { if let errorValue = error, (400...1000).contains(errorValue.errorCode) { return false } return true } } // MARK: - URL extension JSON { //Optional URL public var url: URL? { get { switch type { case .string: // Check for existing percent escapes first to prevent double-escaping of % character if rawString.range(of: "%[0-9A-Fa-f]{2}", options: .regularExpression, range: nil, locale: nil) != nil { return Foundation.URL(string: rawString) } else if let encodedString_ = rawString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) { // We have to use `Foundation.URL` otherwise it conflicts with the variable name. return Foundation.URL(string: encodedString_) } else { return nil } default: return nil } } set { object = newValue?.absoluteString ?? NSNull() } } } // MARK: - Int, Double, Float, Int8, Int16, Int32, Int64 extension JSON { public var double: Double? { get { return number?.doubleValue } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var doubleValue: Double { get { return numberValue.doubleValue } set { object = NSNumber(value: newValue) } } public var float: Float? { get { return number?.floatValue } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var floatValue: Float { get { return numberValue.floatValue } set { object = NSNumber(value: newValue) } } public var int: Int? { get { return number?.intValue } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var intValue: Int { get { return numberValue.intValue } set { object = NSNumber(value: newValue) } } public var uInt: UInt? { get { return number?.uintValue } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var uIntValue: UInt { get { return numberValue.uintValue } set { object = NSNumber(value: newValue) } } public var int8: Int8? { get { return number?.int8Value } set { if let newValue = newValue { object = NSNumber(value: Int(newValue)) } else { object = NSNull() } } } public var int8Value: Int8 { get { return numberValue.int8Value } set { object = NSNumber(value: Int(newValue)) } } public var uInt8: UInt8? { get { return number?.uint8Value } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var uInt8Value: UInt8 { get { return numberValue.uint8Value } set { object = NSNumber(value: newValue) } } public var int16: Int16? { get { return number?.int16Value } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var int16Value: Int16 { get { return numberValue.int16Value } set { object = NSNumber(value: newValue) } } public var uInt16: UInt16? { get { return number?.uint16Value } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var uInt16Value: UInt16 { get { return numberValue.uint16Value } set { object = NSNumber(value: newValue) } } public var int32: Int32? { get { return number?.int32Value } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var int32Value: Int32 { get { return numberValue.int32Value } set { object = NSNumber(value: newValue) } } public var uInt32: UInt32? { get { return number?.uint32Value } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var uInt32Value: UInt32 { get { return numberValue.uint32Value } set { object = NSNumber(value: newValue) } } public var int64: Int64? { get { return number?.int64Value } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var int64Value: Int64 { get { return numberValue.int64Value } set { object = NSNumber(value: newValue) } } public var uInt64: UInt64? { get { return number?.uint64Value } set { if let newValue = newValue { object = NSNumber(value: newValue) } else { object = NSNull() } } } public var uInt64Value: UInt64 { get { return numberValue.uint64Value } set { object = NSNumber(value: newValue) } } } // MARK: - Comparable extension JSON: Swift.Comparable {} public func == (lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { case (.number, .number): return lhs.rawNumber == rhs.rawNumber case (.string, .string): return lhs.rawString == rhs.rawString case (.bool, .bool): return lhs.rawBool == rhs.rawBool case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary case (.null, .null): return true default: return false } } public func <= (lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { case (.number, .number): return lhs.rawNumber <= rhs.rawNumber case (.string, .string): return lhs.rawString <= rhs.rawString case (.bool, .bool): return lhs.rawBool == rhs.rawBool case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary case (.null, .null): return true default: return false } } public func >= (lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { case (.number, .number): return lhs.rawNumber >= rhs.rawNumber case (.string, .string): return lhs.rawString >= rhs.rawString case (.bool, .bool): return lhs.rawBool == rhs.rawBool case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary case (.null, .null): return true default: return false } } public func > (lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { case (.number, .number): return lhs.rawNumber > rhs.rawNumber case (.string, .string): return lhs.rawString > rhs.rawString default: return false } } public func < (lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { case (.number, .number): return lhs.rawNumber < rhs.rawNumber case (.string, .string): return lhs.rawString < rhs.rawString default: return false } } private let trueNumber = NSNumber(value: true) private let falseNumber = NSNumber(value: false) private let trueObjCType = String(cString: trueNumber.objCType) private let falseObjCType = String(cString: falseNumber.objCType) // MARK: - NSNumber: Comparable extension NSNumber { fileprivate var isBool: Bool { let objCType = String(cString: self.objCType) if (self.compare(trueNumber) == .orderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == .orderedSame && objCType == falseObjCType) { return true } else { return false } } } func == (lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: return lhs.compare(rhs) == .orderedSame } } func != (lhs: NSNumber, rhs: NSNumber) -> Bool { return !(lhs == rhs) } func < (lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: return lhs.compare(rhs) == .orderedAscending } } func > (lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: return lhs.compare(rhs) == ComparisonResult.orderedDescending } } func <= (lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: return lhs.compare(rhs) != .orderedDescending } } func >= (lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: return lhs.compare(rhs) != .orderedAscending } } public enum writingOptionsKeys { case jsonSerialization case castNilToNSNull case maxObjextDepth case encoding } // MARK: - JSON: Codable extension JSON: Codable { private static var codableTypes: [Codable.Type] { return [ Bool.self, Int.self, Int8.self, Int16.self, Int32.self, Int64.self, UInt.self, UInt8.self, UInt16.self, UInt32.self, UInt64.self, Double.self, String.self, [JSON].self, [String: JSON].self ] } public init(from decoder: Decoder) throws { var object: Any? if let container = try? decoder.singleValueContainer(), !container.decodeNil() { for type in JSON.codableTypes { if object != nil { break } // try to decode value switch type { case let boolType as Bool.Type: object = try? container.decode(boolType) case let intType as Int.Type: object = try? container.decode(intType) case let int8Type as Int8.Type: object = try? container.decode(int8Type) case let int32Type as Int32.Type: object = try? container.decode(int32Type) case let int64Type as Int64.Type: object = try? container.decode(int64Type) case let uintType as UInt.Type: object = try? container.decode(uintType) case let uint8Type as UInt8.Type: object = try? container.decode(uint8Type) case let uint16Type as UInt16.Type: object = try? container.decode(uint16Type) case let uint32Type as UInt32.Type: object = try? container.decode(uint32Type) case let uint64Type as UInt64.Type: object = try? container.decode(uint64Type) case let doubleType as Double.Type: object = try? container.decode(doubleType) case let stringType as String.Type: object = try? container.decode(stringType) case let jsonValueArrayType as [JSON].Type: object = try? container.decode(jsonValueArrayType) case let jsonValueDictType as [String: JSON].Type: object = try? container.decode(jsonValueDictType) default: break } } } self.init(object ?? NSNull()) } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() if object is NSNull { try container.encodeNil() return } switch object { case let intValue as Int: try container.encode(intValue) case let int8Value as Int8: try container.encode(int8Value) case let int32Value as Int32: try container.encode(int32Value) case let int64Value as Int64: try container.encode(int64Value) case let uintValue as UInt: try container.encode(uintValue) case let uint8Value as UInt8: try container.encode(uint8Value) case let uint16Value as UInt16: try container.encode(uint16Value) case let uint32Value as UInt32: try container.encode(uint32Value) case let uint64Value as UInt64: try container.encode(uint64Value) case let doubleValue as Double: try container.encode(doubleValue) case let boolValue as Bool: try container.encode(boolValue) case let stringValue as String: try container.encode(stringValue) case is [Any]: let jsonValueArray = array ?? [] try container.encode(jsonValueArray) case is [String: Any]: let jsonValueDictValue = dictionary ?? [:] try container.encode(jsonValueDictValue) default: break } } } ================================================ FILE: JetChat/Pods/SwiftyJSON.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 1DC122E6EA910518191F7C60A64CC24D /* SwiftyJSON-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D2FC50497B601DC18B15EF1B5E059AC /* SwiftyJSON-dummy.m */; }; A49D78D0C7155041B4EFE03A63612EB4 /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B0484E03EF57BE6DA621E12219052A /* SwiftyJSON.swift */; }; D22FBB1AC007CBA495A771771AB7AEB9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76B871EE76375656181F36A66DF40085 /* Foundation.framework */; }; FA19CE8ACED0B95E58C2EB90752C2D73 /* SwiftyJSON-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 23DC0335E1CC01DA864D845DF1AD1003 /* SwiftyJSON-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0779F1807A202C6934362511AA0CE9A3 /* SwiftyJSON-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftyJSON-prefix.pch"; sourceTree = ""; }; 07B0484E03EF57BE6DA621E12219052A /* SwiftyJSON.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwiftyJSON.swift; path = Source/SwiftyJSON/SwiftyJSON.swift; sourceTree = ""; }; 1D2FC50497B601DC18B15EF1B5E059AC /* SwiftyJSON-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SwiftyJSON-dummy.m"; sourceTree = ""; }; 23DC0335E1CC01DA864D845DF1AD1003 /* SwiftyJSON-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftyJSON-umbrella.h"; sourceTree = ""; }; 75D5393B2B0C68CFE213C6E93D028CFE /* SwiftyJSON.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SwiftyJSON.modulemap; sourceTree = ""; }; 76B871EE76375656181F36A66DF40085 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 89F94BFC621046BB1741819EE8325060 /* SwiftyJSON.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftyJSON.debug.xcconfig; sourceTree = ""; }; 8B9A69E0E7C5FAA1B47161BCDF63D1D3 /* SwiftyJSON-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SwiftyJSON-Info.plist"; sourceTree = ""; }; BC01D1DC392A1FBD20641873F9903BA4 /* SwiftyJSON.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftyJSON.release.xcconfig; sourceTree = ""; }; D113BAD0A911BB872D877DA5A82A9F23 /* SwiftyJSON */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SwiftyJSON; path = SwiftyJSON.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 7E81505D45720E752CA03138892E7970 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( D22FBB1AC007CBA495A771771AB7AEB9 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2E4205A4E476FF7F192C877088D2B328 /* Frameworks */ = { isa = PBXGroup; children = ( 5FD9281F280F86AF8DCEE22D3075A7FE /* iOS */, ); name = Frameworks; sourceTree = ""; }; 53315E374B189C6B1AA087A686126F44 /* SwiftyJSON */ = { isa = PBXGroup; children = ( 07B0484E03EF57BE6DA621E12219052A /* SwiftyJSON.swift */, A2C21521217971D18429BF050CE88000 /* Support Files */, ); name = SwiftyJSON; path = SwiftyJSON; sourceTree = ""; }; 5FD9281F280F86AF8DCEE22D3075A7FE /* iOS */ = { isa = PBXGroup; children = ( 76B871EE76375656181F36A66DF40085 /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; 6DF89A472492583C809C1BEEF12C28D2 /* Products */ = { isa = PBXGroup; children = ( D113BAD0A911BB872D877DA5A82A9F23 /* SwiftyJSON */, ); name = Products; sourceTree = ""; }; A2C21521217971D18429BF050CE88000 /* Support Files */ = { isa = PBXGroup; children = ( 75D5393B2B0C68CFE213C6E93D028CFE /* SwiftyJSON.modulemap */, 1D2FC50497B601DC18B15EF1B5E059AC /* SwiftyJSON-dummy.m */, 8B9A69E0E7C5FAA1B47161BCDF63D1D3 /* SwiftyJSON-Info.plist */, 0779F1807A202C6934362511AA0CE9A3 /* SwiftyJSON-prefix.pch */, 23DC0335E1CC01DA864D845DF1AD1003 /* SwiftyJSON-umbrella.h */, 89F94BFC621046BB1741819EE8325060 /* SwiftyJSON.debug.xcconfig */, BC01D1DC392A1FBD20641873F9903BA4 /* SwiftyJSON.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/SwiftyJSON"; sourceTree = ""; }; C060B5DF9B5B402B03A451ED373E8F1A = { isa = PBXGroup; children = ( 2E4205A4E476FF7F192C877088D2B328 /* Frameworks */, 6DF89A472492583C809C1BEEF12C28D2 /* Products */, 53315E374B189C6B1AA087A686126F44 /* SwiftyJSON */, ); sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ F4F057E65015180BA5ABC883DE4FAAF7 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( FA19CE8ACED0B95E58C2EB90752C2D73 /* SwiftyJSON-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ AD1A79042DA54FF6DCBF6C015B0E27B5 /* SwiftyJSON */ = { isa = PBXNativeTarget; buildConfigurationList = F709B28CE1A018B2C01C271087806C66 /* Build configuration list for PBXNativeTarget "SwiftyJSON" */; buildPhases = ( F4F057E65015180BA5ABC883DE4FAAF7 /* Headers */, A56DA4B355CF0120E812BEC67A8975B2 /* Sources */, 7E81505D45720E752CA03138892E7970 /* Frameworks */, 8F5B5CCF2454979FD6BA8E96AA7FF9DF /* Resources */, ); buildRules = ( ); dependencies = ( ); name = SwiftyJSON; productName = SwiftyJSON; productReference = D113BAD0A911BB872D877DA5A82A9F23 /* SwiftyJSON */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 4911A93E632B377693AB135E11CC6112 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 9D09FDFF40E526AE302FDF8D4C13AE06 /* Build configuration list for PBXProject "SwiftyJSON" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = C060B5DF9B5B402B03A451ED373E8F1A; productRefGroup = 6DF89A472492583C809C1BEEF12C28D2 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( AD1A79042DA54FF6DCBF6C015B0E27B5 /* SwiftyJSON */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 8F5B5CCF2454979FD6BA8E96AA7FF9DF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ A56DA4B355CF0120E812BEC67A8975B2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A49D78D0C7155041B4EFE03A63612EB4 /* SwiftyJSON.swift in Sources */, 1DC122E6EA910518191F7C60A64CC24D /* SwiftyJSON-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 1D34A531A96A8F0AB7D3150034D83B44 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 89F94BFC621046BB1741819EE8325060 /* SwiftyJSON.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch"; INFOPLIST_FILE = "Target Support Files/SwiftyJSON/SwiftyJSON-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/SwiftyJSON/SwiftyJSON.modulemap"; PRODUCT_MODULE_NAME = SwiftyJSON; PRODUCT_NAME = SwiftyJSON; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; A056161DAC820D6B05189BF1FB318B10 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; C395E8883E724DFB1D78AEC39A5329A8 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; EE6A7EB8D80D03E8E88A660EE55963FC /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = BC01D1DC392A1FBD20641873F9903BA4 /* SwiftyJSON.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch"; INFOPLIST_FILE = "Target Support Files/SwiftyJSON/SwiftyJSON-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/SwiftyJSON/SwiftyJSON.modulemap"; PRODUCT_MODULE_NAME = SwiftyJSON; PRODUCT_NAME = SwiftyJSON; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 9D09FDFF40E526AE302FDF8D4C13AE06 /* Build configuration list for PBXProject "SwiftyJSON" */ = { isa = XCConfigurationList; buildConfigurations = ( A056161DAC820D6B05189BF1FB318B10 /* Debug */, C395E8883E724DFB1D78AEC39A5329A8 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F709B28CE1A018B2C01C271087806C66 /* Build configuration list for PBXNativeTarget "SwiftyJSON" */ = { isa = XCConfigurationList; buildConfigurations = ( 1D34A531A96A8F0AB7D3150034D83B44 /* Debug */, EE6A7EB8D80D03E8E88A660EE55963FC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 4911A93E632B377693AB135E11CC6112 /* Project object */; } ================================================ FILE: JetChat/Pods/TZImagePickerController/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 Zhen Tan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/TZImagePickerController/README.md ================================================ # TZImagePickerController [![CocoaPods](https://img.shields.io/cocoapods/v/TZImagePickerController.svg?style=flat)](https://github.com/banchichen/TZImagePickerController) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) A clone of UIImagePickerController, support picking multiple photos、original photo、video, also allow preview photo and video, support iOS6+. 一个支持多选、选原图和视频的图片选择器,同时有预览功能,支持iOS6+。 ## 重要提示1:提issue前,请先对照Demo、常见问题自查!Demo正常说明你可以升级下新版试试。 ## 重要提示2:3.7.5版本修复了iOS15.2下初次授权相册权限时的长时间卡顿&白屏问题,强烈建议尽快更新 关于iOS14模拟器的问题 PHAuthorizationStatusLimited授权模式下,iOS14模拟器有bug,未授权照片无法显示,真机正常,暂可忽略:https://github.com/banchichen/TZImagePickerController/issues/1347 关于升级iOS10和Xcdoe8的提示: 在Xcode8环境下将项目运行在iOS10的设备/模拟器中,访问相册和相机需要额外配置info.plist文件。分别是Privacy - Photo Library Usage Description和Privacy - Camera Usage Description字段,详见Demo中info.plist中的设置。 项目截图 1.Demo首页 2.照片列表页 3.照片预览页 4.视频预览页 ## 一. Installation 安装 #### CocoaPods > pod 'TZImagePickerController' #iOS8 and later > pod 'TZImagePickerController', '2.2.6' #iOS6、iOS7 #### Carthage > github "banchichen/TZImagePickerController" #### 手动安装 > 将TZImagePickerController文件夹拽入项目中,导入头文件:#import "TZImagePickerController.h" ## 二. Example 例子 TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:9 delegate:self]; // You can get the photos by block, the same as by delegate. // 你可以通过block或者代理,来得到用户选择的照片. [imagePickerVc setDidFinishPickingPhotosHandle:^(NSArray *photos, NSArray *assets, BOOL isSelectOriginalPhoto) { }]; [self presentViewController:imagePickerVc animated:YES completion:nil]; ## 三. Requirements 要求 iOS 6 or later. Requires ARC iOS6及以上系统可使用. ARC环境. When system version is iOS6 or iOS7, Using AssetsLibrary. When system version is iOS8 or later, Using PhotoKit. 如果运行在iOS6或7系统上,用的是AssetsLibrary库获取照片资源。 如果运行在iOS8及以上系统上,用的是PhotoKit库获取照片资源。 TZImagePickerController uses Camera、Location、Microphone、Photo Library,you need add these properties to info.plist like Demo: TZImagePickerController使用了相机、定位、麦克风、相册,请参考Demo添加下列属性到info.plist文件: `Privacy - Camera Usage Description` `Privacy - Location Usage Description` `Privacy - Location When In Use Usage Description` `Privacy - Microphone Usage Description` `Privacy - Photo Library Usage Description` `Prevent limited photos access alert` ## 四. More 更多 If you find a bug, please create a issue. More information please view code. 如果你发现了bug,请提一个issue。 更多信息详见代码,也可查看我的博客: [我的博客](http://www.jianshu.com/p/1975411a31bb "半尺尘 - 简书") 关于issue: 请尽可能详细地描述**系统版本**、**手机型号**、**库的版本**、**崩溃日志**和**复现步骤**,**请先更新到最新版再测试一下**,如果新版还存在再提~如果已有开启的类似issue,请直接在该issue下评论说出你的问题 ## 五. FAQ 常见问题 **Q:pod search TZImagePickerController 搜索出来的不是最新版本** A:需要在终端执行cd转换文件路径命令退回到Desktop,然后执行pod setup命令更新本地spec缓存(可能需要几分钟),然后再搜索就可以了 **Q:拍照后照片保存失败** A:请参考issue481:https://github.com/banchichen/TZImagePickerController/issues/481 的信息排查,若还有问题请直接在issue内评论 **Q:photos数组图片不是原图,如何获取原图?** A:请参考issue457的解释:https://github.com/banchichen/TZImagePickerController/issues/457 **Q:系统语言是中文/英文,界面上却有部分相册名字、返回按钮显示成了英文/中文?** A:请参考 https://github.com/banchichen/TZImagePickerController/issues/443 和 https://github.com/banchichen/TZImagePickerController/issues/929 **Q:预览界面能否支持传入NSURL、UIImage对象?** A:3.0.1版本已支持,需新接一个库:[TZImagePreviewController](https://github.com/banchichen/TZImagePreviewController),请参考里面的Demo使用。 **Q:设置可选视频的最大/最小时长?照片的最小/最大尺寸?不符合要求的不显示** A:可以的,参照Demo的isAssetCanBeDisplayed方法实现。我会返回asset出来,显示与否你来决定,注意这个是一个同步方法,对于需要根据asset去异步获取的信息如视频的大小、视频是否存在iCloud里来过滤的,无法做到。如果真要这样做,相册打开速度会变慢,你需要改我源码。 如果需要显示,选择时才提醒用户不可选,则实现isAssetCanBeSelected,用户选择时会调用它 **Q:预览页面出现了导航栏?** A:https://github.com/banchichen/TZImagePickerController/issues/652 **Q:可否增加微信编辑图片的功能?** A:考虑下,优先级低 **Q:是否有QQ/微信群/钉钉群?** A:有「钉钉群:33192786」和「QQ群:859033147」,推荐加钉钉群,答疑响应更快 **Q:想提交一个Pull Request?** A:请先加钉钉群(33192786)说下方案,和我确认下,避免同时改动同一处内容。**一个PR请只修复1个问题,变动内容越少越好**。 **Q:demo在真机上跑不起来?** A:1、team选你自己的;2、bundleId也改成你自己的或改成一个不会和别人重复的。可参考[简书的这篇博客](https://www.jianshu.com/p/cbe59138fca6) **Q:3.6.4以上版本设置导航栏颜色无效?** A:参考Demo里的代码,加上imagePickerVc.navigationBar.standardAppearance的相关设置 **Q:设置导航栏颜色无效?导航栏颜色总是白色?** A:是否有集成WRNavigationBar?如有,参考其readme调一下它的wr_setBlackList,把TZImagePickerController相关的控制器放到黑名单里,使得不受WRNavigationBar的影响。如果没有集成,可在issues列表里搜一下看看类似的issue参考下,如实在没头绪,可加群提供个能复现该问题的demo,0~2天给你解决。最近发现WRNavigationBar的黑名单会有不生效的情况,临时解决方案大家可参考:[https://github.com/wangrui460/WRNavigationBar/issues/145](https://github.com/wangrui460/WRNavigationBar/issues/145) **Q:导航栏没了?** A:是否有集成GKNavigationBarViewController?需要升级到2.0.4及以上版本,详见issue:[https://github.com/QuintGao/GKNavigationBarViewController/issues/7](https://github.com/QuintGao/GKNavigationBarViewController/issues/7)。 **Q:有的视频导出失败?** A:升级到2.2.6及以上版本试试,发现是修正视频转向导致的,2.2.6开始默认不再主动修正。如需打开,可设置needFixComposition为YES,但有几率导致安卓拍的视频导出失败。此外也可参考这个issue:https://github.com/banchichen/TZImagePickerController/issues/1073 **Q:视频导出慢?** A:视频导出分两步,第一步是通过PHAsset获取AVURLAsset,如是iCloud视频则涉及到网络请求,耗时容易不可控,第二步是通过AVURLAsset把视频保存到沙盒,耗时不算多。但第一步耗时不可控,你可以拷贝我源码出来拿到第一步的进度给用户一个进度提示... **Q:有的图片info里没有PHImageFileURLKey?** A:不要去拿PHImageFileURLKey,没用的,只有通过Photos框架才能访问相册照片,光拿一个路径没用。 如果需要通过路径上传照片,请先把UIImage保存到沙盒,**用沙盒路径**。 如果你上传照片需要一个名字参数,请参考Demo**直接用照片名字**。 ## 六. Release Notes 最近更新 **3.8.1 iOS14下可添加访问更多照片,详见PR内的评论** [#1526](https://github.com/banchichen/TZImagePickerController/pull/1526) **3.7.6 修复iOS15.2下初次授权相册权限时的长时间卡顿&白屏问题** [#1547](https://github.com/banchichen/TZImagePickerController/issues/1547) **3.6.7 修复Xcode13&iOS15下导航栏颜色异常问题** 3.6.2 新增allowEditVideo,单选视频时支持裁剪 3.6.0 修复iOS14下iCloud视频导出失败问题 **3.5.2 适配iPhone12系列设备** 3.4.4 支持Dark Mode 3.4.2 适配iOS14,若干问题修复 3.3.2 适配iOS13,若干问题修复 3.2.1 新增裁剪用scaleAspectFillCrop属性,设置为YES后,照片尺寸小于裁剪框时会自动放大撑满 3.2.0 加入用NSOperationQueue控制获取原图并发数降低内存的示例 3.1.8 批量获取图片时加入队列控制,尝试优化大批量选择图片时CPU和内存占用过高的问题(仍然危险,maxImagesCount谨慎设置过大...) 3.1.5 相册内无照片时给出提示,修复快速滑动时内存一直增加的问题 3.1.3 适配阿拉伯等语言下从右往左布局的特性 3.0.8 新增gifImagePlayBlock允许使用FLAnimatedImage等替换内部的GIF播放方案 3.0.7 适配iPhoneXR、XS、XS Max 3.0.6 优化保存照片、视频的方法 3.0.1 新增对[TZImagePreviewController](https://github.com/banchichen/TZImagePreviewController)库的支持,允许预览UIImage、NSURL、PHAsset对象 **3.0.0 去除iOS6和7的适配代码,更轻量,最低支持iOS8** 2.2.6 新增needFixComposition属性,默认为NO,不再主动修正视频转向,防止部分安卓拍的视频导出失败(**最后一个支持iOS6和7的版本**) 2.1.5 修复开启showSelectedIndex后照片列表页iCloud图片进度条紊乱的bug 2.1.4 新增多个页面和组件的样式自定义block,允许自定义绝大多数UI样式 2.1.2 新增showPhotoCannotSelectLayer属性,当已选照片张数达到最大可选张数时,可像微信一样让其它照片显示一个提示不可选的浮层 2.1.1 新增是否显示图片选中序号的属性,优化一些细节 2.1.0.3 新增拍摄视频功能,优化一些细节 2.0.0.6 优化自定义languageBundle的支持,加入使用示例 2.0.0.5 优化性能,提高选择器打开速度,新增越南语支持 2.0.0.2 新增繁体语言,可设置首选语言,国际化支持更强大;优化一些细节 1.9.8 支持Carthage,优化一些细节 1.9.6 优化视频预览和gif预览页toolbar在iPhoneX上的样式 ... 1.8.4 加入横竖屏适配;支持视频/gif多选;支持视频和照片一起选 1.8.1 新增2个代理方法,支持由上层来决定相册/照片的显示与否 ... 1.7.7 支持GIF图片的播放和选择 1.7.6 支持对共享相册和同步相册的显示 1.7.5 允许不进入预览页面直接选择照片 1.7.4 支持单选模式下裁剪照片,支持任意矩形和圆形裁剪框 1.7.3 优化iCloud照片的显示与选择 ... 1.5.0 可把拍照按钮放在外面;可自定义照片排序方式;Demo页的UI大改版,新增若干开关; ... 1.4.5 性能大幅提升(性能测试截图请去博客查看);可在照片列表页拍照;Demo大幅优化; ... ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/NSBundle+TZImagePicker.h ================================================ // // NSBundle+TZImagePicker.h // TZImagePickerController // // Created by 谭真 on 16/08/18. // Copyright © 2016年 谭真. All rights reserved. // #import @interface NSBundle (TZImagePicker) + (NSBundle *)tz_imagePickerBundle; + (NSString *)tz_localizedStringForKey:(NSString *)key value:(NSString *)value; + (NSString *)tz_localizedStringForKey:(NSString *)key; @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/NSBundle+TZImagePicker.m ================================================ // // NSBundle+TZImagePicker.m // TZImagePickerController // // Created by 谭真 on 16/08/18. // Copyright © 2016年 谭真. All rights reserved. // #import "NSBundle+TZImagePicker.h" #import "TZImagePickerController.h" @implementation NSBundle (TZImagePicker) + (NSBundle *)tz_imagePickerBundle { #ifdef SWIFT_PACKAGE NSBundle *bundle = SWIFTPM_MODULE_BUNDLE; #else NSBundle *bundle = [NSBundle bundleForClass:[TZImagePickerController class]]; #endif NSURL *url = [bundle URLForResource:@"TZImagePickerController" withExtension:@"bundle"]; bundle = [NSBundle bundleWithURL:url]; return bundle; } + (NSString *)tz_localizedStringForKey:(NSString *)key { return [self tz_localizedStringForKey:key value:@""]; } + (NSString *)tz_localizedStringForKey:(NSString *)key value:(NSString *)value { NSBundle *bundle = [TZImagePickerConfig sharedInstance].languageBundle; NSString *value1 = [bundle localizedStringForKey:key value:value table:nil]; return value1; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZAssetCell.h ================================================ // // TZAssetCell.h // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // #import #import typedef enum : NSUInteger { TZAssetCellTypePhoto = 0, TZAssetCellTypeLivePhoto, TZAssetCellTypePhotoGif, TZAssetCellTypeVideo, TZAssetCellTypeAudio, } TZAssetCellType; @class TZAssetModel; @interface TZAssetCell : UICollectionViewCell @property (weak, nonatomic) UIButton *selectPhotoButton; @property (weak, nonatomic) UIButton *cannotSelectLayerButton; @property (nonatomic, strong) TZAssetModel *model; @property (assign, nonatomic) NSInteger index; @property (nonatomic, copy) void (^didSelectPhotoBlock)(BOOL); @property (nonatomic, assign) TZAssetCellType type; @property (nonatomic, assign) BOOL allowPickingGif; @property (nonatomic, assign) BOOL allowPickingMultipleVideo; @property (nonatomic, copy) NSString *representedAssetIdentifier; @property (nonatomic, assign) int32_t imageRequestID; @property (nonatomic, strong) UIImage *photoSelImage; @property (nonatomic, strong) UIImage *photoDefImage; @property (nonatomic, assign) BOOL showSelectBtn; @property (assign, nonatomic) BOOL allowPreview; @property (nonatomic, copy) void (^assetCellDidSetModelBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView); @property (nonatomic, copy) void (^assetCellDidLayoutSubviewsBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView); @end @class TZAlbumModel; @interface TZAlbumCell : UITableViewCell @property (nonatomic, strong) TZAlbumModel *model; @property (weak, nonatomic) UIButton *selectedCountButton; @property (nonatomic, copy) void (^albumCellDidSetModelBlock)(TZAlbumCell *cell, UIImageView *posterImageView, UILabel *titleLabel); @property (nonatomic, copy) void (^albumCellDidLayoutSubviewsBlock)(TZAlbumCell *cell, UIImageView *posterImageView, UILabel *titleLabel); @end @interface TZAssetCameraCell : UICollectionViewCell @property (nonatomic, strong) UIImageView *imageView; @end @interface TZAssetAddMoreCell : TZAssetCameraCell @property (nonatomic, strong) UILabel *tipLabel; @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZAssetCell.m ================================================ // // TZAssetCell.m // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // #import "TZAssetCell.h" #import "TZAssetModel.h" #import "UIView+TZLayout.h" #import "TZImageManager.h" #import "TZImagePickerController.h" #import "TZProgressView.h" @interface TZAssetCell () @property (weak, nonatomic) UIImageView *imageView; // The photo / 照片 @property (weak, nonatomic) UIImageView *selectImageView; @property (weak, nonatomic) UILabel *indexLabel; @property (weak, nonatomic) UIView *bottomView; @property (weak, nonatomic) UILabel *timeLength; @property (strong, nonatomic) UITapGestureRecognizer *tapGesture; @property (nonatomic, weak) UIImageView *videoImgView; @property (nonatomic, strong) TZProgressView *progressView; @property (nonatomic, assign) int32_t bigImageRequestID; @end @implementation TZAssetCell - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reload:) name:@"TZ_PHOTO_PICKER_RELOAD_NOTIFICATION" object:nil]; return self; } - (void)setModel:(TZAssetModel *)model { _model = model; self.representedAssetIdentifier = model.asset.localIdentifier; int32_t imageRequestID = [[TZImageManager manager] getPhotoWithAsset:model.asset photoWidth:self.tz_width completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { // Set the cell's thumbnail image if it's still showing the same asset. if ([self.representedAssetIdentifier isEqualToString:model.asset.localIdentifier]) { self.imageView.image = photo; [self setNeedsLayout]; } else { // NSLog(@"this cell is showing other asset"); [[PHImageManager defaultManager] cancelImageRequest:self.imageRequestID]; } if (!isDegraded) { [self hideProgressView]; self.imageRequestID = 0; } } progressHandler:nil networkAccessAllowed:NO]; if (imageRequestID && self.imageRequestID && imageRequestID != self.imageRequestID) { [[PHImageManager defaultManager] cancelImageRequest:self.imageRequestID]; // NSLog(@"cancelImageRequest %d",self.imageRequestID); } self.imageRequestID = imageRequestID; self.selectPhotoButton.selected = model.isSelected; self.selectImageView.image = self.selectPhotoButton.isSelected ? self.photoSelImage : self.photoDefImage; self.indexLabel.hidden = !self.selectPhotoButton.isSelected; self.type = (NSInteger)model.type; // 让宽度/高度小于 最小可选照片尺寸 的图片不能选中 if (![[TZImageManager manager] isPhotoSelectableWithAsset:model.asset]) { if (_selectImageView.hidden == NO) { self.selectPhotoButton.hidden = YES; _selectImageView.hidden = YES; } } // 如果用户选中了该图片,提前获取一下大图 if (model.isSelected) { [self requestBigImage]; } else { [self cancelBigImageRequest]; } [self setNeedsLayout]; if (self.assetCellDidSetModelBlock) { self.assetCellDidSetModelBlock(self, _imageView, _selectImageView, _indexLabel, _bottomView, _timeLength, _videoImgView); } } - (void)setIndex:(NSInteger)index { _index = index; self.indexLabel.text = [NSString stringWithFormat:@"%zd", index]; [self.contentView bringSubviewToFront:self.indexLabel]; } - (void)setShowSelectBtn:(BOOL)showSelectBtn { _showSelectBtn = showSelectBtn; BOOL selectable = [[TZImageManager manager] isPhotoSelectableWithAsset:self.model.asset]; if (!self.selectPhotoButton.hidden) { self.selectPhotoButton.hidden = !showSelectBtn || !selectable; } if (!self.selectImageView.hidden) { self.selectImageView.hidden = !showSelectBtn || !selectable; } } - (void)setType:(TZAssetCellType)type { _type = type; if (type == TZAssetCellTypePhoto || type == TZAssetCellTypeLivePhoto || (type == TZAssetCellTypePhotoGif && !self.allowPickingGif) || self.allowPickingMultipleVideo) { _selectImageView.hidden = NO; _selectPhotoButton.hidden = NO; _bottomView.hidden = YES; } else { // Video of Gif _selectImageView.hidden = YES; _selectPhotoButton.hidden = YES; } if (type == TZAssetCellTypeVideo) { self.bottomView.hidden = NO; self.timeLength.text = _model.timeLength; self.videoImgView.hidden = NO; _timeLength.tz_left = self.videoImgView.tz_right; _timeLength.textAlignment = NSTextAlignmentRight; } else if (type == TZAssetCellTypePhotoGif && self.allowPickingGif) { self.bottomView.hidden = NO; self.timeLength.text = @"GIF"; self.videoImgView.hidden = YES; _timeLength.tz_left = 5; _timeLength.textAlignment = NSTextAlignmentLeft; } } - (void)setAllowPreview:(BOOL)allowPreview { _allowPreview = allowPreview; if (allowPreview) { _imageView.userInteractionEnabled = NO; _tapGesture.enabled = NO; } else { _imageView.userInteractionEnabled = YES; _tapGesture.enabled = YES; } } - (void)selectPhotoButtonClick:(UIButton *)sender { if (self.didSelectPhotoBlock) { self.didSelectPhotoBlock(sender.isSelected); } self.selectImageView.image = sender.isSelected ? self.photoSelImage : self.photoDefImage; if (sender.isSelected) { [UIView showOscillatoryAnimationWithLayer:_selectImageView.layer type:TZOscillatoryAnimationToBigger]; // 用户选中了该图片,提前获取一下大图 [self requestBigImage]; } else { // 取消选中,取消大图的获取 [self cancelBigImageRequest]; } } /// 只在单选状态且allowPreview为NO时会有该事件 - (void)didTapImageView { if (self.didSelectPhotoBlock) { self.didSelectPhotoBlock(NO); } } - (void)hideProgressView { if (_progressView) { self.progressView.hidden = YES; self.imageView.alpha = 1.0; } } - (void)requestBigImage { if (_bigImageRequestID) { [[PHImageManager defaultManager] cancelImageRequest:_bigImageRequestID]; } _bigImageRequestID = [[TZImageManager manager] requestImageDataForAsset:_model.asset completion:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { BOOL iCloudSyncFailed = !imageData && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]]; self.model.iCloudFailed = iCloudSyncFailed; if (iCloudSyncFailed && self.didSelectPhotoBlock) { self.didSelectPhotoBlock(YES); self.selectImageView.image = self.photoDefImage; } [self hideProgressView]; } progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { if (self.model.isSelected) { progress = progress > 0.02 ? progress : 0.02;; self.progressView.progress = progress; self.progressView.hidden = NO; self.imageView.alpha = 0.4; if (progress >= 1) { [self hideProgressView]; } } else { // 快速连续点几次,会EXC_BAD_ACCESS... // *stop = YES; [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; [self cancelBigImageRequest]; } }]; if (_model.type == TZAssetCellTypeVideo) { [[TZImageManager manager] getVideoWithAsset:_model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) { BOOL iCloudSyncFailed = !playerItem && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]]; self.model.iCloudFailed = iCloudSyncFailed; if (iCloudSyncFailed && self.didSelectPhotoBlock) { dispatch_async(dispatch_get_main_queue(), ^{ self.didSelectPhotoBlock(YES); self.selectImageView.image = self.photoDefImage; }); } }]; } } - (void)cancelBigImageRequest { if (_bigImageRequestID) { [[PHImageManager defaultManager] cancelImageRequest:_bigImageRequestID]; } [self hideProgressView]; } #pragma mark - Notification - (void)reload:(NSNotification *)noti { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)noti.object; UIViewController *parentViewController = nil; UIResponder *responder = self.nextResponder; do { if ([responder isKindOfClass:[UIViewController class]]) { parentViewController = (UIViewController *)responder; break; } responder = responder.nextResponder; } while (responder); if (parentViewController.navigationController != tzImagePickerVc) { return; } if (self.model.isSelected && tzImagePickerVc.showSelectedIndex) { self.index = [tzImagePickerVc.selectedAssetIds indexOfObject:self.model.asset.localIdentifier] + 1; } self.indexLabel.hidden = !self.selectPhotoButton.isSelected; BOOL notSelectable = [TZCommonTools isAssetNotSelectable:self.model tzImagePickerVc:tzImagePickerVc]; if (notSelectable && tzImagePickerVc.showPhotoCannotSelectLayer && !self.model.isSelected) { self.cannotSelectLayerButton.backgroundColor = tzImagePickerVc.cannotSelectLayerColor; self.cannotSelectLayerButton.hidden = NO; } else { self.cannotSelectLayerButton.hidden = YES; } } #pragma mark - Lazy load - (UIButton *)selectPhotoButton { if (_selectPhotoButton == nil) { UIButton *selectPhotoButton = [[UIButton alloc] init]; [selectPhotoButton addTarget:self action:@selector(selectPhotoButtonClick:) forControlEvents:UIControlEventTouchUpInside]; [self.contentView addSubview:selectPhotoButton]; _selectPhotoButton = selectPhotoButton; } return _selectPhotoButton; } - (UIImageView *)imageView { if (_imageView == nil) { UIImageView *imageView = [[UIImageView alloc] init]; imageView.contentMode = UIViewContentModeScaleAspectFill; imageView.clipsToBounds = YES; [self.contentView addSubview:imageView]; _imageView = imageView; _tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapImageView)]; [_imageView addGestureRecognizer:_tapGesture]; self.allowPreview = self.allowPreview; } return _imageView; } - (UIImageView *)selectImageView { if (_selectImageView == nil) { UIImageView *selectImageView = [[UIImageView alloc] init]; selectImageView.contentMode = UIViewContentModeCenter; selectImageView.clipsToBounds = YES; [self.contentView addSubview:selectImageView]; _selectImageView = selectImageView; } return _selectImageView; } - (UIView *)bottomView { if (_bottomView == nil) { UIView *bottomView = [[UIView alloc] init]; static NSInteger rgb = 0; bottomView.userInteractionEnabled = NO; bottomView.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.8]; [self.contentView addSubview:bottomView]; _bottomView = bottomView; } return _bottomView; } - (UIButton *)cannotSelectLayerButton { if (_cannotSelectLayerButton == nil) { UIButton *cannotSelectLayerButton = [[UIButton alloc] init]; [self.contentView addSubview:cannotSelectLayerButton]; _cannotSelectLayerButton = cannotSelectLayerButton; } return _cannotSelectLayerButton; } - (UIImageView *)videoImgView { if (_videoImgView == nil) { UIImageView *videoImgView = [[UIImageView alloc] init]; [videoImgView setImage:[UIImage tz_imageNamedFromMyBundle:@"VideoSendIcon"]]; [self.bottomView addSubview:videoImgView]; _videoImgView = videoImgView; } return _videoImgView; } - (UILabel *)timeLength { if (_timeLength == nil) { UILabel *timeLength = [[UILabel alloc] init]; timeLength.font = [UIFont boldSystemFontOfSize:11]; timeLength.textColor = [UIColor whiteColor]; timeLength.textAlignment = NSTextAlignmentRight; [self.bottomView addSubview:timeLength]; _timeLength = timeLength; } return _timeLength; } - (UILabel *)indexLabel { if (_indexLabel == nil) { UILabel *indexLabel = [[UILabel alloc] init]; indexLabel.font = [UIFont systemFontOfSize:14]; indexLabel.adjustsFontSizeToFitWidth = YES; indexLabel.textColor = [UIColor whiteColor]; indexLabel.textAlignment = NSTextAlignmentCenter; [self.contentView addSubview:indexLabel]; _indexLabel = indexLabel; } return _indexLabel; } - (TZProgressView *)progressView { if (_progressView == nil) { _progressView = [[TZProgressView alloc] init]; _progressView.hidden = YES; [self addSubview:_progressView]; } return _progressView; } - (void)layoutSubviews { [super layoutSubviews]; _cannotSelectLayerButton.frame = self.bounds; if (self.allowPreview) { _selectPhotoButton.frame = CGRectMake(self.tz_width - 44, 0, 44, 44); } else { _selectPhotoButton.frame = self.bounds; } _selectImageView.frame = CGRectMake(self.tz_width - 27, 3, 24, 24); if (_selectImageView.image.size.width <= 27) { _selectImageView.contentMode = UIViewContentModeCenter; } else { _selectImageView.contentMode = UIViewContentModeScaleAspectFit; } _indexLabel.frame = _selectImageView.frame; _imageView.frame = self.bounds; static CGFloat progressWH = 20; CGFloat progressXY = (self.tz_width - progressWH) / 2; _progressView.frame = CGRectMake(progressXY, progressXY, progressWH, progressWH); _bottomView.frame = CGRectMake(0, self.tz_height - 17, self.tz_width, 17); _videoImgView.frame = CGRectMake(8, 0, 17, 17); _timeLength.frame = CGRectMake(self.videoImgView.tz_right, 0, self.tz_width - self.videoImgView.tz_right - 5, 17); self.type = (NSInteger)self.model.type; self.showSelectBtn = self.showSelectBtn; [self.contentView bringSubviewToFront:_bottomView]; [self.contentView bringSubviewToFront:_cannotSelectLayerButton]; [self.contentView bringSubviewToFront:_selectPhotoButton]; [self.contentView bringSubviewToFront:_selectImageView]; [self.contentView bringSubviewToFront:_indexLabel]; if (self.assetCellDidLayoutSubviewsBlock) { self.assetCellDidLayoutSubviewsBlock(self, _imageView, _selectImageView, _indexLabel, _bottomView, _timeLength, _videoImgView); } } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end @interface TZAlbumCell () @property (weak, nonatomic) UIImageView *posterImageView; @property (weak, nonatomic) UILabel *titleLabel; @end @implementation TZAlbumCell - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; self.backgroundColor = [UIColor whiteColor]; self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return self; } - (void)setModel:(TZAlbumModel *)model { _model = model; UIColor *nameColor = UIColor.blackColor; if (@available(iOS 13.0, *)) { nameColor = UIColor.labelColor; } NSMutableAttributedString *nameString = [[NSMutableAttributedString alloc] initWithString:model.name attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:nameColor}]; NSAttributedString *countString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" (%zd)",model.count] attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:[UIColor lightGrayColor]}]; [nameString appendAttributedString:countString]; self.titleLabel.attributedText = nameString; [[TZImageManager manager] getPostImageWithAlbumModel:model completion:^(UIImage *postImage) { self.posterImageView.image = postImage; [self setNeedsLayout]; }]; if (model.selectedCount) { self.selectedCountButton.hidden = NO; [self.selectedCountButton setTitle:[NSString stringWithFormat:@"%zd",model.selectedCount] forState:UIControlStateNormal]; } else { self.selectedCountButton.hidden = YES; } if (self.albumCellDidSetModelBlock) { self.albumCellDidSetModelBlock(self, _posterImageView, _titleLabel); } } - (void)layoutSubviews { [super layoutSubviews]; _selectedCountButton.frame = CGRectMake(self.contentView.tz_width - 24, 23, 24, 24); NSInteger titleHeight = ceil(self.titleLabel.font.lineHeight); self.titleLabel.frame = CGRectMake(80, (self.tz_height - titleHeight) / 2, self.tz_width - 80 - 50, titleHeight); self.posterImageView.frame = CGRectMake(0, 0, 70, 70); if (self.albumCellDidLayoutSubviewsBlock) { self.albumCellDidLayoutSubviewsBlock(self, _posterImageView, _titleLabel); } } - (void)layoutSublayersOfLayer:(CALayer *)layer { [super layoutSublayersOfLayer:layer]; } #pragma mark - Lazy load - (UIImageView *)posterImageView { if (_posterImageView == nil) { UIImageView *posterImageView = [[UIImageView alloc] init]; posterImageView.contentMode = UIViewContentModeScaleAspectFill; posterImageView.clipsToBounds = YES; [self.contentView addSubview:posterImageView]; _posterImageView = posterImageView; } return _posterImageView; } - (UILabel *)titleLabel { if (_titleLabel == nil) { UILabel *titleLabel = [[UILabel alloc] init]; titleLabel.font = [UIFont boldSystemFontOfSize:17]; if (@available(iOS 13.0, *)) { titleLabel.textColor = UIColor.labelColor; } else { titleLabel.textColor = [UIColor blackColor]; } titleLabel.textAlignment = NSTextAlignmentLeft; [self.contentView addSubview:titleLabel]; _titleLabel = titleLabel; } return _titleLabel; } - (UIButton *)selectedCountButton { if (_selectedCountButton == nil) { UIButton *selectedCountButton = [[UIButton alloc] init]; selectedCountButton.titleLabel.adjustsFontSizeToFitWidth = YES; selectedCountButton.layer.cornerRadius = 12; selectedCountButton.clipsToBounds = YES; selectedCountButton.backgroundColor = [UIColor redColor]; [selectedCountButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; selectedCountButton.titleLabel.font = [UIFont systemFontOfSize:15]; [self.contentView addSubview:selectedCountButton]; _selectedCountButton = selectedCountButton; } return _selectedCountButton; } @end @implementation TZAssetCameraCell - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor whiteColor]; _imageView = [[UIImageView alloc] init]; _imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500]; _imageView.contentMode = UIViewContentModeScaleAspectFill; [self.contentView addSubview:_imageView]; self.clipsToBounds = YES; } return self; } - (void)layoutSubviews { [super layoutSubviews]; _imageView.frame = self.bounds; } @end @implementation TZAssetAddMoreCell - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { _tipLabel = [[UILabel alloc] init]; _tipLabel.numberOfLines = 2; _tipLabel.textAlignment = NSTextAlignmentCenter; _tipLabel.font = [UIFont systemFontOfSize:12]; _tipLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; CGFloat rgb = 156 / 255.0; _tipLabel.textColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:1.0]; [self.contentView addSubview:_tipLabel]; self.clipsToBounds = YES; } return self; } - (void)layoutSubviews { [super layoutSubviews]; _tipLabel.frame = CGRectMake(5, self.tz_height / 2, self.tz_width - 10, self.tz_height / 2 - 5); } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZAssetModel.h ================================================ // // TZAssetModel.h // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // #import #import #import typedef enum : NSUInteger { TZAssetModelMediaTypePhoto = 0, TZAssetModelMediaTypeLivePhoto, TZAssetModelMediaTypePhotoGif, TZAssetModelMediaTypeVideo, TZAssetModelMediaTypeAudio } TZAssetModelMediaType; @class PHAsset; @interface TZAssetModel : NSObject @property (nonatomic, strong) PHAsset *asset; @property (nonatomic, assign) BOOL isSelected; ///< The select status of a photo, default is No @property (nonatomic, assign) TZAssetModelMediaType type; @property (nonatomic, copy) NSString *timeLength; @property (nonatomic, assign) BOOL iCloudFailed; /// Init a photo dataModel With a PHAsset /// 用一个PHAsset实例,初始化一个照片模型 + (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type; + (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type timeLength:(NSString *)timeLength; @end @class PHFetchResult; @interface TZAlbumModel : NSObject @property (nonatomic, strong) NSString *name; ///< The album name @property (nonatomic, assign) NSInteger count; ///< Count of photos the album contain @property (nonatomic, strong) PHFetchResult *result; @property (nonatomic, strong) PHAssetCollection *collection; @property (nonatomic, strong) PHFetchOptions *options; @property (nonatomic, strong) NSArray *models; @property (nonatomic, strong) NSArray *selectedModels; @property (nonatomic, assign) NSUInteger selectedCount; @property (nonatomic, assign) BOOL isCameraRoll; - (void)setResult:(PHFetchResult *)result needFetchAssets:(BOOL)needFetchAssets; - (void)refreshFetchResult; @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZAssetModel.m ================================================ // // TZAssetModel.m // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // #import "TZAssetModel.h" #import "TZImageManager.h" @implementation TZAssetModel + (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type{ TZAssetModel *model = [[TZAssetModel alloc] init]; model.asset = asset; model.isSelected = NO; model.type = type; return model; } + (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type timeLength:(NSString *)timeLength { TZAssetModel *model = [self modelWithAsset:asset type:type]; model.timeLength = timeLength; return model; } @end @implementation TZAlbumModel - (void)setResult:(PHFetchResult *)result needFetchAssets:(BOOL)needFetchAssets { _result = result; if (needFetchAssets) { [[TZImageManager manager] getAssetsFromFetchResult:result completion:^(NSArray *models) { self->_models = models; if (self->_selectedModels) { [self checkSelectedModels]; } }]; } } - (void)refreshFetchResult { PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:self.collection options:self.options]; self.count = fetchResult.count; [self setResult:fetchResult]; } - (void)setSelectedModels:(NSArray *)selectedModels { _selectedModels = selectedModels; if (_models) { [self checkSelectedModels]; } } - (void)checkSelectedModels { self.selectedCount = 0; NSMutableSet *selectedAssets = [NSMutableSet setWithCapacity:_selectedModels.count]; for (TZAssetModel *model in _selectedModels) { [selectedAssets addObject:model.asset]; } for (TZAssetModel *model in _models) { if ([selectedAssets containsObject:model.asset]) { self.selectedCount ++; } } } - (NSString *)name { if (_name) { return _name; } return @""; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZAuthLimitedFooterTipView.h ================================================ // // TZAuthLimitedFooterTipView.h // TZImagePickerController // // Created by qiaoxy on 2021/8/24. // #import NS_ASSUME_NONNULL_BEGIN @interface TZAuthLimitedFooterTipView : UIView @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZAuthLimitedFooterTipView.m ================================================ // // TZAuthLimitedFooterTipView.m // TZImagePickerController // // Created by qiaoxy on 2021/8/24. // #import "TZAuthLimitedFooterTipView.h" #import "TZImagePickerController.h" @interface TZAuthLimitedFooterTipView() @property (nonatomic,strong) UIImageView *tipImgView; @property (nonatomic,strong) UILabel *tipLable; @property (nonatomic,strong) UIImageView *detailImgView; @end @implementation TZAuthLimitedFooterTipView - (instancetype)init { self = [super init]; if (self) { [self initSubViews]; } return self; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self initSubViews]; } return self; } - (void)initSubViews { [self addSubview:self.tipImgView]; [self addSubview:self.tipLable]; [self addSubview:self.detailImgView]; CGFloat margin = 15; CGFloat tipImgViewWH = 20; CGFloat detailImgViewWH = 12; CGFloat screenW = [UIScreen mainScreen].bounds.size.width; self.tipImgView.frame = CGRectMake(margin, 0, tipImgViewWH, tipImgViewWH); self.detailImgView.frame = CGRectMake(screenW - margin - detailImgViewWH, 0, detailImgViewWH, detailImgViewWH); CGFloat tipLabelX = CGRectGetMaxX(self.tipImgView.frame) + 10; CGFloat tipLabelW = screenW - tipLabelX - detailImgViewWH - margin - 4; self.tipLable.frame = CGRectMake(tipLabelX, 0, tipLabelW, self.bounds.size.height); self.tipImgView.center = CGPointMake(self.tipImgView.center.x, self.tipLable.center.y); self.detailImgView.center = CGPointMake(self.detailImgView.center.x, self.tipLable.center.y); } #pragma mark - Getter - (UIImageView *)tipImgView { if (!_tipImgView) { _tipImgView = [[UIImageView alloc] init]; _tipImgView.contentMode = UIViewContentModeScaleAspectFit; _tipImgView.image = [UIImage tz_imageNamedFromMyBundle:@"tip"]; } return _tipImgView; } - (UILabel *)tipLable { if (!_tipLable) { _tipLable = [[UILabel alloc] init]; NSString *appName = [TZCommonTools tz_getAppName]; _tipLable.text = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Allow %@ to access your all photos"], appName]; _tipLable.numberOfLines = 0; _tipLable.font = [UIFont systemFontOfSize:14]; _tipLable.textColor = [UIColor colorWithRed:0.40 green:0.40 blue:0.40 alpha:1.0]; } return _tipLable; } - (UIImageView *)detailImgView { if (!_detailImgView) { _detailImgView = [[UIImageView alloc] init]; _detailImgView.contentMode = UIViewContentModeScaleAspectFit; _detailImgView.image = [UIImage tz_imageNamedFromMyBundle:@"right_arrow"]; } return _detailImgView; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZGifPhotoPreviewController.h ================================================ // // TZGifPhotoPreviewController.h // TZImagePickerController // // Created by ttouch on 2016/12/13. // Copyright © 2016年 谭真. All rights reserved. // #import @class TZAssetModel; @interface TZGifPhotoPreviewController : UIViewController @property (nonatomic, strong) TZAssetModel *model; @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZGifPhotoPreviewController.m ================================================ // // TZGifPhotoPreviewController.m // TZImagePickerController // // Created by ttouch on 2016/12/13. // Copyright © 2016年 谭真. All rights reserved. // #import "TZGifPhotoPreviewController.h" #import "TZImagePickerController.h" #import "TZAssetModel.h" #import "UIView+TZLayout.h" #import "TZPhotoPreviewCell.h" #import "TZImageManager.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @interface TZGifPhotoPreviewController () { UIView *_toolBar; UIButton *_doneButton; UIProgressView *_progress; TZPhotoPreviewView *_previewView; UIStatusBarStyle _originStatusBarStyle; } @property (assign, nonatomic) BOOL needShowStatusBar; @end @implementation TZGifPhotoPreviewController - (void)viewDidLoad { [super viewDidLoad]; self.needShowStatusBar = ![UIApplication sharedApplication].statusBarHidden; self.view.backgroundColor = [UIColor blackColor]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (tzImagePickerVc) { self.navigationItem.title = [NSString stringWithFormat:@"GIF %@",tzImagePickerVc.previewBtnTitleStr]; } [self configPreviewView]; [self configBottomToolBar]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; _originStatusBarStyle = [UIApplication sharedApplication].statusBarStyle; [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if (self.needShowStatusBar) { [UIApplication sharedApplication].statusBarHidden = NO; } [UIApplication sharedApplication].statusBarStyle = _originStatusBarStyle; } - (void)configPreviewView { _previewView = [[TZPhotoPreviewView alloc] initWithFrame:CGRectZero]; _previewView.model = self.model; __weak typeof(self) weakSelf = self; [_previewView setSingleTapGestureBlock:^{ __strong typeof(weakSelf) strongSelf = weakSelf; [strongSelf signleTapAction]; }]; [self.view addSubview:_previewView]; } - (void)configBottomToolBar { _toolBar = [[UIView alloc] initWithFrame:CGRectZero]; CGFloat rgb = 34 / 255.0; _toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7]; _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; _doneButton.titleLabel.font = [UIFont systemFontOfSize:16]; [_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (tzImagePickerVc) { [_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal]; [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal]; } else { [_doneButton setTitle:[NSBundle tz_localizedStringForKey:@"Done"] forState:UIControlStateNormal]; [_doneButton setTitleColor:[UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0] forState:UIControlStateNormal]; } [_toolBar addSubview:_doneButton]; UILabel *byteLabel = [[UILabel alloc] init]; byteLabel.textColor = [UIColor whiteColor]; byteLabel.font = [UIFont systemFontOfSize:13]; byteLabel.frame = CGRectMake(10, 0, 100, 44); [[TZImageManager manager] getPhotosBytesWithArray:@[_model] completion:^(NSString *totalBytes) { byteLabel.text = totalBytes; }]; [_toolBar addSubview:byteLabel]; [self.view addSubview:_toolBar]; if (tzImagePickerVc.gifPreviewPageUIConfigBlock) { tzImagePickerVc.gifPreviewPageUIConfigBlock(_toolBar, _doneButton); } } - (UIStatusBarStyle)preferredStatusBarStyle { TZImagePickerController *tzImagePicker = (TZImagePickerController *)self.navigationController; if (tzImagePicker && [tzImagePicker isKindOfClass:[TZImagePickerController class]]) { return tzImagePicker.statusBarStyle; } return [super preferredStatusBarStyle]; } #pragma mark - Layout - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; _previewView.frame = self.view.bounds; _previewView.scrollView.frame = self.view.bounds; CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom; _toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight); [_doneButton sizeToFit]; _doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 44); TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (tzImagePickerVc.gifPreviewPageDidLayoutSubviewsBlock) { tzImagePickerVc.gifPreviewPageDidLayoutSubviewsBlock(_toolBar, _doneButton); } } #pragma mark - Click Event - (void)signleTapAction { _toolBar.hidden = !_toolBar.isHidden; [self.navigationController setNavigationBarHidden:_toolBar.isHidden]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (_toolBar.isHidden) { [UIApplication sharedApplication].statusBarHidden = YES; } else if (tzImagePickerVc.needShowStatusBar) { [UIApplication sharedApplication].statusBarHidden = NO; } } - (void)doneButtonClick { if (self.navigationController) { TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; if (imagePickerVc.autoDismiss) { [self.navigationController dismissViewControllerAnimated:YES completion:^{ [self callDelegateMethod]; }]; } else { [self callDelegateMethod]; } } else { [self dismissViewControllerAnimated:YES completion:^{ [self callDelegateMethod]; }]; } } - (void)callDelegateMethod { TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; UIImage *animatedImage = _previewView.imageView.image; if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingGifImage:sourceAssets:)]) { [imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingGifImage:animatedImage sourceAssets:_model.asset]; } if (imagePickerVc.didFinishPickingGifImageHandle) { imagePickerVc.didFinishPickingGifImageHandle(animatedImage,_model.asset); } } #pragma clang diagnostic pop @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImageCropManager.h ================================================ // // TZImageCropManager.h // TZImagePickerController // // Created by 谭真 on 2016/12/5. // Copyright © 2016年 谭真. All rights reserved. // 图片裁剪管理类 #import #import @interface TZImageCropManager : NSObject /// 裁剪框背景的处理 + (void)overlayClippingWithView:(UIView *)view cropRect:(CGRect)cropRect containerView:(UIView *)containerView needCircleCrop:(BOOL)needCircleCrop; /* 1.7.2 为了解决多位同学对于图片裁剪的需求,我这两天有空便在研究图片裁剪 幸好有tuyou的PhotoTweaks库做参考,裁剪的功能实现起来简单许多 该方法和其内部引用的方法基本来自于tuyou的PhotoTweaks库,我做了稍许删减和修改 感谢tuyou同学在github开源了优秀的裁剪库PhotoTweaks,表示感谢 PhotoTweaks库的github链接:https://github.com/itouch2/PhotoTweaks */ /// 获得裁剪后的图片 + (UIImage *)cropImageView:(UIImageView *)imageView toRect:(CGRect)rect zoomScale:(double)zoomScale containerView:(UIView *)containerView; /// 获取圆形图片 + (UIImage *)circularClipImage:(UIImage *)image; @end /// 该分类的代码来自SDWebImage:https://github.com/rs/SDWebImage /// 为了防止冲突,我将分类名字和方法名字做了修改 @interface UIImage (TZGif) + (UIImage *)sd_tz_animatedGIFWithData:(NSData *)data; @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImageCropManager.m ================================================ // // TZImageCropManager.m // TZImagePickerController // // Created by 谭真 on 2016/12/5. // Copyright © 2016年 谭真. All rights reserved. // #import "TZImageCropManager.h" #import "UIView+TZLayout.h" #import #import "TZImageManager.h" #import "TZImagePickerController.h" @implementation TZImageCropManager /// 裁剪框背景的处理 + (void)overlayClippingWithView:(UIView *)view cropRect:(CGRect)cropRect containerView:(UIView *)containerView needCircleCrop:(BOOL)needCircleCrop { UIBezierPath *path= [UIBezierPath bezierPathWithRect:[UIScreen mainScreen].bounds]; CAShapeLayer *layer = [CAShapeLayer layer]; if (needCircleCrop) { // 圆形裁剪框 [path appendPath:[UIBezierPath bezierPathWithRoundedRect:cropRect cornerRadius:cropRect.size.width / 2]]; } else { // 矩形裁剪框 [path appendPath:[UIBezierPath bezierPathWithRect:cropRect]]; } layer.path = path.CGPath; layer.fillRule = kCAFillRuleEvenOdd; layer.fillColor = [[UIColor blackColor] CGColor]; layer.opacity = 0.5; [view.layer addSublayer:layer]; } /// 获得裁剪后的图片 + (UIImage *)cropImageView:(UIImageView *)imageView toRect:(CGRect)rect zoomScale:(double)zoomScale containerView:(UIView *)containerView { CGAffineTransform transform = CGAffineTransformIdentity; // 平移的处理 CGRect imageViewRect = [imageView convertRect:imageView.bounds toView:containerView]; CGPoint point = CGPointMake(imageViewRect.origin.x + imageViewRect.size.width / 2, imageViewRect.origin.y + imageViewRect.size.height / 2); CGFloat xMargin = containerView.tz_width - CGRectGetMaxX(rect) - rect.origin.x; CGPoint zeroPoint = CGPointMake((CGRectGetWidth(containerView.frame) - xMargin) / 2, containerView.center.y); CGPoint translation = CGPointMake(point.x - zeroPoint.x, point.y - zeroPoint.y); transform = CGAffineTransformTranslate(transform, translation.x, translation.y); // 缩放的处理 transform = CGAffineTransformScale(transform, zoomScale, zoomScale); CGImageRef imageRef = [self newTransformedImage:transform sourceImage:imageView.image.CGImage sourceSize:imageView.image.size outputWidth:rect.size.width * [UIScreen mainScreen].scale cropSize:rect.size imageViewSize:imageView.frame.size]; UIImage *cropedImage = [UIImage imageWithCGImage:imageRef]; cropedImage = [[TZImageManager manager] fixOrientation:cropedImage]; CGImageRelease(imageRef); return cropedImage; } + (CGImageRef)newTransformedImage:(CGAffineTransform)transform sourceImage:(CGImageRef)sourceImage sourceSize:(CGSize)sourceSize outputWidth:(CGFloat)outputWidth cropSize:(CGSize)cropSize imageViewSize:(CGSize)imageViewSize { CGImageRef source = [self newScaledImage:sourceImage toSize:sourceSize]; CGFloat aspect = cropSize.height/cropSize.width; CGSize outputSize = CGSizeMake(outputWidth, outputWidth*aspect); CGContextRef context = CGBitmapContextCreate(NULL, outputSize.width, outputSize.height, CGImageGetBitsPerComponent(source), 0, CGImageGetColorSpace(source), CGImageGetBitmapInfo(source)); CGContextSetFillColorWithColor(context, [[UIColor clearColor] CGColor]); CGContextFillRect(context, CGRectMake(0, 0, outputSize.width, outputSize.height)); CGAffineTransform uiCoords = CGAffineTransformMakeScale(outputSize.width / cropSize.width, outputSize.height / cropSize.height); uiCoords = CGAffineTransformTranslate(uiCoords, cropSize.width/2.0, cropSize.height / 2.0); uiCoords = CGAffineTransformScale(uiCoords, 1.0, -1.0); CGContextConcatCTM(context, uiCoords); CGContextConcatCTM(context, transform); CGContextScaleCTM(context, 1.0, -1.0); CGContextDrawImage(context, CGRectMake(-imageViewSize.width/2, -imageViewSize.height/2.0, imageViewSize.width, imageViewSize.height), source); CGImageRef resultRef = CGBitmapContextCreateImage(context); CGContextRelease(context); CGImageRelease(source); return resultRef; } + (CGImageRef)newScaledImage:(CGImageRef)source toSize:(CGSize)size { CGSize srcSize = size; CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, rgbColorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGColorSpaceRelease(rgbColorSpace); CGContextSetInterpolationQuality(context, kCGInterpolationNone); CGContextTranslateCTM(context, size.width/2, size.height/2); CGContextDrawImage(context, CGRectMake(-srcSize.width/2, -srcSize.height/2, srcSize.width, srcSize.height), source); CGImageRef resultRef = CGBitmapContextCreateImage(context); CGContextRelease(context); return resultRef; } /// 获取圆形图片 + (UIImage *)circularClipImage:(UIImage *)image { UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale); CGContextRef ctx = UIGraphicsGetCurrentContext(); CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height); CGContextAddEllipseInRect(ctx, rect); CGContextClip(ctx); [image drawInRect:rect]; UIImage *circleImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return circleImage; } @end @implementation UIImage (TZGif) + (UIImage *)sd_tz_animatedGIFWithData:(NSData *)data { if (!data) { return nil; } CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); size_t count = CGImageSourceGetCount(source); UIImage *animatedImage; if (count <= 1) { animatedImage = [[UIImage alloc] initWithData:data]; } else { // images数组过大时内存会飙升,在这里限制下最大count NSInteger maxCount = [TZImagePickerConfig sharedInstance].gifPreviewMaxImagesCount ?: 50; NSInteger interval = MAX((count + maxCount / 2) / maxCount, 1); NSMutableArray *images = [NSMutableArray array]; NSTimeInterval duration = 0.0f; for (size_t i = 0; i < count; i+=interval) { CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL); if (!image) { continue; } duration += [self sd_frameDurationAtIndex:i source:source] * MIN(interval, 3); [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]]; CGImageRelease(image); } if (!duration) { duration = (1.0f / 10.0f) * count; } animatedImage = [UIImage animatedImageWithImages:images duration:duration]; } CFRelease(source); return animatedImage; } + (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source { float frameDuration = 0.1f; CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil); NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties; NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary]; NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime]; if (delayTimeUnclampedProp) { frameDuration = [delayTimeUnclampedProp floatValue]; } else { NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime]; if (delayTimeProp) { frameDuration = [delayTimeProp floatValue]; } } // Many annoying ads specify a 0 duration to make an image flash as quickly as possible. // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify // a duration of <= 10 ms. See and // for more information. if (frameDuration < 0.011f) { frameDuration = 0.100f; } CFRelease(cfFrameProperties); return frameDuration; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImageManager.h ================================================ // // TZImageManager.h // TZImagePickerController // // Created by 谭真 on 16/1/4. // Copyright © 2016年 谭真. All rights reserved. // 图片资源获取管理类 #import #import #import #import #import "TZAssetModel.h" @class TZAlbumModel,TZAssetModel; @protocol TZImagePickerControllerDelegate; @interface TZImageManager : NSObject @property (nonatomic, strong) PHCachingImageManager *cachingImageManager; + (instancetype)manager NS_SWIFT_NAME(default()); + (void)deallocManager; @property (weak, nonatomic) id pickerDelegate; @property (nonatomic, assign) BOOL shouldFixOrientation; @property (nonatomic, assign) BOOL isPreviewNetworkImage; /// Default is 600px / 默认600像素宽 @property (nonatomic, assign) CGFloat photoPreviewMaxWidth; /// The pixel width of output image, Default is 828px / 导出图片的宽度,默认828像素宽 @property (nonatomic, assign) CGFloat photoWidth; /// Default is 4, Use in photos collectionView in TZPhotoPickerController /// 默认4列, TZPhotoPickerController中的照片collectionView @property (nonatomic, assign) NSInteger columnNumber; /// Sort photos ascending by modificationDate,Default is YES /// 对照片排序,按修改时间升序,默认是YES。如果设置为NO,最新的照片会显示在最前面,内部的拍照按钮会排在第一个 @property (nonatomic, assign) BOOL sortAscendingByModificationDate; /// Minimum selectable photo width, Default is 0 /// 最小可选中的图片宽度,默认是0,小于这个宽度的图片不可选中 @property (nonatomic, assign) NSInteger minPhotoWidthSelectable; @property (nonatomic, assign) NSInteger minPhotoHeightSelectable; @property (nonatomic, assign) BOOL hideWhenCanNotSelect; /// Return YES if Authorized 返回YES如果得到了授权 - (BOOL)authorizationStatusAuthorized; - (void)requestAuthorizationWithCompletion:(void (^)(void))completion; - (BOOL)isPHAuthorizationStatusLimited; /// Get Album 获得相册/相册数组 - (void)getCameraRollAlbumWithFetchAssets:(BOOL)needFetchAssets completion:(void (^)(TZAlbumModel *model))completion; - (void)getCameraRollAlbum:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage needFetchAssets:(BOOL)needFetchAssets completion:(void (^)(TZAlbumModel *model))completion __attribute__((deprecated("Use -getCameraRollAlbumWithFetchAssets:completion:. You can config allowPickingImage、allowPickingVideo by TZImagePickerConfig"))); - (void)getAllAlbums:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage needFetchAssets:(BOOL)needFetchAssets completion:(void (^)(NSArray *models))completion __attribute__((deprecated("Use -getAllAlbumsWithFetchAssets:completion:. You can config allowPickingImage、allowPickingVideo by TZImagePickerConfig"))); - (void)getAllAlbumsWithFetchAssets:(BOOL)needFetchAssets completion:(void (^)(NSArray *models))completion; /// Get Assets 获得Asset数组 - (void)getAssetsFromFetchResult:(PHFetchResult *)result completion:(void (^)(NSArray *models))completion; - (void)getAssetsFromFetchResult:(PHFetchResult *)result allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage completion:(void (^)(NSArray *models))completion __attribute__((deprecated("Use -getAssetsFromFetchResult:completion:. You can config allowPickingImage、allowPickingVideo by TZImagePickerConfig"))); - (void)getAssetFromFetchResult:(PHFetchResult *)result atIndex:(NSInteger)index allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage completion:(void (^)(TZAssetModel *model))completion __attribute__((deprecated("Use -getAssetFromFetchResult:atIndex:completion:. You can config allowPickingImage、allowPickingVideo by TZImagePickerConfig"))); - (void)getAssetFromFetchResult:(PHFetchResult *)result atIndex:(NSInteger)index completion:(void (^)(TZAssetModel *model))completion; /// Get photo 获得照片 - (PHImageRequestID)getPostImageWithAlbumModel:(TZAlbumModel *)model completion:(void (^)(UIImage *postImage))completion; - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion; - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion; - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler networkAccessAllowed:(BOOL)networkAccessAllowed; - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler networkAccessAllowed:(BOOL)networkAccessAllowed; - (PHImageRequestID)requestImageDataForAsset:(PHAsset *)asset completion:(void (^)(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler; /// Get full Image 获取原图 /// 如下两个方法completion一般会调多次,一般会先返回缩略图,再返回原图(详见方法内部使用的系统API的说明),如果info[PHImageResultIsDegradedKey] 为 YES,则表明当前返回的是缩略图,否则是原图。 - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info))completion; - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset newCompletion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion; - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler newCompletion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion; // 该方法中,completion只会走一次 - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion; - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion; /// Get Image For VideoURL - (UIImage *)getImageWithVideoURL:(NSURL *)videoURL; /// Save photo 保存照片 - (void)savePhotoWithImage:(UIImage *)image completion:(void (^)(PHAsset *asset, NSError *error))completion; - (void)savePhotoWithImage:(UIImage *)image location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion; - (void)savePhotoWithImage:(UIImage *)image meta:(NSDictionary *)meta location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion; /// Save video 保存视频 - (void)saveVideoWithUrl:(NSURL *)url completion:(void (^)(PHAsset *asset, NSError *error))completion; - (void)saveVideoWithUrl:(NSURL *)url location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion; /// Get video 获得视频 - (void)getVideoWithAsset:(PHAsset *)asset completion:(void (^)(AVPlayerItem * playerItem, NSDictionary * info))completion; - (void)getVideoWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(AVPlayerItem *, NSDictionary *))completion; /// Export video 导出视频 presetName: 预设名字,默认值是AVAssetExportPreset640x480 - (void)getVideoOutputPathWithAsset:(PHAsset *)asset success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure; - (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure; - (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName timeRange:(CMTimeRange)timeRange success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure; /// 新的导出视频API,解决iOS14 iCloud视频导出失败的问题,未大量测试,请大家多多测试,有问题群里反馈 - (void)requestVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure; /// 得到视频原始文件地址 - (void)requestVideoURLWithAsset:(PHAsset *)asset success:(void (^)(NSURL *videoURL))success failure:(void (^)(NSDictionary* info))failure; /// Get photo bytes 获得一组照片的大小 - (void)getPhotosBytesWithArray:(NSArray *)photos completion:(void (^)(NSString *totalBytes))completion; - (BOOL)isCameraRollAlbum:(PHAssetCollection *)metadata; /// 检查照片大小是否满足最小要求 - (BOOL)isPhotoSelectableWithAsset:(PHAsset *)asset; /// 检查照片能否被选中 - (BOOL)isAssetCannotBeSelected:(PHAsset *)asset; /// 修正图片转向 - (UIImage *)fixOrientation:(UIImage *)aImage; /// 获取asset的资源类型 - (TZAssetModelMediaType)getAssetType:(PHAsset *)asset; /// 缩放图片至新尺寸 - (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)size; /// 判断asset是否是视频 - (BOOL)isVideo:(PHAsset *)asset; /// for TZImagePreviewController - (NSString *)getNewTimeFromDurationSecond:(NSInteger)duration; - (TZAssetModel *)createModelWithAsset:(PHAsset *)asset; @end //@interface TZSortDescriptor : NSSortDescriptor // //@end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImageManager.m ================================================ // // TZImageManager.m // TZImagePickerController // // Created by 谭真 on 16/1/4. // Copyright © 2016年 谭真. All rights reserved. // #import "TZImageManager.h" #import "TZAssetModel.h" #import "TZImagePickerController.h" #import @interface TZImageManager () #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @end @implementation TZImageManager CGSize AssetGridThumbnailSize; CGFloat TZScreenWidth; CGFloat TZScreenScale; static TZImageManager *manager; static dispatch_once_t onceToken; + (instancetype)manager { dispatch_once(&onceToken, ^{ manager = [[self alloc] init]; // manager.cachingImageManager = [[PHCachingImageManager alloc] init]; // manager.cachingImageManager.allowsCachingHighQualityImages = YES; [manager configTZScreenWidth]; }); return manager; } + (void)deallocManager { onceToken = 0; manager = nil; } - (void)setPhotoWidth:(CGFloat)photoWidth { _photoWidth = photoWidth; TZScreenWidth = photoWidth / 2; } - (void)setColumnNumber:(NSInteger)columnNumber { [self configTZScreenWidth]; _columnNumber = columnNumber; CGFloat margin = 4; CGFloat itemWH = (TZScreenWidth - 2 * margin - 4) / columnNumber - margin; AssetGridThumbnailSize = CGSizeMake(itemWH * TZScreenScale, itemWH * TZScreenScale); } - (void)configTZScreenWidth { TZScreenWidth = [UIScreen mainScreen].bounds.size.width; // 测试发现,如果scale在plus真机上取到3.0,内存会增大特别多。故这里写死成2.0 TZScreenScale = 2.0; if (TZScreenWidth > 700) { TZScreenScale = 1.5; } } - (BOOL)isPHAuthorizationStatusLimited { if (@available(iOS 14,*)) { NSInteger status = [PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite]; if (status == PHAuthorizationStatusLimited) { return YES; } } return NO; } /// Return YES if Authorized 返回YES如果得到了授权 - (BOOL)authorizationStatusAuthorized { if (self.isPreviewNetworkImage) { return YES; } NSInteger status = [PHPhotoLibrary authorizationStatus]; if (status == 0) { /** * 当某些情况下AuthorizationStatus == AuthorizationStatusNotDetermined时,无法弹出系统首次使用的授权alertView,系统应用设置里亦没有相册的设置,此时将无法使用,故作以下操作,弹出系统首次使用的授权alertView */ [self requestAuthorizationWithCompletion:nil]; } return status == 3; } - (void)requestAuthorizationWithCompletion:(void (^)(void))completion { void (^callCompletionBlock)(void) = ^(){ dispatch_async(dispatch_get_main_queue(), ^{ if (completion) { completion(); } }); }; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { callCompletionBlock(); }]; }); } #pragma mark - Get Album - (void)getCameraRollAlbum:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage needFetchAssets:(BOOL)needFetchAssets completion:(void (^)(TZAlbumModel *model))completion { TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance]; config.allowPickingVideo = allowPickingVideo; config.allowPickingImage = allowPickingImage; [self getCameraRollAlbumWithFetchAssets:needFetchAssets completion:completion]; } /// Get Album 获得相册/相册数组 - (void)getCameraRollAlbumWithFetchAssets:(BOOL)needFetchAssets completion:(void (^)(TZAlbumModel *model))completion { __block TZAlbumModel *model; TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance]; PHFetchOptions *option = [[PHFetchOptions alloc] init]; if (!config.allowPickingVideo) option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeImage]; if (!config.allowPickingImage) option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeVideo]; // option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"modificationDate" ascending:self.sortAscendingByModificationDate]]; if (!self.sortAscendingByModificationDate) { option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:self.sortAscendingByModificationDate]]; } PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil]; for (PHAssetCollection *collection in smartAlbums) { // 有可能是PHCollectionList类的的对象,过滤掉 if (![collection isKindOfClass:[PHAssetCollection class]]) continue; // 过滤空相册 if (collection.estimatedAssetCount <= 0) continue; if ([self isCameraRollAlbum:collection]) { PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:option]; model = [self modelWithResult:fetchResult collection:collection isCameraRoll:YES needFetchAssets:needFetchAssets options:option]; if (completion) completion(model); break; } } } - (void)getAllAlbums:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage needFetchAssets:(BOOL)needFetchAssets completion:(void (^)(NSArray *))completion { TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance]; config.allowPickingVideo = allowPickingVideo; config.allowPickingImage = allowPickingImage; [self getAllAlbumsWithFetchAssets:needFetchAssets completion:completion]; } - (void)getAllAlbumsWithFetchAssets:(BOOL)needFetchAssets completion:(void (^)(NSArray *))completion { TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance]; NSMutableArray *albumArr = [NSMutableArray array]; PHFetchOptions *option = [[PHFetchOptions alloc] init]; if (!config.allowPickingVideo) option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeImage]; if (!config.allowPickingImage) option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeVideo]; // option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"modificationDate" ascending:self.sortAscendingByModificationDate]]; if (!self.sortAscendingByModificationDate) { option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:self.sortAscendingByModificationDate]]; } // 我的照片流 1.6.10重新加入.. PHFetchResult *myPhotoStreamAlbum = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumMyPhotoStream options:nil]; PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil]; PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil]; PHFetchResult *syncedAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumSyncedAlbum options:nil]; PHFetchResult *sharedAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumCloudShared options:nil]; NSArray *allAlbums = @[myPhotoStreamAlbum,smartAlbums,topLevelUserCollections,syncedAlbums,sharedAlbums]; for (PHFetchResult *fetchResult in allAlbums) { for (PHAssetCollection *collection in fetchResult) { // 有可能是PHCollectionList类的的对象,过滤掉 if (![collection isKindOfClass:[PHAssetCollection class]]) continue; // 过滤空相册 if (collection.estimatedAssetCount <= 0 && ![self isCameraRollAlbum:collection]) continue; PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:option]; if (fetchResult.count < 1 && ![self isCameraRollAlbum:collection]) continue; if ([self.pickerDelegate respondsToSelector:@selector(isAlbumCanSelect:result:)]) { if (![self.pickerDelegate isAlbumCanSelect:collection.localizedTitle result:fetchResult]) { continue; } } if (collection.assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumAllHidden) continue; if (collection.assetCollectionSubtype == 1000000201) continue; //『最近删除』相册 if ([self isCameraRollAlbum:collection]) { [albumArr insertObject:[self modelWithResult:fetchResult collection:collection isCameraRoll:YES needFetchAssets:needFetchAssets options:option] atIndex:0]; } else { [albumArr addObject:[self modelWithResult:fetchResult collection:collection isCameraRoll:NO needFetchAssets:needFetchAssets options:option]]; } } } if (completion) { completion(albumArr); } } #pragma mark - Get Assets /// Get Assets 获得照片数组 - (void)getAssetsFromFetchResult:(PHFetchResult *)result allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage completion:(void (^)(NSArray *))completion { TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance]; config.allowPickingVideo = allowPickingVideo; config.allowPickingImage = allowPickingImage; return [self getAssetsFromFetchResult:result completion:completion]; } - (void)getAssetsFromFetchResult:(PHFetchResult *)result completion:(void (^)(NSArray *))completion { TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance]; NSMutableArray *photoArr = [NSMutableArray array]; [result enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL * _Nonnull stop) { TZAssetModel *model = [self assetModelWithAsset:asset allowPickingVideo:config.allowPickingVideo allowPickingImage:config.allowPickingImage]; if (model) { [photoArr addObject:model]; } }]; if (completion) completion(photoArr); } /// Get asset at index 获得下标为index的单个照片 /// if index beyond bounds, return nil in callback 如果索引越界, 在回调中返回 nil - (void)getAssetFromFetchResult:(PHFetchResult *)result atIndex:(NSInteger)index allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage completion:(void (^)(TZAssetModel *))completion { TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance]; config.allowPickingVideo = allowPickingVideo; config.allowPickingImage = allowPickingImage; [self getAssetFromFetchResult:result atIndex:index allowPickingVideo:config.allowPickingVideo allowPickingImage:config.allowPickingImage completion:completion]; } - (void)getAssetFromFetchResult:(PHFetchResult *)result atIndex:(NSInteger)index completion:(void (^)(TZAssetModel *))completion { PHAsset *asset; @try { asset = result[index]; } @catch (NSException* e) { if (completion) completion(nil); return; } TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance]; TZAssetModel *model = [self assetModelWithAsset:asset allowPickingVideo:config.allowPickingVideo allowPickingImage:config.allowPickingImage]; if (completion) completion(model); } - (TZAssetModel *)assetModelWithAsset:(PHAsset *)asset allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage { BOOL canSelect = YES; if ([self.pickerDelegate respondsToSelector:@selector(isAssetCanSelect:)]) { canSelect = [self.pickerDelegate isAssetCanSelect:asset]; } if ([self.pickerDelegate respondsToSelector:@selector(isAssetCanBeDisplayed:)]){ canSelect = [self.pickerDelegate isAssetCanBeDisplayed:asset]; } if (!canSelect) return nil; TZAssetModel *model; TZAssetModelMediaType type = [self getAssetType:asset]; if (!allowPickingVideo && type == TZAssetModelMediaTypeVideo) return nil; if (!allowPickingImage && type == TZAssetModelMediaTypePhoto) return nil; if (!allowPickingImage && type == TZAssetModelMediaTypePhotoGif) return nil; PHAsset *phAsset = (PHAsset *)asset; if (self.hideWhenCanNotSelect) { // 过滤掉尺寸不满足要求的图片 if (![self isPhotoSelectableWithAsset:phAsset]) { return nil; } } NSString *timeLength = type == TZAssetModelMediaTypeVideo ? [NSString stringWithFormat:@"%0.0f",phAsset.duration] : @""; timeLength = [self getNewTimeFromDurationSecond:timeLength.integerValue]; model = [TZAssetModel modelWithAsset:asset type:type timeLength:timeLength]; return model; } - (TZAssetModelMediaType)getAssetType:(PHAsset *)asset { TZAssetModelMediaType type = TZAssetModelMediaTypePhoto; PHAsset *phAsset = (PHAsset *)asset; if (phAsset.mediaType == PHAssetMediaTypeVideo) type = TZAssetModelMediaTypeVideo; else if (phAsset.mediaType == PHAssetMediaTypeAudio) type = TZAssetModelMediaTypeAudio; else if (phAsset.mediaType == PHAssetMediaTypeImage) { if (@available(iOS 9.1, *)) { // if (asset.mediaSubtypes == PHAssetMediaSubtypePhotoLive) type = TZAssetModelMediaTypeLivePhoto; } // Gif if ([[phAsset valueForKey:@"filename"] hasSuffix:@"GIF"]) { type = TZAssetModelMediaTypePhotoGif; } } return type; } - (NSString *)getNewTimeFromDurationSecond:(NSInteger)duration { NSString *newTime; if (duration < 10) { newTime = [NSString stringWithFormat:@"0:0%zd",duration]; } else if (duration < 60) { newTime = [NSString stringWithFormat:@"0:%zd",duration]; } else { NSInteger min = duration / 60; NSInteger sec = duration - (min * 60); if (sec < 10) { newTime = [NSString stringWithFormat:@"%zd:0%zd",min,sec]; } else { newTime = [NSString stringWithFormat:@"%zd:%zd",min,sec]; } } return newTime; } /// Get photo bytes 获得一组照片的大小 - (void)getPhotosBytesWithArray:(NSArray *)photos completion:(void (^)(NSString *totalBytes))completion { if (!photos || !photos.count) { if (completion) completion(@"0B"); return; } __block NSInteger dataLength = 0; __block NSInteger assetCount = 0; for (NSInteger i = 0; i < photos.count; i++) { TZAssetModel *model = photos[i]; PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; options.resizeMode = PHImageRequestOptionsResizeModeFast; options.networkAccessAllowed = YES; if (model.type == TZAssetModelMediaTypePhotoGif) { options.version = PHImageRequestOptionsVersionOriginal; } [[PHImageManager defaultManager] requestImageDataForAsset:model.asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { if (model.type != TZAssetModelMediaTypeVideo) dataLength += imageData.length; assetCount ++; if (assetCount >= photos.count) { NSString *bytes = [self getBytesFromDataLength:dataLength]; if (completion) completion(bytes); } }]; } } - (NSString *)getBytesFromDataLength:(NSInteger)dataLength { NSString *bytes; if (dataLength >= 0.1 * (1024 * 1024)) { bytes = [NSString stringWithFormat:@"%0.1fM",dataLength/1024/1024.0]; } else if (dataLength >= 1024) { bytes = [NSString stringWithFormat:@"%0.0fK",dataLength/1024.0]; } else { bytes = [NSString stringWithFormat:@"%zdB",dataLength]; } return bytes; } #pragma mark - Get Photo /// Get photo 获得照片本身 - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *, NSDictionary *, BOOL isDegraded))completion { return [self getPhotoWithAsset:asset completion:completion progressHandler:nil networkAccessAllowed:YES]; } - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion { return [self getPhotoWithAsset:asset photoWidth:photoWidth completion:completion progressHandler:nil networkAccessAllowed:YES]; } - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler networkAccessAllowed:(BOOL)networkAccessAllowed { CGFloat fullScreenWidth = TZScreenWidth; if (_photoPreviewMaxWidth > 0 && fullScreenWidth > _photoPreviewMaxWidth) { fullScreenWidth = _photoPreviewMaxWidth; } return [self getPhotoWithAsset:asset photoWidth:fullScreenWidth completion:completion progressHandler:progressHandler networkAccessAllowed:networkAccessAllowed]; } - (PHImageRequestID)requestImageDataForAsset:(PHAsset *)asset completion:(void (^)(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler { PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { dispatch_async(dispatch_get_main_queue(), ^{ if (progressHandler) { progressHandler(progress, error, stop, info); } }); }; options.networkAccessAllowed = YES; options.resizeMode = PHImageRequestOptionsResizeModeFast; int32_t imageRequestID = [[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { if (completion) completion(imageData,dataUTI,orientation,info); }]; return imageRequestID; } - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler networkAccessAllowed:(BOOL)networkAccessAllowed { CGSize imageSize; if (photoWidth < TZScreenWidth && photoWidth < _photoPreviewMaxWidth) { imageSize = AssetGridThumbnailSize; } else { PHAsset *phAsset = (PHAsset *)asset; CGFloat aspectRatio = phAsset.pixelWidth / (CGFloat)phAsset.pixelHeight; CGFloat pixelWidth = photoWidth * TZScreenScale; // 超宽图片 if (aspectRatio > 1.8) { pixelWidth = pixelWidth * aspectRatio; } // 超高图片 if (aspectRatio < 0.2) { pixelWidth = pixelWidth * 0.5; } CGFloat pixelHeight = pixelWidth / aspectRatio; imageSize = CGSizeMake(pixelWidth, pixelHeight); } // 修复获取图片时出现的瞬间内存过高问题 // 下面两行代码,来自hsjcom,他的github是:https://github.com/hsjcom 表示感谢 PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init]; option.resizeMode = PHImageRequestOptionsResizeModeFast; int32_t imageRequestID = [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:imageSize contentMode:PHImageContentModeAspectFill options:option resultHandler:^(UIImage *result, NSDictionary *info) { BOOL cancelled = [[info objectForKey:PHImageCancelledKey] boolValue]; if (!cancelled && result) { result = [self fixOrientation:result]; if (completion) completion(result,info,[[info objectForKey:PHImageResultIsDegradedKey] boolValue]); } // Download image from iCloud / 从iCloud下载图片 if ([info objectForKey:PHImageResultIsInCloudKey] && !result && networkAccessAllowed) { PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { dispatch_async(dispatch_get_main_queue(), ^{ if (progressHandler) { progressHandler(progress, error, stop, info); } }); }; options.networkAccessAllowed = YES; options.resizeMode = PHImageRequestOptionsResizeModeFast; [[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { UIImage *resultImage = [UIImage imageWithData:imageData]; if (![TZImagePickerConfig sharedInstance].notScaleImage) { resultImage = [self scaleImage:resultImage toSize:imageSize]; } if (!resultImage && result) { resultImage = result; } resultImage = [self fixOrientation:resultImage]; if (completion) completion(resultImage,info,NO); }]; } }]; return imageRequestID; } /// Get postImage / 获取封面图 - (PHImageRequestID)getPostImageWithAlbumModel:(TZAlbumModel *)model completion:(void (^)(UIImage *))completion { id asset = [model.result lastObject]; if (!self.sortAscendingByModificationDate) { asset = [model.result firstObject]; } if (!asset) { return -1; } return [[TZImageManager manager] getPhotoWithAsset:asset photoWidth:80 completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { if (completion) completion(photo); }]; } /// Get Original Photo / 获取原图 - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info))completion { return [self getOriginalPhotoWithAsset:asset newCompletion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { if (completion) { completion(photo,info); } }]; } - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset newCompletion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion { return [self getOriginalPhotoWithAsset:asset progressHandler:nil newCompletion:completion]; } - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler newCompletion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion { PHImageRequestOptions *option = [[PHImageRequestOptions alloc]init]; option.networkAccessAllowed = YES; if (progressHandler) { [option setProgressHandler:progressHandler]; } option.resizeMode = PHImageRequestOptionsResizeModeFast; return [[PHImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) { BOOL cancelled = [[info objectForKey:PHImageCancelledKey] boolValue]; if (!cancelled && imageData) { UIImage *result = [self fixOrientation:[UIImage imageWithData:imageData]]; BOOL isDegraded = [[info objectForKey:PHImageResultIsDegradedKey] boolValue]; if (completion) completion(result,info,isDegraded); } }]; } - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion { return [self getOriginalPhotoDataWithAsset:asset progressHandler:nil completion:completion]; } - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion { PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init]; option.networkAccessAllowed = YES; if ([[asset valueForKey:@"filename"] hasSuffix:@"GIF"]) { // if version isn't PHImageRequestOptionsVersionOriginal, the gif may cann't play option.version = PHImageRequestOptionsVersionOriginal; } [option setProgressHandler:progressHandler]; option.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; return [[PHImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { BOOL cancelled = [[info objectForKey:PHImageCancelledKey] boolValue]; if (!cancelled && imageData) { if (completion) completion(imageData,info,NO); } }]; } - (UIImage *)getImageWithVideoURL:(NSURL *)videoURL { AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil]; if (!asset) { return nil; } AVAssetImageGenerator *generator =[[AVAssetImageGenerator alloc] initWithAsset:asset]; generator.appliesPreferredTrackTransform = YES; generator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels; CFTimeInterval time = 0.1; CGImageRef imageRef = [generator copyCGImageAtTime:CMTimeMake(time, 60) actualTime:NULL error:nil]; UIImage *image = [[UIImage alloc] initWithCGImage:imageRef]; CGImageRelease(imageRef); return image; } #pragma mark - Save photo - (void)savePhotoWithImage:(UIImage *)image completion:(void (^)(PHAsset *asset, NSError *error))completion { [self savePhotoWithImage:image location:nil completion:completion]; } - (void)savePhotoWithImage:(UIImage *)image location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion { __block NSString *localIdentifier = nil; [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromImage:image]; localIdentifier = request.placeholderForCreatedAsset.localIdentifier; if (location) { request.location = location; } request.creationDate = [NSDate date]; } completionHandler:^(BOOL success, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (success && completion && localIdentifier) { [self fetchAssetByIocalIdentifier:localIdentifier retryCount:10 completion:completion]; } else { if (error) { NSLog(@"保存照片出错:%@",error.localizedDescription); } if (completion) { completion(nil, error); } } }); }]; } - (void)savePhotoWithImage:(UIImage *)image meta:(NSDictionary *)meta location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion { NSData *imageData = UIImageJPEGRepresentation(image, 1.0f); CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); NSDateFormatter *formater = [[NSDateFormatter alloc] init]; [formater setDateFormat:@"yyyy-MM-dd-HH:mm:ss-SSS"]; NSString *path = [NSTemporaryDirectory() stringByAppendingFormat:@"image-%@.jpg", [formater stringFromDate:[NSDate date]]]; NSURL *tmpURL = [NSURL fileURLWithPath:path]; CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)tmpURL, kUTTypeJPEG, 1, NULL); CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef)meta); CGImageDestinationFinalize(destination); CFRelease(source); CFRelease(destination); __block NSString *localIdentifier = nil; [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:tmpURL]; localIdentifier = request.placeholderForCreatedAsset.localIdentifier; if (location) { request.location = location; } request.creationDate = [NSDate date]; } completionHandler:^(BOOL success, NSError *error) { [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; dispatch_async(dispatch_get_main_queue(), ^{ if (success && completion && localIdentifier) { [self fetchAssetByIocalIdentifier:localIdentifier retryCount:10 completion:completion]; } else { if (error) { NSLog(@"保存照片出错:%@",error.localizedDescription); } if (completion) { completion(nil, error); } } }); }]; } - (void)fetchAssetByIocalIdentifier:(NSString *)localIdentifier retryCount:(NSInteger)retryCount completion:(void (^)(PHAsset *asset, NSError *error))completion { PHAsset *asset = [[PHAsset fetchAssetsWithLocalIdentifiers:@[localIdentifier] options:nil] firstObject]; if (asset || retryCount <= 0) { if (completion) { completion(asset, nil); } return; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self fetchAssetByIocalIdentifier:localIdentifier retryCount:retryCount - 1 completion:completion]; }); } #pragma mark - Save video - (void)saveVideoWithUrl:(NSURL *)url completion:(void (^)(PHAsset *asset, NSError *error))completion { [self saveVideoWithUrl:url location:nil completion:completion]; } - (void)saveVideoWithUrl:(NSURL *)url location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion { __block NSString *localIdentifier = nil; [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url]; localIdentifier = request.placeholderForCreatedAsset.localIdentifier; if (location) { request.location = location; } request.creationDate = [NSDate date]; } completionHandler:^(BOOL success, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (success && completion && localIdentifier) { [self fetchAssetByIocalIdentifier:localIdentifier retryCount:10 completion:completion]; } else { if (error) { NSLog(@"保存视频出错:%@",error.localizedDescription); } if (completion) { completion(nil, error); } } }); }]; } #pragma mark - Get Video /// Get Video / 获取视频 - (void)getVideoWithAsset:(PHAsset *)asset completion:(void (^)(AVPlayerItem *, NSDictionary *))completion { [self getVideoWithAsset:asset progressHandler:nil completion:completion]; } - (void)getVideoWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(AVPlayerItem *, NSDictionary *))completion { PHVideoRequestOptions *option = [[PHVideoRequestOptions alloc] init]; option.networkAccessAllowed = YES; option.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { dispatch_async(dispatch_get_main_queue(), ^{ if (progressHandler) { progressHandler(progress, error, stop, info); } }); }; [[PHImageManager defaultManager] requestPlayerItemForVideo:asset options:option resultHandler:^(AVPlayerItem *playerItem, NSDictionary *info) { if (completion) completion(playerItem,info); }]; } #pragma mark - Export video /// Export Video / 导出视频 - (void)getVideoOutputPathWithAsset:(PHAsset *)asset success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure { [self getVideoOutputPathWithAsset:asset presetName:AVAssetExportPresetMediumQuality success:success failure:failure]; } - (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure { [self getVideoOutputPathWithAsset:asset presetName:presetName timeRange:kCMTimeRangeZero success:success failure:failure]; } - (void)startExportVideoWithVideoAsset:(AVURLAsset *)videoAsset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure { [self startExportVideoWithVideoAsset:videoAsset timeRange:kCMTimeRangeZero presetName:presetName success:success failure:failure]; } - (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName timeRange:(CMTimeRange)timeRange success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure { if (@available(iOS 14.0, *)) { [self requestVideoOutputPathWithAsset:asset presetName:presetName timeRange:timeRange success:success failure:failure]; return; } [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:[self getVideoRequestOptions] resultHandler:^(AVAsset* avasset, AVAudioMix* audioMix, NSDictionary* info){ // NSLog(@"Info:\n%@",info); AVURLAsset *videoAsset = (AVURLAsset*)avasset; // NSLog(@"AVAsset URL: %@",myAsset.URL); [self startExportVideoWithVideoAsset:videoAsset timeRange:timeRange presetName:presetName success:success failure:failure]; }]; } - (void)startExportVideoWithVideoAsset:(AVURLAsset *)videoAsset timeRange:(CMTimeRange)timeRange presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure { if (!presetName) { presetName = AVAssetExportPresetMediumQuality; } // Find compatible presets by video asset. NSArray *presets = [AVAssetExportSession exportPresetsCompatibleWithAsset:videoAsset]; // Begin to compress video // Now we just compress to low resolution if it supports // If you need to upload to the server, but server does't support to upload by streaming, // You can compress the resolution to lower. Or you can support more higher resolution. if ([presets containsObject:presetName]) { AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:videoAsset presetName:presetName]; NSString *outputPath = [self getVideoOutputPath]; // Optimize for network use. session.shouldOptimizeForNetworkUse = true; if (!CMTimeRangeEqual(timeRange, kCMTimeRangeZero)) { session.timeRange = timeRange; } NSArray *supportedTypeArray = session.supportedFileTypes; if ([supportedTypeArray containsObject:AVFileTypeMPEG4]) { session.outputFileType = AVFileTypeMPEG4; } else if (supportedTypeArray.count == 0) { if (failure) { failure(@"该视频类型暂不支持导出", nil); } NSLog(@"No supported file types 视频类型暂不支持导出"); return; } else { session.outputFileType = [supportedTypeArray objectAtIndex:0]; if (videoAsset.URL && videoAsset.URL.lastPathComponent) { outputPath = [outputPath stringByReplacingOccurrencesOfString:@".mp4" withString:[NSString stringWithFormat:@"-%@", videoAsset.URL.lastPathComponent]]; } } // NSLog(@"video outputPath = %@",outputPath); session.outputURL = [NSURL fileURLWithPath:outputPath]; if (![[NSFileManager defaultManager] fileExistsAtPath:[NSHomeDirectory() stringByAppendingFormat:@"/tmp"]]) { [[NSFileManager defaultManager] createDirectoryAtPath:[NSHomeDirectory() stringByAppendingFormat:@"/tmp"] withIntermediateDirectories:YES attributes:nil error:nil]; } if ([TZImagePickerConfig sharedInstance].needFixComposition) { AVMutableVideoComposition *videoComposition = [self fixedCompositionWithAsset:videoAsset]; if (videoComposition.renderSize.width) { // 修正视频转向 session.videoComposition = videoComposition; } } // Begin to export video to the output path asynchronously. [session exportAsynchronouslyWithCompletionHandler:^(void) { [self handleVideoExportResult:session outputPath:outputPath success:success failure:failure]; }]; } else { if (failure) { NSString *errorMessage = [NSString stringWithFormat:@"当前设备不支持该预设:%@", presetName]; failure(errorMessage, nil); } } } - (void)requestVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure { [self requestVideoOutputPathWithAsset:asset presetName:presetName timeRange:kCMTimeRangeZero success:success failure:failure]; } - (void)requestVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName timeRange:(CMTimeRange)timeRange success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure { if (!presetName) { presetName = AVAssetExportPresetMediumQuality; } [[PHImageManager defaultManager] requestExportSessionForVideo:asset options:[self getVideoRequestOptions] exportPreset:presetName resultHandler:^(AVAssetExportSession *_Nullable exportSession, NSDictionary *_Nullable info) { NSString *outputPath = [self getVideoOutputPath]; exportSession.outputURL = [NSURL fileURLWithPath:outputPath]; exportSession.shouldOptimizeForNetworkUse = NO; exportSession.outputFileType = AVFileTypeMPEG4; if (!CMTimeRangeEqual(timeRange, kCMTimeRangeZero)) { exportSession.timeRange = timeRange; } [exportSession exportAsynchronouslyWithCompletionHandler:^{ [self handleVideoExportResult:exportSession outputPath:outputPath success:success failure:failure]; }]; }]; } - (void)requestVideoURLWithAsset:(PHAsset *)asset success:(void (^)(NSURL *videoURL))success failure:(void (^)(NSDictionary* info))failure { [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:[self getVideoRequestOptions] resultHandler:^(AVAsset* avasset, AVAudioMix* audioMix, NSDictionary* info){ // NSLog(@"AVAsset URL: %@",myAsset.URL); if ([avasset isKindOfClass:[AVURLAsset class]]) { NSURL *url = [(AVURLAsset *)avasset URL]; if (success) { success(url); } } else if (failure) { failure(info); } }]; } - (void)handleVideoExportResult:(AVAssetExportSession *)session outputPath:(NSString *)outputPath success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure { dispatch_async(dispatch_get_main_queue(), ^{ switch (session.status) { case AVAssetExportSessionStatusUnknown: { NSLog(@"AVAssetExportSessionStatusUnknown"); } break; case AVAssetExportSessionStatusWaiting: { NSLog(@"AVAssetExportSessionStatusWaiting"); } break; case AVAssetExportSessionStatusExporting: { NSLog(@"AVAssetExportSessionStatusExporting"); } break; case AVAssetExportSessionStatusCompleted: { NSLog(@"AVAssetExportSessionStatusCompleted"); if (success) { success(outputPath); } } break; case AVAssetExportSessionStatusFailed: { NSLog(@"AVAssetExportSessionStatusFailed"); if (failure) { failure(@"视频导出失败", session.error); } } break; case AVAssetExportSessionStatusCancelled: { NSLog(@"AVAssetExportSessionStatusCancelled"); if (failure) { failure(@"导出任务已被取消", nil); } } break; default: break; } }); } - (PHVideoRequestOptions *)getVideoRequestOptions { PHVideoRequestOptions* options = [[PHVideoRequestOptions alloc] init]; options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic; options.networkAccessAllowed = YES; return options; } - (NSString *)getVideoOutputPath { NSDateFormatter *formater = [[NSDateFormatter alloc] init]; [formater setDateFormat:@"yyyy-MM-dd-HH-mm-ss-SSS"]; NSString *outputPath = [NSHomeDirectory() stringByAppendingFormat:@"/tmp/video-%@-%d.mp4", [formater stringFromDate:[NSDate date]], arc4random_uniform(10000000)]; return outputPath; } - (BOOL)isCameraRollAlbum:(PHAssetCollection *)metadata { NSString *versionStr = [[UIDevice currentDevice].systemVersion stringByReplacingOccurrencesOfString:@"." withString:@""]; if (versionStr.length <= 1) { versionStr = [versionStr stringByAppendingString:@"00"]; } else if (versionStr.length <= 2) { versionStr = [versionStr stringByAppendingString:@"0"]; } CGFloat version = versionStr.floatValue; // 目前已知8.0.0 ~ 8.0.2系统,拍照后的图片会保存在最近添加中 if (version >= 800 && version <= 802) { return ((PHAssetCollection *)metadata).assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumRecentlyAdded; } else { return ((PHAssetCollection *)metadata).assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumUserLibrary; } } /// 检查照片大小是否满足最小要求 - (BOOL)isPhotoSelectableWithAsset:(PHAsset *)asset { CGSize photoSize = CGSizeMake(asset.pixelWidth, asset.pixelHeight); if (self.minPhotoWidthSelectable > photoSize.width || self.minPhotoHeightSelectable > photoSize.height) { return NO; } return YES; } /// 检查照片能否被选中 - (BOOL)isAssetCannotBeSelected:(PHAsset *)asset { if ([self.pickerDelegate respondsToSelector:@selector(isAssetCanBeSelected:)]) { BOOL canSelectAsset = [self.pickerDelegate isAssetCanBeSelected:asset]; return !canSelectAsset; } return NO; } #pragma mark - Private Method - (TZAlbumModel *)modelWithResult:(PHFetchResult *)result collection:(PHAssetCollection *)collection isCameraRoll:(BOOL)isCameraRoll needFetchAssets:(BOOL)needFetchAssets options:(PHFetchOptions *)options { TZAlbumModel *model = [[TZAlbumModel alloc] init]; [model setResult:result needFetchAssets:needFetchAssets]; model.name = collection.localizedTitle; model.collection = collection; model.options = options; model.isCameraRoll = isCameraRoll; model.count = result.count; return model; } /// 缩放图片至新尺寸 - (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)size { if (image.size.width > size.width) { UIGraphicsBeginImageContext(size); [image drawInRect:CGRectMake(0, 0, size.width, size.height)]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage; /* 好像不怎么管用:https://mp.weixin.qq.com/s/CiqMlEIp1Ir2EJSDGgMooQ CGFloat maxPixelSize = MAX(size.width, size.height); CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)UIImageJPEGRepresentation(image, 0.9), nil); NSDictionary *options = @{(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways:(__bridge id)kCFBooleanTrue, (__bridge id)kCGImageSourceThumbnailMaxPixelSize:[NSNumber numberWithFloat:maxPixelSize] }; CGImageRef imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options); UIImage *newImage = [UIImage imageWithCGImage:imageRef scale:2 orientation:image.imageOrientation]; CGImageRelease(imageRef); CFRelease(sourceRef); return newImage; */ } else { return image; } } /// 判断asset是否是视频 - (BOOL)isVideo:(PHAsset *)asset { return asset.mediaType == PHAssetMediaTypeVideo; } - (TZAssetModel *)createModelWithAsset:(PHAsset *)asset { TZAssetModelMediaType type = [[TZImageManager manager] getAssetType:asset]; NSString *timeLength = type == TZAssetModelMediaTypeVideo ? [NSString stringWithFormat:@"%0.0f",asset.duration] : @""; timeLength = [[TZImageManager manager] getNewTimeFromDurationSecond:timeLength.integerValue]; TZAssetModel *model = [TZAssetModel modelWithAsset:asset type:type timeLength:timeLength]; return model; } /// 获取优化后的视频转向信息 - (AVMutableVideoComposition *)fixedCompositionWithAsset:(AVAsset *)videoAsset { AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition]; // 视频转向 int degrees = [self degressFromVideoFileWithAsset:videoAsset]; if (degrees != 0) { CGAffineTransform translateToCenter; CGAffineTransform mixedTransform; videoComposition.frameDuration = CMTimeMake(1, 30); NSArray *tracks = [videoAsset tracksWithMediaType:AVMediaTypeVideo]; AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; AVMutableVideoCompositionInstruction *roateInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; roateInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, [videoAsset duration]); AVMutableVideoCompositionLayerInstruction *roateLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack]; if (degrees == 90) { // 顺时针旋转90° translateToCenter = CGAffineTransformMakeTranslation(videoTrack.naturalSize.height, 0.0); mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2); videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.height,videoTrack.naturalSize.width); [roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero]; } else if(degrees == 180){ // 顺时针旋转180° translateToCenter = CGAffineTransformMakeTranslation(videoTrack.naturalSize.width, videoTrack.naturalSize.height); mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI); videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.width,videoTrack.naturalSize.height); [roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero]; } else if(degrees == 270){ // 顺时针旋转270° translateToCenter = CGAffineTransformMakeTranslation(0.0, videoTrack.naturalSize.width); mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2*3.0); videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.height,videoTrack.naturalSize.width); [roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero]; } roateInstruction.layerInstructions = @[roateLayerInstruction]; // 加入视频方向信息 videoComposition.instructions = @[roateInstruction]; } return videoComposition; } /// 获取视频角度 - (int)degressFromVideoFileWithAsset:(AVAsset *)asset { int degress = 0; NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; if([tracks count] > 0) { AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; CGAffineTransform t = videoTrack.preferredTransform; if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){ // Portrait degress = 90; } else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){ // PortraitUpsideDown degress = 270; } else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){ // LandscapeRight degress = 0; } else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){ // LandscapeLeft degress = 180; } } return degress; } /// 修正图片转向 - (UIImage *)fixOrientation:(UIImage *)aImage { if (!self.shouldFixOrientation) return aImage; // No-op if the orientation is already correct if (aImage.imageOrientation == UIImageOrientationUp) return aImage; // We need to calculate the proper transformation to make the image upright. // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored. CGAffineTransform transform = CGAffineTransformIdentity; switch (aImage.imageOrientation) { case UIImageOrientationDown: case UIImageOrientationDownMirrored: transform = CGAffineTransformTranslate(transform, aImage.size.width, aImage.size.height); transform = CGAffineTransformRotate(transform, M_PI); break; case UIImageOrientationLeft: case UIImageOrientationLeftMirrored: transform = CGAffineTransformTranslate(transform, aImage.size.width, 0); transform = CGAffineTransformRotate(transform, M_PI_2); break; case UIImageOrientationRight: case UIImageOrientationRightMirrored: transform = CGAffineTransformTranslate(transform, 0, aImage.size.height); transform = CGAffineTransformRotate(transform, -M_PI_2); break; default: break; } switch (aImage.imageOrientation) { case UIImageOrientationUpMirrored: case UIImageOrientationDownMirrored: transform = CGAffineTransformTranslate(transform, aImage.size.width, 0); transform = CGAffineTransformScale(transform, -1, 1); break; case UIImageOrientationLeftMirrored: case UIImageOrientationRightMirrored: transform = CGAffineTransformTranslate(transform, aImage.size.height, 0); transform = CGAffineTransformScale(transform, -1, 1); break; default: break; } // Now we draw the underlying CGImage into a new context, applying the transform // calculated above. CGContextRef ctx = CGBitmapContextCreate(NULL, aImage.size.width, aImage.size.height, CGImageGetBitsPerComponent(aImage.CGImage), 0, CGImageGetColorSpace(aImage.CGImage), CGImageGetBitmapInfo(aImage.CGImage)); CGContextConcatCTM(ctx, transform); switch (aImage.imageOrientation) { case UIImageOrientationLeft: case UIImageOrientationLeftMirrored: case UIImageOrientationRight: case UIImageOrientationRightMirrored: // Grr... CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.height,aImage.size.width), aImage.CGImage); break; default: CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.width,aImage.size.height), aImage.CGImage); break; } // And now we just create a new UIImage from the drawing context CGImageRef cgimg = CGBitmapContextCreateImage(ctx); UIImage *img = [UIImage imageWithCGImage:cgimg]; CGContextRelease(ctx); CGImageRelease(cgimg); return img; } #pragma clang diagnostic pop @end //@implementation TZSortDescriptor // //- (id)reversedSortDescriptor { // return [NSNumber numberWithBool:![TZImageManager manager].sortAscendingByModificationDate]; //} // //@end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/ar.lproj/Localizable.strings ================================================ "KEY" = "阿拉伯语"; "OK" = "حسنا"; "Back" = "الى الخلف"; "Done" = "فعله"; "Edit" = "تعديل"; "Sorry" = "آسف"; "Cancel" = "إلغاء"; "Setting" = "ضبط"; "Photos" = "الصور"; "Videos" = "أشرطة فيديو"; "Preview" = "معاينة"; "Full image" = "الصورة كاملة"; "Processing..." = "معالجة..."; "No Photos or Videos" = "لا توجد صور أو مقاطع فيديو"; "Synchronizing photos from iCloud" = "مزامنة الصور من iCloud"; "iCloud sync failed" = "iCloud فشلت المزامنة"; "Can not use camera" = "لا يمكن استخدام الكاميرا"; "Can not choose both video and photo" = "لا يمكن اختيار كل من الفيديو والصور"; "Can not choose both photo and GIF" = "لا يمكن اختيار كل من الصور و GIF"; "Select the video when in multi state, we will handle the video as a photo" = "حدد مقطع الفيديو عندما يكون في حالة متعددة، وسنعمل على معالجة مقطع الفيديو كصورة"; "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "إذا تعذّر الانتقال إلى صفحة "إعدادات الخصوصية"، فيرجى الانتقال إلى صفحة "الإعدادات" بنفسك، شكرًا لك"; "Select a maximum of %zd photos" = "حدد فقط ما يصل إلى %zd صورة"; "Select a minimum of %zd photos" = "الرجاء تحديد %zd صورة على الأقل"; "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "السماح لـ %@ بالوصول إلى الألبوم في \"الإعدادات > الخصوصية > الصور\""; "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "الرجاء السماح لـ %@ بالوصول إلى الكاميرا في \"الإعدادات > الخصوصية > الكاميرا\""; "Selected for %ld seconds" = "محدد لمدة %ld ثانية"; ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/de.lproj/Localizable.strings ================================================ "KEY" = "德语"; "OK" = "OK"; "Back" = "Zurück"; "Done" = "Erledigt"; "Edit" = "Bearbeiten"; "Sorry" = "Es tut uns leid"; "Cancel" = "Stornieren"; "Setting" = "Rahmen"; "Photos" = "Fotos"; "Videos" = "Videos"; "Preview" = "Vorschau"; "Full image" = "Vollbild"; "Processing..." = "Wird bearbeitet..."; "No Photos or Videos" = "Keine Fotos oder Videos"; "Synchronizing photos from iCloud" = "Fotos aus iCloud synchronisieren"; "iCloud sync failed" = "iCloud Synchronisierung fehlgeschlagen"; "Can not use camera" = "Kann die Kamera nicht benutzen"; "Can not choose both video and photo" = "Video und Foto können nicht ausgewählt werden"; "Can not choose both photo and GIF" = "Foto und GIF können nicht ausgewählt werden"; "Select the video when in multi state, we will handle the video as a photo" = "Wenn Sie das Video im Multi-Status auswählen, wird es als Foto behandelt"; "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Sie können nicht zur Seite mit den Datenschutz-Einstellungen springen; bitte navigieren Sie selbst zur Einstellungsseite. Vielen Dank."; "Select a maximum of %zd photos" = "Wählen Sie maximal %zd Bilder aus"; "Select a minimum of %zd photos" = "Bitte wählen Sie mindestens %zd Fotos aus"; "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Erlauben Sie %@ den Zugriff auf Ihr Album unter: „Einstellungen > Datenschutz > Fotos“"; "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Erlauben Sie %@ den Zugriff auf Ihre Kamera unter: „Einstellungen > Datenschutz > Kamera“"; "Selected for %ld seconds" = "Ausgewählt für %ld Sekunden"; ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/es.lproj/Localizable.strings ================================================ "KEY" = "西班牙语"; "OK" = "DE ACUERDO"; "Back" = "Espalda"; "Done" = "Hecho"; "Edit" = "επεξεργασία"; "Sorry" = "Lo siento"; "Cancel" = "Cancelar"; "Setting" = "Ajuste"; "Photos" = "Las fotos"; "Videos" = "Videos"; "Preview" = "Avance"; "Full image" = "Imagen completa"; "Processing..." = "Tratamiento..."; "No Photos or Videos" = "No hay fotos o videos"; "Synchronizing photos from iCloud" = "Sincronizando fotos desde iCloud"; "iCloud sync failed" = "la sincronización falló"; "Can not use camera" = "No puedo usar la camara"; "Can not choose both video and photo" = "No se puede elegir tanto el video como la foto."; "Can not choose both photo and GIF" = "No se puede elegir tanto foto como GIF"; "Select the video when in multi state, we will handle the video as a photo" = "Seleccione el vídeo en estado múltiple, trataremos el vídeo como una fotografía"; "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "No se puede saltar a la página de ajustes de privacidad, vaya a la página de ajustes manualmente, muchas gracias"; "Select a maximum of %zd photos" = "Seleccione solamente hasta %zd imágenes"; "Select a minimum of %zd photos" = "Seleccione al menos %zd fotografías"; "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Permita que %@ acceda a su galería en \"Ajustes > Privacidad > Fotografías\""; "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Permita que %@ acceda a su cámara en \"Ajustes > Privacidad > Cámara\""; "Selected for %ld seconds" = "Seleccionado para %ld segundos"; ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/fr.lproj/Localizable.strings ================================================ "KEY" = "法语"; "OK" = "D'accord"; "Back" = "Retour"; "Done" = "Terminé"; "Edit" = "Éditer"; "Sorry" = "Pardon"; "Cancel" = "Annuler"; "Setting" = "Réglage"; "Photos" = "Photos"; "Videos" = "Vidéos"; "Preview" = "Aperçu"; "Full image" = "Image complète"; "Processing..." = "En traitement..."; "No Photos or Videos" = "Aucune photo ou vidéo"; "Synchronizing photos from iCloud" = "Synchroniser des photos depuis iCloud"; "iCloud sync failed" = "iCloud échec de la synchronisation"; "Can not use camera" = "Impossible d'utiliser la caméra"; "Can not choose both video and photo" = "Impossible de choisir à la fois la vidéo et la photo"; "Can not choose both photo and GIF" = "Impossible de choisir à la fois photo et GIF"; "Select the video when in multi state, we will handle the video as a photo" = "Sélectionnez la vidéo lorsqu’elle est en état multiple, nous la traiterons comme une photo"; "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Impossible d'ouvrir la page des paramètres de confidentialité, veuillez accéder vous-même à la page des paramètres, merci"; "Select a maximum of %zd photos" = "Vous pouvez uniquement sélectionner un maximum de %zd images"; "Select a minimum of %zd photos" = "Veuillez sélectionner un minimum de %zd photos"; "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Autorisez %@ à accéder à votre album dans « Paramètres > Confidentialité > Photos »"; "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Autorisez %@ à accéder à votre appareil photo dans « Paramètres > Confidentialité > Appareil photo »"; "Selected for %ld seconds" = "Sélectionné pendant %ld secondes"; ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/ja.lproj/Localizable.strings ================================================ "KEY" = "日语"; "OK" = "OK"; "Back" = "バック"; "Done" = "完了"; "Edit" = "編集する"; "Sorry" = "ごめんなさい"; "Cancel" = "キャンセル"; "Setting" = "設定"; "Photos" = "写真"; "Videos" = "動画"; "Preview" = "プレビュー"; "Full image" = "フルイメージ"; "Processing..." = "処理..."; "No Photos or Videos" = "写真やビデオはありません"; "Synchronizing photos from iCloud" = "iCloudから写真を同期する"; "iCloud sync failed" = "iCloud同期に失敗しました"; "Can not use camera" = "カメラが使えない"; "Can not choose both video and photo" = "ビデオと写真の両方を選択することはできません"; "Can not choose both photo and GIF" = "写真とGIFの両方を選択することはできません"; "Select the video when in multi state, we will handle the video as a photo" = "多肢選択の状態で、ビデオを選択すると、ビデオをデフォルトに画像として送信します。"; "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "プライバシー設定画面にジャンプできません。手動で設定画面を表示してください。"; "Select a maximum of %zd photos" = "写真は多くとも%zd 枚選択できます。"; "Select a minimum of %zd photos" = "少なくとも %zd 枚の写真を選択してください。"; "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "iPhoneの「設定-プライバシー-写真」のオプションで、r%@の携帯電話のアルバムへのアクセス権限を許可してください。"; "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "iPhoneの「設定-プライバシー-カメラ」で、%@のカメラへのアクセス権限を許可してください。"; "Selected for %ld seconds" = "%ld 秒間選択されました"; ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/ko-KP.lproj/Localizable.strings ================================================ "KEY" = "朝鲜语"; "OK" = "그래"; "Back" = "뒤로"; "Done" = "완료"; "Edit" = "편집하다"; "Sorry" = "미안해요"; "Cancel" = "취소"; "Setting" = "설정"; "Photos" = "사진"; "Videos" = "동영상"; "Preview" = "미리 보기"; "Full image" = "전체 이미지"; "Processing..." = "처리..."; "No Photos or Videos" = "아무 사진이 나 동영상"; "Synchronizing photos from iCloud" = "ICloud에서 사진을 동기화"; "iCloud sync failed" = "iCloud동기화 실패"; "Can not use camera" = "카메라를 사용할 수 없습니다."; "Can not choose both video and photo" = "비디오와 사진 둘 다를 선택할 수 없습니다."; "Can not choose both photo and GIF" = "사진 및 GIF를 선택할 수 없습니다."; "Select the video when in multi state, we will handle the video as a photo" = "다중 선택 모드에서 비디오를 선택하면 비디오를 사진으로 처리합니다."; "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "개인 정보 보호 설정 페이지로 바로 이동할 수 없습니다. 설정 페이지로 직접 이동해 주세요. 감사합니다."; "Select a maximum of %zd photos" = "최대 %zd장의 이미지만 선택할 수 있습니다."; "Select a minimum of %zd photos" = "최소 %zd장의 사진을 선택해 주세요."; "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "\"설정 > 개인 정보 보호 > 사진\"에서 %@이(가) 앨범에 접근할 수 있도록 허용하세요."; "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "\"설정 > 개인 정보 보호 > 카메라\"에서 %@이(가) 카메라에 접근할 수 있도록 허용하세요."; "Selected for %ld seconds" = "%ld 초 동안 선택됨"; ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/pt.lproj/Localizable.strings ================================================ "KEY" = "葡萄牙语"; "OK" = "Está bem"; "Back" = "De volta"; "Done" = "Feito"; "Edit" = "editar"; "Sorry" = "Desculpa"; "Cancel" = "Cancelar"; "Setting" = "Configuração"; "Photos" = "Fotos"; "Videos" = "Vídeos"; "Preview" = "Visualizar"; "Full image" = "Imagem Completa"; "Processing..." = "Em processamento..."; "No Photos or Videos" = "Sem fotos ou vídeos"; "Synchronizing photos from iCloud" = "Sincronizando fotos do iCloud"; "iCloud sync failed" = "iCloud falha na sincronização"; "Can not use camera" = "Não pode usar a câmera"; "Can not choose both video and photo" = "Não é possível escolher vídeo e foto"; "Can not choose both photo and GIF" = "Não é possível escolher foto e GIF"; "Select the video when in multi state, we will handle the video as a photo" = "Se estiver em estado múltiplo, selecione a opção vídeo; iremos utilizar o vídeo como uma foto"; "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Não é possível avançar para a página de definições de privacidade, aceda à página de definições você mesmo, obrigado"; "Select a maximum of %zd photos" = "Selecione apenas %zd imagens,no máximo"; "Select a minimum of %zd photos" = "Selecione %zd fotos,no mínimo"; "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Permita a %@ aceder ao seu álbum em “Definições > Privacidade > Fotos”"; "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Permita a %@ aceder à sua câmara em “Definições > Privacidade > Câmara”"; "Selected for %ld seconds" = "Selecionado por %ld segundos"; ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/ru.lproj/Localizable.strings ================================================ "KEY" = "俄语"; "OK" = "Хорошо"; "Back" = "назад"; "Done" = "Готово"; "Edit" = "редактировать"; "Sorry" = "сожалею"; "Cancel" = "отменить"; "Setting" = "настройка"; "Photos" = "Фото"; "Videos" = "Видео"; "Preview" = "предварительный просмотр"; "Full image" = "Полное изображение"; "Processing..." = "Обработка ..."; "No Photos or Videos" = "Нет фото или видео"; "Synchronizing photos from iCloud" = "Синхронизация фотографий из iCloud"; "iCloud sync failed" = "iCloud сбой синхронизации"; "Can not use camera" = "Не могу использовать камеру"; "Can not choose both video and photo" = "Не могу выбрать как видео,так и фото"; "Can not choose both photo and GIF" = "Не могу выбрать фото и GIF"; "Select the video when in multi state, we will handle the video as a photo" = "В случае выбора видео при нахождении в мультирежиме видео будет обработано как фотография"; "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Не удается перейти на страницу настроек конфиденциальности. Перейдите на эту страницу самостоятельно"; "Select a maximum of %zd photos" = "Вы можете выбрать до %zd изображений"; "Select a minimum of %zd photos" = "Вы можете выбрать не менее %zd изображений"; "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Разрешите доступ %@ к вашему альбому,перейдя в Настройки > Конфиденциальность > Фото"; "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Разрешите доступ %@ к камере вашего устройства,перейдя в Настройки > Конфиденциальность > Камера"; "Selected for %ld seconds" = "Выбрано для %ld секунд"; ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/vi.lproj/Localizable.strings ================================================ "KEY" = "越南语"; "OK" = "Xác nhận"; "Back" = "Quay lại"; "Done" = "Hoàn thành"; "Edit" = "biên tập"; "Sorry" = "Xin lỗi"; "Cancel" = "Hủy"; "Setting" = "Cài đặt"; "Photos" = "Hình"; "Videos" = "Clip"; "Preview" = "Xem trước"; "Full image" = "Hình gốc"; "Processing..." = "Đang xử lý..."; "No Photos or Videos" = "Không có ảnh hoặc video"; "Can not use camera" = "Máy chụp hình không khả dụng"; "Synchronizing photos from iCloud" = "Đang đồng bộ hình ảnh từ ICloud"; "iCloud sync failed" = "iCloud đồng bộ hóa không thành công"; "Can not choose both video and photo" = "Trong lúc chọn hình ảnh không cùng lúc chọn video"; "Can not choose both photo and GIF" = "Trong lúc chọn hình ảnh không cùng lúc chọn hình GIF"; "Select the video when in multi state, we will handle the video as a photo" = "Chọn hình ảnh cùng video, video sẽ bị mặc nhận thành hình ảnh và gửi đi."; "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Không thể chuyển tự động qua trang cài đặt riêng tư, bạn hãy thoát ra cà điều chỉnh lại, cám ơn bạn."; "Select a maximum of %zd photos" = "Bạn chỉ được chọn nhiều nhất %zd tấm hình"; "Select a minimum of %zd photos" = "Chọn ít nhất %zd tấm hình"; "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Vui lòng tại mục iPhone \" Cài đặt – quyền riêng tư - Ảnh\" mở quyền cho phép %@ truy cập ảnh."; "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Vui lòng tại mục iPhone \" Cài đặt – quyền riêng tư - Ảnh\" mở quyền cho phép %@ truy cập máy ảnh"; "Selected for %ld seconds" = "Đã chọn cho %ld giây"; ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.h ================================================ // // TZImagePickerController.h // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // version 3.8.1 - 2022.04.15 // 更多信息,请前往项目的github地址:https://github.com/banchichen/TZImagePickerController /* 经过测试,比起xib的方式,把TZAssetCell改用纯代码的方式来写,滑动帧数明显提高了(约提高10帧左右) 最初发现这个问题并修复的是@小鱼周凌宇同学,她的博客地址: http://zhoulingyu.com/ 表示感谢~ 原来xib确实会导致性能问题啊...大家也要注意了... */ #import #import "TZAssetModel.h" #import "NSBundle+TZImagePicker.h" #import "TZImageManager.h" #import "TZVideoPlayerController.h" #import "TZGifPhotoPreviewController.h" #import "TZLocationManager.h" #import "TZPhotoPreviewController.h" #import "TZPhotoPreviewCell.h" #define CURRENT_SYSTEM_VERSION [[UIDevice currentDevice] systemVersion] #define SYSTEM_VERSION_GREATER_THAN_15 ([CURRENT_SYSTEM_VERSION floatValue] >= 15.0) @class TZAlbumCell, TZAssetCell; @protocol TZImagePickerControllerDelegate; @interface TZImagePickerController : UINavigationController #pragma mark - /// Use this init method / 用这个初始化方法 - (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount delegate:(id)delegate; - (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(NSInteger)columnNumber delegate:(id)delegate; - (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(NSInteger)columnNumber delegate:(id)delegate pushPhotoPickerVc:(BOOL)pushPhotoPickerVc; /// This init method just for previewing photos / 用这个初始化方法以预览图片 - (instancetype)initWithSelectedAssets:(NSMutableArray *)selectedAssets selectedPhotos:(NSMutableArray *)selectedPhotos index:(NSInteger)index; /// This init method for crop photo / 用这个初始化方法以裁剪图片 - (instancetype)initCropTypeWithAsset:(PHAsset *)asset photo:(UIImage *)photo completion:(void (^)(UIImage *cropImage,PHAsset *asset))completion; #pragma mark - /// Default is 9 / 默认最大可选9张图片 @property (nonatomic, assign) NSInteger maxImagesCount; /// The minimum count photos user must pick, Default is 0 /// 最小照片必选张数,默认是0 @property (nonatomic, assign) NSInteger minImagesCount; /// If the user does not select any pictures, the current picture is automatically selected when the Finish button is clicked, Default is YES /// 如果用户未选择任何图片,在点击完成按钮时自动选中当前图片,默认YES @property (nonatomic, assign) BOOL autoSelectCurrentWhenDone; /// Always enale the done button, not require minimum 1 photo be picked /// 让完成按钮一直可以点击,无须最少选择一张图片 @property (nonatomic, assign) BOOL alwaysEnableDoneBtn; /// Sort photos ascending by modificationDate,Default is YES /// 对照片排序,按修改时间升序,默认是YES。如果设置为NO,最新的照片会显示在最前面,内部的拍照按钮会排在第一个 @property (nonatomic, assign) BOOL sortAscendingByModificationDate; /// The pixel width of output image, Default is 828px,you need to set photoPreviewMaxWidth at the same time /// 导出图片的宽度,默认828像素宽,你需要同时设置photoPreviewMaxWidth的值 @property (nonatomic, assign) CGFloat photoWidth; /// Default is 600px / 默认600像素宽 @property (nonatomic, assign) CGFloat photoPreviewMaxWidth; /// Default is 30, While fetching photo, HUD will dismiss automatic if timeout; /// 超时时间,默认为30秒,当取图片时间超过30秒还没有取成功时,会自动dismiss HUD; @property (nonatomic, assign) NSInteger timeout; /// Default is YES, if set NO, the original photo button will hide. user can't picking original photo. /// 默认为YES,如果设置为NO,原图按钮将隐藏,用户不能选择发送原图 @property (nonatomic, assign) BOOL allowPickingOriginalPhoto; /// Default is YES, if set NO, user can't picking video. /// 默认为YES,如果设置为NO,用户将不能选择视频 @property (nonatomic, assign) BOOL allowPickingVideo; /// Default is NO, if set YES, user can edit video. /// 默认为NO,如果设置为YES, 用户能编辑视频 @property (nonatomic, assign) BOOL allowEditVideo; /// Export quality of cropped video, Default is AVAssetExportPresetMediumQuality /// 裁剪视频的导出质量,默认是 AVAssetExportPresetMediumQuality @property (nonatomic, assign) NSString *presetName; /// Default is 30s. If it exceeds the video duration, it is the video duration.The minimum duration of video crop is 1s. /// 默认是30s,如果超过视频时长,则为视频时长,小于1s不裁剪 @property (nonatomic, assign) NSInteger maxCropVideoDuration; /// Default is NO, if set YES, The edited video will be automatically saved to the album. /// 默认为NO,如果设置为YES,编辑后的视频会自动保存到相册 @property (nonatomic, assign) BOOL saveEditedVideoToAlbum; /// Default is NO / 默认为NO,为YES时可以多选视频/gif/图片,和照片共享最大可选张数maxImagesCount的限制 @property (nonatomic, assign) BOOL allowPickingMultipleVideo; /// Default is NO, if set YES, user can picking gif image. /// 默认为NO,如果设置为YES,用户可以选择gif图片 @property (nonatomic, assign) BOOL allowPickingGif; /// Default is YES, if set NO, user can't picking image. /// 默认为YES,如果设置为NO,用户将不能选择发送图片 @property (nonatomic, assign) BOOL allowPickingImage; /// Default is YES, if set NO, user can't take picture. /// 默认为YES,如果设置为NO, 用户将不能拍摄照片 @property (nonatomic, assign) BOOL allowTakePicture; @property (nonatomic, assign) BOOL allowCameraLocation; /// Default is YES, if set NO, user can't take video. /// 默认为YES,如果设置为NO, 用户将不能拍摄视频 @property(nonatomic, assign) BOOL allowTakeVideo; /// Default value is 10 minutes / 视频最大拍摄时间,默认是10分钟,单位是秒 @property (assign, nonatomic) NSTimeInterval videoMaximumDuration; /// Customizing UIImagePickerController's other properties, such as videoQuality / 定制UIImagePickerController的其它属性,比如视频拍摄质量videoQuality @property (nonatomic, copy) void(^uiImagePickerControllerSettingBlock)(UIImagePickerController *imagePickerController); /// 首选语言,如果设置了就用该语言,不设则取当前系统语言。 /// 支持zh-Hans、zh-Hant、en、vi等值,详见TZImagePickerController.bundle内的语言资源 @property (copy, nonatomic) NSString *preferredLanguage; /// 语言bundle,preferredLanguage变化时languageBundle会变化 /// 可通过手动设置bundle,让选择器支持新的的语言(需要在设置preferredLanguage后设置languageBundle)。欢迎提交PR把语言文件提交上来~ @property (strong, nonatomic) NSBundle *languageBundle; /// Default is YES, if set NO, user can't preview photo. /// 默认为YES,如果设置为NO,预览按钮将隐藏,用户将不能去预览照片 @property (nonatomic, assign) BOOL allowPreview; /// Default is YES, if set NO, the picker don't dismiss itself. /// 默认为YES,如果设置为NO, 选择器将不会自己dismiss @property(nonatomic, assign) BOOL autoDismiss; /// Default is NO, if set YES, in the delegate method the photos and infos will be nil, only assets hava value. /// 默认为NO,如果设置为YES,代理方法里photos和infos会是nil,只返回assets @property (assign, nonatomic) BOOL onlyReturnAsset; /// Default is NO, if set YES, will show the image's selected index. /// 默认为NO,如果设置为YES,会显示照片的选中序号 @property (assign, nonatomic) BOOL showSelectedIndex; /// Default is NO, if set YES, when selected photos's count up to maxImagesCount, other photo will show float layer what's color is cannotSelectLayerColor. /// 默认是NO,如果设置为YES,当照片选择张数达到maxImagesCount时,其它照片会显示颜色为cannotSelectLayerColor的浮层 @property (assign, nonatomic) BOOL showPhotoCannotSelectLayer; /// Default is white color with 0.8 alpha; @property (strong, nonatomic) UIColor *cannotSelectLayerColor; /// Default is YES, if set NO, the result photo will be scaled to photoWidth pixel width. The photoWidth default is 828px /// 默认是YES,如果设置为NO,内部会缩放图片到photoWidth像素宽 @property (assign, nonatomic) BOOL notScaleImage; /// 默认是NO,如果设置为YES,导出视频时会修正转向(慎重设为YES,可能导致部分安卓下拍的视频导出失败) @property (assign, nonatomic) BOOL needFixComposition; /// The photos user have selected /// 用户选中过的图片数组 @property (nonatomic, strong) NSMutableArray *selectedAssets; @property (nonatomic, strong) NSMutableArray *selectedModels; @property (nonatomic, strong) NSMutableArray *selectedAssetIds; - (void)addSelectedModel:(TZAssetModel *)model; - (void)removeSelectedModel:(TZAssetModel *)model; /// Minimum selectable photo width, Default is 0 /// 最小可选中的图片宽度,默认是0,小于这个宽度的图片不可选中 @property (nonatomic, assign) NSInteger minPhotoWidthSelectable; @property (nonatomic, assign) NSInteger minPhotoHeightSelectable; /// Hide the photo what can not be selected, Default is NO /// 隐藏不可以选中的图片,默认是NO,不推荐将其设置为YES @property (nonatomic, assign) BOOL hideWhenCanNotSelect; /// Deprecated, Use statusBarStyle (顶部statusBar 是否为系统默认的黑色,默认为NO) @property (nonatomic, assign) BOOL isStatusBarDefault __attribute__((deprecated("Use -statusBarStyle."))); /// statusBar的样式,默认为UIStatusBarStyleLightContent @property (assign, nonatomic) UIStatusBarStyle statusBarStyle; #pragma mark - /// Single selection mode, valid when maxImagesCount = 1 /// 单选模式,maxImagesCount为1时才生效 @property (nonatomic, assign) BOOL showSelectBtn; ///< 在单选模式下,照片列表页中,显示选择按钮,默认为NO @property (nonatomic, assign) BOOL allowCrop; ///< 允许裁剪,默认为YES,showSelectBtn为NO才生效 @property (nonatomic, assign) BOOL scaleAspectFillCrop; ///< 是否图片等比缩放填充cropRect区域,开启后预览页面无法左右滑动切换图片 @property (nonatomic, assign) CGRect cropRect; ///< 裁剪框的尺寸 @property (nonatomic, assign) CGRect cropRectPortrait; ///< 裁剪框的尺寸(竖屏) @property (nonatomic, assign) CGRect cropRectLandscape; ///< 裁剪框的尺寸(横屏) @property (nonatomic, assign) BOOL needCircleCrop; ///< 需要圆形裁剪框 @property (nonatomic, assign) NSInteger circleCropRadius; ///< 圆形裁剪框半径大小 @property (nonatomic, copy) void (^cropViewSettingBlock)(UIView *cropView); ///< 自定义裁剪框的其他属性 @property (nonatomic, copy) void (^navLeftBarButtonSettingBlock)(UIButton *leftButton); ///< 自定义返回按钮样式及其属性 /// 【自定义各页面/组件的样式】在界面初始化/组件setModel完成后调用,允许外界修改样式等 @property (nonatomic, copy) void (^photoPickerPageUIConfigBlock)(UICollectionView *collectionView, UIView *bottomToolBar, UIButton *previewButton, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel, UIView *divideLine); @property (nonatomic, copy) void (^photoPreviewPageUIConfigBlock)(UICollectionView *collectionView, UIView *naviBar, UIButton *backButton, UIButton *selectButton, UILabel *indexLabel, UIView *toolBar, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel); @property (nonatomic, copy) void (^videoPreviewPageUIConfigBlock)(UIButton *playButton, UIView *toolBar, UIButton *editBtn, UIButton *doneButton); @property (nonatomic, copy) void (^videoEditViewPageUIConfigBlock)(UIButton *playButton,UILabel *cropVideoDurationLabel, UIButton *editButton, UIButton *doneButton); @property (nonatomic, copy) void (^gifPreviewPageUIConfigBlock)(UIView *toolBar, UIButton *doneButton); @property (nonatomic, copy) void (^albumPickerPageUIConfigBlock)(UITableView *tableView); @property (nonatomic, copy) void (^assetCellDidSetModelBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView); @property (nonatomic, copy) void (^albumCellDidSetModelBlock)(TZAlbumCell *cell, UIImageView *posterImageView, UILabel *titleLabel); /// 【自定义各页面/组件的frame】在界面viewDidLayoutSubviews/组件layoutSubviews后调用,允许外界修改frame等 @property (nonatomic, copy) void (^photoPickerPageDidLayoutSubviewsBlock)(UICollectionView *collectionView, UIView *bottomToolBar, UIButton *previewButton, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel, UIView *divideLine); @property (nonatomic, copy) void (^photoPreviewPageDidLayoutSubviewsBlock)(UICollectionView *collectionView, UIView *naviBar, UIButton *backButton, UIButton *selectButton, UILabel *indexLabel, UIView *toolBar, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel); @property (nonatomic, copy) void (^videoPreviewPageDidLayoutSubviewsBlock)(UIButton *playButton, UIView *toolBar, UIButton *editButton, UIButton *doneButton); @property (nonatomic, copy) void (^videoEditViewPageDidLayoutSubviewsBlock)(UIButton *playButton, UILabel *cropVideoDurationLabel, UIButton *cancelButton, UIButton *doneButton); @property (nonatomic, copy) void (^gifPreviewPageDidLayoutSubviewsBlock)(UIView *toolBar, UIButton *doneButton); @property (nonatomic, copy) void (^albumPickerPageDidLayoutSubviewsBlock)(UITableView *tableView); @property (nonatomic, copy) void (^assetCellDidLayoutSubviewsBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView); @property (nonatomic, copy) void (^albumCellDidLayoutSubviewsBlock)(TZAlbumCell *cell, UIImageView *posterImageView, UILabel *titleLabel); /// 自定义各页面/组件的frame】刷新底部状态(refreshNaviBarAndBottomBarState)使用的 @property (nonatomic, copy) void (^photoPickerPageDidRefreshStateBlock)(UICollectionView *collectionView, UIView *bottomToolBar, UIButton *previewButton, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel, UIView *divideLine); @property (nonatomic, copy) void (^photoPreviewPageDidRefreshStateBlock)(UICollectionView *collectionView, UIView *naviBar, UIButton *backButton, UIButton *selectButton, UILabel *indexLabel, UIView *toolBar, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel); #pragma mark - - (UIAlertController *)showAlertWithTitle:(NSString *)title; - (void)showProgressHUD; - (void)hideProgressHUD; @property (nonatomic, assign) BOOL isSelectOriginalPhoto; @property (assign, nonatomic) BOOL needShowStatusBar; #pragma mark - @property (nonatomic, copy) NSString *takePictureImageName __attribute__((deprecated("Use -takePictureImage."))); @property (nonatomic, copy) NSString *photoSelImageName __attribute__((deprecated("Use -photoSelImage."))); @property (nonatomic, copy) NSString *photoDefImageName __attribute__((deprecated("Use -photoDefImage."))); @property (nonatomic, copy) NSString *photoOriginSelImageName __attribute__((deprecated("Use -photoOriginSelImage."))); @property (nonatomic, copy) NSString *photoOriginDefImageName __attribute__((deprecated("Use -photoOriginDefImage."))); @property (nonatomic, copy) NSString *photoPreviewOriginDefImageName __attribute__((deprecated("Use -photoPreviewOriginDefImage."))); @property (nonatomic, copy) NSString *photoNumberIconImageName __attribute__((deprecated("Use -photoNumberIconImage."))); @property (nonatomic, strong) UIImage *takePictureImage; @property (nonatomic, strong) UIImage *addMorePhotoImage; @property (nonatomic, strong) UIImage *photoSelImage; @property (nonatomic, strong) UIImage *photoDefImage; @property (nonatomic, strong) UIImage *photoOriginSelImage; @property (nonatomic, strong) UIImage *photoOriginDefImage; @property (nonatomic, strong) UIImage *photoPreviewOriginDefImage; @property (nonatomic, strong) UIImage *photoNumberIconImage; #pragma mark - /// Appearance / 外观颜色 + 按钮文字 @property (nonatomic, strong) UIColor *oKButtonTitleColorNormal; @property (nonatomic, strong) UIColor *oKButtonTitleColorDisabled; @property (nonatomic, strong) UIColor *naviBgColor; @property (nonatomic, strong) UIColor *naviTitleColor; @property (nonatomic, strong) UIFont *naviTitleFont; @property (nonatomic, strong) UIColor *barItemTextColor; @property (nonatomic, strong) UIFont *barItemTextFont; @property (nonatomic, copy) NSString *doneBtnTitleStr; @property (nonatomic, copy) NSString *cancelBtnTitleStr; @property (nonatomic, copy) NSString *previewBtnTitleStr; @property (nonatomic, copy) NSString *fullImageBtnTitleStr; @property (nonatomic, copy) NSString *settingBtnTitleStr; @property (nonatomic, copy) NSString *processHintStr; @property (nonatomic, copy) NSString *editBtnTitleStr; @property (nonatomic, copy) NSString *editViewCancelBtnTitleStr; /// Icon theme color, default is green color like wechat, the value is r:31 g:185 b:34. Currently only support image selection icon when showSelectedIndex is YES. If you need it, please set it as soon as possible /// icon主题色,默认是微信的绿色,值是r:31 g:185 b:34。目前仅支持showSelectedIndex为YES时的图片选中icon。如需要,请尽早设置它。 @property (strong, nonatomic) UIColor *iconThemeColor; #pragma mark - - (void)cancelButtonClick; // For method annotations, see the corresponding method in TZImagePickerControllerDelegate / 方法注释见TZImagePickerControllerDelegate中对应方法 @property (nonatomic, copy) void (^didFinishPickingPhotosHandle)(NSArray *photos,NSArray *assets,BOOL isSelectOriginalPhoto); @property (nonatomic, copy) void (^didFinishPickingPhotosWithInfosHandle)(NSArray *photos,NSArray *assets,BOOL isSelectOriginalPhoto,NSArray *infos); @property (nonatomic, copy) void (^imagePickerControllerDidCancelHandle)(void); @property (nonatomic, copy) void (^didFinishPickingVideoHandle)(UIImage *coverImage,PHAsset *asset); @property (nonatomic, copy) void (^didFinishPickingAndEditingVideoHandle)(UIImage *coverImage,NSString *outputPath,NSString *errorMsg); @property (nonatomic, copy) void (^didFinishPickingGifImageHandle)(UIImage *animatedImage,id sourceAssets); @property (nonatomic, weak) id pickerDelegate; @end @protocol TZImagePickerControllerDelegate @optional // The picker should dismiss itself; when it dismissed these callback will be called. // You can also set autoDismiss to NO, then the picker don't dismiss itself. // If isOriginalPhoto is YES, user picked the original photo. // You can get original photo with asset, by the method [[TZImageManager manager] getOriginalPhotoWithAsset:completion:]. // The UIImage Object in photos default width is 828px, you can set it by photoWidth property. // 这个照片选择器会自己dismiss,当选择器dismiss的时候,会执行下面的代理方法 // 你也可以设置autoDismiss属性为NO,选择器就不会自己dismis了 // 如果isSelectOriginalPhoto为YES,表明用户选择了原图 // 你可以通过一个asset获得原图,通过这个方法:[[TZImageManager manager] getOriginalPhotoWithAsset:completion:] // photos数组里的UIImage对象,默认是828像素宽,你可以通过设置photoWidth属性的值来改变它 - (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto; - (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto infos:(NSArray *)infos; - (void)tz_imagePickerControllerDidCancel:(TZImagePickerController *)picker; /// 如果用户选择了某张照片下面的代理方法会被执行 /// 如果isSelectOriginalPhoto为YES,表明用户选择了原图 /// 你可以通过一个asset获得原图,通过这个方法:[[TZImageManager manager] getOriginalPhotoWithAsset:completion:] - (void)imagePickerController:(TZImagePickerController *)picker didSelectAsset:(PHAsset *)asset photo:(UIImage *)photo isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto; /// 如果用户取消选择了某张照片下面的代理方法会被执行 /// 如果isSelectOriginalPhoto为YES,表明用户选择了原图 /// 你可以通过一个asset获得原图,通过这个方法:[[TZImageManager manager] getOriginalPhotoWithAsset:completion:] - (void)imagePickerController:(TZImagePickerController *)picker didDeselectAsset:(PHAsset *)asset photo:(UIImage *)photo isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto; // If user picking a video and allowPickingMultipleVideo is NO, this callback will be called. // If allowPickingMultipleVideo is YES, will call imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto: // 如果用户选择了一个视频且allowPickingMultipleVideo是NO,下面的代理方法会被执行 // 如果allowPickingMultipleVideo是YES,将会调用imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto: - (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingVideo:(UIImage *)coverImage sourceAssets:(PHAsset *)asset; // If allowEditVideo is YES and allowPickingMultipleVideo is NO, When user picking a video, this callback will be called. // If allowPickingMultipleVideo is YES, video editing is not supported, will call imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto: // 当allowEditVideo是YES且allowPickingMultipleVideo是NO是,如果用户选择了一个视频,下面的代理方法会被执行 // 如果allowPickingMultipleVideo是YES,则不支持编辑视频,将会调用imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto: - (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingAndEditingVideo:(UIImage *)coverImage outputPath:(NSString *)outputPath error:(NSString *)errorMsg; // When saving the edited video to the album fails, this callback will be called. // 编辑后的视频自动保存到相册失败时,下面的代理方法会被执行 - (void)imagePickerController:(TZImagePickerController *)picker didFailToSaveEditedVideoWithError:(NSError *)error; // If user picking a gif image and allowPickingMultipleVideo is NO, this callback will be called. // If allowPickingMultipleVideo is YES, will call imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto: // 如果用户选择了一个gif图片且allowPickingMultipleVideo是NO,下面的代理方法会被执行 // 如果allowPickingMultipleVideo是YES,将会调用imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto: - (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingGifImage:(UIImage *)animatedImage sourceAssets:(PHAsset *)asset; // Decide album show or not't // 决定相册显示与否 albumName:相册名字 result:相册原始数据 - (BOOL)isAlbumCanSelect:(NSString *)albumName result:(PHFetchResult *)result; // Decide asset show or not't // 决定照片显示与否 - (BOOL)isAssetCanSelect:(PHAsset *)asset __attribute__((deprecated("Use -isAssetCanBeDisplayed:."))); - (BOOL)isAssetCanBeDisplayed:(PHAsset *)asset; // Decide asset can be selected // 决定照片能否被选中 - (BOOL)isAssetCanBeSelected:(PHAsset *)asset; @end @interface TZAlbumPickerController : UIViewController @property (nonatomic, assign) NSInteger columnNumber; @property (assign, nonatomic) BOOL isFirstAppear; - (void)configTableView; @end @interface UIImage (MyBundle) + (UIImage *)tz_imageNamedFromMyBundle:(NSString *)name; @end @interface TZCommonTools : NSObject + (UIEdgeInsets)tz_safeAreaInsets; + (BOOL)tz_isIPhoneX; + (BOOL)tz_isLandscape; + (CGFloat)tz_statusBarHeight; // 获得Info.plist数据字典 + (NSDictionary *)tz_getInfoDictionary; + (NSString *)tz_getAppName; + (BOOL)tz_isRightToLeftLayout; + (void)configBarButtonItem:(UIBarButtonItem *)item tzImagePickerVc:(TZImagePickerController *)tzImagePickerVc; + (BOOL)isICloudSyncError:(NSError *)error; + (BOOL)isAssetNotSelectable:(TZAssetModel *)model tzImagePickerVc:(TZImagePickerController *)tzImagePickerVc; @end @interface TZImagePickerConfig : NSObject + (instancetype)sharedInstance; @property (copy, nonatomic) NSString *preferredLanguage; @property(nonatomic, assign) BOOL allowPickingImage; @property (nonatomic, assign) BOOL allowPickingVideo; @property (strong, nonatomic) NSBundle *languageBundle; @property (assign, nonatomic) BOOL showSelectedIndex; @property (assign, nonatomic) BOOL showPhotoCannotSelectLayer; @property (assign, nonatomic) BOOL notScaleImage; @property (assign, nonatomic) BOOL needFixComposition; /// 默认是50,如果一个GIF过大,里面图片个数可能超过1000,会导致内存飙升而崩溃 @property (assign, nonatomic) NSInteger gifPreviewMaxImagesCount; /// 【自定义GIF播放方案】为了避免内存过大,内部默认限制只播放50帧(平均取),可通过gifPreviewMaxImagesCount属性调整,若对GIF预览有更好的效果要求,可实现这个block采用FLAnimatedImage等三方库来播放,但注意FLAnimatedImage有播放速度较慢问题,自行取舍下。 @property (nonatomic, copy) void (^gifImagePlayBlock)(TZPhotoPreviewView *view, UIImageView *imageView, NSData *gifData, NSDictionary *info); @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.m ================================================ // // TZImagePickerController.m // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // version 3.8.1 - 2022.04.15 // 更多信息,请前往项目的github地址:https://github.com/banchichen/TZImagePickerController #import "TZImagePickerController.h" #import "TZPhotoPickerController.h" #import "TZPhotoPreviewController.h" #import "TZAssetModel.h" #import "TZAssetCell.h" #import "UIView+TZLayout.h" #import "TZImageManager.h" #import "TZVideoCropController.h" @interface TZImagePickerController () { NSTimer *_timer; UILabel *_tipLabel; UIButton *_settingBtn; BOOL _pushPhotoPickerVc; BOOL _didPushPhotoPickerVc; CGRect _cropRect; UIButton *_progressHUD; UIView *_HUDContainer; UIActivityIndicatorView *_HUDIndicatorView; UILabel *_HUDLabel; UIStatusBarStyle _originStatusBarStyle; } /// Default is 4, Use in photos collectionView in TZPhotoPickerController /// 默认4列, TZPhotoPickerController中的照片collectionView @property (nonatomic, assign) NSInteger columnNumber; @property (nonatomic, assign) NSInteger HUDTimeoutCount; ///< 超时隐藏HUD计数 @end @implementation TZImagePickerController - (instancetype)init { self = [super init]; if (self) { self = [self initWithMaxImagesCount:9 delegate:nil]; } return self; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (void)viewDidLoad { [super viewDidLoad]; self.needShowStatusBar = ![UIApplication sharedApplication].statusBarHidden; if (@available(iOS 13.0, *)) { self.view.backgroundColor = UIColor.tertiarySystemBackgroundColor; } else { self.view.backgroundColor = [UIColor whiteColor]; } self.navigationBar.barStyle = UIBarStyleBlack; self.navigationBar.translucent = YES; [TZImageManager manager].shouldFixOrientation = NO; // Default appearance, you can reset these after this method // 默认的外观,你可以在这个方法后重置 self.oKButtonTitleColorNormal = [UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0]; self.oKButtonTitleColorDisabled = [UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:0.5]; self.navigationBar.barTintColor = [UIColor colorWithRed:(34/255.0) green:(34/255.0) blue:(34/255.0) alpha:1.0]; self.navigationBar.tintColor = [UIColor whiteColor]; self.automaticallyAdjustsScrollViewInsets = NO; if (self.needShowStatusBar) [UIApplication sharedApplication].statusBarHidden = NO; } - (void)setNaviBgColor:(UIColor *)naviBgColor { _naviBgColor = naviBgColor; self.navigationBar.barTintColor = naviBgColor; [self configNavigationBarAppearance]; } - (void)setNaviTitleColor:(UIColor *)naviTitleColor { _naviTitleColor = naviTitleColor; [self configNaviTitleAppearance]; } - (void)setNaviTitleFont:(UIFont *)naviTitleFont { _naviTitleFont = naviTitleFont; [self configNaviTitleAppearance]; } - (void)configNaviTitleAppearance { NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary]; if (self.naviTitleColor) { textAttrs[NSForegroundColorAttributeName] = self.naviTitleColor; } if (self.naviTitleFont) { textAttrs[NSFontAttributeName] = self.naviTitleFont; } self.navigationBar.titleTextAttributes = textAttrs; [self configNavigationBarAppearance]; } - (void)configNavigationBarAppearance { if (@available(iOS 13.0, *)) { UINavigationBarAppearance *barAppearance = [[UINavigationBarAppearance alloc] init]; if (self.navigationBar.isTranslucent) { UIColor *barTintColor = self.navigationBar.barTintColor; barAppearance.backgroundColor = [barTintColor colorWithAlphaComponent:0.85]; } else { barAppearance.backgroundColor = self.navigationBar.barTintColor; } barAppearance.titleTextAttributes = self.navigationBar.titleTextAttributes; self.navigationBar.standardAppearance = barAppearance; self.navigationBar.scrollEdgeAppearance = barAppearance; } } - (void)setBarItemTextFont:(UIFont *)barItemTextFont { _barItemTextFont = barItemTextFont; [self configBarButtonItemAppearance]; } - (void)setBarItemTextColor:(UIColor *)barItemTextColor { _barItemTextColor = barItemTextColor; [self configBarButtonItemAppearance]; } - (void)setIsStatusBarDefault:(BOOL)isStatusBarDefault { _isStatusBarDefault = isStatusBarDefault; if (isStatusBarDefault) { self.statusBarStyle = UIStatusBarStyleDefault; } else { self.statusBarStyle = UIStatusBarStyleLightContent; } } - (void)configBarButtonItemAppearance { UIBarButtonItem *barItem; if (@available(iOS 9, *)) { barItem = [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[TZImagePickerController class]]]; } else { barItem = [UIBarButtonItem appearanceWhenContainedIn:[TZImagePickerController class], nil]; } NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary]; textAttrs[NSForegroundColorAttributeName] = self.barItemTextColor; textAttrs[NSFontAttributeName] = self.barItemTextFont; [barItem setTitleTextAttributes:textAttrs forState:UIControlStateNormal]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; _originStatusBarStyle = [UIApplication sharedApplication].statusBarStyle; [UIApplication sharedApplication].statusBarStyle = self.statusBarStyle; [self configNavigationBarAppearance]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [UIApplication sharedApplication].statusBarStyle = _originStatusBarStyle; [self hideProgressHUD]; } - (UIStatusBarStyle)preferredStatusBarStyle { return self.statusBarStyle; } - (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount delegate:(id)delegate { return [self initWithMaxImagesCount:maxImagesCount columnNumber:4 delegate:delegate pushPhotoPickerVc:YES]; } - (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(NSInteger)columnNumber delegate:(id)delegate { return [self initWithMaxImagesCount:maxImagesCount columnNumber:columnNumber delegate:delegate pushPhotoPickerVc:YES]; } - (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(NSInteger)columnNumber delegate:(id)delegate pushPhotoPickerVc:(BOOL)pushPhotoPickerVc { _pushPhotoPickerVc = pushPhotoPickerVc; TZAlbumPickerController *albumPickerVc = [[TZAlbumPickerController alloc] init]; albumPickerVc.isFirstAppear = YES; albumPickerVc.columnNumber = columnNumber; self = [super initWithRootViewController:albumPickerVc]; if (self) { self.maxImagesCount = maxImagesCount > 0 ? maxImagesCount : 9; // Default is 9 / 默认最大可选9张图片 self.pickerDelegate = delegate; self.selectedAssets = [NSMutableArray array]; // Allow user picking original photo and video, you also can set No after this method // 默认准许用户选择原图和视频, 你也可以在这个方法后置为NO self.allowPickingOriginalPhoto = YES; self.allowPickingVideo = YES; self.allowPickingImage = YES; self.allowTakePicture = YES; self.allowTakeVideo = YES; self.videoMaximumDuration = 10 * 60; self.sortAscendingByModificationDate = YES; self.autoDismiss = YES; self.columnNumber = columnNumber; [self configDefaultSetting]; if (![[TZImageManager manager] authorizationStatusAuthorized]) { _tipLabel = [[UILabel alloc] init]; _tipLabel.frame = CGRectMake(8, 120, self.view.tz_width - 16, 60); _tipLabel.textAlignment = NSTextAlignmentCenter; _tipLabel.numberOfLines = 0; _tipLabel.font = [UIFont systemFontOfSize:16]; _tipLabel.textColor = [UIColor blackColor]; _tipLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; NSString *appName = [TZCommonTools tz_getAppName]; NSString *tipText = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Allow %@ to access your album in \"Settings -> Privacy -> Photos\""],appName]; _tipLabel.text = tipText; [self.view addSubview:_tipLabel]; _settingBtn = [UIButton buttonWithType:UIButtonTypeSystem]; [_settingBtn setTitle:self.settingBtnTitleStr forState:UIControlStateNormal]; _settingBtn.frame = CGRectMake(0, 180, self.view.tz_width, 44); _settingBtn.titleLabel.font = [UIFont systemFontOfSize:18]; [_settingBtn addTarget:self action:@selector(settingBtnClick) forControlEvents:UIControlEventTouchUpInside]; _settingBtn.autoresizingMask = UIViewAutoresizingFlexibleWidth; [self.view addSubview:_settingBtn]; if ([PHPhotoLibrary authorizationStatus] == 0) { _timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(observeAuthrizationStatusChange) userInfo:nil repeats:NO]; } } else { [self pushPhotoPickerVc]; } } return self; } /// This init method just for previewing photos / 用这个初始化方法以预览图片 - (instancetype)initWithSelectedAssets:(NSMutableArray *)selectedAssets selectedPhotos:(NSMutableArray *)selectedPhotos index:(NSInteger)index{ TZPhotoPreviewController *previewVc = [[TZPhotoPreviewController alloc] init]; self = [super initWithRootViewController:previewVc]; if (self) { self.selectedAssets = [NSMutableArray arrayWithArray:selectedAssets]; self.allowPickingOriginalPhoto = self.allowPickingOriginalPhoto; [self configDefaultSetting]; previewVc.photos = [NSMutableArray arrayWithArray:selectedPhotos]; previewVc.currentIndex = index; __weak typeof(self) weakSelf = self; [previewVc setDoneButtonClickBlockWithPreviewType:^(NSArray *photos, NSArray *assets, BOOL isSelectOriginalPhoto) { __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf.autoDismiss) { if (strongSelf.didFinishPickingPhotosHandle) { strongSelf.didFinishPickingPhotosHandle(photos,assets,isSelectOriginalPhoto); } return; } [strongSelf dismissViewControllerAnimated:YES completion:^{ if (!strongSelf) return; if (strongSelf.didFinishPickingPhotosHandle) { strongSelf.didFinishPickingPhotosHandle(photos,assets,isSelectOriginalPhoto); } }]; }]; } return self; } /// This init method for crop photo / 用这个初始化方法以裁剪图片 - (instancetype)initCropTypeWithAsset:(PHAsset *)asset photo:(UIImage *)photo completion:(void (^)(UIImage *cropImage,PHAsset *asset))completion { TZPhotoPreviewController *previewVc = [[TZPhotoPreviewController alloc] init]; self = [super initWithRootViewController:previewVc]; if (self) { self.maxImagesCount = 1; self.allowPickingImage = YES; self.allowCrop = YES; self.selectedAssets = [NSMutableArray arrayWithArray:@[asset]]; [self configDefaultSetting]; previewVc.photos = [NSMutableArray arrayWithArray:@[photo]]; previewVc.isCropImage = YES; previewVc.currentIndex = 0; __weak typeof(self) weakSelf = self; [previewVc setDoneButtonClickBlockCropMode:^(UIImage *cropImage, id asset) { __strong typeof(weakSelf) strongSelf = weakSelf; [strongSelf dismissViewControllerAnimated:YES completion:^{ if (completion) { completion(cropImage,asset); } }]; }]; } return self; } - (void)configDefaultSetting { self.autoSelectCurrentWhenDone = YES; self.timeout = 30; self.photoWidth = 828.0; self.photoPreviewMaxWidth = 600; self.naviTitleColor = [UIColor whiteColor]; self.naviTitleFont = [UIFont systemFontOfSize:17]; self.barItemTextFont = [UIFont systemFontOfSize:15]; self.barItemTextColor = [UIColor whiteColor]; self.allowPreview = YES; // 2.2.26版本,不主动缩放图片,降低内存占用 self.notScaleImage = YES; self.needFixComposition = NO; self.statusBarStyle = UIStatusBarStyleLightContent; self.cannotSelectLayerColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8]; self.allowCameraLocation = YES; self.presetName = AVAssetExportPresetMediumQuality; self.maxCropVideoDuration = 30; self.iconThemeColor = [UIColor colorWithRed:31 / 255.0 green:185 / 255.0 blue:34 / 255.0 alpha:1.0]; [self configDefaultBtnTitle]; CGFloat cropViewWH = MIN(self.view.tz_width, self.view.tz_height) / 3 * 2; self.cropRect = CGRectMake((self.view.tz_width - cropViewWH) / 2, (self.view.tz_height - cropViewWH) / 2, cropViewWH, cropViewWH); } - (void)configDefaultImageName { self.takePictureImageName = @"takePicture80"; self.photoSelImageName = @"photo_sel_photoPickerVc"; self.photoDefImageName = @"photo_def_photoPickerVc"; self.photoNumberIconImage = [self createImageWithColor:nil size:CGSizeMake(24, 24) radius:12]; // @"photo_number_icon"; self.photoPreviewOriginDefImageName = @"preview_original_def"; self.photoOriginDefImageName = @"photo_original_def"; self.photoOriginSelImageName = @"photo_original_sel"; self.addMorePhotoImage = [UIImage tz_imageNamedFromMyBundle:@"addMore"]; } - (void)setTakePictureImageName:(NSString *)takePictureImageName { _takePictureImageName = takePictureImageName; _takePictureImage = [UIImage tz_imageNamedFromMyBundle:takePictureImageName]; } - (void)setPhotoSelImageName:(NSString *)photoSelImageName { _photoSelImageName = photoSelImageName; _photoSelImage = [UIImage tz_imageNamedFromMyBundle:photoSelImageName]; } - (void)setPhotoDefImageName:(NSString *)photoDefImageName { _photoDefImageName = photoDefImageName; _photoDefImage = [UIImage tz_imageNamedFromMyBundle:photoDefImageName]; } - (void)setPhotoNumberIconImageName:(NSString *)photoNumberIconImageName { _photoNumberIconImageName = photoNumberIconImageName; _photoNumberIconImage = [UIImage tz_imageNamedFromMyBundle:photoNumberIconImageName]; } - (void)setPhotoPreviewOriginDefImageName:(NSString *)photoPreviewOriginDefImageName { _photoPreviewOriginDefImageName = photoPreviewOriginDefImageName; _photoPreviewOriginDefImage = [UIImage tz_imageNamedFromMyBundle:photoPreviewOriginDefImageName]; } - (void)setPhotoOriginDefImageName:(NSString *)photoOriginDefImageName { _photoOriginDefImageName = photoOriginDefImageName; _photoOriginDefImage = [UIImage tz_imageNamedFromMyBundle:photoOriginDefImageName]; } - (void)setPhotoOriginSelImageName:(NSString *)photoOriginSelImageName { _photoOriginSelImageName = photoOriginSelImageName; _photoOriginSelImage = [UIImage tz_imageNamedFromMyBundle:photoOriginSelImageName]; } - (void)setTakePictureImage:(UIImage *)takePictureImage { _takePictureImage = takePictureImage; _takePictureImageName = @""; } - (void)setIconThemeColor:(UIColor *)iconThemeColor { _iconThemeColor = iconThemeColor; [self configDefaultImageName]; } - (void)configDefaultBtnTitle { self.doneBtnTitleStr = [NSBundle tz_localizedStringForKey:@"Done"]; self.cancelBtnTitleStr = [NSBundle tz_localizedStringForKey:@"Cancel"]; self.previewBtnTitleStr = [NSBundle tz_localizedStringForKey:@"Preview"]; self.fullImageBtnTitleStr = [NSBundle tz_localizedStringForKey:@"Full image"]; self.settingBtnTitleStr = [NSBundle tz_localizedStringForKey:@"Setting"]; self.processHintStr = [NSBundle tz_localizedStringForKey:@"Processing..."]; self.editBtnTitleStr = [NSBundle tz_localizedStringForKey:@"Edit"]; } - (void)setShowSelectedIndex:(BOOL)showSelectedIndex { _showSelectedIndex = showSelectedIndex; if (showSelectedIndex) { self.photoSelImage = [self createImageWithColor:nil size:CGSizeMake(24, 24) radius:12]; } [TZImagePickerConfig sharedInstance].showSelectedIndex = showSelectedIndex; } - (void)setShowPhotoCannotSelectLayer:(BOOL)showPhotoCannotSelectLayer { _showPhotoCannotSelectLayer = showPhotoCannotSelectLayer; [TZImagePickerConfig sharedInstance].showPhotoCannotSelectLayer = showPhotoCannotSelectLayer; } - (void)setNotScaleImage:(BOOL)notScaleImage { _notScaleImage = notScaleImage; [TZImagePickerConfig sharedInstance].notScaleImage = notScaleImage; } - (void)setNeedFixComposition:(BOOL)needFixComposition { _needFixComposition = needFixComposition; [TZImagePickerConfig sharedInstance].needFixComposition = needFixComposition; } - (void)observeAuthrizationStatusChange { [_timer invalidate]; _timer = nil; if ([PHPhotoLibrary authorizationStatus] == 0) { _timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(observeAuthrizationStatusChange) userInfo:nil repeats:NO]; } if ([[TZImageManager manager] authorizationStatusAuthorized]) { [_tipLabel removeFromSuperview]; [_settingBtn removeFromSuperview]; [self pushPhotoPickerVc]; TZAlbumPickerController *albumPickerVc = (TZAlbumPickerController *)self.visibleViewController; if ([albumPickerVc isKindOfClass:[TZAlbumPickerController class]]) { [albumPickerVc configTableView]; } } } - (void)pushPhotoPickerVc { _didPushPhotoPickerVc = NO; // 1.6.8 判断是否需要push到照片选择页,如果_pushPhotoPickerVc为NO,则不push if (!_didPushPhotoPickerVc && _pushPhotoPickerVc) { TZPhotoPickerController *photoPickerVc = [[TZPhotoPickerController alloc] init]; photoPickerVc.isFirstAppear = YES; photoPickerVc.columnNumber = self.columnNumber; [[TZImageManager manager] getCameraRollAlbumWithFetchAssets:NO completion:^(TZAlbumModel *model) { photoPickerVc.model = model; [self pushViewController:photoPickerVc animated:YES]; self->_didPushPhotoPickerVc = YES; }]; } } - (UIAlertController *)showAlertWithTitle:(NSString *)title { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:[NSBundle tz_localizedStringForKey:@"OK"] style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; return alertController; } - (void)showProgressHUD { if (!_progressHUD) { _progressHUD = [UIButton buttonWithType:UIButtonTypeCustom]; [_progressHUD setBackgroundColor:[UIColor clearColor]]; _HUDContainer = [[UIView alloc] init]; _HUDContainer.layer.cornerRadius = 8; _HUDContainer.clipsToBounds = YES; _HUDContainer.backgroundColor = [UIColor darkGrayColor]; _HUDContainer.alpha = 0.7; _HUDIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; _HUDLabel = [[UILabel alloc] init]; _HUDLabel.textAlignment = NSTextAlignmentCenter; _HUDLabel.text = self.processHintStr; _HUDLabel.font = [UIFont systemFontOfSize:15]; _HUDLabel.textColor = [UIColor whiteColor]; [_HUDContainer addSubview:_HUDLabel]; [_HUDContainer addSubview:_HUDIndicatorView]; [_progressHUD addSubview:_HUDContainer]; } [_HUDIndicatorView startAnimating]; UIWindow *applicationWindow; if ([[[UIApplication sharedApplication] delegate] respondsToSelector:@selector(window)]) { applicationWindow = [[[UIApplication sharedApplication] delegate] window]; } else { applicationWindow = [[UIApplication sharedApplication] keyWindow]; } [applicationWindow addSubview:_progressHUD]; [self.view setNeedsLayout]; // if over time, dismiss HUD automatic self.HUDTimeoutCount++; __weak typeof(self) weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.HUDTimeoutCount--; if (strongSelf.HUDTimeoutCount <= 0) { strongSelf.HUDTimeoutCount = 0; [strongSelf hideProgressHUD]; } }); } - (void)hideProgressHUD { if (_progressHUD) { [_HUDIndicatorView stopAnimating]; [_progressHUD removeFromSuperview]; } } - (void)setMaxImagesCount:(NSInteger)maxImagesCount { _maxImagesCount = maxImagesCount; if (maxImagesCount > 1) { _showSelectBtn = YES; _allowCrop = NO; } } - (void)setShowSelectBtn:(BOOL)showSelectBtn { _showSelectBtn = showSelectBtn; // 多选模式下,不允许让showSelectBtn为NO if (!showSelectBtn && _maxImagesCount > 1) { _showSelectBtn = YES; } } - (void)setAllowCrop:(BOOL)allowCrop { _allowCrop = _maxImagesCount > 1 ? NO : allowCrop; if (allowCrop) { // 允许裁剪的时候,不能选原图和GIF self.allowPickingOriginalPhoto = NO; self.allowPickingGif = NO; } } - (void)setCircleCropRadius:(NSInteger)circleCropRadius { _circleCropRadius = circleCropRadius; self.cropRect = CGRectMake(self.view.tz_width / 2 - circleCropRadius, self.view.tz_height / 2 - _circleCropRadius, _circleCropRadius * 2, _circleCropRadius * 2); } - (void)setCropRect:(CGRect)cropRect { _cropRect = cropRect; if ([TZCommonTools tz_isLandscape]) { _cropRectLandscape = cropRect; CGFloat widthHeight = cropRect.size.height; _cropRectPortrait = CGRectMake(cropRect.origin.y, (self.view.tz_width - widthHeight) / 2, widthHeight, widthHeight); } else { _cropRectPortrait = cropRect; CGFloat widthHeight = cropRect.size.width; _cropRectLandscape = CGRectMake((self.view.tz_height - widthHeight) / 2, cropRect.origin.x, widthHeight, widthHeight); } } - (CGRect)cropRect { CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height; BOOL isFullScreen = self.view.tz_height == screenHeight; if (isFullScreen) { return _cropRect; } else { CGRect newCropRect = _cropRect; newCropRect.origin.y -= ((screenHeight - self.view.tz_height) / 2); return newCropRect; } } - (void)setTimeout:(NSInteger)timeout { _timeout = timeout; if (timeout < 5) { _timeout = 5; } else if (_timeout > 600) { _timeout = 600; } } - (void)setPickerDelegate:(id)pickerDelegate { _pickerDelegate = pickerDelegate; [TZImageManager manager].pickerDelegate = pickerDelegate; } - (void)setColumnNumber:(NSInteger)columnNumber { _columnNumber = columnNumber; if (columnNumber <= 2) { _columnNumber = 2; } else if (columnNumber >= 6) { _columnNumber = 6; } TZAlbumPickerController *albumPickerVc = [self.childViewControllers firstObject]; albumPickerVc.columnNumber = _columnNumber; [TZImageManager manager].columnNumber = _columnNumber; } - (void)setMinPhotoWidthSelectable:(NSInteger)minPhotoWidthSelectable { _minPhotoWidthSelectable = minPhotoWidthSelectable; [TZImageManager manager].minPhotoWidthSelectable = minPhotoWidthSelectable; } - (void)setMinPhotoHeightSelectable:(NSInteger)minPhotoHeightSelectable { _minPhotoHeightSelectable = minPhotoHeightSelectable; [TZImageManager manager].minPhotoHeightSelectable = minPhotoHeightSelectable; } - (void)setHideWhenCanNotSelect:(BOOL)hideWhenCanNotSelect { _hideWhenCanNotSelect = hideWhenCanNotSelect; [TZImageManager manager].hideWhenCanNotSelect = hideWhenCanNotSelect; } - (void)setPhotoPreviewMaxWidth:(CGFloat)photoPreviewMaxWidth { _photoPreviewMaxWidth = photoPreviewMaxWidth; if (photoPreviewMaxWidth > 800) { _photoPreviewMaxWidth = 800; } else if (photoPreviewMaxWidth < 500) { _photoPreviewMaxWidth = 500; } [TZImageManager manager].photoPreviewMaxWidth = _photoPreviewMaxWidth; } - (void)setPhotoWidth:(CGFloat)photoWidth { _photoWidth = photoWidth; [TZImageManager manager].photoWidth = photoWidth; } - (void)setSelectedAssets:(NSMutableArray *)selectedAssets { _selectedAssets = selectedAssets; _selectedModels = [NSMutableArray array]; _selectedAssetIds = [NSMutableArray array]; for (PHAsset *asset in selectedAssets) { TZAssetModel *model = [TZAssetModel modelWithAsset:asset type:[[TZImageManager manager] getAssetType:asset]]; model.isSelected = YES; [self addSelectedModel:model]; } } - (void)setAllowPickingImage:(BOOL)allowPickingImage { _allowPickingImage = allowPickingImage; [TZImagePickerConfig sharedInstance].allowPickingImage = allowPickingImage; if (!allowPickingImage) { _allowTakePicture = NO; } } - (void)setAllowPickingVideo:(BOOL)allowPickingVideo { _allowPickingVideo = allowPickingVideo; [TZImagePickerConfig sharedInstance].allowPickingVideo = allowPickingVideo; if (!allowPickingVideo) { _allowTakeVideo = NO; } } - (void)setPreferredLanguage:(NSString *)preferredLanguage { _preferredLanguage = preferredLanguage; [TZImagePickerConfig sharedInstance].preferredLanguage = preferredLanguage; [self configDefaultBtnTitle]; } - (void)setLanguageBundle:(NSBundle *)languageBundle { _languageBundle = languageBundle; [TZImagePickerConfig sharedInstance].languageBundle = languageBundle; [self configDefaultBtnTitle]; } - (void)setSortAscendingByModificationDate:(BOOL)sortAscendingByModificationDate { _sortAscendingByModificationDate = sortAscendingByModificationDate; [TZImageManager manager].sortAscendingByModificationDate = sortAscendingByModificationDate; } - (void)settingBtnClick { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; } - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { viewController.automaticallyAdjustsScrollViewInsets = NO; [super pushViewController:viewController animated:animated]; } - (void)dealloc { // NSLog(@"%@ dealloc",NSStringFromClass(self.class)); } - (void)addSelectedModel:(TZAssetModel *)model { [_selectedModels addObject:model]; [_selectedAssetIds addObject:model.asset.localIdentifier]; } - (void)removeSelectedModel:(TZAssetModel *)model { [_selectedModels removeObject:model]; [_selectedAssetIds removeObject:model.asset.localIdentifier]; } - (UIImage *)createImageWithColor:(UIColor *)color size:(CGSize)size radius:(CGFloat)radius { if (!color) { color = self.iconThemeColor; } CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height); UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, [color CGColor]); UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius]; CGContextAddPath(context, path.CGPath); CGContextFillPath(context); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } #pragma mark - UIContentContainer - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.02 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (![UIApplication sharedApplication].statusBarHidden) { if (self.needShowStatusBar) [UIApplication sharedApplication].statusBarHidden = NO; } }); if (size.width > size.height) { _cropRect = _cropRectLandscape; } else { _cropRect = _cropRectPortrait; } } #pragma mark - Layout - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; CGFloat progressHUDY = CGRectGetMaxY(self.navigationBar.frame); _progressHUD.frame = CGRectMake(0, progressHUDY, self.view.tz_width, self.view.tz_height - progressHUDY); _HUDContainer.frame = CGRectMake((self.view.tz_width - 120) / 2, (_progressHUD.tz_height - 90 - progressHUDY) / 2, 120, 90); _HUDIndicatorView.frame = CGRectMake(45, 15, 30, 30); _HUDLabel.frame = CGRectMake(0,40, 120, 50); } #pragma mark - Public - (void)cancelButtonClick { if (self.autoDismiss) { [self dismissViewControllerAnimated:YES completion:^{ [self callDelegateMethod]; }]; } else { [self callDelegateMethod]; } } - (void)callDelegateMethod { if ([self.pickerDelegate respondsToSelector:@selector(tz_imagePickerControllerDidCancel:)]) { [self.pickerDelegate tz_imagePickerControllerDidCancel:self]; } if (self.imagePickerControllerDidCancelHandle) { self.imagePickerControllerDidCancelHandle(); } } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { if ([self.topViewController isKindOfClass:TZVideoPlayerController.class] && self.topViewController.presentedViewController) { return UIInterfaceOrientationMaskPortrait; } return UIInterfaceOrientationMaskAll; } @end @interface TZAlbumPickerController () { UITableView *_tableView; } @property (nonatomic, strong) NSMutableArray *albumArr; @end @implementation TZAlbumPickerController - (void)viewDidLoad { [super viewDidLoad]; if ([[TZImageManager manager] authorizationStatusAuthorized] || !SYSTEM_VERSION_GREATER_THAN_15) { [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; } self.isFirstAppear = YES; if (@available(iOS 13.0, *)) { self.view.backgroundColor = UIColor.tertiarySystemBackgroundColor; } else { self.view.backgroundColor = [UIColor whiteColor]; } TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; UIBarButtonItem *cancelItem = [[UIBarButtonItem alloc] initWithTitle:imagePickerVc.cancelBtnTitleStr style:UIBarButtonItemStylePlain target:imagePickerVc action:@selector(cancelButtonClick)]; [TZCommonTools configBarButtonItem:cancelItem tzImagePickerVc:imagePickerVc]; self.navigationItem.rightBarButtonItem = cancelItem; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; [imagePickerVc hideProgressHUD]; if (imagePickerVc.allowPickingImage) { self.navigationItem.title = [NSBundle tz_localizedStringForKey:@"Photos"]; } else if (imagePickerVc.allowPickingVideo) { self.navigationItem.title = [NSBundle tz_localizedStringForKey:@"Videos"]; } if (self.isFirstAppear && !imagePickerVc.navLeftBarButtonSettingBlock) { self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:[NSBundle tz_localizedStringForKey:@"Back"] style:UIBarButtonItemStylePlain target:nil action:nil]; } [self configTableView]; } - (void)configTableView { if (![[TZImageManager manager] authorizationStatusAuthorized]) { return; } TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; if (self.isFirstAppear) { [imagePickerVc showProgressHUD]; } dispatch_async(dispatch_get_global_queue(0, 0), ^{ [[TZImageManager manager] getAllAlbumsWithFetchAssets:!self.isFirstAppear completion:^(NSArray *models) { dispatch_async(dispatch_get_main_queue(), ^{ self->_albumArr = [NSMutableArray arrayWithArray:models]; for (TZAlbumModel *albumModel in self->_albumArr) { albumModel.selectedModels = imagePickerVc.selectedModels; } [imagePickerVc hideProgressHUD]; if (self.isFirstAppear) { self.isFirstAppear = NO; [self configTableView]; } if (!self->_tableView) { self->_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; self->_tableView.rowHeight = 70; if (@available(iOS 13.0, *)) { self->_tableView.backgroundColor = [UIColor tertiarySystemBackgroundColor]; } else { self->_tableView.backgroundColor = [UIColor whiteColor]; } self->_tableView.tableFooterView = [[UIView alloc] init]; self->_tableView.dataSource = self; self->_tableView.delegate = self; [self->_tableView registerClass:[TZAlbumCell class] forCellReuseIdentifier:@"TZAlbumCell"]; [self.view addSubview:self->_tableView]; if (imagePickerVc.albumPickerPageUIConfigBlock) { imagePickerVc.albumPickerPageUIConfigBlock(self->_tableView); } } else { [self->_tableView reloadData]; } }); }]; }); } - (void)dealloc { [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; // NSLog(@"%@ dealloc",NSStringFromClass(self.class)); } - (UIStatusBarStyle)preferredStatusBarStyle { TZImagePickerController *tzImagePicker = (TZImagePickerController *)self.navigationController; if (tzImagePicker && [tzImagePicker isKindOfClass:[TZImagePickerController class]]) { return tzImagePicker.statusBarStyle; } return [super preferredStatusBarStyle]; } #pragma mark - PHPhotoLibraryChangeObserver - (void)photoLibraryDidChange:(PHChange *)changeInstance { dispatch_async(dispatch_get_main_queue(), ^{ [self configTableView]; }); } #pragma mark - Layout - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; CGFloat top = 0; CGFloat tableViewHeight = 0; CGFloat naviBarHeight = self.navigationController.navigationBar.tz_height; BOOL isStatusBarHidden = [UIApplication sharedApplication].isStatusBarHidden; BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height; if (self.navigationController.navigationBar.isTranslucent) { top = naviBarHeight; if (!isStatusBarHidden && isFullScreen) top += [TZCommonTools tz_statusBarHeight]; tableViewHeight = self.view.tz_height - top; } else { tableViewHeight = self.view.tz_height; } _tableView.frame = CGRectMake(0, top, self.view.tz_width, tableViewHeight); TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; if (imagePickerVc.albumPickerPageDidLayoutSubviewsBlock) { imagePickerVc.albumPickerPageDidLayoutSubviewsBlock(_tableView); } } #pragma mark - UITableViewDataSource && Delegate - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return _albumArr.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { TZAlbumCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TZAlbumCell"]; if (@available(iOS 13.0, *)) { cell.backgroundColor = UIColor.tertiarySystemBackgroundColor; } TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; cell.albumCellDidLayoutSubviewsBlock = imagePickerVc.albumCellDidLayoutSubviewsBlock; cell.albumCellDidSetModelBlock = imagePickerVc.albumCellDidSetModelBlock; cell.selectedCountButton.backgroundColor = imagePickerVc.iconThemeColor; cell.model = _albumArr[indexPath.row]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { TZPhotoPickerController *photoPickerVc = [[TZPhotoPickerController alloc] init]; photoPickerVc.columnNumber = self.columnNumber; TZAlbumModel *model = _albumArr[indexPath.row]; photoPickerVc.model = model; [self.navigationController pushViewController:photoPickerVc animated:YES]; [tableView deselectRowAtIndexPath:indexPath animated:NO]; } #pragma clang diagnostic pop @end @implementation UIImage (MyBundle) + (UIImage *)tz_imageNamedFromMyBundle:(NSString *)name { NSBundle *imageBundle = [NSBundle tz_imagePickerBundle]; name = [name stringByAppendingString:@"@2x"]; NSString *imagePath = [imageBundle pathForResource:name ofType:@"png"]; UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; if (!image) { // 兼容业务方自己设置图片的方式 name = [name stringByReplacingOccurrencesOfString:@"@2x" withString:@""]; image = [UIImage imageNamed:name]; } return image; } @end @implementation TZCommonTools + (UIEdgeInsets)tz_safeAreaInsets { UIWindow *window = [UIApplication sharedApplication].windows.firstObject; if (![window isKeyWindow]) { UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; if (CGRectEqualToRect(keyWindow.bounds, [UIScreen mainScreen].bounds)) { window = keyWindow; } } if (@available(iOS 11.0, *)) { UIEdgeInsets insets = [window safeAreaInsets]; return insets; } return UIEdgeInsetsZero; } + (BOOL)tz_isIPhoneX { if ([UIWindow instancesRespondToSelector:@selector(safeAreaInsets)]) { return [self tz_safeAreaInsets].bottom > 0; } return (CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(375, 812)) || CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(812, 375)) || CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(414, 896)) || CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(896, 414)) || CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(390, 844)) || CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(844, 390)) || CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(428, 926)) || CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(926, 428))); } + (BOOL)tz_isLandscape { if ([UIApplication sharedApplication].statusBarOrientation == UIDeviceOrientationLandscapeRight || [UIApplication sharedApplication].statusBarOrientation == UIDeviceOrientationLandscapeLeft) { return true; } return false; } + (CGFloat)tz_statusBarHeight { if ([UIWindow instancesRespondToSelector:@selector(safeAreaInsets)]) { return [self tz_safeAreaInsets].top ?: 20; } return 20; } // 获得Info.plist数据字典 + (NSDictionary *)tz_getInfoDictionary { NSDictionary *infoDict = [NSBundle mainBundle].localizedInfoDictionary; if (!infoDict || !infoDict.count) { infoDict = [NSBundle mainBundle].infoDictionary; } if (!infoDict || !infoDict.count) { NSString *path = [[NSBundle mainBundle] pathForResource:@"Info" ofType:@"plist"]; infoDict = [NSDictionary dictionaryWithContentsOfFile:path]; } return infoDict ? infoDict : @{}; } + (NSString *)tz_getAppName { NSDictionary *infoDict = [self tz_getInfoDictionary]; NSString *appName = [infoDict valueForKey:@"CFBundleDisplayName"]; if (!appName) appName = [infoDict valueForKey:@"CFBundleName"]; if (!appName) appName = [infoDict valueForKey:@"CFBundleExecutable"]; if (!appName) { infoDict = [NSBundle mainBundle].infoDictionary; appName = [infoDict valueForKey:@"CFBundleDisplayName"]; if (!appName) appName = [infoDict valueForKey:@"CFBundleName"]; if (!appName) appName = [infoDict valueForKey:@"CFBundleExecutable"]; } return appName; } + (BOOL)tz_isRightToLeftLayout { if (@available(iOS 9.0, *)) { if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:UISemanticContentAttributeUnspecified] == UIUserInterfaceLayoutDirectionRightToLeft) { return YES; } } else { NSString *preferredLanguage = [NSLocale preferredLanguages].firstObject; if ([preferredLanguage hasPrefix:@"ar-"]) { return YES; } } return NO; } + (void)configBarButtonItem:(UIBarButtonItem *)item tzImagePickerVc:(TZImagePickerController *)tzImagePickerVc { item.tintColor = tzImagePickerVc.barItemTextColor; NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary]; textAttrs[NSForegroundColorAttributeName] = tzImagePickerVc.barItemTextColor; textAttrs[NSFontAttributeName] = tzImagePickerVc.barItemTextFont; [item setTitleTextAttributes:textAttrs forState:UIControlStateNormal]; NSMutableDictionary *textAttrsHighlighted = [NSMutableDictionary dictionary]; textAttrsHighlighted[NSFontAttributeName] = tzImagePickerVc.barItemTextFont; [item setTitleTextAttributes:textAttrsHighlighted forState:UIControlStateHighlighted]; } + (BOOL)isICloudSyncError:(NSError *)error { if (!error) return NO; if ([error.domain isEqualToString:@"CKErrorDomain"] || [error.domain isEqualToString:@"CloudPhotoLibraryErrorDomain"]) { return YES; } return NO; } + (BOOL)isAssetNotSelectable:(TZAssetModel *)model tzImagePickerVc:(TZImagePickerController *)tzImagePickerVc { BOOL notSelectable = tzImagePickerVc.selectedModels.count >= tzImagePickerVc.maxImagesCount; if (tzImagePickerVc.selectedModels && tzImagePickerVc.selectedModels.count > 0 && !tzImagePickerVc.allowPickingMultipleVideo) { if (model.asset.mediaType == PHAssetMediaTypeVideo) { notSelectable = true; } } return notSelectable; } @end @interface TZImagePickerConfig () @property (strong, nonatomic) NSSet *supportedLanguages; @end @implementation TZImagePickerConfig + (instancetype)sharedInstance { static dispatch_once_t onceToken; static TZImagePickerConfig *config = nil; dispatch_once(&onceToken, ^{ if (config == nil) { config = [[TZImagePickerConfig alloc] init]; config.supportedLanguages = [NSSet setWithObjects:@"zh-Hans", @"zh-Hant", @"en", @"ar", @"de", @"es", @"fr", @"ja", @"ko-KP", @"pt", @"ru", @"vi", nil]; config.preferredLanguage = nil; config.gifPreviewMaxImagesCount = 50; } }); return config; } - (void)setPreferredLanguage:(NSString *)preferredLanguage { _preferredLanguage = preferredLanguage; if (!preferredLanguage || !preferredLanguage.length) { preferredLanguage = [NSLocale preferredLanguages].firstObject; } NSString *usedLanguage = @"en"; for (NSString *language in self.supportedLanguages) { if ([preferredLanguage hasPrefix:language]) { usedLanguage = language; break; } } _languageBundle = [NSBundle bundleWithPath:[[NSBundle tz_imagePickerBundle] pathForResource:usedLanguage ofType:@"lproj"]]; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImageRequestOperation.h ================================================ // // TZImageRequestOperation.h // TZImagePickerControllerFramework // // Created by 谭真 on 2018/12/20. // Copyright © 2018 谭真. All rights reserved. // #import #import NS_ASSUME_NONNULL_BEGIN @interface TZImageRequestOperation : NSOperation typedef void(^TZImageRequestCompletedBlock)(UIImage *photo, NSDictionary *info, BOOL isDegraded); typedef void(^TZImageRequestProgressBlock)(double progress, NSError *error, BOOL *stop, NSDictionary *info); @property (nonatomic, copy, nullable) TZImageRequestCompletedBlock completedBlock; @property (nonatomic, copy, nullable) TZImageRequestProgressBlock progressBlock; @property (nonatomic, strong, nullable) PHAsset *asset; @property (assign, nonatomic, getter = isExecuting) BOOL executing; @property (assign, nonatomic, getter = isFinished) BOOL finished; - (instancetype)initWithAsset:(PHAsset *)asset completion:(TZImageRequestCompletedBlock)completionBlock progressHandler:(TZImageRequestProgressBlock)progressHandler; - (void)done; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImageRequestOperation.m ================================================ // // TZImageRequestOperation.m // TZImagePickerControllerFramework // // Created by 谭真 on 2018/12/20. // Copyright © 2018 谭真. All rights reserved. // #import "TZImageRequestOperation.h" #import "TZImageManager.h" @implementation TZImageRequestOperation @synthesize executing = _executing; @synthesize finished = _finished; - (instancetype)initWithAsset:(PHAsset *)asset completion:(TZImageRequestCompletedBlock)completionBlock progressHandler:(TZImageRequestProgressBlock)progressHandler { self = [super init]; self.asset = asset; self.completedBlock = completionBlock; self.progressBlock = progressHandler; _executing = NO; _finished = NO; return self; } - (void)start { self.executing = YES; [[TZImageManager manager] getPhotoWithAsset:self.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { dispatch_async(dispatch_get_main_queue(), ^{ if (!isDegraded) { if (self.completedBlock) { self.completedBlock(photo, info, isDegraded); } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self done]; }); } }); } progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { dispatch_async(dispatch_get_main_queue(), ^{ if (self.progressBlock) { self.progressBlock(progress, error, stop, info); } }); } networkAccessAllowed:YES]; } - (void)done { self.finished = YES; self.executing = NO; [self reset]; } - (void)reset { self.asset = nil; self.completedBlock = nil; self.progressBlock = nil; } - (void)setFinished:(BOOL)finished { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } - (void)setExecuting:(BOOL)executing { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } - (BOOL)isAsynchronous { return YES; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZLocationManager.h ================================================ // // TZLocationManager.h // TZImagePickerController // // Created by 谭真 on 2017/06/03. // Copyright © 2017年 谭真. All rights reserved. // 定位管理类 #import #import @interface TZLocationManager : NSObject + (instancetype)manager NS_SWIFT_NAME(default()); /// 开始定位 - (void)startLocation; - (void)startLocationWithSuccessBlock:(void (^)(NSArray *))successBlock failureBlock:(void (^)(NSError *error))failureBlock; - (void)startLocationWithGeocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock; - (void)startLocationWithSuccessBlock:(void (^)(NSArray *))successBlock failureBlock:(void (^)(NSError *error))failureBlock geocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock; /// 结束定位 - (void)stopUpdatingLocation; @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZLocationManager.m ================================================ // // TZLocationManager.m // TZImagePickerController // // Created by 谭真 on 2017/06/03. // Copyright © 2017年 谭真. All rights reserved. // 定位管理类 #import "TZLocationManager.h" #import "TZImagePickerController.h" @interface TZLocationManager () @property (nonatomic, strong) CLLocationManager *locationManager; /// 定位成功的回调block @property (nonatomic, copy) void (^successBlock)(NSArray *); /// 编码成功的回调block @property (nonatomic, copy) void (^geocodeBlock)(NSArray *geocodeArray); /// 定位失败的回调block @property (nonatomic, copy) void (^failureBlock)(NSError *error); @end @implementation TZLocationManager + (instancetype)manager { static TZLocationManager *manager; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ manager = [[self alloc] init]; manager.locationManager = [[CLLocationManager alloc] init]; manager.locationManager.delegate = manager; [manager.locationManager requestWhenInUseAuthorization]; }); return manager; } - (void)startLocation { [self startLocationWithSuccessBlock:nil failureBlock:nil geocoderBlock:nil]; } - (void)startLocationWithSuccessBlock:(void (^)(NSArray *))successBlock failureBlock:(void (^)(NSError *error))failureBlock { [self startLocationWithSuccessBlock:successBlock failureBlock:failureBlock geocoderBlock:nil]; } - (void)startLocationWithGeocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock { [self startLocationWithSuccessBlock:nil failureBlock:nil geocoderBlock:geocoderBlock]; } - (void)startLocationWithSuccessBlock:(void (^)(NSArray *))successBlock failureBlock:(void (^)(NSError *error))failureBlock geocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock { [self.locationManager startUpdatingLocation]; _successBlock = successBlock; _geocodeBlock = geocoderBlock; _failureBlock = failureBlock; } - (void)stopUpdatingLocation { [self.locationManager stopUpdatingLocation]; } #pragma mark - CLLocationManagerDelegate /// 地理位置发生改变时触发 - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { [manager stopUpdatingLocation]; if (_successBlock) { _successBlock(locations); } if (_geocodeBlock && locations.count) { CLGeocoder *geocoder = [[CLGeocoder alloc] init]; [geocoder reverseGeocodeLocation:[locations firstObject] completionHandler:^(NSArray *array, NSError *error) { self->_geocodeBlock(array); }]; } } /// 定位失败回调方法 - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { NSLog(@"定位失败, 错误: %@",error); switch([error code]) { case kCLErrorDenied: { // 用户禁止了定位权限 } break; default: break; } if (_failureBlock) { _failureBlock(error); } } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZPhotoPickerController.h ================================================ // // TZPhotoPickerController.h // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // #import @class TZAlbumModel; @interface TZPhotoPickerController : UIViewController @property (nonatomic, assign) BOOL isFirstAppear; @property (nonatomic, assign) NSInteger columnNumber; @property (nonatomic, strong) TZAlbumModel *model; @end @interface TZCollectionView : UICollectionView @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZPhotoPickerController.m ================================================ // // TZPhotoPickerController.m // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // #import "TZPhotoPickerController.h" #import "TZImagePickerController.h" #import "TZPhotoPreviewController.h" #import "TZAssetCell.h" #import "TZAssetModel.h" #import "UIView+TZLayout.h" #import "TZImageManager.h" #import "TZVideoPlayerController.h" #import "TZGifPhotoPreviewController.h" #import "TZLocationManager.h" #import #import "TZImageRequestOperation.h" #import "TZAuthLimitedFooterTipView.h" #import @interface TZPhotoPickerController () { NSMutableArray *_models; UIView *_bottomToolBar; UIButton *_previewButton; UIButton *_doneButton; UIImageView *_numberImageView; UILabel *_numberLabel; UIButton *_originalPhotoButton; UILabel *_originalPhotoLabel; UIView *_divideLine; BOOL _shouldScrollToBottom; BOOL _showTakePhotoBtn; BOOL _authorizationLimited; CGFloat _offsetItemCount; } @property CGRect previousPreheatRect; @property (nonatomic, assign) BOOL isSelectOriginalPhoto; @property (nonatomic, strong) TZCollectionView *collectionView; @property (nonatomic, strong) TZAuthLimitedFooterTipView *authFooterTipView; @property (nonatomic, strong) UILabel *noDataLabel; @property (strong, nonatomic) UICollectionViewFlowLayout *layout; @property (nonatomic, strong) UIImagePickerController *imagePickerVc; @property (strong, nonatomic) CLLocation *location; @property (nonatomic, strong) NSOperationQueue *operationQueue; @property (nonatomic, assign) BOOL isSavingMedia; @property (nonatomic, assign) BOOL isFetchingMedia; @end static CGSize AssetGridThumbnailSize; static CGFloat itemMargin = 5; @implementation TZPhotoPickerController #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (UIImagePickerController *)imagePickerVc { if (_imagePickerVc == nil) { _imagePickerVc = [[UIImagePickerController alloc] init]; _imagePickerVc.delegate = self; // set appearance / 改变相册选择页的导航栏外观 _imagePickerVc.navigationBar.barTintColor = self.navigationController.navigationBar.barTintColor; _imagePickerVc.navigationBar.tintColor = self.navigationController.navigationBar.tintColor; UIBarButtonItem *tzBarItem, *BarItem; if (@available(iOS 9, *)) { tzBarItem = [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[TZImagePickerController class]]]; BarItem = [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UIImagePickerController class]]]; } else { tzBarItem = [UIBarButtonItem appearanceWhenContainedIn:[TZImagePickerController class], nil]; BarItem = [UIBarButtonItem appearanceWhenContainedIn:[UIImagePickerController class], nil]; } NSDictionary *titleTextAttributes = [tzBarItem titleTextAttributesForState:UIControlStateNormal]; [BarItem setTitleTextAttributes:titleTextAttributes forState:UIControlStateNormal]; } return _imagePickerVc; } - (void)viewDidLoad { [super viewDidLoad]; if ([[TZImageManager manager] authorizationStatusAuthorized] || !SYSTEM_VERSION_GREATER_THAN_15) { [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; } self.isFirstAppear = YES; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; _isSelectOriginalPhoto = tzImagePickerVc.isSelectOriginalPhoto; _shouldScrollToBottom = YES; if (@available(iOS 13.0, *)) { self.view.backgroundColor = UIColor.tertiarySystemBackgroundColor; } else { self.view.backgroundColor = [UIColor whiteColor]; } self.navigationItem.title = _model.name; UIBarButtonItem *cancelItem = [[UIBarButtonItem alloc] initWithTitle:tzImagePickerVc.cancelBtnTitleStr style:UIBarButtonItemStylePlain target:tzImagePickerVc action:@selector(cancelButtonClick)]; [TZCommonTools configBarButtonItem:cancelItem tzImagePickerVc:tzImagePickerVc]; self.navigationItem.rightBarButtonItem = cancelItem; if (tzImagePickerVc.navLeftBarButtonSettingBlock) { UIButton *leftButton = [UIButton buttonWithType:UIButtonTypeCustom]; leftButton.frame = CGRectMake(0, 0, 44, 44); [leftButton addTarget:self action:@selector(navLeftBarButtonClick) forControlEvents:UIControlEventTouchUpInside]; tzImagePickerVc.navLeftBarButtonSettingBlock(leftButton); self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:leftButton]; } else if (tzImagePickerVc.childViewControllers.count) { UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:[NSBundle tz_localizedStringForKey:@"Back"] style:UIBarButtonItemStylePlain target:self action:@selector(navLeftBarButtonClick)]; [TZCommonTools configBarButtonItem:backItem tzImagePickerVc:tzImagePickerVc]; [tzImagePickerVc.childViewControllers firstObject].navigationItem.backBarButtonItem = backItem; } _showTakePhotoBtn = _model.isCameraRoll && ((tzImagePickerVc.allowTakePicture && tzImagePickerVc.allowPickingImage) || (tzImagePickerVc.allowTakeVideo && tzImagePickerVc.allowPickingVideo)); _authorizationLimited = _model.isCameraRoll && [[TZImageManager manager] isPHAuthorizationStatusLimited]; // [self resetCachedAssets]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarOrientationNotification:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; self.operationQueue = [[NSOperationQueue alloc] init]; self.operationQueue.maxConcurrentOperationCount = 3; } - (void)fetchAssetModels { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (_isFirstAppear && !_model.models.count) { [tzImagePickerVc showProgressHUD]; } dispatch_async(dispatch_get_global_queue(0, 0), ^{ CGFloat systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue]; if (!tzImagePickerVc.sortAscendingByModificationDate && self->_isFirstAppear && self->_model.isCameraRoll) { [[TZImageManager manager] getCameraRollAlbumWithFetchAssets:YES completion:^(TZAlbumModel *model) { self->_model = model; self->_models = [NSMutableArray arrayWithArray:self->_model.models]; [self initSubviews]; }]; } else if (self->_showTakePhotoBtn || self->_isFirstAppear || !self.model.models || systemVersion >= 14.0) { [[TZImageManager manager] getAssetsFromFetchResult:self->_model.result completion:^(NSArray *models) { self->_models = [NSMutableArray arrayWithArray:models]; [self initSubviews]; }]; } else { self->_models = [NSMutableArray arrayWithArray:self->_model.models]; [self initSubviews]; } }); } - (void)initSubviews { dispatch_async(dispatch_get_main_queue(), ^{ TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; [tzImagePickerVc hideProgressHUD]; [self checkSelectedModels]; [self configCollectionView]; self->_collectionView.hidden = YES; [self configBottomToolBar]; [self prepareScrollCollectionViewToBottom]; }); } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; tzImagePickerVc.isSelectOriginalPhoto = _isSelectOriginalPhoto; } - (BOOL)prefersStatusBarHidden { return NO; } - (UIStatusBarStyle)preferredStatusBarStyle { TZImagePickerController *tzImagePicker = (TZImagePickerController *)self.navigationController; if (tzImagePicker && [tzImagePicker isKindOfClass:[TZImagePickerController class]]) { return tzImagePicker.statusBarStyle; } return [super preferredStatusBarStyle]; } - (void)configCollectionView { if (!_collectionView) { _layout = [[UICollectionViewFlowLayout alloc] init]; _collectionView = [[TZCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_layout]; if (@available(iOS 13.0, *)) { _collectionView.backgroundColor = UIColor.tertiarySystemBackgroundColor; } else { _collectionView.backgroundColor = [UIColor whiteColor]; } _collectionView.dataSource = self; _collectionView.delegate = self; _collectionView.alwaysBounceHorizontal = NO; _collectionView.contentInset = UIEdgeInsetsMake(itemMargin, itemMargin, itemMargin, itemMargin); [self.view addSubview:_collectionView]; [_collectionView registerClass:[TZAssetCell class] forCellWithReuseIdentifier:@"TZAssetCell"]; [_collectionView registerClass:[TZAssetCameraCell class] forCellWithReuseIdentifier:@"TZAssetCameraCell"]; [_collectionView registerClass:[TZAssetAddMoreCell class] forCellWithReuseIdentifier:@"TZAssetAddMoreCell"]; } else { [_collectionView reloadData]; } if (!_authFooterTipView && _authorizationLimited) { _authFooterTipView = [[TZAuthLimitedFooterTipView alloc] initWithFrame:CGRectMake(0, 0, self.view.tz_width, 80)]; UITapGestureRecognizer *footTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(openSettingsApplication)]; [_authFooterTipView addGestureRecognizer:footTap]; [self.view addSubview:_authFooterTipView]; } _collectionView.contentSize = CGSizeMake(self.view.tz_width, (([self getAllCellCount] + self.columnNumber - 1) / self.columnNumber) * self.view.tz_width); if (_models.count == 0) { _noDataLabel = [UILabel new]; _noDataLabel.textAlignment = NSTextAlignmentCenter; _noDataLabel.text = [NSBundle tz_localizedStringForKey:@"No Photos or Videos"]; CGFloat rgb = 153 / 256.0; _noDataLabel.textColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:1.0]; _noDataLabel.font = [UIFont boldSystemFontOfSize:20]; _noDataLabel.frame = _collectionView.bounds; [_collectionView addSubview:_noDataLabel]; } else if (_noDataLabel) { [_noDataLabel removeFromSuperview]; _noDataLabel = nil; } } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // Determine the size of the thumbnails to request from the PHCachingImageManager CGFloat scale = 2.0; if ([UIScreen mainScreen].bounds.size.width > 600) { scale = 1.0; } CGSize cellSize = ((UICollectionViewFlowLayout *)_collectionView.collectionViewLayout).itemSize; AssetGridThumbnailSize = CGSizeMake(cellSize.width * scale, cellSize.height * scale); if (!_models) { [self fetchAssetModels]; } } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; self.isFirstAppear = NO; // [self updateCachedAssets]; } - (void)configBottomToolBar { if (_bottomToolBar) return; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (!tzImagePickerVc.showSelectBtn) return; _bottomToolBar = [[UIView alloc] initWithFrame:CGRectZero]; CGFloat rgb = 253 / 255.0; if (@available(iOS 13.0, *)) { _bottomToolBar.backgroundColor = UIColor.tertiarySystemBackgroundColor; } else { _bottomToolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:1.0]; } _previewButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_previewButton addTarget:self action:@selector(previewButtonClick) forControlEvents:UIControlEventTouchUpInside]; _previewButton.titleLabel.font = [UIFont systemFontOfSize:16]; [_previewButton setTitle:tzImagePickerVc.previewBtnTitleStr forState:UIControlStateNormal]; [_previewButton setTitle:tzImagePickerVc.previewBtnTitleStr forState:UIControlStateDisabled]; if (@available(iOS 13.0, *)) { [_previewButton setTitleColor:UIColor.labelColor forState:UIControlStateNormal]; } else { [_previewButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; } [_previewButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled]; _previewButton.enabled = tzImagePickerVc.selectedModels.count; if (tzImagePickerVc.allowPickingOriginalPhoto) { _originalPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom]; _originalPhotoButton.imageEdgeInsets = UIEdgeInsetsMake(0, [TZCommonTools tz_isRightToLeftLayout] ? 10 : -10, 0, 0); [_originalPhotoButton addTarget:self action:@selector(originalPhotoButtonClick) forControlEvents:UIControlEventTouchUpInside]; _originalPhotoButton.titleLabel.font = [UIFont systemFontOfSize:16]; [_originalPhotoButton setTitle:tzImagePickerVc.fullImageBtnTitleStr forState:UIControlStateNormal]; [_originalPhotoButton setTitle:tzImagePickerVc.fullImageBtnTitleStr forState:UIControlStateSelected]; [_originalPhotoButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal]; if (@available(iOS 13.0, *)) { [_originalPhotoButton setTitleColor:[UIColor labelColor] forState:UIControlStateSelected]; } else { [_originalPhotoButton setTitleColor:[UIColor blackColor] forState:UIControlStateSelected]; } [_originalPhotoButton setImage:tzImagePickerVc.photoOriginDefImage forState:UIControlStateNormal]; [_originalPhotoButton setImage:tzImagePickerVc.photoOriginSelImage forState:UIControlStateSelected]; _originalPhotoButton.imageView.clipsToBounds = YES; _originalPhotoButton.imageView.contentMode = UIViewContentModeScaleAspectFit; _originalPhotoButton.selected = _isSelectOriginalPhoto; _originalPhotoButton.enabled = tzImagePickerVc.selectedModels.count > 0; _originalPhotoLabel = [[UILabel alloc] init]; _originalPhotoLabel.textAlignment = NSTextAlignmentLeft; _originalPhotoLabel.font = [UIFont systemFontOfSize:16]; if (@available(iOS 13.0, *)) { _originalPhotoLabel.textColor = [UIColor labelColor]; } else { _originalPhotoLabel.textColor = [UIColor blackColor]; } if (_isSelectOriginalPhoto) [self getSelectedPhotoBytes]; } _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; _doneButton.titleLabel.font = [UIFont systemFontOfSize:16]; [_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside]; [_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal]; [_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateDisabled]; [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal]; [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled]; _doneButton.enabled = tzImagePickerVc.selectedModels.count || tzImagePickerVc.alwaysEnableDoneBtn; _numberImageView = [[UIImageView alloc] initWithImage:tzImagePickerVc.photoNumberIconImage]; _numberImageView.hidden = tzImagePickerVc.selectedModels.count <= 0; _numberImageView.clipsToBounds = YES; _numberImageView.contentMode = UIViewContentModeScaleAspectFit; _numberImageView.backgroundColor = [UIColor clearColor]; _numberLabel = [[UILabel alloc] init]; _numberLabel.font = [UIFont systemFontOfSize:15]; _numberLabel.adjustsFontSizeToFitWidth = YES; _numberLabel.textColor = [UIColor whiteColor]; _numberLabel.textAlignment = NSTextAlignmentCenter; _numberLabel.text = [NSString stringWithFormat:@"%zd",tzImagePickerVc.selectedModels.count]; _numberLabel.hidden = tzImagePickerVc.selectedModels.count <= 0; _numberLabel.backgroundColor = [UIColor clearColor]; _numberLabel.userInteractionEnabled = YES; UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doneButtonClick)]; [_numberLabel addGestureRecognizer:tapGesture]; _divideLine = [[UIView alloc] init]; CGFloat rgb2 = 222 / 255.0; if (@available(iOS 13.0, *)) { UIColor *divideLineDyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) { if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) { return [UIColor colorWithRed:rgb2 green:rgb2 blue:rgb2 alpha:1.0]; } else { CGFloat lineDarkRgb = 100 / 255.0; return [UIColor colorWithRed:lineDarkRgb green:lineDarkRgb blue:lineDarkRgb alpha:1.0]; } }]; _divideLine.backgroundColor = divideLineDyColor; } else { _divideLine.backgroundColor = [UIColor colorWithRed:rgb2 green:rgb2 blue:rgb2 alpha:1.0]; } [_bottomToolBar addSubview:_divideLine]; [_bottomToolBar addSubview:_previewButton]; [_bottomToolBar addSubview:_doneButton]; [_bottomToolBar addSubview:_numberImageView]; [_bottomToolBar addSubview:_numberLabel]; [_bottomToolBar addSubview:_originalPhotoButton]; [self.view addSubview:_bottomToolBar]; [_originalPhotoButton addSubview:_originalPhotoLabel]; if (tzImagePickerVc.photoPickerPageUIConfigBlock) { tzImagePickerVc.photoPickerPageUIConfigBlock(_collectionView, _bottomToolBar, _previewButton, _originalPhotoButton, _originalPhotoLabel, _doneButton, _numberImageView, _numberLabel, _divideLine); } } #pragma mark - Layout - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; CGFloat top = 0; CGFloat collectionViewHeight = 0; CGFloat naviBarHeight = self.navigationController.navigationBar.tz_height; CGFloat footerTipViewH = _authorizationLimited ? 80 : 0; BOOL isStatusBarHidden = [UIApplication sharedApplication].isStatusBarHidden; BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height; CGFloat toolBarHeight = 50 + [TZCommonTools tz_safeAreaInsets].bottom; if (self.navigationController.navigationBar.isTranslucent) { top = naviBarHeight; if (!isStatusBarHidden && isFullScreen) top += [TZCommonTools tz_statusBarHeight]; collectionViewHeight = tzImagePickerVc.showSelectBtn ? self.view.tz_height - toolBarHeight - top : self.view.tz_height - top;; } else { collectionViewHeight = tzImagePickerVc.showSelectBtn ? self.view.tz_height - toolBarHeight : self.view.tz_height; } collectionViewHeight -= footerTipViewH; _collectionView.frame = CGRectMake(0, top, self.view.tz_width, collectionViewHeight); _noDataLabel.frame = _collectionView.bounds; CGFloat itemWH = (self.view.tz_width - (self.columnNumber + 1) * itemMargin) / self.columnNumber; _layout.itemSize = CGSizeMake(itemWH, itemWH); _layout.minimumInteritemSpacing = itemMargin; _layout.minimumLineSpacing = itemMargin; [_collectionView setCollectionViewLayout:_layout]; if (_offsetItemCount > 0) { CGFloat offsetY = _offsetItemCount * (_layout.itemSize.height + _layout.minimumLineSpacing); [_collectionView setContentOffset:CGPointMake(0, offsetY)]; } CGFloat toolBarTop = 0; if (!self.navigationController.navigationBar.isHidden) { toolBarTop = self.view.tz_height - toolBarHeight; } else { CGFloat navigationHeight = naviBarHeight + [TZCommonTools tz_statusBarHeight]; toolBarTop = self.view.tz_height - toolBarHeight - navigationHeight; } _bottomToolBar.frame = CGRectMake(0, toolBarTop, self.view.tz_width, toolBarHeight); if (_authFooterTipView) { CGFloat footerTipViewY = _bottomToolBar ? toolBarTop - footerTipViewH : self.view.tz_height - footerTipViewH; _authFooterTipView.frame = CGRectMake(0, footerTipViewY, self.view.tz_width, footerTipViewH);; } CGFloat previewWidth = [tzImagePickerVc.previewBtnTitleStr boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil].size.width + 2; if (!tzImagePickerVc.allowPreview) { previewWidth = 0.0; } _previewButton.frame = CGRectMake(10, 3, previewWidth, 44); _previewButton.tz_width = !tzImagePickerVc.showSelectBtn ? 0 : previewWidth; if (tzImagePickerVc.allowPickingOriginalPhoto) { CGFloat fullImageWidth = [tzImagePickerVc.fullImageBtnTitleStr boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:13]} context:nil].size.width; _originalPhotoButton.frame = CGRectMake(CGRectGetMaxX(_previewButton.frame), 0, fullImageWidth + 56, 50); _originalPhotoLabel.frame = CGRectMake(fullImageWidth + 46, 0, 80, 50); } [_doneButton sizeToFit]; _doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 50); _numberImageView.frame = CGRectMake(_doneButton.tz_left - 24 - 5, 13, 24, 24); _numberLabel.frame = _numberImageView.frame; _divideLine.frame = CGRectMake(0, 0, self.view.tz_width, 1); [TZImageManager manager].columnNumber = [TZImageManager manager].columnNumber; [TZImageManager manager].photoWidth = tzImagePickerVc.photoWidth; [self.collectionView reloadData]; if (tzImagePickerVc.photoPickerPageDidLayoutSubviewsBlock) { tzImagePickerVc.photoPickerPageDidLayoutSubviewsBlock(_collectionView, _bottomToolBar, _previewButton, _originalPhotoButton, _originalPhotoLabel, _doneButton, _numberImageView, _numberLabel, _divideLine); } } #pragma mark - Notification - (void)didChangeStatusBarOrientationNotification:(NSNotification *)noti { _offsetItemCount = _collectionView.contentOffset.y / (_layout.itemSize.height + _layout.minimumLineSpacing); } #pragma mark - Click Event - (void)navLeftBarButtonClick{ [self.navigationController popViewControllerAnimated:YES]; } - (void)previewButtonClick { TZPhotoPreviewController *photoPreviewVc = [[TZPhotoPreviewController alloc] init]; [self pushPhotoPrevireViewController:photoPreviewVc needCheckSelectedModels:YES]; } - (void)originalPhotoButtonClick { _originalPhotoButton.selected = !_originalPhotoButton.isSelected; _isSelectOriginalPhoto = _originalPhotoButton.isSelected; _originalPhotoLabel.hidden = !_originalPhotoButton.isSelected; if (_isSelectOriginalPhoto) { [self getSelectedPhotoBytes]; } } - (void)doneButtonClick { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; // 1.6.8 判断是否满足最小必选张数的限制 if (tzImagePickerVc.minImagesCount && tzImagePickerVc.selectedModels.count < tzImagePickerVc.minImagesCount) { NSString *title = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Select a minimum of %zd photos"], tzImagePickerVc.minImagesCount]; [tzImagePickerVc showAlertWithTitle:title]; return; } [tzImagePickerVc showProgressHUD]; _doneButton.enabled = NO; self.isFetchingMedia = YES; NSMutableArray *assets = [NSMutableArray array]; NSMutableArray *photos; NSMutableArray *infoArr; if (tzImagePickerVc.onlyReturnAsset) { // not fetch image for (NSInteger i = 0; i < tzImagePickerVc.selectedModels.count; i++) { TZAssetModel *model = tzImagePickerVc.selectedModels[i]; [assets addObject:model.asset]; } } else { // fetch image photos = [NSMutableArray array]; infoArr = [NSMutableArray array]; for (NSInteger i = 0; i < tzImagePickerVc.selectedModels.count; i++) { [photos addObject:@1];[assets addObject:@1];[infoArr addObject:@1]; } __block BOOL havenotShowAlert = YES; [TZImageManager manager].shouldFixOrientation = YES; __block UIAlertController *alertView; for (NSInteger i = 0; i < tzImagePickerVc.selectedModels.count; i++) { TZAssetModel *model = tzImagePickerVc.selectedModels[i]; TZImageRequestOperation *operation = [[TZImageRequestOperation alloc] initWithAsset:model.asset completion:^(UIImage * _Nonnull photo, NSDictionary * _Nonnull info, BOOL isDegraded) { if (isDegraded) return; if (photo) { if (![TZImagePickerConfig sharedInstance].notScaleImage) { photo = [[TZImageManager manager] scaleImage:photo toSize:CGSizeMake(tzImagePickerVc.photoWidth, (int)(tzImagePickerVc.photoWidth * photo.size.height / photo.size.width))]; } [photos replaceObjectAtIndex:i withObject:photo]; } if (info) [infoArr replaceObjectAtIndex:i withObject:info]; [assets replaceObjectAtIndex:i withObject:model.asset]; for (id item in photos) { if ([item isKindOfClass:[NSNumber class]]) return; } if (havenotShowAlert && alertView) { [alertView dismissViewControllerAnimated:YES completion:^{ alertView = nil; [self didGetAllPhotos:photos assets:assets infoArr:infoArr]; }]; } else { [self didGetAllPhotos:photos assets:assets infoArr:infoArr]; } } progressHandler:^(double progress, NSError * _Nonnull error, BOOL * _Nonnull stop, NSDictionary * _Nonnull info) { // 如果图片正在从iCloud同步中,提醒用户 if (progress < 1 && havenotShowAlert && !alertView) { alertView = [tzImagePickerVc showAlertWithTitle:[NSBundle tz_localizedStringForKey:@"Synchronizing photos from iCloud"]]; havenotShowAlert = NO; return; } if (progress >= 1) { havenotShowAlert = YES; } }]; [self.operationQueue addOperation:operation]; } } if (tzImagePickerVc.selectedModels.count <= 0 || tzImagePickerVc.onlyReturnAsset) { [self didGetAllPhotos:photos assets:assets infoArr:infoArr]; } } - (void)didGetAllPhotos:(NSArray *)photos assets:(NSArray *)assets infoArr:(NSArray *)infoArr { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; [tzImagePickerVc hideProgressHUD]; _doneButton.enabled = YES; self.isFetchingMedia = NO; if (tzImagePickerVc.autoDismiss) { [self.navigationController dismissViewControllerAnimated:YES completion:^{ [self callDelegateMethodWithPhotos:photos assets:assets infoArr:infoArr]; }]; } else { [self callDelegateMethodWithPhotos:photos assets:assets infoArr:infoArr]; } } - (void)callDelegateMethodWithPhotos:(NSArray *)photos assets:(NSArray *)assets infoArr:(NSArray *)infoArr { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (tzImagePickerVc.allowPickingVideo && tzImagePickerVc.maxImagesCount == 1) { if ([[TZImageManager manager] isVideo:[assets firstObject]]) { BOOL triggered = NO; if ([tzImagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingVideo:sourceAssets:)]) { [tzImagePickerVc.pickerDelegate imagePickerController:tzImagePickerVc didFinishPickingVideo:[photos firstObject] sourceAssets:[assets firstObject]]; triggered = YES; } if (tzImagePickerVc.didFinishPickingVideoHandle) { tzImagePickerVc.didFinishPickingVideoHandle([photos firstObject], [assets firstObject]); triggered = YES; } if (triggered) return; } } if ([tzImagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:)]) { [tzImagePickerVc.pickerDelegate imagePickerController:tzImagePickerVc didFinishPickingPhotos:photos sourceAssets:assets isSelectOriginalPhoto:_isSelectOriginalPhoto]; } if ([tzImagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:infos:)]) { [tzImagePickerVc.pickerDelegate imagePickerController:tzImagePickerVc didFinishPickingPhotos:photos sourceAssets:assets isSelectOriginalPhoto:_isSelectOriginalPhoto infos:infoArr]; } if (tzImagePickerVc.didFinishPickingPhotosHandle) { tzImagePickerVc.didFinishPickingPhotosHandle(photos,assets,_isSelectOriginalPhoto); } if (tzImagePickerVc.didFinishPickingPhotosWithInfosHandle) { tzImagePickerVc.didFinishPickingPhotosWithInfosHandle(photos,assets,_isSelectOriginalPhoto,infoArr); } } #pragma mark - UICollectionViewDataSource && Delegate - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return [self getAllCellCount]; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; // the cell lead to add more photo / 去添加更多照片的cell if (indexPath.item == [self getAddMorePhotoCellIndex]) { TZAssetAddMoreCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZAssetAddMoreCell" forIndexPath:indexPath]; cell.imageView.image = tzImagePickerVc.addMorePhotoImage; cell.tipLabel.text = [NSBundle tz_localizedStringForKey:@"Add more accessible photos"]; cell.imageView.contentMode = UIViewContentModeScaleAspectFit; cell.imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500]; return cell; } // the cell lead to take a picture / 去拍照的cell if (indexPath.item == [self getTakePhotoCellIndex]) { TZAssetCameraCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZAssetCameraCell" forIndexPath:indexPath]; cell.imageView.image = tzImagePickerVc.takePictureImage; if ([tzImagePickerVc.takePictureImageName isEqualToString:@"takePicture80"]) { cell.imageView.contentMode = UIViewContentModeCenter; CGFloat rgb = 223 / 255.0; cell.imageView.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:1.0]; } else { cell.imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500]; } return cell; } // the cell dipaly photo or video / 展示照片或视频的cell TZAssetCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZAssetCell" forIndexPath:indexPath]; cell.allowPickingMultipleVideo = tzImagePickerVc.allowPickingMultipleVideo; cell.photoDefImage = tzImagePickerVc.photoDefImage; cell.photoSelImage = tzImagePickerVc.photoSelImage; cell.assetCellDidSetModelBlock = tzImagePickerVc.assetCellDidSetModelBlock; cell.assetCellDidLayoutSubviewsBlock = tzImagePickerVc.assetCellDidLayoutSubviewsBlock; TZAssetModel *model; if (tzImagePickerVc.sortAscendingByModificationDate) { model = _models[indexPath.item]; } else { NSInteger diff = [self getAllCellCount] - _models.count; model = _models[indexPath.item - diff];; } cell.allowPickingGif = tzImagePickerVc.allowPickingGif; cell.model = model; if (model.isSelected && tzImagePickerVc.showSelectedIndex) { cell.index = [tzImagePickerVc.selectedAssetIds indexOfObject:model.asset.localIdentifier] + 1; } cell.showSelectBtn = tzImagePickerVc.showSelectBtn; cell.allowPreview = tzImagePickerVc.allowPreview; BOOL notSelectable = [TZCommonTools isAssetNotSelectable:model tzImagePickerVc:tzImagePickerVc]; if (notSelectable && tzImagePickerVc.showPhotoCannotSelectLayer && !model.isSelected) { cell.cannotSelectLayerButton.backgroundColor = tzImagePickerVc.cannotSelectLayerColor; cell.cannotSelectLayerButton.hidden = NO; } else { cell.cannotSelectLayerButton.hidden = YES; } __weak typeof(cell) weakCell = cell; __weak typeof(self) weakSelf = self; __weak typeof(_numberImageView.layer) weakLayer = _numberImageView.layer; cell.didSelectPhotoBlock = ^(BOOL isSelected) { __strong typeof(weakCell) strongCell = weakCell; __strong typeof(weakSelf) strongSelf = weakSelf; __strong typeof(weakLayer) strongLayer = weakLayer; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)strongSelf.navigationController; // 1. cancel select / 取消选择 if (isSelected) { strongCell.selectPhotoButton.selected = NO; model.isSelected = NO; NSArray *selectedModels = [NSArray arrayWithArray:tzImagePickerVc.selectedModels]; for (TZAssetModel *model_item in selectedModels) { if ([model.asset.localIdentifier isEqualToString:model_item.asset.localIdentifier]) { [tzImagePickerVc removeSelectedModel:model_item]; [strongSelf setAsset:model_item.asset isSelect:NO]; break; } } [strongSelf refreshBottomToolBarStatus]; if (tzImagePickerVc.showSelectedIndex || tzImagePickerVc.showPhotoCannotSelectLayer) { [[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_PHOTO_PICKER_RELOAD_NOTIFICATION" object:strongSelf.navigationController]; } [UIView showOscillatoryAnimationWithLayer:strongLayer type:TZOscillatoryAnimationToSmaller]; if (strongCell.model.iCloudFailed) { NSString *title = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"]; [tzImagePickerVc showAlertWithTitle:title]; } } else { // 2. select:check if over the maxImagesCount / 选择照片,检查是否超过了最大个数的限制 if (tzImagePickerVc.selectedModels.count < tzImagePickerVc.maxImagesCount) { if ([[TZImageManager manager] isAssetCannotBeSelected:model.asset]) { return; } if (!tzImagePickerVc.allowPreview) { BOOL shouldDone = tzImagePickerVc.maxImagesCount == 1; if (!tzImagePickerVc.allowPickingMultipleVideo && (model.type == TZAssetModelMediaTypeVideo || model.type == TZAssetModelMediaTypePhotoGif)) { shouldDone = YES; } if (shouldDone) { model.isSelected = YES; [tzImagePickerVc addSelectedModel:model]; [strongSelf doneButtonClick]; return; } } strongCell.selectPhotoButton.selected = YES; model.isSelected = YES; [tzImagePickerVc addSelectedModel:model]; if (tzImagePickerVc.showSelectedIndex || tzImagePickerVc.showPhotoCannotSelectLayer) { [[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_PHOTO_PICKER_RELOAD_NOTIFICATION" object:strongSelf.navigationController]; } [strongSelf setAsset:model.asset isSelect:YES]; [strongSelf refreshBottomToolBarStatus]; [UIView showOscillatoryAnimationWithLayer:strongLayer type:TZOscillatoryAnimationToSmaller]; } else { NSString *title = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Select a maximum of %zd photos"], tzImagePickerVc.maxImagesCount]; [tzImagePickerVc showAlertWithTitle:title]; } } }; return cell; } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { // take a photo / 去拍照 TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (indexPath.item == [self getAddMorePhotoCellIndex]) { [self addMorePhoto]; return; } if (indexPath.item == [self getTakePhotoCellIndex]) { [self takePhoto]; return; } // preview phote or video / 预览照片或视频 NSInteger index = indexPath.item; if (!tzImagePickerVc.sortAscendingByModificationDate) { index -= [self getAllCellCount] - _models.count; } TZAssetModel *model = _models[index]; if (model.type == TZAssetModelMediaTypeVideo && !tzImagePickerVc.allowPickingMultipleVideo) { if (tzImagePickerVc.selectedModels.count > 0) { TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; [imagePickerVc showAlertWithTitle:[NSBundle tz_localizedStringForKey:@"Can not choose both video and photo"]]; } else { TZVideoPlayerController *videoPlayerVc = [[TZVideoPlayerController alloc] init]; videoPlayerVc.model = model; [self.navigationController pushViewController:videoPlayerVc animated:YES]; } } else if (model.type == TZAssetModelMediaTypePhotoGif && tzImagePickerVc.allowPickingGif && !tzImagePickerVc.allowPickingMultipleVideo) { if (tzImagePickerVc.selectedModels.count > 0) { TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; [imagePickerVc showAlertWithTitle:[NSBundle tz_localizedStringForKey:@"Can not choose both photo and GIF"]]; } else { TZGifPhotoPreviewController *gifPreviewVc = [[TZGifPhotoPreviewController alloc] init]; gifPreviewVc.model = model; [self.navigationController pushViewController:gifPreviewVc animated:YES]; } } else { TZPhotoPreviewController *photoPreviewVc = [[TZPhotoPreviewController alloc] init]; photoPreviewVc.currentIndex = index; photoPreviewVc.models = _models; [self pushPhotoPrevireViewController:photoPreviewVc]; } } #pragma mark - UIScrollViewDelegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView { // [self updateCachedAssets]; } #pragma mark - Private Method - (NSInteger)getAllCellCount { NSInteger count = _models.count; if (_showTakePhotoBtn) { count += 1; } if (_authorizationLimited) { count += 1; } return count; } - (NSInteger)getTakePhotoCellIndex { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (!_showTakePhotoBtn) { return -1; } if (tzImagePickerVc.sortAscendingByModificationDate) { return [self getAllCellCount] - 1; } else { return 0; } } - (NSInteger)getAddMorePhotoCellIndex { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (!_authorizationLimited) { return -1; } if (tzImagePickerVc.sortAscendingByModificationDate) { if (_showTakePhotoBtn) { return [self getAllCellCount] - 2; } return [self getAllCellCount] - 1; } else { return _showTakePhotoBtn ? 1 : 0; } } /// 拍照按钮点击事件 - (void)takePhoto { AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; if ((authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied)) { // 无权限 做一个友好的提示 NSString *appName = [TZCommonTools tz_getAppName]; NSString *title = [NSBundle tz_localizedStringForKey:@"Can not use camera"]; NSString *message = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\""],appName]; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancelAct = [UIAlertAction actionWithTitle:[NSBundle tz_localizedStringForKey:@"Cancel"] style:UIAlertActionStyleCancel handler:nil]; [alertController addAction:cancelAct]; UIAlertAction *settingAct = [UIAlertAction actionWithTitle:[NSBundle tz_localizedStringForKey:@"Setting"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; }]; [alertController addAction:settingAct]; [self.navigationController presentViewController:alertController animated:YES completion:nil]; } else if (authStatus == AVAuthorizationStatusNotDetermined) { // fix issue 466, 防止用户首次拍照拒绝授权时相机页黑屏 [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { if (granted) { dispatch_async(dispatch_get_main_queue(), ^{ [self pushImagePickerController]; }); } }]; } else { [self pushImagePickerController]; } } - (void)openSettingsApplication { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; } - (void)addMorePhoto { if (@available(iOS 14, *)) { [[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self]; } } // 调用相机 - (void)pushImagePickerController { // 提前定位 TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (tzImagePickerVc.allowCameraLocation) { __weak typeof(self) weakSelf = self; [[TZLocationManager manager] startLocationWithSuccessBlock:^(NSArray *locations) { __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.location = [locations firstObject]; } failureBlock:^(NSError *error) { __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.location = nil; }]; } UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera; if ([UIImagePickerController isSourceTypeAvailable: sourceType]) { self.imagePickerVc.sourceType = sourceType; NSMutableArray *mediaTypes = [NSMutableArray array]; if (tzImagePickerVc.allowTakePicture) { [mediaTypes addObject:(NSString *)kUTTypeImage]; } if (tzImagePickerVc.allowTakeVideo) { [mediaTypes addObject:(NSString *)kUTTypeMovie]; self.imagePickerVc.videoMaximumDuration = tzImagePickerVc.videoMaximumDuration; } self.imagePickerVc.mediaTypes= mediaTypes; if (tzImagePickerVc.uiImagePickerControllerSettingBlock) { tzImagePickerVc.uiImagePickerControllerSettingBlock(_imagePickerVc); } [self presentViewController:_imagePickerVc animated:YES completion:nil]; } else { NSLog(@"模拟器中无法打开照相机,请在真机中使用"); } } - (void)refreshBottomToolBarStatus { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; _previewButton.enabled = tzImagePickerVc.selectedModels.count > 0; _doneButton.enabled = tzImagePickerVc.selectedModels.count > 0 || tzImagePickerVc.alwaysEnableDoneBtn; _numberImageView.hidden = tzImagePickerVc.selectedModels.count <= 0; _numberLabel.hidden = tzImagePickerVc.selectedModels.count <= 0; _numberLabel.text = [NSString stringWithFormat:@"%zd",tzImagePickerVc.selectedModels.count]; _originalPhotoButton.enabled = tzImagePickerVc.selectedModels.count > 0; _originalPhotoButton.selected = (_isSelectOriginalPhoto && _originalPhotoButton.enabled); _originalPhotoLabel.hidden = (!_originalPhotoButton.isSelected); if (_isSelectOriginalPhoto) [self getSelectedPhotoBytes]; if (tzImagePickerVc.photoPickerPageDidRefreshStateBlock) { tzImagePickerVc.photoPickerPageDidRefreshStateBlock(_collectionView, _bottomToolBar, _previewButton, _originalPhotoButton, _originalPhotoLabel, _doneButton, _numberImageView, _numberLabel, _divideLine);; } } - (void)pushPhotoPrevireViewController:(TZPhotoPreviewController *)photoPreviewVc { [self pushPhotoPrevireViewController:photoPreviewVc needCheckSelectedModels:NO]; } - (void)pushPhotoPrevireViewController:(TZPhotoPreviewController *)photoPreviewVc needCheckSelectedModels:(BOOL)needCheckSelectedModels { __weak typeof(self) weakSelf = self; photoPreviewVc.isSelectOriginalPhoto = _isSelectOriginalPhoto; [photoPreviewVc setBackButtonClickBlock:^(BOOL isSelectOriginalPhoto) { __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.isSelectOriginalPhoto = isSelectOriginalPhoto; if (needCheckSelectedModels) { [strongSelf checkSelectedModels]; } [strongSelf.collectionView reloadData]; [strongSelf refreshBottomToolBarStatus]; }]; [photoPreviewVc setDoneButtonClickBlock:^(BOOL isSelectOriginalPhoto) { __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.isSelectOriginalPhoto = isSelectOriginalPhoto; [strongSelf doneButtonClick]; }]; [photoPreviewVc setDoneButtonClickBlockCropMode:^(UIImage *cropedImage, id asset) { __strong typeof(weakSelf) strongSelf = weakSelf; NSArray *assets = @[]; if (asset) { assets = @[asset]; } NSArray *photos = @[]; if (cropedImage) { photos = @[cropedImage]; } [strongSelf didGetAllPhotos:photos assets:assets infoArr:nil]; }]; [self.navigationController pushViewController:photoPreviewVc animated:YES]; } - (void)getSelectedPhotoBytes { // 越南语 && 5屏幕时会显示不下,暂时这样处理 if ([[TZImagePickerConfig sharedInstance].preferredLanguage isEqualToString:@"vi"] && self.view.tz_width <= 320) { return; } TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; [[TZImageManager manager] getPhotosBytesWithArray:imagePickerVc.selectedModels completion:^(NSString *totalBytes) { self->_originalPhotoLabel.text = [NSString stringWithFormat:@"(%@)",totalBytes]; }]; } - (void)prepareScrollCollectionViewToBottom { if (_shouldScrollToBottom && _models.count > 0) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self scrollCollectionViewToBottom]; // try fix #1562:https://github.com/banchichen/TZImagePickerController/issues/1562 if (@available(iOS 15.0, *)) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self scrollCollectionViewToBottom]; }); } }); } else { _collectionView.hidden = NO; } } - (void)scrollCollectionViewToBottom { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; NSInteger item = 0; if (tzImagePickerVc.sortAscendingByModificationDate) { item = [self getAllCellCount] - 1; } [self->_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:NO]; self->_shouldScrollToBottom = NO; self->_collectionView.hidden = NO; } - (void)checkSelectedModels { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; NSArray *selectedModels = tzImagePickerVc.selectedModels; NSMutableSet *selectedAssets = [NSMutableSet setWithCapacity:selectedModels.count]; for (TZAssetModel *model in selectedModels) { [selectedAssets addObject:model.asset]; } for (TZAssetModel *model in _models) { model.isSelected = NO; if ([selectedAssets containsObject:model.asset]) { model.isSelected = YES; } } } /// 选中/取消选中某张照片 - (void)setAsset:(PHAsset *)asset isSelect:(BOOL)isSelect { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (isSelect && [tzImagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didSelectAsset:photo:isSelectOriginalPhoto:)]) { [self callDelegate:asset isSelect:YES]; } if (!isSelect && [tzImagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didDeselectAsset:photo:isSelectOriginalPhoto:)]) { [self callDelegate:asset isSelect:NO]; } } /// 调用选中/取消选中某张照片的代理方法 - (void)callDelegate:(PHAsset *)asset isSelect:(BOOL)isSelect { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; __weak typeof(self) weakSelf = self; __weak typeof(tzImagePickerVc) weakImagePickerVc= tzImagePickerVc; [[TZImageManager manager] getPhotoWithAsset:asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { if (isDegraded) return; __strong typeof(weakSelf) strongSelf = weakSelf; __strong typeof(weakImagePickerVc) strongImagePickerVc = weakImagePickerVc; if (isSelect) { [strongImagePickerVc.pickerDelegate imagePickerController:strongImagePickerVc didSelectAsset:asset photo:photo isSelectOriginalPhoto:strongSelf.isSelectOriginalPhoto]; } else { [strongImagePickerVc.pickerDelegate imagePickerController:strongImagePickerVc didDeselectAsset:asset photo:photo isSelectOriginalPhoto:strongSelf.isSelectOriginalPhoto]; } }]; } #pragma mark - UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { [picker dismissViewControllerAnimated:YES completion:nil]; NSString *type = [info objectForKey:UIImagePickerControllerMediaType]; if ([type isEqualToString:@"public.image"]) { TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; [imagePickerVc showProgressHUD]; UIImage *photo = [info objectForKey:UIImagePickerControllerOriginalImage]; NSDictionary *meta = [info objectForKey:UIImagePickerControllerMediaMetadata]; if (photo) { self.isSavingMedia = YES; [[TZImageManager manager] savePhotoWithImage:photo meta:meta location:self.location completion:^(PHAsset *asset, NSError *error){ self.isSavingMedia = NO; if (!error && asset) { [self addPHAsset:asset]; } else { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; [tzImagePickerVc hideProgressHUD]; } }]; self.location = nil; } } else if ([type isEqualToString:@"public.movie"]) { TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; [imagePickerVc showProgressHUD]; NSURL *videoUrl = [info objectForKey:UIImagePickerControllerMediaURL]; if (videoUrl) { self.isSavingMedia = YES; [[TZImageManager manager] saveVideoWithUrl:videoUrl location:self.location completion:^(PHAsset *asset, NSError *error) { self.isSavingMedia = NO; if (!error && asset) { [self addPHAsset:asset]; } else { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; [tzImagePickerVc hideProgressHUD]; } }]; self.location = nil; } } } - (void)addPHAsset:(PHAsset *)asset { TZAssetModel *assetModel = [[TZImageManager manager] createModelWithAsset:asset]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; [tzImagePickerVc hideProgressHUD]; if (tzImagePickerVc.sortAscendingByModificationDate) { [_models addObject:assetModel]; } else { [_models insertObject:assetModel atIndex:0]; } if (tzImagePickerVc.maxImagesCount <= 1) { if (tzImagePickerVc.allowCrop && asset.mediaType == PHAssetMediaTypeImage) { TZPhotoPreviewController *photoPreviewVc = [[TZPhotoPreviewController alloc] init]; if (tzImagePickerVc.sortAscendingByModificationDate) { photoPreviewVc.currentIndex = _models.count - 1; } else { photoPreviewVc.currentIndex = 0; } photoPreviewVc.models = _models; [self pushPhotoPrevireViewController:photoPreviewVc]; } else if (tzImagePickerVc.selectedModels.count < 1) { [tzImagePickerVc addSelectedModel:assetModel]; [self doneButtonClick]; } return; } if (tzImagePickerVc.selectedModels.count < tzImagePickerVc.maxImagesCount) { if (assetModel.type == TZAssetModelMediaTypeVideo && !tzImagePickerVc.allowPickingMultipleVideo) { // 不能多选视频的情况下,不选中拍摄的视频 } else { if ([[TZImageManager manager] isAssetCannotBeSelected:assetModel.asset]) { return; } assetModel.isSelected = YES; [tzImagePickerVc addSelectedModel:assetModel]; [self refreshBottomToolBarStatus]; } } _collectionView.hidden = YES; [_collectionView reloadData]; _shouldScrollToBottom = YES; [self prepareScrollCollectionViewToBottom]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [picker dismissViewControllerAnimated:YES completion:nil]; } - (void)dealloc { [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self]; // NSLog(@"%@ dealloc",NSStringFromClass(self.class)); } #pragma mark - PHPhotoLibraryChangeObserver - (void)photoLibraryDidChange:(PHChange *)changeInstance { if (self.isSavingMedia || self.isFetchingMedia) { return; } dispatch_async(dispatch_get_main_queue(), ^{ PHFetchResultChangeDetails *changeDetail = [changeInstance changeDetailsForFetchResult:self.model.result]; if (changeDetail == nil) return; if (changeDetail.hasIncrementalChanges == NO) { [self.model refreshFetchResult]; [self fetchAssetModels]; } else { NSInteger insertedCount = changeDetail.insertedObjects.count; NSInteger removedCount = changeDetail.removedObjects.count; NSInteger changedCount = changeDetail.changedObjects.count; if (insertedCount > 0 || removedCount > 0 || changedCount > 0) { self.model.result = changeDetail.fetchResultAfterChanges; self.model.count = changeDetail.fetchResultAfterChanges.count; [self fetchAssetModels]; } } }); } #pragma mark - Asset Caching - (void)resetCachedAssets { [[TZImageManager manager].cachingImageManager stopCachingImagesForAllAssets]; self.previousPreheatRect = CGRectZero; } - (void)updateCachedAssets { BOOL isViewVisible = [self isViewLoaded] && [[self view] window] != nil; if (!isViewVisible) { return; } // The preheat window is twice the height of the visible rect. CGRect preheatRect = _collectionView.bounds; preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect)); /* Check if the collection view is showing an area that is significantly different to the last preheated area. */ CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect)); if (delta > CGRectGetHeight(_collectionView.bounds) / 3.0f) { // Compute the assets to start caching and to stop caching. NSMutableArray *addedIndexPaths = [NSMutableArray array]; NSMutableArray *removedIndexPaths = [NSMutableArray array]; [self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) { NSArray *indexPaths = [self aapl_indexPathsForElementsInRect:removedRect]; [removedIndexPaths addObjectsFromArray:indexPaths]; } addedHandler:^(CGRect addedRect) { NSArray *indexPaths = [self aapl_indexPathsForElementsInRect:addedRect]; [addedIndexPaths addObjectsFromArray:indexPaths]; }]; NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths]; NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths]; // Update the assets the PHCachingImageManager is caching. [[TZImageManager manager].cachingImageManager startCachingImagesForAssets:assetsToStartCaching targetSize:AssetGridThumbnailSize contentMode:PHImageContentModeAspectFill options:nil]; [[TZImageManager manager].cachingImageManager stopCachingImagesForAssets:assetsToStopCaching targetSize:AssetGridThumbnailSize contentMode:PHImageContentModeAspectFill options:nil]; // Store the preheat rect to compare against in the future. self.previousPreheatRect = preheatRect; } } - (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler { if (CGRectIntersectsRect(newRect, oldRect)) { CGFloat oldMaxY = CGRectGetMaxY(oldRect); CGFloat oldMinY = CGRectGetMinY(oldRect); CGFloat newMaxY = CGRectGetMaxY(newRect); CGFloat newMinY = CGRectGetMinY(newRect); if (newMaxY > oldMaxY) { CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY)); addedHandler(rectToAdd); } if (oldMinY > newMinY) { CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY)); addedHandler(rectToAdd); } if (newMaxY < oldMaxY) { CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY)); removedHandler(rectToRemove); } if (oldMinY < newMinY) { CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY)); removedHandler(rectToRemove); } } else { addedHandler(newRect); removedHandler(oldRect); } } - (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths { if (indexPaths.count == 0) { return nil; } NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count]; for (NSIndexPath *indexPath in indexPaths) { if (indexPath.item < _models.count) { TZAssetModel *model = _models[indexPath.item]; [assets addObject:model.asset]; } } return assets; } - (NSArray *)aapl_indexPathsForElementsInRect:(CGRect)rect { NSArray *allLayoutAttributes = [_collectionView.collectionViewLayout layoutAttributesForElementsInRect:rect]; if (allLayoutAttributes.count == 0) { return nil; } NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count]; for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) { NSIndexPath *indexPath = layoutAttributes.indexPath; [indexPaths addObject:indexPath]; } return indexPaths; } #pragma clang diagnostic pop @end @implementation TZCollectionView - (BOOL)touchesShouldCancelInContentView:(UIView *)view { if ([view isKindOfClass:[UIControl class]]) { return YES; } return [super touchesShouldCancelInContentView:view]; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.h ================================================ // // TZPhotoPreviewCell.h // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // #import @class TZAssetModel; @interface TZAssetPreviewCell : UICollectionViewCell @property (nonatomic, strong) TZAssetModel *model; @property (nonatomic, copy) void (^singleTapGestureBlock)(void); - (void)configSubviews; - (void)photoPreviewCollectionViewDidScroll; @end @class TZAssetModel,TZProgressView,TZPhotoPreviewView; @interface TZPhotoPreviewCell : TZAssetPreviewCell @property (nonatomic, copy) void (^imageProgressUpdateBlock)(double progress); @property (nonatomic, strong) TZPhotoPreviewView *previewView; @property (nonatomic, assign) BOOL allowCrop; @property (nonatomic, assign) CGRect cropRect; @property (nonatomic, assign) BOOL scaleAspectFillCrop; - (void)recoverSubviews; @end @interface TZPhotoPreviewView : UIView @property (nonatomic, strong) UIImageView *imageView; @property (nonatomic, strong) UIScrollView *scrollView; @property (nonatomic, strong) UIView *imageContainerView; @property (nonatomic, strong) TZProgressView *progressView; @property (nonatomic, strong) UIImageView *iCloudErrorIcon; @property (nonatomic, strong) UILabel *iCloudErrorLabel; @property (nonatomic, copy) void (^iCloudSyncFailedHandle)(id asset, BOOL isSyncFailed); @property (nonatomic, assign) BOOL allowCrop; @property (nonatomic, assign) CGRect cropRect; @property (nonatomic, assign) BOOL scaleAspectFillCrop; @property (nonatomic, strong) TZAssetModel *model; @property (nonatomic, strong) id asset; @property (nonatomic, copy) void (^singleTapGestureBlock)(void); @property (nonatomic, copy) void (^imageProgressUpdateBlock)(double progress); @property (nonatomic, assign) int32_t imageRequestID; - (void)recoverSubviews; @end @class AVPlayer, AVPlayerLayer; @interface TZVideoPreviewCell : TZAssetPreviewCell @property (strong, nonatomic) AVPlayer *player; @property (strong, nonatomic) AVPlayerLayer *playerLayer; @property (strong, nonatomic) UIButton *playButton; @property (strong, nonatomic) UIImage *cover; @property (nonatomic, strong) NSURL *videoURL; @property (nonatomic, strong) UIImageView *iCloudErrorIcon; @property (nonatomic, strong) UILabel *iCloudErrorLabel; @property (nonatomic, copy) void (^iCloudSyncFailedHandle)(id asset, BOOL isSyncFailed); - (void)pausePlayerAndShowNaviBar; @end @interface TZGifPreviewCell : TZAssetPreviewCell @property (strong, nonatomic) TZPhotoPreviewView *previewView; @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.m ================================================ // // TZPhotoPreviewCell.m // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // #import "TZPhotoPreviewCell.h" #import "TZAssetModel.h" #import "UIView+TZLayout.h" #import "TZImageManager.h" #import "TZProgressView.h" #import "TZImageCropManager.h" #import #import "TZImagePickerController.h" @implementation TZAssetPreviewCell - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor blackColor]; [self configSubviews]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(photoPreviewCollectionViewDidScroll) name:@"photoPreviewCollectionViewDidScroll" object:nil]; } return self; } - (void)configSubviews { } #pragma mark - Notification - (void)photoPreviewCollectionViewDidScroll { } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end @implementation TZPhotoPreviewCell - (void)configSubviews { self.previewView = [[TZPhotoPreviewView alloc] initWithFrame:CGRectZero]; __weak typeof(self) weakSelf = self; [self.previewView setSingleTapGestureBlock:^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf.singleTapGestureBlock) { strongSelf.singleTapGestureBlock(); } }]; [self.previewView setImageProgressUpdateBlock:^(double progress) { __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf.imageProgressUpdateBlock) { strongSelf.imageProgressUpdateBlock(progress); } }]; [self.contentView addSubview:self.previewView]; } - (void)setModel:(TZAssetModel *)model { [super setModel:model]; _previewView.model = model; } - (void)recoverSubviews { [_previewView recoverSubviews]; } - (void)setAllowCrop:(BOOL)allowCrop { _allowCrop = allowCrop; _previewView.allowCrop = allowCrop; } - (void)setScaleAspectFillCrop:(BOOL)scaleAspectFillCrop { _scaleAspectFillCrop = scaleAspectFillCrop; _previewView.scaleAspectFillCrop = scaleAspectFillCrop; } - (void)setCropRect:(CGRect)cropRect { _cropRect = cropRect; _previewView.cropRect = cropRect; } - (void)layoutSubviews { [super layoutSubviews]; self.previewView.frame = self.bounds; } @end @interface TZPhotoPreviewView () @property (assign, nonatomic) BOOL isRequestingGIF; @end @implementation TZPhotoPreviewView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { _scrollView = [[UIScrollView alloc] init]; _scrollView.bouncesZoom = YES; _scrollView.maximumZoomScale = 4; _scrollView.minimumZoomScale = 1.0; _scrollView.multipleTouchEnabled = YES; _scrollView.delegate = self; _scrollView.scrollsToTop = NO; _scrollView.showsHorizontalScrollIndicator = NO; _scrollView.showsVerticalScrollIndicator = YES; _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _scrollView.delaysContentTouches = NO; _scrollView.canCancelContentTouches = YES; _scrollView.alwaysBounceVertical = NO; if (@available(iOS 11, *)) { _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } [self addSubview:_scrollView]; _imageContainerView = [[UIView alloc] init]; _imageContainerView.clipsToBounds = YES; _imageContainerView.contentMode = UIViewContentModeScaleAspectFill; [_scrollView addSubview:_imageContainerView]; _imageView = [[UIImageView alloc] init]; _imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500]; _imageView.contentMode = UIViewContentModeScaleAspectFill; _imageView.clipsToBounds = YES; [_imageContainerView addSubview:_imageView]; _iCloudErrorIcon = [[UIImageView alloc] init]; _iCloudErrorIcon.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"]; _iCloudErrorIcon.hidden = YES; [self addSubview:_iCloudErrorIcon]; _iCloudErrorLabel = [[UILabel alloc] init]; _iCloudErrorLabel.font = [UIFont systemFontOfSize:10]; _iCloudErrorLabel.textColor = [UIColor whiteColor]; _iCloudErrorLabel.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"]; _iCloudErrorLabel.hidden = YES; [self addSubview:_iCloudErrorLabel]; UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)]; [self addGestureRecognizer:tap1]; UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)]; tap2.numberOfTapsRequired = 2; [tap1 requireGestureRecognizerToFail:tap2]; [self addGestureRecognizer:tap2]; [self configProgressView]; } return self; } - (void)configProgressView { _progressView = [[TZProgressView alloc] init]; _progressView.hidden = YES; [self addSubview:_progressView]; } - (void)setModel:(TZAssetModel *)model { _model = model; self.isRequestingGIF = NO; [_scrollView setZoomScale:1.0 animated:NO]; if (model.type == TZAssetModelMediaTypePhotoGif) { // 先显示缩略图 [[TZImageManager manager] getPhotoWithAsset:model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { if (photo) { self.imageView.image = photo; } [self resizeSubviews]; if (self.isRequestingGIF) { return; } // 再显示gif动图 self.isRequestingGIF = YES; [[TZImageManager manager] getOriginalPhotoDataWithAsset:model.asset progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { progress = progress > 0.02 ? progress : 0.02; dispatch_async(dispatch_get_main_queue(), ^{ BOOL iCloudSyncFailed = [TZCommonTools isICloudSyncError:error]; self.iCloudErrorLabel.hidden = !iCloudSyncFailed; self.iCloudErrorIcon.hidden = !iCloudSyncFailed; if (self.iCloudSyncFailedHandle) { self.iCloudSyncFailedHandle(model.asset, iCloudSyncFailed); } self.progressView.progress = progress; if (progress >= 1) { self.progressView.hidden = YES; } else { self.progressView.hidden = NO; } }); #ifdef DEBUG NSLog(@"[TZImagePickerController] getOriginalPhotoDataWithAsset:%f error:%@", progress, error); #endif } completion:^(NSData *data, NSDictionary *info, BOOL isDegraded) { if (!isDegraded) { self.isRequestingGIF = NO; self.progressView.hidden = YES; if ([TZImagePickerConfig sharedInstance].gifImagePlayBlock) { [TZImagePickerConfig sharedInstance].gifImagePlayBlock(self, self.imageView, data, info); } else { self.imageView.image = [UIImage sd_tz_animatedGIFWithData:data]; } [self resizeSubviews]; } }]; } progressHandler:nil networkAccessAllowed:NO]; } else { self.asset = model.asset; } } - (void)setAsset:(PHAsset *)asset { if (_asset && self.imageRequestID) { [[PHImageManager defaultManager] cancelImageRequest:self.imageRequestID]; } _asset = asset; self.imageRequestID = [[TZImageManager manager] getPhotoWithAsset:asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { BOOL iCloudSyncFailed = !photo && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]]; self.iCloudErrorLabel.hidden = !iCloudSyncFailed; self.iCloudErrorIcon.hidden = !iCloudSyncFailed; if (self.iCloudSyncFailedHandle) { self.iCloudSyncFailedHandle(asset, iCloudSyncFailed); } if (![asset isEqual:self->_asset]) return; if (photo) { self.imageView.image = photo; } [self resizeSubviews]; if (self.imageView.tz_height && self.allowCrop) { CGFloat scale = MAX(self.cropRect.size.width / self.imageView.tz_width, self.cropRect.size.height / self.imageView.tz_height); if (self.scaleAspectFillCrop && scale > 1) { // 如果设置图片缩放裁剪并且图片需要缩放 CGFloat multiple = self.scrollView.maximumZoomScale / self.scrollView.minimumZoomScale; self.scrollView.minimumZoomScale = scale; self.scrollView.maximumZoomScale = scale * MAX(multiple, 2); [self.scrollView setZoomScale:scale animated:YES]; } } self->_progressView.hidden = YES; if (self.imageProgressUpdateBlock) { self.imageProgressUpdateBlock(1); } if (!isDegraded) { self.imageRequestID = 0; } } progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { if (![asset isEqual:self->_asset]) return; self->_progressView.hidden = NO; [self bringSubviewToFront:self->_progressView]; progress = progress > 0.02 ? progress : 0.02; self->_progressView.progress = progress; if (self.imageProgressUpdateBlock && progress < 1) { self.imageProgressUpdateBlock(progress); } if (progress >= 1) { self->_progressView.hidden = YES; self.imageRequestID = 0; } } networkAccessAllowed:YES]; [self configMaximumZoomScale]; } - (void)recoverSubviews { [_scrollView setZoomScale:_scrollView.minimumZoomScale animated:NO]; [self resizeSubviews]; } - (void)resizeSubviews { _imageContainerView.tz_origin = CGPointZero; _imageContainerView.tz_width = self.scrollView.tz_width; UIImage *image = _imageView.image; if (image.size.height / image.size.width > self.tz_height / self.scrollView.tz_width) { CGFloat width = image.size.width / image.size.height * self.scrollView.tz_height; if (width < 1 || isnan(width)) width = self.tz_width; width = floor(width); _imageContainerView.tz_width = width; _imageContainerView.tz_height = self.tz_height; _imageContainerView.tz_centerX = self.scrollView.tz_width / 2; } else { CGFloat height = image.size.height / image.size.width * self.scrollView.tz_width; if (height < 1 || isnan(height)) height = self.tz_height; height = floor(height); _imageContainerView.tz_height = height; _imageContainerView.tz_centerY = self.tz_height / 2; } if (_imageContainerView.tz_height > self.tz_height && _imageContainerView.tz_height - self.tz_height <= 1) { _imageContainerView.tz_height = self.tz_height; } CGFloat contentSizeH = MAX(_imageContainerView.tz_height, self.tz_height); _scrollView.contentSize = CGSizeMake(self.scrollView.tz_width, contentSizeH); [_scrollView scrollRectToVisible:self.bounds animated:NO]; _scrollView.alwaysBounceVertical = _imageContainerView.tz_height <= self.tz_height ? NO : YES; _imageView.frame = _imageContainerView.bounds; [self refreshScrollViewContentSize]; } - (void)configMaximumZoomScale { _scrollView.maximumZoomScale = _allowCrop ? 6.0 : 4.0; if ([self.asset isKindOfClass:[PHAsset class]]) { PHAsset *phAsset = (PHAsset *)self.asset; CGFloat aspectRatio = phAsset.pixelWidth / (CGFloat)phAsset.pixelHeight; // 优化超宽图片的显示 if (aspectRatio > 1.5) { self.scrollView.maximumZoomScale *= aspectRatio / 1.5; } } } - (void)refreshScrollViewContentSize { if (_allowCrop) { // 1.7.2 如果允许裁剪,需要让图片的任意部分都能在裁剪框内,于是对_scrollView做了如下处理: // 1.让contentSize增大(裁剪框右下角的图片部分) CGFloat contentWidthAdd = (MIN(_imageContainerView.tz_width, self.scrollView.tz_width) - _cropRect.size.width) / 2; CGFloat contentHeightAdd = (MIN(_imageContainerView.tz_height, self.scrollView.tz_height) - _cropRect.size.height) / 2; CGFloat newSizeW = MAX(self.scrollView.contentSize.width, self.scrollView.tz_width) + contentWidthAdd; CGFloat newSizeH = MAX(self.scrollView.contentSize.height, self.scrollView.tz_height) + contentHeightAdd; _scrollView.contentSize = CGSizeMake(newSizeW, newSizeH); _scrollView.alwaysBounceVertical = YES; // 2.让scrollView新增滑动区域(裁剪框左上角的图片部分) if (contentHeightAdd > 0 || contentWidthAdd > 0) { _scrollView.contentInset = UIEdgeInsetsMake(MAX(contentHeightAdd, 0), MAX(contentWidthAdd, 0), 0, 0); } else { _scrollView.contentInset = UIEdgeInsetsZero; } } } - (void)layoutSubviews { [super layoutSubviews]; _scrollView.frame = CGRectMake(10, 0, self.tz_width - 20, self.tz_height); static CGFloat progressWH = 40; CGFloat progressX = (self.tz_width - progressWH) / 2; CGFloat progressY = (self.tz_height - progressWH) / 2; _progressView.frame = CGRectMake(progressX, progressY, progressWH, progressWH); [self recoverSubviews]; _iCloudErrorIcon.frame = CGRectMake(20, [TZCommonTools tz_statusBarHeight] + 44 + 10, 28, 28); _iCloudErrorLabel.frame = CGRectMake(53, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.tz_width - 63, 28); } #pragma mark - UITapGestureRecognizer Event - (void)doubleTap:(UITapGestureRecognizer *)tap { if (_scrollView.zoomScale > _scrollView.minimumZoomScale) { _scrollView.contentInset = UIEdgeInsetsZero; [_scrollView setZoomScale:_scrollView.minimumZoomScale animated:YES]; } else { CGPoint touchPoint = [tap locationInView:self.imageView]; CGFloat newZoomScale = MIN(_scrollView.maximumZoomScale, 2.5); CGFloat xsize = self.frame.size.width / newZoomScale; CGFloat ysize = self.frame.size.height / newZoomScale; [_scrollView zoomToRect:CGRectMake(touchPoint.x - xsize/2, touchPoint.y - ysize/2, xsize, ysize) animated:YES]; } } - (void)singleTap:(UITapGestureRecognizer *)tap { if (self.singleTapGestureBlock) { self.singleTapGestureBlock(); } } #pragma mark - UIScrollViewDelegate - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return _imageContainerView; } - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { scrollView.contentInset = UIEdgeInsetsZero; } - (void)scrollViewDidZoom:(UIScrollView *)scrollView { [self refreshImageContainerViewCenter]; } - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale { [self refreshScrollViewContentSize]; } #pragma mark - Private - (void)refreshImageContainerViewCenter { CGFloat offsetX = (_scrollView.tz_width > _scrollView.contentSize.width) ? ((_scrollView.tz_width - _scrollView.contentSize.width) * 0.5) : 0.0; CGFloat offsetY = (_scrollView.tz_height > _scrollView.contentSize.height) ? ((_scrollView.tz_height - _scrollView.contentSize.height) * 0.5) : 0.0; self.imageContainerView.center = CGPointMake(_scrollView.contentSize.width * 0.5 + offsetX, _scrollView.contentSize.height * 0.5 + offsetY); } @end @implementation TZVideoPreviewCell - (void)configSubviews { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil]; _iCloudErrorIcon = [[UIImageView alloc] init]; _iCloudErrorIcon.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"]; _iCloudErrorIcon.hidden = YES; _iCloudErrorLabel = [[UILabel alloc] init]; _iCloudErrorLabel.font = [UIFont systemFontOfSize:10]; _iCloudErrorLabel.textColor = [UIColor whiteColor]; _iCloudErrorLabel.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"]; _iCloudErrorLabel.hidden = YES; } - (void)configPlayButton { if (_playButton) { [_playButton removeFromSuperview]; } _playButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted]; [_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside]; _playButton.frame = CGRectMake(0, 64, self.tz_width, self.tz_height - 64 - 44); [self.contentView addSubview:_playButton]; [self.contentView addSubview:_iCloudErrorIcon]; [self.contentView addSubview:_iCloudErrorLabel]; } - (void)setModel:(TZAssetModel *)model { [super setModel:model]; [self configMoviePlayer]; } - (void)setVideoURL:(NSURL *)videoURL { _videoURL = videoURL; [self configMoviePlayer]; } - (void)configMoviePlayer { if (_player) { [_playerLayer removeFromSuperlayer]; _playerLayer = nil; [_player pause]; _player = nil; } if (self.model && self.model.asset) { [[TZImageManager manager] getPhotoWithAsset:self.model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { BOOL iCloudSyncFailed = !photo && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]]; self.iCloudErrorLabel.hidden = !iCloudSyncFailed; self.iCloudErrorIcon.hidden = !iCloudSyncFailed; if (self.iCloudSyncFailedHandle) { self.iCloudSyncFailedHandle(self.model.asset, iCloudSyncFailed); } if (photo) { self.cover = photo; } }]; [[TZImageManager manager] getVideoWithAsset:self.model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) { dispatch_async(dispatch_get_main_queue(), ^{ BOOL iCloudSyncFailed = !playerItem && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]]; self.iCloudErrorLabel.hidden = !iCloudSyncFailed; self.iCloudErrorIcon.hidden = !iCloudSyncFailed; if (self.iCloudSyncFailedHandle) { self.iCloudSyncFailedHandle(self.model.asset, iCloudSyncFailed); } [self configPlayerWithItem:playerItem]; }); }]; } else { AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:self.videoURL]; [self configPlayerWithItem:playerItem]; } } - (void)configPlayerWithItem:(AVPlayerItem *)playerItem { self.player = [AVPlayer playerWithPlayerItem:playerItem]; self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; self.playerLayer.backgroundColor = [UIColor blackColor].CGColor; self.playerLayer.frame = self.bounds; [self.contentView.layer addSublayer:self.playerLayer]; [self configPlayButton]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem]; } - (void)layoutSubviews { [super layoutSubviews]; _playerLayer.frame = self.bounds; _playButton.frame = CGRectMake(0, 64, self.tz_width, self.tz_height - 64 - 44); _iCloudErrorIcon.frame = CGRectMake(20, [TZCommonTools tz_statusBarHeight] + 44 + 10, 28, 28); _iCloudErrorLabel.frame = CGRectMake(53, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.tz_width - 63, 28); } - (void)photoPreviewCollectionViewDidScroll { if (_player && _player.rate != 0.0) { [self pausePlayerAndShowNaviBar]; } } #pragma mark - Notification - (void)appWillResignActiveNotification { if (_player && _player.rate != 0.0) { [self pausePlayerAndShowNaviBar]; } } #pragma mark - Click Event - (void)playButtonClick { CMTime currentTime = _player.currentItem.currentTime; CMTime durationTime = _player.currentItem.duration; if (_player.rate == 0.0f) { [[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_VIDEO_PLAY_NOTIFICATION" object:_player]; if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)]; [_player play]; [_playButton setImage:nil forState:UIControlStateNormal]; [UIApplication sharedApplication].statusBarHidden = YES; if (self.singleTapGestureBlock) { self.singleTapGestureBlock(); } } else { [self pausePlayerAndShowNaviBar]; } } - (void)pausePlayerAndShowNaviBar { [_player pause]; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; if (self.singleTapGestureBlock) { self.singleTapGestureBlock(); } } @end @implementation TZGifPreviewCell - (void)configSubviews { [self configPreviewView]; } - (void)configPreviewView { _previewView = [[TZPhotoPreviewView alloc] initWithFrame:CGRectZero]; __weak typeof(self) weakSelf = self; [_previewView setSingleTapGestureBlock:^{ __strong typeof(weakSelf) strongSelf = weakSelf; [strongSelf signleTapAction]; }]; [self.contentView addSubview:_previewView]; } - (void)setModel:(TZAssetModel *)model { [super setModel:model]; _previewView.model = self.model; } - (void)layoutSubviews { [super layoutSubviews]; _previewView.frame = self.bounds; } #pragma mark - Click Event - (void)signleTapAction { if (self.singleTapGestureBlock) { self.singleTapGestureBlock(); } } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZPhotoPreviewController.h ================================================ // // TZPhotoPreviewController.h // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // #import @interface TZPhotoPreviewController : UIViewController @property (nonatomic, strong) NSMutableArray *models; ///< All photo models / 所有图片模型数组 @property (nonatomic, strong) NSMutableArray *photos; ///< All photos / 所有图片数组 @property (nonatomic, assign) NSInteger currentIndex; ///< Index of the photo user click / 用户点击的图片的索引 @property (nonatomic, assign) BOOL isSelectOriginalPhoto; ///< If YES,return original photo / 是否返回原图 @property (nonatomic, assign) BOOL isCropImage; /// Return the new selected photos / 返回最新的选中图片数组 @property (nonatomic, copy) void (^backButtonClickBlock)(BOOL isSelectOriginalPhoto); @property (nonatomic, copy) void (^doneButtonClickBlock)(BOOL isSelectOriginalPhoto); @property (nonatomic, copy) void (^doneButtonClickBlockCropMode)(UIImage *cropedImage,id asset); @property (nonatomic, copy) void (^doneButtonClickBlockWithPreviewType)(NSArray *photos,NSArray *assets,BOOL isSelectOriginalPhoto); @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZPhotoPreviewController.m ================================================ // // TZPhotoPreviewController.m // TZImagePickerController // // Created by 谭真 on 15/12/24. // Copyright © 2015年 谭真. All rights reserved. // #import "TZPhotoPreviewController.h" #import "TZPhotoPreviewCell.h" #import "TZAssetModel.h" #import "UIView+TZLayout.h" #import "TZImagePickerController.h" #import "TZImageManager.h" #import "TZImageCropManager.h" @interface TZPhotoPreviewController () { UICollectionView *_collectionView; UICollectionViewFlowLayout *_layout; NSArray *_photosTemp; NSArray *_assetsTemp; UIView *_naviBar; UIButton *_backButton; UIButton *_selectButton; UILabel *_indexLabel; UIView *_toolBar; UIButton *_doneButton; UIImageView *_numberImageView; UILabel *_numberLabel; UIButton *_originalPhotoButton; UILabel *_originalPhotoLabel; CGFloat _offsetItemCount; BOOL _didSetIsSelectOriginalPhoto; } @property (nonatomic, assign) BOOL isHideNaviBar; @property (nonatomic, strong) UIView *cropBgView; @property (nonatomic, strong) UIView *cropView; @property (nonatomic, assign) double progress; @property (strong, nonatomic) UIAlertController *alertView; @property (nonatomic, strong) UIView *iCloudErrorView; @end @implementation TZPhotoPreviewController - (void)viewDidLoad { [super viewDidLoad]; [TZImageManager manager].shouldFixOrientation = YES; TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (!_didSetIsSelectOriginalPhoto) { _isSelectOriginalPhoto = _tzImagePickerVc.isSelectOriginalPhoto; } if (!self.models.count) { self.models = [NSMutableArray arrayWithArray:_tzImagePickerVc.selectedModels]; _assetsTemp = [NSMutableArray arrayWithArray:_tzImagePickerVc.selectedAssets]; } [self configCollectionView]; [self configCustomNaviBar]; [self configBottomToolBar]; self.view.clipsToBounds = YES; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarOrientationNotification:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; } - (void)setIsSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto { _isSelectOriginalPhoto = isSelectOriginalPhoto; _didSetIsSelectOriginalPhoto = YES; } - (void)setPhotos:(NSMutableArray *)photos { _photos = photos; _photosTemp = [NSArray arrayWithArray:photos]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.navigationController setNavigationBarHidden:YES animated:YES]; [UIApplication sharedApplication].statusBarHidden = YES; if (_currentIndex) { [_collectionView setContentOffset:CGPointMake((self.view.tz_width + 20) * self.currentIndex, 0) animated:NO]; } [self refreshNaviBarAndBottomBarState]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (tzImagePickerVc.needShowStatusBar) { [UIApplication sharedApplication].statusBarHidden = NO; } [self.navigationController setNavigationBarHidden:NO animated:YES]; [TZImageManager manager].shouldFixOrientation = NO; } - (BOOL)prefersStatusBarHidden { return YES; } - (void)configCustomNaviBar { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; _naviBar = [[UIView alloc] initWithFrame:CGRectZero]; _naviBar.backgroundColor = [UIColor colorWithRed:(34/255.0) green:(34/255.0) blue:(34/255.0) alpha:0.7]; _backButton = [[UIButton alloc] initWithFrame:CGRectZero]; [_backButton setImage:[UIImage tz_imageNamedFromMyBundle:@"navi_back"] forState:UIControlStateNormal]; [_backButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [_backButton addTarget:self action:@selector(backButtonClick) forControlEvents:UIControlEventTouchUpInside]; _selectButton = [[UIButton alloc] initWithFrame:CGRectZero]; [_selectButton setImage:tzImagePickerVc.photoDefImage forState:UIControlStateNormal]; [_selectButton setImage:tzImagePickerVc.photoSelImage forState:UIControlStateSelected]; _selectButton.imageView.clipsToBounds = YES; _selectButton.imageEdgeInsets = UIEdgeInsetsMake(10, 0, 10, 0); _selectButton.imageView.contentMode = UIViewContentModeScaleAspectFit; [_selectButton addTarget:self action:@selector(select:) forControlEvents:UIControlEventTouchUpInside]; _selectButton.hidden = !tzImagePickerVc.showSelectBtn; _indexLabel = [[UILabel alloc] init]; _indexLabel.adjustsFontSizeToFitWidth = YES; _indexLabel.font = [UIFont systemFontOfSize:14]; _indexLabel.textColor = [UIColor whiteColor]; _indexLabel.textAlignment = NSTextAlignmentCenter; [_naviBar addSubview:_selectButton]; [_naviBar addSubview:_indexLabel]; [_naviBar addSubview:_backButton]; [self.view addSubview:_naviBar]; } - (void)configBottomToolBar { _toolBar = [[UIView alloc] initWithFrame:CGRectZero]; static CGFloat rgb = 34 / 255.0; _toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7]; TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (_tzImagePickerVc.allowPickingOriginalPhoto) { _originalPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom]; _originalPhotoButton.imageEdgeInsets = UIEdgeInsetsMake(0, [TZCommonTools tz_isRightToLeftLayout] ? 10 : -10, 0, 0); _originalPhotoButton.backgroundColor = [UIColor clearColor]; [_originalPhotoButton addTarget:self action:@selector(originalPhotoButtonClick) forControlEvents:UIControlEventTouchUpInside]; _originalPhotoButton.titleLabel.font = [UIFont systemFontOfSize:13]; [_originalPhotoButton setTitle:_tzImagePickerVc.fullImageBtnTitleStr forState:UIControlStateNormal]; [_originalPhotoButton setTitle:_tzImagePickerVc.fullImageBtnTitleStr forState:UIControlStateSelected]; [_originalPhotoButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal]; [_originalPhotoButton setTitleColor:[UIColor whiteColor] forState:UIControlStateSelected]; [_originalPhotoButton setImage:_tzImagePickerVc.photoPreviewOriginDefImage forState:UIControlStateNormal]; [_originalPhotoButton setImage:_tzImagePickerVc.photoOriginSelImage forState:UIControlStateSelected]; _originalPhotoLabel = [[UILabel alloc] init]; _originalPhotoLabel.textAlignment = NSTextAlignmentLeft; _originalPhotoLabel.font = [UIFont systemFontOfSize:13]; _originalPhotoLabel.textColor = [UIColor whiteColor]; _originalPhotoLabel.backgroundColor = [UIColor clearColor]; if (_isSelectOriginalPhoto) [self showPhotoBytes]; } _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; _doneButton.titleLabel.font = [UIFont systemFontOfSize:16]; [_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside]; [_doneButton setTitle:_tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal]; [_doneButton setTitleColor:_tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal]; _numberImageView = [[UIImageView alloc] initWithImage:_tzImagePickerVc.photoNumberIconImage]; _numberImageView.backgroundColor = [UIColor clearColor]; _numberImageView.clipsToBounds = YES; _numberImageView.contentMode = UIViewContentModeScaleAspectFit; _numberImageView.hidden = _tzImagePickerVc.selectedModels.count <= 0; _numberLabel = [[UILabel alloc] init]; _numberLabel.font = [UIFont systemFontOfSize:15]; _numberLabel.adjustsFontSizeToFitWidth = YES; _numberLabel.textColor = [UIColor whiteColor]; _numberLabel.textAlignment = NSTextAlignmentCenter; _numberLabel.text = [NSString stringWithFormat:@"%zd",_tzImagePickerVc.selectedModels.count]; _numberLabel.hidden = _tzImagePickerVc.selectedModels.count <= 0; _numberLabel.backgroundColor = [UIColor clearColor]; _numberLabel.userInteractionEnabled = YES; UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doneButtonClick)]; [_numberLabel addGestureRecognizer:tapGesture]; [_originalPhotoButton addSubview:_originalPhotoLabel]; [_toolBar addSubview:_doneButton]; [_toolBar addSubview:_originalPhotoButton]; [_toolBar addSubview:_numberImageView]; [_toolBar addSubview:_numberLabel]; [self.view addSubview:_toolBar]; if (_tzImagePickerVc.photoPreviewPageUIConfigBlock) { _tzImagePickerVc.photoPreviewPageUIConfigBlock(_collectionView, _naviBar, _backButton, _selectButton, _indexLabel, _toolBar, _originalPhotoButton, _originalPhotoLabel, _doneButton, _numberImageView, _numberLabel); } } - (void)configCollectionView { _layout = [[UICollectionViewFlowLayout alloc] init]; _layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_layout]; _collectionView.backgroundColor = [UIColor blackColor]; _collectionView.dataSource = self; _collectionView.delegate = self; _collectionView.pagingEnabled = YES; _collectionView.scrollsToTop = NO; _collectionView.showsHorizontalScrollIndicator = NO; _collectionView.contentOffset = CGPointMake(0, 0); _collectionView.contentSize = CGSizeMake(self.models.count * (self.view.tz_width + 20), 0); if (@available(iOS 11, *)) { _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } [self.view addSubview:_collectionView]; [_collectionView registerClass:[TZPhotoPreviewCell class] forCellWithReuseIdentifier:@"TZPhotoPreviewCell"]; [_collectionView registerClass:[TZPhotoPreviewCell class] forCellWithReuseIdentifier:@"TZPhotoPreviewCellGIF"]; [_collectionView registerClass:[TZVideoPreviewCell class] forCellWithReuseIdentifier:@"TZVideoPreviewCell"]; [_collectionView registerClass:[TZGifPreviewCell class] forCellWithReuseIdentifier:@"TZGifPreviewCell"]; TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (_tzImagePickerVc.scaleAspectFillCrop && _tzImagePickerVc.allowCrop) { _collectionView.scrollEnabled = NO; } } - (void)configCropView { TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (_tzImagePickerVc.maxImagesCount <= 1 && _tzImagePickerVc.allowCrop && _tzImagePickerVc.allowPickingImage) { [_cropView removeFromSuperview]; [_cropBgView removeFromSuperview]; _cropBgView = [UIView new]; _cropBgView.userInteractionEnabled = NO; _cropBgView.frame = self.view.bounds; _cropBgView.backgroundColor = [UIColor clearColor]; [self.view addSubview:_cropBgView]; [TZImageCropManager overlayClippingWithView:_cropBgView cropRect:_tzImagePickerVc.cropRect containerView:self.view needCircleCrop:_tzImagePickerVc.needCircleCrop]; _cropView = [UIView new]; _cropView.userInteractionEnabled = NO; _cropView.frame = _tzImagePickerVc.cropRect; _cropView.backgroundColor = [UIColor clearColor]; _cropView.layer.borderColor = [UIColor whiteColor].CGColor; _cropView.layer.borderWidth = 1.0; if (_tzImagePickerVc.needCircleCrop) { _cropView.layer.cornerRadius = _tzImagePickerVc.cropRect.size.width / 2; _cropView.clipsToBounds = YES; } [self.view addSubview:_cropView]; if (_tzImagePickerVc.cropViewSettingBlock) { _tzImagePickerVc.cropViewSettingBlock(_cropView); } [self.view bringSubviewToFront:_naviBar]; [self.view bringSubviewToFront:_toolBar]; } } #pragma mark - Layout - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height; CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0; CGFloat statusBarHeightInterval = isFullScreen ? (statusBarHeight - 20) : 0; CGFloat naviBarHeight = statusBarHeight + _tzImagePickerVc.navigationBar.tz_height; _naviBar.frame = CGRectMake(0, 0, self.view.tz_width, naviBarHeight); _backButton.frame = CGRectMake(10, 10 + statusBarHeightInterval, 44, 44); _selectButton.frame = CGRectMake(self.view.tz_width - 56, 10 + statusBarHeightInterval, 44, 44); _indexLabel.frame = _selectButton.frame; _layout.itemSize = CGSizeMake(self.view.tz_width + 20, self.view.tz_height); _layout.minimumInteritemSpacing = 0; _layout.minimumLineSpacing = 0; _collectionView.frame = CGRectMake(-10, 0, self.view.tz_width + 20, self.view.tz_height); [_collectionView setCollectionViewLayout:_layout]; if (_offsetItemCount > 0) { CGFloat offsetX = _offsetItemCount * _layout.itemSize.width; [_collectionView setContentOffset:CGPointMake(offsetX, 0)]; } if (_tzImagePickerVc.allowCrop) { [_collectionView reloadData]; } CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom; CGFloat toolBarTop = self.view.tz_height - toolBarHeight; _toolBar.frame = CGRectMake(0, toolBarTop, self.view.tz_width, toolBarHeight); if (_tzImagePickerVc.allowPickingOriginalPhoto) { CGFloat fullImageWidth = [_tzImagePickerVc.fullImageBtnTitleStr boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:13]} context:nil].size.width; _originalPhotoButton.frame = CGRectMake(0, 0, fullImageWidth + 56, 44); _originalPhotoLabel.frame = CGRectMake(fullImageWidth + 42, 0, 80, 44); } [_doneButton sizeToFit]; _doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 44); _numberImageView.frame = CGRectMake(_doneButton.tz_left - 24 - 5, 10, 24, 24); _numberLabel.frame = _numberImageView.frame; [self configCropView]; if (_tzImagePickerVc.photoPreviewPageDidLayoutSubviewsBlock) { _tzImagePickerVc.photoPreviewPageDidLayoutSubviewsBlock(_collectionView, _naviBar, _backButton, _selectButton, _indexLabel, _toolBar, _originalPhotoButton, _originalPhotoLabel, _doneButton, _numberImageView, _numberLabel); } } #pragma mark - Notification - (void)didChangeStatusBarOrientationNotification:(NSNotification *)noti { _offsetItemCount = _collectionView.contentOffset.x / _layout.itemSize.width; } #pragma mark - Click Event - (void)select:(UIButton *)selectButton { [self select:selectButton refreshCount:YES]; } - (void)select:(UIButton *)selectButton refreshCount:(BOOL)refreshCount { TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; TZAssetModel *model = _models[self.currentIndex]; if (!selectButton.isSelected) { // 1. select:check if over the maxImagesCount / 选择照片,检查是否超过了最大个数的限制 if (_tzImagePickerVc.selectedModels.count >= _tzImagePickerVc.maxImagesCount) { NSString *title = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Select a maximum of %zd photos"], _tzImagePickerVc.maxImagesCount]; [_tzImagePickerVc showAlertWithTitle:title]; return; // 2. if not over the maxImagesCount / 如果没有超过最大个数限制 } else { if ([[TZImageManager manager] isAssetCannotBeSelected:model.asset]) { return; } [_tzImagePickerVc addSelectedModel:model]; [self setAsset:model.asset isSelect:YES]; if (self.photos) { [_tzImagePickerVc.selectedAssets addObject:_assetsTemp[self.currentIndex]]; [self.photos addObject:_photosTemp[self.currentIndex]]; } if (model.type == TZAssetModelMediaTypeVideo && !_tzImagePickerVc.allowPickingMultipleVideo) { [_tzImagePickerVc showAlertWithTitle:[NSBundle tz_localizedStringForKey:@"Select the video when in multi state, we will handle the video as a photo"]]; } } } else { NSArray *selectedModels = [NSArray arrayWithArray:_tzImagePickerVc.selectedModels]; for (TZAssetModel *model_item in selectedModels) { if ([model.asset.localIdentifier isEqualToString:model_item.asset.localIdentifier]) { // 1.6.7版本更新:防止有多个一样的model,一次性被移除了 NSArray *selectedModelsTmp = [NSArray arrayWithArray:_tzImagePickerVc.selectedModels]; for (NSInteger i = 0; i < selectedModelsTmp.count; i++) { TZAssetModel *model = selectedModelsTmp[i]; if ([model isEqual:model_item]) { [_tzImagePickerVc removeSelectedModel:model]; // [_tzImagePickerVc.selectedModels removeObjectAtIndex:i]; break; } } if (self.photos) { // 1.6.7版本更新:防止有多个一样的asset,一次性被移除了 NSArray *selectedAssetsTmp = [NSArray arrayWithArray:_tzImagePickerVc.selectedAssets]; for (NSInteger i = 0; i < selectedAssetsTmp.count; i++) { id asset = selectedAssetsTmp[i]; if ([asset isEqual:_assetsTemp[self.currentIndex]]) { [_tzImagePickerVc.selectedAssets removeObjectAtIndex:i]; break; } } // [_tzImagePickerVc.selectedAssets removeObject:_assetsTemp[self.currentIndex]]; [self.photos removeObject:_photosTemp[self.currentIndex]]; } [self setAsset:model.asset isSelect:NO]; break; } } } model.isSelected = !selectButton.isSelected; if (refreshCount) { [self refreshNaviBarAndBottomBarState]; } if (model.isSelected) { [UIView showOscillatoryAnimationWithLayer:selectButton.imageView.layer type:TZOscillatoryAnimationToBigger]; } [UIView showOscillatoryAnimationWithLayer:_numberImageView.layer type:TZOscillatoryAnimationToSmaller]; } - (void)backButtonClick { if (self.navigationController.childViewControllers.count < 2) { [self.navigationController dismissViewControllerAnimated:YES completion:nil]; if ([self.navigationController isKindOfClass: [TZImagePickerController class]]) { TZImagePickerController *nav = (TZImagePickerController *)self.navigationController; if (nav.imagePickerControllerDidCancelHandle) { nav.imagePickerControllerDidCancelHandle(); } } return; } [self.navigationController popViewControllerAnimated:YES]; if (self.backButtonClickBlock) { self.backButtonClickBlock(_isSelectOriginalPhoto); } } - (void)doneButtonClick { TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; // 如果图片正在从iCloud同步中,提醒用户 if (_progress > 0 && _progress < 1 && (_selectButton.isSelected || !_tzImagePickerVc.selectedModels.count )) { _alertView = [_tzImagePickerVc showAlertWithTitle:[NSBundle tz_localizedStringForKey:@"Synchronizing photos from iCloud"]]; return; } // 如果没有选中过照片 点击确定时选中当前预览的照片 if (_tzImagePickerVc.selectedModels.count == 0 && _tzImagePickerVc.minImagesCount <= 0 && _tzImagePickerVc.autoSelectCurrentWhenDone) { TZAssetModel *model = _models[self.currentIndex]; if ([[TZImageManager manager] isAssetCannotBeSelected:model.asset]) { return; } [self select:_selectButton refreshCount:NO]; } NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.currentIndex inSection:0]; TZPhotoPreviewCell *cell = (TZPhotoPreviewCell *)[_collectionView cellForItemAtIndexPath:indexPath]; if (_tzImagePickerVc.allowCrop && [cell isKindOfClass:[TZPhotoPreviewCell class]]) { // 裁剪状态 _doneButton.enabled = NO; [_tzImagePickerVc showProgressHUD]; UIImage *cropedImage = [TZImageCropManager cropImageView:cell.previewView.imageView toRect:_tzImagePickerVc.cropRect zoomScale:cell.previewView.scrollView.zoomScale containerView:self.view]; if (_tzImagePickerVc.needCircleCrop) { cropedImage = [TZImageCropManager circularClipImage:cropedImage]; } _doneButton.enabled = YES; [_tzImagePickerVc hideProgressHUD]; if (self.doneButtonClickBlockCropMode) { TZAssetModel *model = _models[self.currentIndex]; self.doneButtonClickBlockCropMode(cropedImage,model.asset); } } else if (self.doneButtonClickBlock) { // 非裁剪状态 self.doneButtonClickBlock(_isSelectOriginalPhoto); } if (self.doneButtonClickBlockWithPreviewType) { self.doneButtonClickBlockWithPreviewType(self.photos,_tzImagePickerVc.selectedAssets,self.isSelectOriginalPhoto); } } - (void)originalPhotoButtonClick { TZAssetModel *model = _models[self.currentIndex]; if ([[TZImageManager manager] isAssetCannotBeSelected:model.asset]) { return; } _originalPhotoButton.selected = !_originalPhotoButton.isSelected; _isSelectOriginalPhoto = _originalPhotoButton.isSelected; _originalPhotoLabel.hidden = !_originalPhotoButton.isSelected; if (_isSelectOriginalPhoto) { [self showPhotoBytes]; if (!_selectButton.isSelected) { // 如果当前已选择照片张数 < 最大可选张数 && 最大可选张数大于1,就选中该张图 TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (_tzImagePickerVc.selectedModels.count < _tzImagePickerVc.maxImagesCount && _tzImagePickerVc.showSelectBtn) { [self select:_selectButton]; } } } } - (void)didTapPreviewCell { self.isHideNaviBar = !self.isHideNaviBar; _naviBar.hidden = self.isHideNaviBar; _toolBar.hidden = self.isHideNaviBar; } #pragma mark - UIScrollViewDelegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGFloat offSetWidth = scrollView.contentOffset.x; offSetWidth = offSetWidth + ((self.view.tz_width + 20) * 0.5); NSInteger currentIndex = offSetWidth / (self.view.tz_width + 20); if (currentIndex < _models.count && _currentIndex != currentIndex) { _currentIndex = currentIndex; [self refreshNaviBarAndBottomBarState]; } [[NSNotificationCenter defaultCenter] postNotificationName:@"photoPreviewCollectionViewDidScroll" object:nil]; } #pragma mark - UICollectionViewDataSource && Delegate - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return _models.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; TZAssetModel *model = _models[indexPath.item]; TZAssetPreviewCell *cell; __weak typeof(self) weakSelf = self; if (_tzImagePickerVc.allowPickingMultipleVideo && model.type == TZAssetModelMediaTypeVideo) { cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZVideoPreviewCell" forIndexPath:indexPath]; TZVideoPreviewCell *currentCell = (TZVideoPreviewCell *)cell; currentCell.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) { model.iCloudFailed = isSyncFailed; [weakSelf didICloudSyncStatusChanged:model]; }; } else if (_tzImagePickerVc.allowPickingMultipleVideo && model.type == TZAssetModelMediaTypePhotoGif && _tzImagePickerVc.allowPickingGif) { cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZGifPreviewCell" forIndexPath:indexPath]; TZGifPreviewCell *currentCell = (TZGifPreviewCell *)cell; currentCell.previewView.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) { model.iCloudFailed = isSyncFailed; [weakSelf didICloudSyncStatusChanged:model]; }; } else { NSString *reuseId = model.type == TZAssetModelMediaTypePhotoGif ? @"TZPhotoPreviewCellGIF" : @"TZPhotoPreviewCell"; cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseId forIndexPath:indexPath]; TZPhotoPreviewCell *photoPreviewCell = (TZPhotoPreviewCell *)cell; photoPreviewCell.cropRect = _tzImagePickerVc.cropRect; photoPreviewCell.allowCrop = _tzImagePickerVc.allowCrop; photoPreviewCell.scaleAspectFillCrop = _tzImagePickerVc.scaleAspectFillCrop; __weak typeof(_collectionView) weakCollectionView = _collectionView; __weak typeof(photoPreviewCell) weakCell = photoPreviewCell; [photoPreviewCell setImageProgressUpdateBlock:^(double progress) { __strong typeof(weakSelf) strongSelf = weakSelf; __strong typeof(weakCollectionView) strongCollectionView = weakCollectionView; __strong typeof(weakCell) strongCell = weakCell; strongSelf.progress = progress; if (progress >= 1) { if (strongSelf.isSelectOriginalPhoto) [strongSelf showPhotoBytes]; if (strongSelf.alertView && [strongCollectionView.visibleCells containsObject:strongCell]) { [strongSelf.alertView dismissViewControllerAnimated:YES completion:^{ strongSelf.alertView = nil; [strongSelf doneButtonClick]; }]; } } }]; photoPreviewCell.previewView.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) { model.iCloudFailed = isSyncFailed; [weakSelf didICloudSyncStatusChanged:model]; }; } cell.model = model; [cell setSingleTapGestureBlock:^{ __strong typeof(weakSelf) strongSelf = weakSelf; [strongSelf didTapPreviewCell]; }]; return cell; } - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { if ([cell isKindOfClass:[TZPhotoPreviewCell class]]) { [(TZPhotoPreviewCell *)cell recoverSubviews]; } } - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { if ([cell isKindOfClass:[TZPhotoPreviewCell class]]) { [(TZPhotoPreviewCell *)cell recoverSubviews]; } else if ([cell isKindOfClass:[TZVideoPreviewCell class]]) { TZVideoPreviewCell *videoCell = (TZVideoPreviewCell *)cell; if (videoCell.player && videoCell.player.rate != 0.0) { [videoCell pausePlayerAndShowNaviBar]; } } } #pragma mark - Private Method - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; // NSLog(@"%@ dealloc",NSStringFromClass(self.class)); } - (void)refreshNaviBarAndBottomBarState { TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; TZAssetModel *model = _models[self.currentIndex]; _selectButton.selected = model.isSelected; [self refreshSelectButtonImageViewContentMode]; if (_selectButton.isSelected && _tzImagePickerVc.showSelectedIndex && _tzImagePickerVc.showSelectBtn) { NSString *index = [NSString stringWithFormat:@"%d", (int)([_tzImagePickerVc.selectedAssetIds indexOfObject:model.asset.localIdentifier] + 1)]; _indexLabel.text = index; _indexLabel.hidden = NO; } else { _indexLabel.hidden = YES; } _numberLabel.text = [NSString stringWithFormat:@"%zd",_tzImagePickerVc.selectedModels.count]; _numberImageView.hidden = (_tzImagePickerVc.selectedModels.count <= 0 || _isHideNaviBar || _isCropImage); _numberLabel.hidden = (_tzImagePickerVc.selectedModels.count <= 0 || _isHideNaviBar || _isCropImage); _originalPhotoButton.selected = _isSelectOriginalPhoto; _originalPhotoLabel.hidden = !_originalPhotoButton.isSelected; if (_isSelectOriginalPhoto) [self showPhotoBytes]; // If is previewing video, hide original photo button // 如果正在预览的是视频,隐藏原图按钮 if (!_isHideNaviBar) { if (model.type == TZAssetModelMediaTypeVideo) { _originalPhotoButton.hidden = YES; _originalPhotoLabel.hidden = YES; } else { _originalPhotoButton.hidden = NO; if (_isSelectOriginalPhoto) _originalPhotoLabel.hidden = NO; } } _doneButton.hidden = NO; _selectButton.hidden = !_tzImagePickerVc.showSelectBtn; // 让宽度/高度小于 最小可选照片尺寸 的图片不能选中 if (![[TZImageManager manager] isPhotoSelectableWithAsset:model.asset]) { _numberLabel.hidden = YES; _numberImageView.hidden = YES; _selectButton.hidden = YES; _originalPhotoButton.hidden = YES; _originalPhotoLabel.hidden = YES; _doneButton.hidden = YES; } // iCloud同步失败的UI刷新 [self didICloudSyncStatusChanged:model]; if (_tzImagePickerVc.photoPreviewPageDidRefreshStateBlock) { _tzImagePickerVc.photoPreviewPageDidRefreshStateBlock(_collectionView, _naviBar, _backButton, _selectButton, _indexLabel, _toolBar, _originalPhotoButton, _originalPhotoLabel, _doneButton, _numberImageView, _numberLabel); } } - (void)refreshSelectButtonImageViewContentMode { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (self->_selectButton.imageView.image.size.width <= 27) { self->_selectButton.imageView.contentMode = UIViewContentModeCenter; } else { self->_selectButton.imageView.contentMode = UIViewContentModeScaleAspectFit; } }); } - (void)didICloudSyncStatusChanged:(TZAssetModel *)model{ dispatch_async(dispatch_get_main_queue(), ^{ TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; // onlyReturnAsset为NO时,依赖TZ返回大图,所以需要有iCloud同步失败的提示,并且不能选择, if (_tzImagePickerVc.onlyReturnAsset) { return; } TZAssetModel *currentModel = self.models[self.currentIndex]; if (_tzImagePickerVc.selectedModels.count <= 0) { self->_doneButton.enabled = !currentModel.iCloudFailed; } else { self->_doneButton.enabled = YES; } self->_selectButton.hidden = currentModel.iCloudFailed || !_tzImagePickerVc.showSelectBtn; if (currentModel.iCloudFailed) { self->_originalPhotoButton.hidden = YES; self->_originalPhotoLabel.hidden = YES; } }); } - (void)showPhotoBytes { [[TZImageManager manager] getPhotosBytesWithArray:@[_models[self.currentIndex]] completion:^(NSString *totalBytes) { self->_originalPhotoLabel.text = [NSString stringWithFormat:@"(%@)",totalBytes]; }]; } - (NSInteger)currentIndex { return [TZCommonTools tz_isRightToLeftLayout] ? self.models.count - _currentIndex - 1 : _currentIndex; } /// 选中/取消选中某张照片 - (void)setAsset:(PHAsset *)asset isSelect:(BOOL)isSelect { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (isSelect && [tzImagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didSelectAsset:photo:isSelectOriginalPhoto:)]) { [self callDelegate:asset isSelect:YES]; } if (!isSelect && [tzImagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didDeselectAsset:photo:isSelectOriginalPhoto:)]) { [self callDelegate:asset isSelect:NO]; } } /// 调用选中/取消选中某张照片的代理方法 - (void)callDelegate:(PHAsset *)asset isSelect:(BOOL)isSelect { TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; __weak typeof(self) weakSelf = self; __weak typeof(tzImagePickerVc) weakImagePickerVc= tzImagePickerVc; [[TZImageManager manager] getPhotoWithAsset:asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { if (isDegraded) return; __strong typeof(weakSelf) strongSelf = weakSelf; __strong typeof(weakImagePickerVc) strongImagePickerVc = weakImagePickerVc; if (isSelect) { [strongImagePickerVc.pickerDelegate imagePickerController:strongImagePickerVc didSelectAsset:asset photo:photo isSelectOriginalPhoto:strongSelf.isSelectOriginalPhoto]; } else { [strongImagePickerVc.pickerDelegate imagePickerController:strongImagePickerVc didDeselectAsset:asset photo:photo isSelectOriginalPhoto:strongSelf.isSelectOriginalPhoto]; } }]; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZProgressView.h ================================================ // // TZProgressView.h // TZImagePickerController // // Created by ttouch on 2016/12/6. // Copyright © 2016年 谭真. All rights reserved. // #import @interface TZProgressView : UIView @property (nonatomic, assign) double progress; @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZProgressView.m ================================================ // // TZProgressView.m // TZImagePickerController // // Created by ttouch on 2016/12/6. // Copyright © 2016年 谭真. All rights reserved. // #import "TZProgressView.h" @interface TZProgressView () @property (nonatomic, strong) CAShapeLayer *progressLayer; @end @implementation TZProgressView - (instancetype)init { self = [super init]; if (self) { self.backgroundColor = [UIColor clearColor]; _progressLayer = [CAShapeLayer layer]; _progressLayer.fillColor = [[UIColor clearColor] CGColor]; _progressLayer.strokeColor = [[UIColor whiteColor] CGColor]; _progressLayer.opacity = 1; _progressLayer.lineCap = kCALineCapRound; _progressLayer.lineWidth = 5; [_progressLayer setShadowColor:[UIColor blackColor].CGColor]; [_progressLayer setShadowOffset:CGSizeMake(1, 1)]; [_progressLayer setShadowOpacity:0.5]; [_progressLayer setShadowRadius:2]; } return self; } - (void)drawRect:(CGRect)rect { CGPoint center = CGPointMake(rect.size.width / 2, rect.size.height / 2); CGFloat radius = rect.size.width / 2; CGFloat startA = - M_PI_2; CGFloat endA = - M_PI_2 + M_PI * 2 * _progress; _progressLayer.frame = self.bounds; UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES]; _progressLayer.path =[path CGPath]; [_progressLayer removeFromSuperlayer]; [self.layer addSublayer:_progressLayer]; } - (void)setProgress:(double)progress { _progress = progress; [self setNeedsDisplay]; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZVideoCropController.h ================================================ // // TZVideoCropController.h // TZImagePickerController // // Created by 肖兰月 on 2021/5/27. // Copyright © 2021 谭真. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @class TZAssetModel,TZImagePickerController; @interface TZVideoCropController : UIViewController @property (nonatomic, strong) TZAssetModel *model; @property (nonatomic, weak) TZImagePickerController *imagePickerVc; @end @protocol TZVideoEditViewDelegate - (void)editViewCropRectBeginChange; - (void)editViewCropRectEndChange; @end @interface TZVideoEditView : UIView @property (strong, nonatomic) UIImageView *beginImgView; @property (strong, nonatomic) UIImageView *endImgView; @property (strong, nonatomic) UIView *indicatorLine; @property (assign, nonatomic) CGFloat videoDuration; @property (assign, nonatomic) NSInteger maxCropVideoDuration; @property (assign, nonatomic) CGRect cropRect; @property (assign, nonatomic) CGFloat allImgWidth; @property (assign, nonatomic) CGFloat minCropRectWidth; @property (nonatomic, weak) id delegate; - (void)resetIndicatorLine; - (void)indicatorLineAnimateWithDuration:(NSTimeInterval)duration cropRect:(CGRect)cropRect; @end @interface TZVideoPictureCell : UICollectionViewCell @property (strong, nonatomic) UIImageView *imgView; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZVideoCropController.m ================================================ // // TZVideoCropController.m // TZImagePickerController // // Created by 肖兰月 on 2021/5/27. // Copyright © 2021 谭真. All rights reserved. // #import "TZVideoCropController.h" #import #import "UIView+TZLayout.h" #import "TZImageManager.h" #import "TZAssetModel.h" #import "TZImagePickerController.h" @interface TZVideoCropController () { AVPlayer *_player; AVPlayerLayer *_playerLayer; UIButton *_playButton; UIImage *_cover; NSString *_outputPath; NSString *_errorMsg; UIButton *_cancelButton; UIButton *_doneButton; UIProgressView *_progress; UILabel *_cropVideoDurationLabel; AVAssetImageGenerator *_imageGenerator; AVAsset *_asset; CGFloat _collectionViewBeginOffsetX; BOOL _isPlayed; CGFloat _itemW; BOOL _isDraging; UIStatusBarStyle _originStatusBarStyle; } // iCloud无法同步提示UI @property (nonatomic, strong) UIView *iCloudErrorView; @property (strong, nonatomic) UICollectionView *collectionView; @property (strong, nonatomic) TZVideoEditView *videoEditView; @property (strong, nonatomic) NSMutableArray *videoImgArray; @property (strong, nonatomic) NSArray *imageTimes; @property (strong, nonatomic) NSTimer *timer; @end @implementation TZVideoCropController #define VideoEditLeftMargin 40 #define PanImageWidth 10 #define MinCropVideoDuration 1 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor blackColor]; [self configMoviePlayer]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayer) name:UIApplicationWillResignActiveNotification object:nil]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self stopTimer]; } - (void)configMoviePlayer { [[TZImageManager manager] getPhotoWithAsset:_model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { BOOL iCloudSyncFailed = !photo && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]]; self.iCloudErrorView.hidden = !iCloudSyncFailed; self->_doneButton.enabled = !iCloudSyncFailed; }]; [[TZImageManager manager] getVideoWithAsset:_model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) { dispatch_async(dispatch_get_main_queue(), ^{ self->_asset = playerItem.asset; self->_player = [AVPlayer playerWithPlayerItem:playerItem]; self->_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self->_player]; self->_playerLayer.frame = self.view.bounds; [self.view.layer addSublayer:self->_playerLayer]; [self configPlayButton]; [self configBottomToolBar]; if (self.imagePickerVc.allowEditVideo) { [self configVideoImageCollectionView]; [self configVideoEditView]; [self generateVideoImage]; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayer) name:AVPlayerItemDidPlayToEndTimeNotification object:self->_player.currentItem]; }); }]; } - (void)configPlayButton { _playButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted]; [_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_playButton]; } - (void)configBottomToolBar { _cropVideoDurationLabel = UILabel.new; _cropVideoDurationLabel.textAlignment = NSTextAlignmentCenter; _cropVideoDurationLabel.textColor = UIColor.whiteColor; _cropVideoDurationLabel.font = [UIFont systemFontOfSize:12]; [self.view addSubview:_cropVideoDurationLabel]; _cancelButton = [UIButton buttonWithType:UIButtonTypeCustom]; _cancelButton.titleLabel.font = [UIFont systemFontOfSize:16]; [_cancelButton setTitle:[NSBundle tz_localizedStringForKey:@"Cancel"] forState:0]; [_cancelButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; [_cancelButton addTarget:self action:@selector(cancelButtonClick) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_cancelButton]; _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; _doneButton.titleLabel.font = [UIFont systemFontOfSize:16]; [_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside]; [_doneButton setTitle:self.imagePickerVc.doneBtnTitleStr forState:UIControlStateNormal]; [_doneButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; [_doneButton setTitleColor:self.imagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled]; [self.view addSubview:_doneButton]; if (self.imagePickerVc.videoEditViewPageUIConfigBlock) { self.imagePickerVc.videoEditViewPageUIConfigBlock(_playButton, _cropVideoDurationLabel, _cancelButton, _doneButton); } } - (void)configVideoImageCollectionView { _itemW = (self.view.tz_width - VideoEditLeftMargin * 2 - 2 * PanImageWidth) / 10.0; UICollectionViewFlowLayout *layout = UICollectionViewFlowLayout.new; layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; layout.itemSize = CGSizeMake(_itemW, _itemW * 2); layout.minimumLineSpacing = 0; layout.minimumInteritemSpacing = 0; _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; _collectionView.dataSource = self; _collectionView.delegate = self; _collectionView.contentInset = UIEdgeInsetsMake(0, VideoEditLeftMargin + PanImageWidth, 0, VideoEditLeftMargin + PanImageWidth); _collectionView.clipsToBounds = NO; _collectionView.showsHorizontalScrollIndicator = NO; _collectionView.alwaysBounceHorizontal = YES; [_collectionView registerClass:TZVideoPictureCell.class forCellWithReuseIdentifier:@"TZVideoPictureCell"]; [self.view addSubview:_collectionView]; } - (void)configVideoEditView { _videoEditView = TZVideoEditView.new; _videoEditView.backgroundColor = UIColor.clearColor; _videoEditView.delegate = self; _videoEditView.maxCropVideoDuration = self.imagePickerVc.maxCropVideoDuration; [self.view addSubview:_videoEditView]; } - (UIStatusBarStyle)preferredStatusBarStyle { if (self.imagePickerVc && [self.imagePickerVc isKindOfClass:[TZImagePickerController class]]) { return self.imagePickerVc.statusBarStyle; } return [super preferredStatusBarStyle]; } #pragma mark - Layout - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height; CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0; CGFloat statusBarAndNaviBarHeight = statusBarHeight + self.navigationController.navigationBar.tz_height; CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom; CGFloat doneButtonWidth = [_doneButton.currentTitle boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil].size.width; doneButtonWidth = MAX(44, doneButtonWidth); _cancelButton.frame = CGRectMake(12, self.view.tz_height - toolBarHeight, 44, 44); [_cancelButton sizeToFit]; _cancelButton.tz_height = 44; _doneButton.frame = CGRectMake(self.view.tz_width - doneButtonWidth - 12, self.view.tz_height - toolBarHeight, doneButtonWidth, 44); _playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight); CGFloat collectionViewH = (self.view.tz_width - VideoEditLeftMargin * 2 - 2 * PanImageWidth) / 10.0 * 2; _collectionView.frame = CGRectMake(0, self.view.tz_height - collectionViewH - toolBarHeight - statusBarHeight, self.view.tz_width, collectionViewH); _videoEditView.frame = _collectionView.frame; _cropVideoDurationLabel.frame = CGRectMake(0, _videoEditView.tz_bottom, self.view.tz_width, 20); CGFloat playerLayerHeight = CGRectGetMinY(_collectionView.frame) - statusBarHeight * 2; CGFloat playerLayerWidth = self.view.tz_width/self.view.tz_height * playerLayerHeight; CGFloat playerLayerLeft = (self.view.tz_width - playerLayerWidth) / 2.0; CGRect playerLayerFrame = CGRectMake(playerLayerLeft, statusBarHeight, playerLayerWidth, playerLayerHeight); _playerLayer.frame = playerLayerFrame; _playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, playerLayerHeight - statusBarAndNaviBarHeight); if (self.imagePickerVc.videoEditViewPageDidLayoutSubviewsBlock) { self.imagePickerVc.videoEditViewPageDidLayoutSubviewsBlock(_playButton, _cropVideoDurationLabel, _cancelButton, _doneButton); } } - (void)generateVideoImage { _imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset]; _imageGenerator.appliesPreferredTrackTransform = YES; _imageGenerator.requestedTimeToleranceBefore = kCMTimeZero; _imageGenerator.requestedTimeToleranceAfter = kCMTimeZero; _imageGenerator.maximumSize = CGSizeMake(100, 100); NSTimeInterval durationSeconds = self.model.asset.duration; self.videoEditView.videoDuration = durationSeconds; NSUInteger imageCount = 10; CGFloat maxCropWidth = self.view.tz_width - (VideoEditLeftMargin + PanImageWidth) * 2; if (durationSeconds <= MinCropVideoDuration) return; if (durationSeconds <= self.imagePickerVc.maxCropVideoDuration) { imageCount = 10; self.videoEditView.allImgWidth = maxCropWidth; _cropVideoDurationLabel.text = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Selected for %ld seconds"], (NSInteger)durationSeconds]; } else { CGFloat singleWidthSecond = maxCropWidth / self.imagePickerVc.maxCropVideoDuration; CGFloat allImgWidth = singleWidthSecond * durationSeconds; self.videoEditView.allImgWidth = allImgWidth; imageCount = allImgWidth / _itemW; _cropVideoDurationLabel.text = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Selected for %ld seconds"],(long)self.imagePickerVc.maxCropVideoDuration]; } NSArray *assetTracks = [_asset tracksWithMediaType:AVMediaTypeVideo]; if (!assetTracks.count) { self.iCloudErrorView.hidden = NO; _doneButton.enabled = NO; _cropVideoDurationLabel.hidden = YES; return; }; Float64 frameRate = [[_asset tracksWithMediaType:AVMediaTypeVideo][0] nominalFrameRate];; NSMutableArray *times = NSMutableArray.array; NSTimeInterval intervalSecond = durationSeconds/imageCount; CMTime timeFrame; for (NSInteger i = 0; i < imageCount; i++) { timeFrame = CMTimeMake(intervalSecond * i *frameRate, frameRate); NSValue *timeValue = [NSValue valueWithCMTime:timeFrame]; [times addObject:timeValue]; } self.videoImgArray = NSMutableArray.new; self.imageTimes = times; typeof(self) weakSelf = self; [_imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) { if (image) { UIImage *img = [[UIImage alloc] initWithCGImage:image]; [weakSelf.videoImgArray addObject:img]; dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.collectionView reloadData]; }); } }]; } #pragma mark - UICollectiobViewDataSource & UIcollectionViewDelegate - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.videoImgArray.count; } - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { TZVideoPictureCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZVideoPictureCell" forIndexPath:indexPath]; cell.imgView.image = self.videoImgArray[indexPath.item]; return cell; } #pragma mark - UIScrollViewDelegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (!_isDraging) return; CGFloat offsetX = scrollView.contentOffset.x; if (offsetX - _collectionViewBeginOffsetX >= self.view.tz_width) { [self.collectionView setContentOffset:CGPointMake(self.view.tz_width + _collectionViewBeginOffsetX, 0) animated:NO]; } else if (_collectionViewBeginOffsetX - offsetX >= self.view.tz_width) { [self.collectionView setContentOffset:CGPointMake(_collectionViewBeginOffsetX - self.view.tz_width, 0) animated:NO]; } [self editViewCropRectBeginChange]; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { _isDraging = YES; _collectionViewBeginOffsetX = scrollView.contentOffset.x; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { _isDraging = NO; [self editViewCropRectEndChange]; } #pragma mark - TZVideoEditViewDelegate - (void)editViewCropRectBeginChange { [self stopTimer]; [_playerLayer.player seekToTime:[self getCropStartTime] toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; NSTimeInterval second = [self getCropVideoDuration]; _cropVideoDurationLabel.text = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Selected for %ld seconds"], (NSInteger)second]; } - (void)editViewCropRectEndChange { if (_isPlayed) { [self starTimer]; } } #pragma mark - Click Event - (void)playButtonClick { CMTime currentTime = _player.currentItem.currentTime; CMTime durationTime = _player.currentItem.duration; if (_player.rate == 0.0f) { [[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_VIDEO_PLAY_NOTIFICATION" object:_player]; if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)]; _isPlayed = YES; [self starTimer]; [_playButton setImage:nil forState:UIControlStateNormal]; } else { _isPlayed = NO; [self stopTimer]; [self pausePlayer]; } } - (void)cancelButtonClick { [self dismissViewControllerAnimated:YES completion:nil]; } - (void)doneButtonClick { if ([[TZImageManager manager] isAssetCannotBeSelected:_model.asset]) { return; } [self stopTimer]; TZImagePickerController *imagePickerVc = self.imagePickerVc; [imagePickerVc showProgressHUD]; [[TZImageManager manager] getVideoOutputPathWithAsset:_model.asset presetName:imagePickerVc.presetName timeRange:[self getCropVideoTimeRange] success:^(NSString *outputPath) { [imagePickerVc hideProgressHUD]; self->_outputPath = outputPath; [self dismissAndCallDelegateMethod]; } failure:^(NSString *errorMessage, NSError *error) { [imagePickerVc hideProgressHUD]; self->_errorMsg = errorMessage; [self dismissAndCallDelegateMethod]; }]; } - (void)dismissAndCallDelegateMethod { [self dismissViewControllerAnimated:NO completion:^{ [self callDelegateMethod]; }]; [self.imagePickerVc dismissViewControllerAnimated:YES completion:nil]; } - (void)callDelegateMethod { if (_outputPath) { NSURL *videoURL = [NSURL fileURLWithPath:_outputPath]; if (self.imagePickerVc.saveEditedVideoToAlbum) { [[TZImageManager manager] saveVideoWithUrl:videoURL completion:^(PHAsset *asset, NSError *error) { if (error) { // 视频保存失败 if ([self.imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFailToSaveEditedVideoWithError:)]) { [self.imagePickerVc.pickerDelegate imagePickerController:self.imagePickerVc didFailToSaveEditedVideoWithError:error]; } } }]; } UIImage *coverImage = [[TZImageManager manager] getImageWithVideoURL:videoURL]; if ([self.imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) { [self.imagePickerVc.pickerDelegate imagePickerController:self.imagePickerVc didFinishPickingAndEditingVideo:coverImage outputPath:_outputPath error:nil]; } if (self.imagePickerVc.didFinishPickingAndEditingVideoHandle) { self.imagePickerVc.didFinishPickingAndEditingVideoHandle(coverImage, _outputPath, nil); } } else { if ([self.imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) { [self.imagePickerVc.pickerDelegate imagePickerController:self.imagePickerVc didFinishPickingAndEditingVideo:nil outputPath:nil error:_errorMsg]; } if (self.imagePickerVc.didFinishPickingAndEditingVideoHandle) { self.imagePickerVc.didFinishPickingAndEditingVideoHandle(nil, nil, _errorMsg); } } } #pragma mark - private method - (CMTime)getCropStartTime { NSTimeInterval second = [self getCropVideoStartSecond]; if (second > self.model.asset.duration) { second = roundf(self.model.asset.duration); } return CMTimeMakeWithSeconds(second, _playerLayer.player.currentTime.timescale); } - (CMTimeRange)getCropVideoTimeRange { NSTimeInterval startSecond = [self getCropVideoStartSecond]; CMTime start = CMTimeMakeWithSeconds(startSecond, _playerLayer.player.currentTime.timescale); NSTimeInterval second = [self getCropVideoDuration]; CMTime duration = CMTimeMakeWithSeconds(second, _playerLayer.player.currentTime.timescale); return CMTimeRangeMake(start, duration); } - (NSTimeInterval)getCropVideoDuration { CGFloat rectW = self.videoEditView.cropRect.size.width; CGFloat contentW = self.videoEditView.allImgWidth; CGFloat second = rectW / contentW * roundf(self.model.asset.duration); return roundf(second); } - (NSTimeInterval)getCropVideoStartSecond { CGFloat offsetX = self.collectionView.contentOffset.x; CGFloat contentW = self.videoEditView.allImgWidth; CGFloat cropRectX = self.videoEditView.cropRect.origin.x - VideoEditLeftMargin - PanImageWidth; NSTimeInterval second = (offsetX + cropRectX) / contentW * roundf(self.model.asset.duration); if (second < 0) second = 0; return roundf(second); } - (CMTime)getTimeOfSeek { NSTimeInterval second = [self getCropVideoStartSecond]; if (second > self.model.asset.duration) { second = roundf(self.model.asset.duration); } return CMTimeMakeWithSeconds(second, _playerLayer.player.currentTime.timescale); } - (void)starTimer { [self stopTimer]; NSTimeInterval timeInterval = [self getCropVideoDuration]; self.timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(playCropVideo) userInfo:nil repeats:YES]; [self.timer fire]; } - (void)stopTimer { if (self.timer) { [self.videoEditView resetIndicatorLine]; [_player pause]; [self.timer invalidate]; self.timer = nil; } } - (void)playCropVideo { [_player seekToTime:[self getCropStartTime] toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; [_player play]; [self.videoEditView indicatorLineAnimateWithDuration:[self getCropVideoDuration] cropRect:self.videoEditView.cropRect]; } #pragma mark - Notification Method - (void)pausePlayer { [_player pause]; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; } #pragma mark - lazy - (UIView *)iCloudErrorView{ if (!_iCloudErrorView) { _iCloudErrorView = [[UIView alloc] initWithFrame:CGRectMake(0, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.view.tz_width, 28)]; UIImageView *icloud = [[UIImageView alloc] init]; icloud.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"]; icloud.frame = CGRectMake(20, 0, 28, 28); [_iCloudErrorView addSubview:icloud]; UILabel *label = [[UILabel alloc] init]; label.frame = CGRectMake(53, 0, self.view.tz_width - 63, 28); label.font = [UIFont systemFontOfSize:10]; label.textColor = [UIColor whiteColor]; label.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"]; [_iCloudErrorView addSubview:label]; [self.view addSubview:_iCloudErrorView]; _iCloudErrorView.hidden = YES; } return _iCloudErrorView; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return UIInterfaceOrientationMaskPortrait; } - (void)dealloc { NSLog(@"%s",__func__); [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma clang diagnostic pop @end @implementation TZVideoEditView { UILabel *_dragingLabel; CGFloat _itemWidth; CGFloat _beginOffsetX; CGFloat _endOffsetX; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self initSubViews]; } return self; } - (void)initSubViews { _indicatorLine = UIView.new; _indicatorLine.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7]; [self addSubview:_indicatorLine]; _beginImgView = UIImageView.new; _beginImgView.image = [UIImage imageNamed:@"leftVideoEdit"]; _beginImgView.userInteractionEnabled = YES; _beginImgView.tag = 0; UIPanGestureRecognizer *beginPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)]; [_beginImgView addGestureRecognizer:beginPanGesture]; [self addSubview:_beginImgView]; _endImgView = UIImageView.new; _endImgView.image = [UIImage imageNamed:@"rightVideoEdit"]; _endImgView.userInteractionEnabled = YES; _endImgView.tag = 1; UIPanGestureRecognizer *endPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)]; [_endImgView addGestureRecognizer:endPanGesture]; [self addSubview:_endImgView]; } - (void)layoutSubviews { _beginImgView.frame = CGRectMake(VideoEditLeftMargin, 0, PanImageWidth, self.tz_height); _indicatorLine.frame = CGRectMake(_beginImgView.tz_right - 2, 2, 2, self.tz_height - 4); _endImgView.frame = CGRectMake(self.tz_width - PanImageWidth - VideoEditLeftMargin, 0, PanImageWidth, self.tz_height); self.cropRect = CGRectMake(VideoEditLeftMargin + PanImageWidth, 0, self.tz_width - VideoEditLeftMargin * 2 - PanImageWidth * 2, self.tz_height); } - (void)setAllImgWidth:(CGFloat)allImgWidth { _allImgWidth = allImgWidth; if ((NSInteger)roundf(self.videoDuration) <= 0) { self.minCropRectWidth = allImgWidth; return; } CGFloat scale = MinCropVideoDuration / self.videoDuration; self.minCropRectWidth = scale * allImgWidth; } - (void)setCropRect:(CGRect)cropRect { _cropRect = cropRect; self.beginImgView.tz_left = cropRect.origin.x - PanImageWidth; self.indicatorLine.tz_left = cropRect.origin.x - self.indicatorLine.tz_width; self.endImgView.tz_left = CGRectGetMaxX(cropRect); [self setNeedsDisplay]; } - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextClearRect(context, rect); CGPoint topPoints[] = { CGPointMake(self.cropRect.origin.x, 0), CGPointMake(CGRectGetMaxX(self.cropRect), 0) }; CGPoint bottomPoints[] = { CGPointMake(self.cropRect.origin.x, self.tz_height), CGPointMake(CGRectGetMaxX(self.cropRect), self.tz_height) }; CGContextAddLines(context, topPoints, 2); CGContextAddLines(context, bottomPoints, 2); CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); CGContextSetLineWidth(context, 4.0); CGContextDrawPath(context, kCGPathStroke); } - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { CGRect beginImgViewFrame = self.beginImgView.frame; beginImgViewFrame.origin.x -= PanImageWidth; beginImgViewFrame.size.width += PanImageWidth * 2; if (CGRectContainsPoint(beginImgViewFrame, point)) return self.beginImgView; CGRect endImgViewFrame = self.endImgView.frame; endImgViewFrame.origin.x -= PanImageWidth; endImgViewFrame.size.width += PanImageWidth * 2; if (CGRectContainsPoint(endImgViewFrame, point)) return self.endImgView; return nil; } #pragma mark - private - (void)indicatorLineAnimateWithDuration:(NSTimeInterval)duration cropRect:(CGRect)cropRect { [self resetIndicatorLine]; [UIView animateWithDuration:duration delay:.0 options:UIViewAnimationOptionCurveLinear animations:^{ self.indicatorLine.tz_left = CGRectGetMaxX(cropRect); } completion:nil]; } - (void)resetIndicatorLine { [self.indicatorLine.layer removeAllAnimations]; self.indicatorLine.tz_left = CGRectGetMinX(self.cropRect) - self.indicatorLine.tz_width; } - (void)panGestureAction:(UIGestureRecognizer *)gesture { CGPoint point = [gesture locationInView:self]; CGRect rect = self.cropRect; CGFloat minCropRectLeft = VideoEditLeftMargin + PanImageWidth; switch (gesture.view.tag) { case 0: { // 左边拖拽 CGFloat maxX = self.endImgView.tz_left - self.minCropRectWidth; point.x = MAX(minCropRectLeft, MIN(point.x, maxX)); point.y = 0; rect.size.width = CGRectGetMaxX(rect) - point.x; rect.origin.x = point.x; } break; case 1: { // 右边拖拽 minCropRectLeft = CGRectGetMaxX(self.beginImgView.frame) + self.minCropRectWidth; CGFloat maxX = self.tz_width - VideoEditLeftMargin - PanImageWidth; point.x = MAX(minCropRectLeft, MIN(point.x, maxX)); point.y = 0; rect.size.width = (point.x - rect.origin.x); } break; default:break; } self.cropRect = rect; switch (gesture.state) { case UIGestureRecognizerStateBegan: case UIGestureRecognizerStateChanged: { if (self.delegate && [self.delegate respondsToSelector:@selector(editViewCropRectBeginChange)]) { [self.delegate editViewCropRectBeginChange]; } } break; case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: { if (self.delegate && [self.delegate respondsToSelector:@selector(editViewCropRectEndChange)]) { [self.delegate editViewCropRectEndChange]; } } break; default: break; } } @end @implementation TZVideoPictureCell - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self initSubViews]; } return self; } - (void)initSubViews { _imgView = [[UIImageView alloc] initWithFrame:self.bounds]; _imgView.contentMode = UIViewContentModeScaleAspectFill; _imgView.clipsToBounds = YES; [self.contentView addSubview:_imgView]; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZVideoEditedPreviewController.h ================================================ // // TZVideoEditedPreviewController.h // TZImagePickerController // // Created by 肖兰月 on 2021/5/29. // Copyright © 2021 谭真. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface TZVideoEditedPreviewController : UIViewController @property (nonatomic, copy) NSURL *videoURL; @end NS_ASSUME_NONNULL_END ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZVideoEditedPreviewController.m ================================================ // // TZVideoEditedPreviewController.m // TZImagePickerController // // Created by 肖兰月 on 2021/5/29. // Copyright © 2021 谭真. All rights reserved. // #import "TZVideoEditedPreviewController.h" #import #import "TZImageManager.h" #import "TZImagePickerController.h" #import "UIView+TZLayout.h" @interface TZVideoEditedPreviewController () { AVPlayer *_player; AVPlayerLayer *_playerLayer; UIButton *_playButton; UIImage *_cover; UIView *_toolBar; UIButton *_doneButton; UIProgressView *_progress; UIStatusBarStyle _originStatusBarStyle; } @end @implementation TZVideoEditedPreviewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor blackColor]; [self configMoviePlayer]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:UIApplicationWillResignActiveNotification object:nil]; } - (void)configMoviePlayer { _player = [AVPlayer playerWithURL:self.videoURL]; _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; [self.view.layer addSublayer:_playerLayer]; [self configPlayButton]; [self configBottomToolBar]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self->_player.currentItem]; } - (void)configPlayButton { _playButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted]; [_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_playButton]; } - (void)configBottomToolBar { _toolBar = [[UIView alloc] initWithFrame:CGRectZero]; CGFloat rgb = 34 / 255.0; _toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7]; _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; _doneButton.titleLabel.font = [UIFont systemFontOfSize:16]; [_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (tzImagePickerVc) { [_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal]; [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal]; } else { [_doneButton setTitle:[NSBundle tz_localizedStringForKey:@"Done"] forState:UIControlStateNormal]; [_doneButton setTitleColor:[UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0] forState:UIControlStateNormal]; } [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled]; [_toolBar addSubview:_doneButton]; [self.view addSubview:_toolBar]; } #pragma mark - Layout - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height; CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0; CGFloat statusBarAndNaviBarHeight = statusBarHeight + self.navigationController.navigationBar.tz_height; CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom; _toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight); [_doneButton sizeToFit]; _doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 44); _playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight); _playerLayer.frame = self.view.bounds; } #pragma mark - Click Event - (void)playButtonClick { CMTime currentTime = _player.currentItem.currentTime; CMTime durationTime = _player.currentItem.duration; if (_player.rate == 0.0f) { if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)]; [_player play]; _toolBar.hidden = YES; [_playButton setImage:nil forState:UIControlStateNormal]; } else { [self pausePlayerAndShowNaviBar]; } } - (void)doneButtonClick { [self dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - Notification Method - (void)pausePlayerAndShowNaviBar { [_player pause]; _toolBar.hidden = NO; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma clang diagnostic pop @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZVideoPlayerController.h ================================================ // // TZVideoPlayerController.h // TZImagePickerController // // Created by 谭真 on 16/1/5. // Copyright © 2016年 谭真. All rights reserved. // #import @class TZAssetModel; @interface TZVideoPlayerController : UIViewController @property (nonatomic, strong) TZAssetModel *model; @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZVideoPlayerController.m ================================================ // // TZVideoPlayerController.m // TZImagePickerController // // Created by 谭真 on 16/1/5. // Copyright © 2016年 谭真. All rights reserved. // #import "TZVideoPlayerController.h" #import #import "UIView+TZLayout.h" #import "TZImageManager.h" #import "TZAssetModel.h" #import "TZImagePickerController.h" #import "TZPhotoPreviewController.h" #import "TZVideoCropController.h" @interface TZVideoPlayerController () { AVPlayer *_player; AVPlayerLayer *_playerLayer; UIButton *_playButton; UIImage *_playButtonNormalImage; UIImage *_cover; NSString *_outputPath; NSString *_errorMsg; UIView *_toolBar; UIButton *_doneButton; UIButton *_editButton; UIProgressView *_progress; UIStatusBarStyle _originStatusBarStyle; } @property (assign, nonatomic) BOOL needShowStatusBar; // iCloud无法同步提示UI @property (nonatomic, strong) UIView *iCloudErrorView; @end #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @implementation TZVideoPlayerController - (void)viewDidLoad { [super viewDidLoad]; self.needShowStatusBar = ![UIApplication sharedApplication].statusBarHidden; self.view.backgroundColor = [UIColor blackColor]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (tzImagePickerVc) { self.navigationItem.title = tzImagePickerVc.previewBtnTitleStr; } [self configMoviePlayer]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:UIApplicationWillResignActiveNotification object:nil]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; _originStatusBarStyle = [UIApplication sharedApplication].statusBarStyle; [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if (self.needShowStatusBar) { [UIApplication sharedApplication].statusBarHidden = NO; } [UIApplication sharedApplication].statusBarStyle = _originStatusBarStyle; } - (void)configMoviePlayer { [[TZImageManager manager] getPhotoWithAsset:_model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { BOOL iCloudSyncFailed = !photo && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]]; self.iCloudErrorView.hidden = !iCloudSyncFailed; if (!isDegraded && photo) { self->_cover = photo; self->_doneButton.enabled = YES; self->_editButton.enabled = YES; } }]; [[TZImageManager manager] getVideoWithAsset:_model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) { dispatch_async(dispatch_get_main_queue(), ^{ self->_player = [AVPlayer playerWithPlayerItem:playerItem]; self->_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self->_player]; self->_playerLayer.frame = self.view.bounds; [self.view.layer addSublayer:self->_playerLayer]; [self addProgressObserver]; [self configPlayButton]; [self configBottomToolBar]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self->_player.currentItem]; }); }]; } /// Show progress,do it next time / 给播放器添加进度更新,下次加上 - (void)addProgressObserver{ AVPlayerItem *playerItem = _player.currentItem; UIProgressView *progress = _progress; [_player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) { float current = CMTimeGetSeconds(time); float total = CMTimeGetSeconds([playerItem duration]); if (current) { [progress setProgress:(current/total) animated:YES]; } }]; } - (void)configPlayButton { _playButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted]; [_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_playButton]; } - (void)configBottomToolBar { _toolBar = [[UIView alloc] initWithFrame:CGRectZero]; CGFloat rgb = 34 / 255.0; _toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7]; _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; _doneButton.titleLabel.font = [UIFont systemFontOfSize:16]; if (!_cover) { _doneButton.enabled = NO; } [_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (tzImagePickerVc) { [_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal]; [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal]; } else { [_doneButton setTitle:[NSBundle tz_localizedStringForKey:@"Done"] forState:UIControlStateNormal]; [_doneButton setTitleColor:[UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0] forState:UIControlStateNormal]; } [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled]; [_toolBar addSubview:_doneButton]; [self.view addSubview:_toolBar]; if (tzImagePickerVc && tzImagePickerVc.allowEditVideo && roundf(self.model.asset.duration) > 1) { _editButton = [UIButton buttonWithType:UIButtonTypeCustom]; _editButton.titleLabel.font = [UIFont systemFontOfSize:16]; if (!_cover) { _editButton.enabled = NO; } [_editButton addTarget:self action:@selector(editButtonClick) forControlEvents:UIControlEventTouchUpInside]; [_editButton setTitle:tzImagePickerVc.editBtnTitleStr forState:UIControlStateNormal]; [_editButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal]; [_editButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled]; [_toolBar addSubview:_editButton]; } if (tzImagePickerVc.videoPreviewPageUIConfigBlock) { tzImagePickerVc.videoPreviewPageUIConfigBlock(_playButton, _toolBar, _editButton, _doneButton); } } - (UIStatusBarStyle)preferredStatusBarStyle { TZImagePickerController *tzImagePicker = (TZImagePickerController *)self.navigationController; if (tzImagePicker && [tzImagePicker isKindOfClass:[TZImagePickerController class]]) { return tzImagePicker.statusBarStyle; } return [super preferredStatusBarStyle]; } #pragma mark - Layout - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height; CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0; CGFloat statusBarAndNaviBarHeight = statusBarHeight + self.navigationController.navigationBar.tz_height; _playerLayer.frame = self.view.bounds; CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom; _toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight); [_doneButton sizeToFit]; _doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 44); _playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight); if (tzImagePickerVc.allowEditVideo) { _editButton.frame = CGRectMake(12, 0, 44, 44); [_editButton sizeToFit]; _editButton.tz_height = 44; } if (tzImagePickerVc.videoPreviewPageDidLayoutSubviewsBlock) { tzImagePickerVc.videoPreviewPageDidLayoutSubviewsBlock(_playButton, _toolBar, _editButton, _doneButton); } } #pragma mark - Click Event - (void)playButtonClick { CMTime currentTime = _player.currentItem.currentTime; CMTime durationTime = _player.currentItem.duration; if (_player.rate == 0.0f) { [[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_VIDEO_PLAY_NOTIFICATION" object:_player]; if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)]; [_player play]; [self.navigationController setNavigationBarHidden:YES]; _toolBar.hidden = YES; _playButtonNormalImage = [_playButton imageForState:UIControlStateNormal]; [_playButton setImage:nil forState:UIControlStateNormal]; [UIApplication sharedApplication].statusBarHidden = YES; } else { [self pausePlayerAndShowNaviBar]; } } - (void)editButtonClick { TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; TZVideoCropController *videoCropVc = [[TZVideoCropController alloc] init]; videoCropVc.model = self.model; videoCropVc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; videoCropVc.modalPresentationStyle = UIModalPresentationFullScreen; videoCropVc.modalPresentationCapturesStatusBarAppearance = YES; videoCropVc.imagePickerVc = imagePickerVc; [self presentViewController:videoCropVc animated:YES completion:nil]; } - (void)doneButtonClick { if ([[TZImageManager manager] isAssetCannotBeSelected:_model.asset]) { return; } TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; if (imagePickerVc.allowEditVideo) { [imagePickerVc showProgressHUD]; [[TZImageManager manager] getVideoOutputPathWithAsset:_model.asset presetName:imagePickerVc.presetName success:^(NSString *outputPath) { [imagePickerVc hideProgressHUD]; self->_outputPath = outputPath; [self dismissAndCallDelegateMethod]; } failure:^(NSString *errorMessage, NSError *error) { [imagePickerVc hideProgressHUD]; self->_errorMsg = errorMessage; [self dismissAndCallDelegateMethod]; }]; } else { [self dismissAndCallDelegateMethod]; } } - (void)dismissAndCallDelegateMethod { TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; if (!imagePickerVc) { [self dismissViewControllerAnimated:YES completion:nil]; return; } if (imagePickerVc.autoDismiss) { [imagePickerVc dismissViewControllerAnimated:YES completion:^{ [self callDelegateMethod]; }]; } else { [self callDelegateMethod]; } } - (void)callDelegateMethod { TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; if (imagePickerVc.allowEditVideo) { if (_outputPath) { if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) { [imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingAndEditingVideo:self->_cover outputPath:self->_outputPath error:nil]; } if (imagePickerVc.didFinishPickingAndEditingVideoHandle) { imagePickerVc.didFinishPickingAndEditingVideoHandle(self->_cover, self->_outputPath, nil); } } else { if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) { [imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingAndEditingVideo:nil outputPath:nil error:self->_errorMsg]; } if (imagePickerVc.didFinishPickingAndEditingVideoHandle) { imagePickerVc.didFinishPickingAndEditingVideoHandle(nil, nil, self->_errorMsg); } } } else { if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingVideo:sourceAssets:)]) { [imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingVideo:_cover sourceAssets:_model.asset]; } if (imagePickerVc.didFinishPickingVideoHandle) { imagePickerVc.didFinishPickingVideoHandle(_cover,_model.asset); } } } #pragma mark - Notification Method - (void)pausePlayerAndShowNaviBar { [_player pause]; _toolBar.hidden = NO; [self.navigationController setNavigationBarHidden:NO]; UIImage *normalImage = _playButtonNormalImage ?: [UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"]; [_playButton setImage:normalImage forState:UIControlStateNormal]; if (self.needShowStatusBar) { [UIApplication sharedApplication].statusBarHidden = NO; } } #pragma mark - lazy - (UIView *)iCloudErrorView{ if (!_iCloudErrorView) { _iCloudErrorView = [[UIView alloc] initWithFrame:CGRectMake(0, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.view.tz_width, 28)]; UIImageView *icloud = [[UIImageView alloc] init]; icloud.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"]; icloud.frame = CGRectMake(20, 0, 28, 28); [_iCloudErrorView addSubview:icloud]; UILabel *label = [[UILabel alloc] init]; label.frame = CGRectMake(53, 0, self.view.tz_width - 63, 28); label.font = [UIFont systemFontOfSize:10]; label.textColor = [UIColor whiteColor]; label.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"]; [_iCloudErrorView addSubview:label]; [self.view addSubview:_iCloudErrorView]; _iCloudErrorView.hidden = YES; } return _iCloudErrorView; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma clang diagnostic pop @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/UIView+TZLayout.h ================================================ // // UIView+TZLayout.h // TZImagePickerController // // Created by 谭真 on 15/2/24. // Copyright © 2015年 谭真. All rights reserved. // #import typedef enum : NSUInteger { TZOscillatoryAnimationToBigger, TZOscillatoryAnimationToSmaller, } TZOscillatoryAnimationType; @interface UIView (TZLayout) @property (nonatomic) CGFloat tz_left; ///< Shortcut for frame.origin.x. @property (nonatomic) CGFloat tz_top; ///< Shortcut for frame.origin.y @property (nonatomic) CGFloat tz_right; ///< Shortcut for frame.origin.x + frame.size.width @property (nonatomic) CGFloat tz_bottom; ///< Shortcut for frame.origin.y + frame.size.height @property (nonatomic) CGFloat tz_width; ///< Shortcut for frame.size.width. @property (nonatomic) CGFloat tz_height; ///< Shortcut for frame.size.height. @property (nonatomic) CGFloat tz_centerX; ///< Shortcut for center.x @property (nonatomic) CGFloat tz_centerY; ///< Shortcut for center.y @property (nonatomic) CGPoint tz_origin; ///< Shortcut for frame.origin. @property (nonatomic) CGSize tz_size; ///< Shortcut for frame.size. + (void)showOscillatoryAnimationWithLayer:(CALayer *)layer type:(TZOscillatoryAnimationType)type; @end ================================================ FILE: JetChat/Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/UIView+TZLayout.m ================================================ // // UIView+TZLayout.m // TZImagePickerController // // Created by 谭真 on 15/2/24. // Copyright © 2015年 谭真. All rights reserved. // #import "UIView+TZLayout.h" @implementation UIView (TZLayout) - (CGFloat)tz_left { return self.frame.origin.x; } - (void)setTz_left:(CGFloat)x { CGRect frame = self.frame; frame.origin.x = x; self.frame = frame; } - (CGFloat)tz_top { return self.frame.origin.y; } - (void)setTz_top:(CGFloat)y { CGRect frame = self.frame; frame.origin.y = y; self.frame = frame; } - (CGFloat)tz_right { return self.frame.origin.x + self.frame.size.width; } - (void)setTz_right:(CGFloat)right { CGRect frame = self.frame; frame.origin.x = right - frame.size.width; self.frame = frame; } - (CGFloat)tz_bottom { return self.frame.origin.y + self.frame.size.height; } - (void)setTz_bottom:(CGFloat)bottom { CGRect frame = self.frame; frame.origin.y = bottom - frame.size.height; self.frame = frame; } - (CGFloat)tz_width { return self.frame.size.width; } - (void)setTz_width:(CGFloat)width { CGRect frame = self.frame; frame.size.width = width; self.frame = frame; } - (CGFloat)tz_height { return self.frame.size.height; } - (void)setTz_height:(CGFloat)height { CGRect frame = self.frame; frame.size.height = height; self.frame = frame; } - (CGFloat)tz_centerX { return self.center.x; } - (void)setTz_centerX:(CGFloat)centerX { self.center = CGPointMake(centerX, self.center.y); } - (CGFloat)tz_centerY { return self.center.y; } - (void)setTz_centerY:(CGFloat)centerY { self.center = CGPointMake(self.center.x, centerY); } - (CGPoint)tz_origin { return self.frame.origin; } - (void)setTz_origin:(CGPoint)origin { CGRect frame = self.frame; frame.origin = origin; self.frame = frame; } - (CGSize)tz_size { return self.frame.size; } - (void)setTz_size:(CGSize)size { CGRect frame = self.frame; frame.size = size; self.frame = frame; } + (void)showOscillatoryAnimationWithLayer:(CALayer *)layer type:(TZOscillatoryAnimationType)type{ NSNumber *animationScale1 = type == TZOscillatoryAnimationToBigger ? @(1.15) : @(0.5); NSNumber *animationScale2 = type == TZOscillatoryAnimationToBigger ? @(0.92) : @(1.15); [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{ [layer setValue:animationScale1 forKeyPath:@"transform.scale"]; } completion:^(BOOL finished) { [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{ [layer setValue:animationScale2 forKeyPath:@"transform.scale"]; } completion:^(BOOL finished) { [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{ [layer setValue:@(1.0) forKeyPath:@"transform.scale"]; } completion:nil]; }]; }]; } @end ================================================ FILE: JetChat/Pods/TZImagePickerController.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 034B10F260F7FEAFD9547E2D063C914D /* TZGifPhotoPreviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E21C136665BF59789EB2133EDFA37F2 /* TZGifPhotoPreviewController.m */; }; 037D9ECB6247D17E2B5D0F6D8F35420C /* TZImagePickerController-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A23AAD7252C3AE86989A8FE85A8EB9E /* TZImagePickerController-dummy.m */; }; 0730D0B582AB40AB96FAA74001BEEC33 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E4A393432B58E8A982E19459DFB001C /* Foundation.framework */; }; 09E8D055B6D21F61BA0583968C91412D /* TZAssetCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 99F459D3428E65B07E238B6454BD2EB2 /* TZAssetCell.m */; }; 13B36CD5CEA47640ABE19D7A9BFD1A3A /* TZVideoCropController.m in Sources */ = {isa = PBXBuildFile; fileRef = EE2B19D937DAB0FB52768520F9AA6F0E /* TZVideoCropController.m */; }; 176058F07BE278AE8BC50AE5F2239181 /* TZVideoEditedPreviewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 8FD9CF3E67A7B43F0DA6A050140B27DB /* TZVideoEditedPreviewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18E8C502FB85F0517471D9153C3C11AD /* TZImagePickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C6610AFA642EA43E40DD712FF0465F4 /* TZImagePickerController.m */; }; 23180A75EAA1C133E50C5EF5DB253A9C /* TZLocationManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 63C513DB5CE4C167174054BB2359AA92 /* TZLocationManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25900CB456C8B34A0BCCFF2DFF17B6CC /* NSBundle+TZImagePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = B6DAFA2AF17496996E11F0AE491360B5 /* NSBundle+TZImagePicker.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25B31A801B7280EA46996403201DC155 /* TZAssetModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C39F5F0D1A78DA8B296C3EB28597F03 /* TZAssetModel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3514C46309F2215BA2E9EFBD7B61EAB3 /* TZImagePickerController.bundle in Resources */ = {isa = PBXBuildFile; fileRef = EEA0F901B2C30143CAB062577E9211D0 /* TZImagePickerController.bundle */; }; 366400DCA09ACD68EA5DCA1213DEC8DB /* TZPhotoPreviewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = EEEF085F33E2A32AAD53D0E633231EF5 /* TZPhotoPreviewCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; 508495A6ED2780904C9DF9FAAB612C3B /* TZVideoPlayerController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3335F26F1BB4517AB1B2932A4202C2E0 /* TZVideoPlayerController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5354F42908B1BCEE5DECDF257F26FEA7 /* TZAssetModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EE620B2F763336A7069DB97E2478C55 /* TZAssetModel.m */; }; 54893A13ADAE8FA1874A92E157311ACB /* TZAuthLimitedFooterTipView.h in Headers */ = {isa = PBXBuildFile; fileRef = 9590E85381ABB5DCA5A5BB2C8BDA3EF9 /* TZAuthLimitedFooterTipView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 65E6185AD3801C3526C2891C04E4E41C /* TZImagePickerController.h in Headers */ = {isa = PBXBuildFile; fileRef = 401B46BA3C535C9B953B767EC0BA449C /* TZImagePickerController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68AF51698C5CEA352A18377874BD55CC /* TZPhotoPreviewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 06EC2B0B6C952552E1DABF4B894D4E98 /* TZPhotoPreviewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6C60E90C70E42BB2C1C3BDC5069953D0 /* TZImageRequestOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = FBADB8D3C76C9234424E2064BC4C2B96 /* TZImageRequestOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 752349FF1269E924F53E4CF0A714A079 /* TZLocationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2D89A2A1FCC74BEFAE3D1233B7EE61 /* TZLocationManager.m */; }; 767D98A178AAF141CCE6A5FE03DC9E47 /* TZProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 40A2F310E9995E49FC5368A2DD66D9CA /* TZProgressView.m */; }; 79B4FFE39357632099F104440941DC74 /* TZPhotoPickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = E511D8191689376DF9B852F4469BF9B2 /* TZPhotoPickerController.m */; }; 805CB18AB38DC45F7FE0E176C5E85E3A /* TZPhotoPreviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B932A6E4E829379223F210BA5F9085D /* TZPhotoPreviewController.m */; }; 9000118DE63892C43C76F562A0313D2A /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B43172B64AF87506786D6020CE20734F /* Photos.framework */; }; 935AF0C6C3DA8F8F4BA8104B21FC1E4F /* TZPhotoPreviewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = E99AF284405E2435959EF09B5489F01D /* TZPhotoPreviewCell.m */; }; 96E90C10582BBF7C30A5ED2B0696D963 /* TZAssetCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B52A1066DAE5FF487DA01471157B277 /* TZAssetCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; 982FE76A3D77F9E70C6E0892A2805D98 /* TZAuthLimitedFooterTipView.m in Sources */ = {isa = PBXBuildFile; fileRef = 18B71F829D8172FD5AEF3E9E447493F3 /* TZAuthLimitedFooterTipView.m */; }; 98D9BE282D76D02288B05D448B85183D /* TZImageCropManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 3699672A3DC4C9E30F04BED708A79FC1 /* TZImageCropManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; B283E2A016CF9BD2139890469A07A930 /* UIView+TZLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 93F155CC40CDD113F634E7E4B6E7DC8F /* UIView+TZLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; B4630D5A8C2186F1E27F9C06E4F7857C /* TZVideoPlayerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5356B272DBF15F3D33939D8341A4B262 /* TZVideoPlayerController.m */; }; B954790AAF573D4F02D0830AE25CD9B8 /* TZImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C857115962741A452067E21328147648 /* TZImageManager.m */; }; B9F326D39023969724323CAFF8C34FFC /* TZVideoEditedPreviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE52B8FD5A7B6CCD9E4B603C92F52FCC /* TZVideoEditedPreviewController.m */; }; BCEE42145EF2F5D867AF485724436AEF /* TZVideoCropController.h in Headers */ = {isa = PBXBuildFile; fileRef = 288FCC4FB2D4BFCBE3C5DDCE984984CF /* TZVideoCropController.h */; settings = {ATTRIBUTES = (Public, ); }; }; BDAE73F7AE61B164D587F85AE0ABA4BD /* TZProgressView.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F778A426CACCF5A11E1CD26F4480A0B /* TZProgressView.h */; settings = {ATTRIBUTES = (Public, ); }; }; C21C052721968129963969D49823427F /* TZImagePickerController-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = E5800CCC85CF90D88F0DBEC6D83808DD /* TZImagePickerController-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; C3F1A292EB6419AD7AECF1FCB19A063B /* UIView+TZLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 59955B4A75A957B74BBC0B8E77AD25BA /* UIView+TZLayout.m */; }; C7BD6486D6D4AE177907659B7F7F3870 /* NSBundle+TZImagePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = B130CAD9D4B148951593B1120EDDF4C4 /* NSBundle+TZImagePicker.m */; }; D23336A5CD373056912FD8BBDC203F39 /* TZImageCropManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 29C53B6138B5F243676AB99CD8EE794B /* TZImageCropManager.m */; }; DB4FBBC974C4CAFDD570D7AE544C9623 /* TZImageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 146072AEF2BEE6BAB1A10AD83106A444 /* TZImageManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; DEF28854F443A88BE4B5A5154CCFD69A /* PhotosUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86E9858900CEA4F67BD0B616EB926D9C /* PhotosUI.framework */; }; E7E703D98D659317CD0D630C4C3B86CA /* TZImageRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AF7767700AA9039D196EB1061A63662 /* TZImageRequestOperation.m */; }; F942B4B0253561ABA81994FA2B948902 /* TZGifPhotoPreviewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 33A214882A65FBEF13E78EDEAACA6002 /* TZGifPhotoPreviewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; FEB3E4BC159DFE29683AFF9ADCDC6C13 /* TZPhotoPickerController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7C4117A638E93D156A24975BBF55AEE8 /* TZPhotoPickerController.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 06EC2B0B6C952552E1DABF4B894D4E98 /* TZPhotoPreviewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZPhotoPreviewController.h; path = TZImagePickerController/TZImagePickerController/TZPhotoPreviewController.h; sourceTree = ""; }; 0DBF2C5C77D8316134001812A5BD25EF /* TZImagePickerController */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = TZImagePickerController; path = TZImagePickerController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E4A393432B58E8A982E19459DFB001C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 146072AEF2BEE6BAB1A10AD83106A444 /* TZImageManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZImageManager.h; path = TZImagePickerController/TZImagePickerController/TZImageManager.h; sourceTree = ""; }; 18B71F829D8172FD5AEF3E9E447493F3 /* TZAuthLimitedFooterTipView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZAuthLimitedFooterTipView.m; path = TZImagePickerController/TZImagePickerController/TZAuthLimitedFooterTipView.m; sourceTree = ""; }; 288FCC4FB2D4BFCBE3C5DDCE984984CF /* TZVideoCropController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZVideoCropController.h; path = TZImagePickerController/TZImagePickerController/TZVideoCropController.h; sourceTree = ""; }; 29C53B6138B5F243676AB99CD8EE794B /* TZImageCropManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZImageCropManager.m; path = TZImagePickerController/TZImagePickerController/TZImageCropManager.m; sourceTree = ""; }; 2B932A6E4E829379223F210BA5F9085D /* TZPhotoPreviewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZPhotoPreviewController.m; path = TZImagePickerController/TZImagePickerController/TZPhotoPreviewController.m; sourceTree = ""; }; 2F778A426CACCF5A11E1CD26F4480A0B /* TZProgressView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZProgressView.h; path = TZImagePickerController/TZImagePickerController/TZProgressView.h; sourceTree = ""; }; 3335F26F1BB4517AB1B2932A4202C2E0 /* TZVideoPlayerController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZVideoPlayerController.h; path = TZImagePickerController/TZImagePickerController/TZVideoPlayerController.h; sourceTree = ""; }; 33A214882A65FBEF13E78EDEAACA6002 /* TZGifPhotoPreviewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZGifPhotoPreviewController.h; path = TZImagePickerController/TZImagePickerController/TZGifPhotoPreviewController.h; sourceTree = ""; }; 3699672A3DC4C9E30F04BED708A79FC1 /* TZImageCropManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZImageCropManager.h; path = TZImagePickerController/TZImagePickerController/TZImageCropManager.h; sourceTree = ""; }; 3AF7767700AA9039D196EB1061A63662 /* TZImageRequestOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZImageRequestOperation.m; path = TZImagePickerController/TZImagePickerController/TZImageRequestOperation.m; sourceTree = ""; }; 3EE620B2F763336A7069DB97E2478C55 /* TZAssetModel.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZAssetModel.m; path = TZImagePickerController/TZImagePickerController/TZAssetModel.m; sourceTree = ""; }; 401B46BA3C535C9B953B767EC0BA449C /* TZImagePickerController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZImagePickerController.h; path = TZImagePickerController/TZImagePickerController/TZImagePickerController.h; sourceTree = ""; }; 404028CEE6475EEF443DBCE7A1361E26 /* TZImagePickerController.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = TZImagePickerController.debug.xcconfig; sourceTree = ""; }; 40A2F310E9995E49FC5368A2DD66D9CA /* TZProgressView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZProgressView.m; path = TZImagePickerController/TZImagePickerController/TZProgressView.m; sourceTree = ""; }; 5356B272DBF15F3D33939D8341A4B262 /* TZVideoPlayerController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZVideoPlayerController.m; path = TZImagePickerController/TZImagePickerController/TZVideoPlayerController.m; sourceTree = ""; }; 59955B4A75A957B74BBC0B8E77AD25BA /* UIView+TZLayout.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+TZLayout.m"; path = "TZImagePickerController/TZImagePickerController/UIView+TZLayout.m"; sourceTree = ""; }; 63C513DB5CE4C167174054BB2359AA92 /* TZLocationManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZLocationManager.h; path = TZImagePickerController/TZImagePickerController/TZLocationManager.h; sourceTree = ""; }; 6A23AAD7252C3AE86989A8FE85A8EB9E /* TZImagePickerController-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "TZImagePickerController-dummy.m"; sourceTree = ""; }; 79AF540873E2761FA3E85C022D01098A /* TZImagePickerController.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = TZImagePickerController.release.xcconfig; sourceTree = ""; }; 7C4117A638E93D156A24975BBF55AEE8 /* TZPhotoPickerController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZPhotoPickerController.h; path = TZImagePickerController/TZImagePickerController/TZPhotoPickerController.h; sourceTree = ""; }; 86E9858900CEA4F67BD0B616EB926D9C /* PhotosUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PhotosUI.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/PhotosUI.framework; sourceTree = DEVELOPER_DIR; }; 8B52A1066DAE5FF487DA01471157B277 /* TZAssetCell.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZAssetCell.h; path = TZImagePickerController/TZImagePickerController/TZAssetCell.h; sourceTree = ""; }; 8C39F5F0D1A78DA8B296C3EB28597F03 /* TZAssetModel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZAssetModel.h; path = TZImagePickerController/TZImagePickerController/TZAssetModel.h; sourceTree = ""; }; 8C6610AFA642EA43E40DD712FF0465F4 /* TZImagePickerController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZImagePickerController.m; path = TZImagePickerController/TZImagePickerController/TZImagePickerController.m; sourceTree = ""; }; 8FD9CF3E67A7B43F0DA6A050140B27DB /* TZVideoEditedPreviewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZVideoEditedPreviewController.h; path = TZImagePickerController/TZImagePickerController/TZVideoEditedPreviewController.h; sourceTree = ""; }; 93F155CC40CDD113F634E7E4B6E7DC8F /* UIView+TZLayout.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+TZLayout.h"; path = "TZImagePickerController/TZImagePickerController/UIView+TZLayout.h"; sourceTree = ""; }; 9590E85381ABB5DCA5A5BB2C8BDA3EF9 /* TZAuthLimitedFooterTipView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZAuthLimitedFooterTipView.h; path = TZImagePickerController/TZImagePickerController/TZAuthLimitedFooterTipView.h; sourceTree = ""; }; 99F459D3428E65B07E238B6454BD2EB2 /* TZAssetCell.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZAssetCell.m; path = TZImagePickerController/TZImagePickerController/TZAssetCell.m; sourceTree = ""; }; 9A46BFC226685E337CF52FDB0393AC57 /* TZImagePickerController-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "TZImagePickerController-Info.plist"; sourceTree = ""; }; 9D2D89A2A1FCC74BEFAE3D1233B7EE61 /* TZLocationManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZLocationManager.m; path = TZImagePickerController/TZImagePickerController/TZLocationManager.m; sourceTree = ""; }; 9E21C136665BF59789EB2133EDFA37F2 /* TZGifPhotoPreviewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZGifPhotoPreviewController.m; path = TZImagePickerController/TZImagePickerController/TZGifPhotoPreviewController.m; sourceTree = ""; }; B130CAD9D4B148951593B1120EDDF4C4 /* NSBundle+TZImagePicker.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSBundle+TZImagePicker.m"; path = "TZImagePickerController/TZImagePickerController/NSBundle+TZImagePicker.m"; sourceTree = ""; }; B43172B64AF87506786D6020CE20734F /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Photos.framework; sourceTree = DEVELOPER_DIR; }; B6DAFA2AF17496996E11F0AE491360B5 /* NSBundle+TZImagePicker.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSBundle+TZImagePicker.h"; path = "TZImagePickerController/TZImagePickerController/NSBundle+TZImagePicker.h"; sourceTree = ""; }; BE1F74B737E26ABD169D9870AE376872 /* TZImagePickerController.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = TZImagePickerController.modulemap; sourceTree = ""; }; C857115962741A452067E21328147648 /* TZImageManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZImageManager.m; path = TZImagePickerController/TZImagePickerController/TZImageManager.m; sourceTree = ""; }; CE52B8FD5A7B6CCD9E4B603C92F52FCC /* TZVideoEditedPreviewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZVideoEditedPreviewController.m; path = TZImagePickerController/TZImagePickerController/TZVideoEditedPreviewController.m; sourceTree = ""; }; DF7E3EDD4A960A79E0D311767E208B6C /* TZImagePickerController-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "TZImagePickerController-prefix.pch"; sourceTree = ""; }; E511D8191689376DF9B852F4469BF9B2 /* TZPhotoPickerController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZPhotoPickerController.m; path = TZImagePickerController/TZImagePickerController/TZPhotoPickerController.m; sourceTree = ""; }; E5800CCC85CF90D88F0DBEC6D83808DD /* TZImagePickerController-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "TZImagePickerController-umbrella.h"; sourceTree = ""; }; E99AF284405E2435959EF09B5489F01D /* TZPhotoPreviewCell.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZPhotoPreviewCell.m; path = TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.m; sourceTree = ""; }; EE2B19D937DAB0FB52768520F9AA6F0E /* TZVideoCropController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TZVideoCropController.m; path = TZImagePickerController/TZImagePickerController/TZVideoCropController.m; sourceTree = ""; }; EEA0F901B2C30143CAB062577E9211D0 /* TZImagePickerController.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = TZImagePickerController.bundle; path = TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle; sourceTree = ""; }; EEEF085F33E2A32AAD53D0E633231EF5 /* TZPhotoPreviewCell.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZPhotoPreviewCell.h; path = TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.h; sourceTree = ""; }; FBADB8D3C76C9234424E2064BC4C2B96 /* TZImageRequestOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TZImageRequestOperation.h; path = TZImagePickerController/TZImagePickerController/TZImageRequestOperation.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 0C36196FE3D46A7422F69E1726BC580C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 0730D0B582AB40AB96FAA74001BEEC33 /* Foundation.framework in Frameworks */, 9000118DE63892C43C76F562A0313D2A /* Photos.framework in Frameworks */, DEF28854F443A88BE4B5A5154CCFD69A /* PhotosUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0A67B964E38F47CE7A0E4880BD407AEA /* Frameworks */ = { isa = PBXGroup; children = ( A6F2E459F4A0703301C2343C7386E6AF /* iOS */, ); name = Frameworks; sourceTree = ""; }; 1A205C7D38FD0DD0B460DFCB164E261C /* Resources */ = { isa = PBXGroup; children = ( EEA0F901B2C30143CAB062577E9211D0 /* TZImagePickerController.bundle */, ); name = Resources; sourceTree = ""; }; 39008159B294EAE29777D0A1797B0276 = { isa = PBXGroup; children = ( 0A67B964E38F47CE7A0E4880BD407AEA /* Frameworks */, 4C2672EC9F84CBE81BDD3EE0F22FAE85 /* Products */, 4EAF81689127E1647F677EFB3A7FB3B0 /* TZImagePickerController */, ); sourceTree = ""; }; 4C2672EC9F84CBE81BDD3EE0F22FAE85 /* Products */ = { isa = PBXGroup; children = ( 0DBF2C5C77D8316134001812A5BD25EF /* TZImagePickerController */, ); name = Products; sourceTree = ""; }; 4EAF81689127E1647F677EFB3A7FB3B0 /* TZImagePickerController */ = { isa = PBXGroup; children = ( B6DAFA2AF17496996E11F0AE491360B5 /* NSBundle+TZImagePicker.h */, B130CAD9D4B148951593B1120EDDF4C4 /* NSBundle+TZImagePicker.m */, 8B52A1066DAE5FF487DA01471157B277 /* TZAssetCell.h */, 99F459D3428E65B07E238B6454BD2EB2 /* TZAssetCell.m */, 8C39F5F0D1A78DA8B296C3EB28597F03 /* TZAssetModel.h */, 3EE620B2F763336A7069DB97E2478C55 /* TZAssetModel.m */, 9590E85381ABB5DCA5A5BB2C8BDA3EF9 /* TZAuthLimitedFooterTipView.h */, 18B71F829D8172FD5AEF3E9E447493F3 /* TZAuthLimitedFooterTipView.m */, 33A214882A65FBEF13E78EDEAACA6002 /* TZGifPhotoPreviewController.h */, 9E21C136665BF59789EB2133EDFA37F2 /* TZGifPhotoPreviewController.m */, 3699672A3DC4C9E30F04BED708A79FC1 /* TZImageCropManager.h */, 29C53B6138B5F243676AB99CD8EE794B /* TZImageCropManager.m */, 146072AEF2BEE6BAB1A10AD83106A444 /* TZImageManager.h */, C857115962741A452067E21328147648 /* TZImageManager.m */, 401B46BA3C535C9B953B767EC0BA449C /* TZImagePickerController.h */, 8C6610AFA642EA43E40DD712FF0465F4 /* TZImagePickerController.m */, FBADB8D3C76C9234424E2064BC4C2B96 /* TZImageRequestOperation.h */, 3AF7767700AA9039D196EB1061A63662 /* TZImageRequestOperation.m */, 63C513DB5CE4C167174054BB2359AA92 /* TZLocationManager.h */, 9D2D89A2A1FCC74BEFAE3D1233B7EE61 /* TZLocationManager.m */, 7C4117A638E93D156A24975BBF55AEE8 /* TZPhotoPickerController.h */, E511D8191689376DF9B852F4469BF9B2 /* TZPhotoPickerController.m */, EEEF085F33E2A32AAD53D0E633231EF5 /* TZPhotoPreviewCell.h */, E99AF284405E2435959EF09B5489F01D /* TZPhotoPreviewCell.m */, 06EC2B0B6C952552E1DABF4B894D4E98 /* TZPhotoPreviewController.h */, 2B932A6E4E829379223F210BA5F9085D /* TZPhotoPreviewController.m */, 2F778A426CACCF5A11E1CD26F4480A0B /* TZProgressView.h */, 40A2F310E9995E49FC5368A2DD66D9CA /* TZProgressView.m */, 288FCC4FB2D4BFCBE3C5DDCE984984CF /* TZVideoCropController.h */, EE2B19D937DAB0FB52768520F9AA6F0E /* TZVideoCropController.m */, 8FD9CF3E67A7B43F0DA6A050140B27DB /* TZVideoEditedPreviewController.h */, CE52B8FD5A7B6CCD9E4B603C92F52FCC /* TZVideoEditedPreviewController.m */, 3335F26F1BB4517AB1B2932A4202C2E0 /* TZVideoPlayerController.h */, 5356B272DBF15F3D33939D8341A4B262 /* TZVideoPlayerController.m */, 93F155CC40CDD113F634E7E4B6E7DC8F /* UIView+TZLayout.h */, 59955B4A75A957B74BBC0B8E77AD25BA /* UIView+TZLayout.m */, 1A205C7D38FD0DD0B460DFCB164E261C /* Resources */, A6425FA88D907D65F310991D27AABD47 /* Support Files */, ); name = TZImagePickerController; path = TZImagePickerController; sourceTree = ""; }; A6425FA88D907D65F310991D27AABD47 /* Support Files */ = { isa = PBXGroup; children = ( BE1F74B737E26ABD169D9870AE376872 /* TZImagePickerController.modulemap */, 6A23AAD7252C3AE86989A8FE85A8EB9E /* TZImagePickerController-dummy.m */, 9A46BFC226685E337CF52FDB0393AC57 /* TZImagePickerController-Info.plist */, DF7E3EDD4A960A79E0D311767E208B6C /* TZImagePickerController-prefix.pch */, E5800CCC85CF90D88F0DBEC6D83808DD /* TZImagePickerController-umbrella.h */, 404028CEE6475EEF443DBCE7A1361E26 /* TZImagePickerController.debug.xcconfig */, 79AF540873E2761FA3E85C022D01098A /* TZImagePickerController.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/TZImagePickerController"; sourceTree = ""; }; A6F2E459F4A0703301C2343C7386E6AF /* iOS */ = { isa = PBXGroup; children = ( 0E4A393432B58E8A982E19459DFB001C /* Foundation.framework */, B43172B64AF87506786D6020CE20734F /* Photos.framework */, 86E9858900CEA4F67BD0B616EB926D9C /* PhotosUI.framework */, ); name = iOS; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ E53654DA2E7D49372C3BC9A9C4E4FF69 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 25900CB456C8B34A0BCCFF2DFF17B6CC /* NSBundle+TZImagePicker.h in Headers */, 96E90C10582BBF7C30A5ED2B0696D963 /* TZAssetCell.h in Headers */, 25B31A801B7280EA46996403201DC155 /* TZAssetModel.h in Headers */, 54893A13ADAE8FA1874A92E157311ACB /* TZAuthLimitedFooterTipView.h in Headers */, F942B4B0253561ABA81994FA2B948902 /* TZGifPhotoPreviewController.h in Headers */, 98D9BE282D76D02288B05D448B85183D /* TZImageCropManager.h in Headers */, DB4FBBC974C4CAFDD570D7AE544C9623 /* TZImageManager.h in Headers */, 65E6185AD3801C3526C2891C04E4E41C /* TZImagePickerController.h in Headers */, C21C052721968129963969D49823427F /* TZImagePickerController-umbrella.h in Headers */, 6C60E90C70E42BB2C1C3BDC5069953D0 /* TZImageRequestOperation.h in Headers */, 23180A75EAA1C133E50C5EF5DB253A9C /* TZLocationManager.h in Headers */, FEB3E4BC159DFE29683AFF9ADCDC6C13 /* TZPhotoPickerController.h in Headers */, 366400DCA09ACD68EA5DCA1213DEC8DB /* TZPhotoPreviewCell.h in Headers */, 68AF51698C5CEA352A18377874BD55CC /* TZPhotoPreviewController.h in Headers */, BDAE73F7AE61B164D587F85AE0ABA4BD /* TZProgressView.h in Headers */, BCEE42145EF2F5D867AF485724436AEF /* TZVideoCropController.h in Headers */, 176058F07BE278AE8BC50AE5F2239181 /* TZVideoEditedPreviewController.h in Headers */, 508495A6ED2780904C9DF9FAAB612C3B /* TZVideoPlayerController.h in Headers */, B283E2A016CF9BD2139890469A07A930 /* UIView+TZLayout.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 6090C91802307673CBF0A57EF1608978 /* TZImagePickerController */ = { isa = PBXNativeTarget; buildConfigurationList = DEF28D4F6A69F3722F75FC0F04BA6086 /* Build configuration list for PBXNativeTarget "TZImagePickerController" */; buildPhases = ( E53654DA2E7D49372C3BC9A9C4E4FF69 /* Headers */, D733F2B532642770F13E221E671077AD /* Sources */, 0C36196FE3D46A7422F69E1726BC580C /* Frameworks */, 2AD7A18BD3529192D23CCBA2375C2FD9 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = TZImagePickerController; productName = TZImagePickerController; productReference = 0DBF2C5C77D8316134001812A5BD25EF /* TZImagePickerController */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 5B50B2C00161813BCE250E120E9466A2 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = A085F08C446C82E654931299CE861F77 /* Build configuration list for PBXProject "TZImagePickerController" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 39008159B294EAE29777D0A1797B0276; productRefGroup = 4C2672EC9F84CBE81BDD3EE0F22FAE85 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 6090C91802307673CBF0A57EF1608978 /* TZImagePickerController */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2AD7A18BD3529192D23CCBA2375C2FD9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3514C46309F2215BA2E9EFBD7B61EAB3 /* TZImagePickerController.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ D733F2B532642770F13E221E671077AD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( C7BD6486D6D4AE177907659B7F7F3870 /* NSBundle+TZImagePicker.m in Sources */, 09E8D055B6D21F61BA0583968C91412D /* TZAssetCell.m in Sources */, 5354F42908B1BCEE5DECDF257F26FEA7 /* TZAssetModel.m in Sources */, 982FE76A3D77F9E70C6E0892A2805D98 /* TZAuthLimitedFooterTipView.m in Sources */, 034B10F260F7FEAFD9547E2D063C914D /* TZGifPhotoPreviewController.m in Sources */, D23336A5CD373056912FD8BBDC203F39 /* TZImageCropManager.m in Sources */, B954790AAF573D4F02D0830AE25CD9B8 /* TZImageManager.m in Sources */, 18E8C502FB85F0517471D9153C3C11AD /* TZImagePickerController.m in Sources */, 037D9ECB6247D17E2B5D0F6D8F35420C /* TZImagePickerController-dummy.m in Sources */, E7E703D98D659317CD0D630C4C3B86CA /* TZImageRequestOperation.m in Sources */, 752349FF1269E924F53E4CF0A714A079 /* TZLocationManager.m in Sources */, 79B4FFE39357632099F104440941DC74 /* TZPhotoPickerController.m in Sources */, 935AF0C6C3DA8F8F4BA8104B21FC1E4F /* TZPhotoPreviewCell.m in Sources */, 805CB18AB38DC45F7FE0E176C5E85E3A /* TZPhotoPreviewController.m in Sources */, 767D98A178AAF141CCE6A5FE03DC9E47 /* TZProgressView.m in Sources */, 13B36CD5CEA47640ABE19D7A9BFD1A3A /* TZVideoCropController.m in Sources */, B9F326D39023969724323CAFF8C34FFC /* TZVideoEditedPreviewController.m in Sources */, B4630D5A8C2186F1E27F9C06E4F7857C /* TZVideoPlayerController.m in Sources */, C3F1A292EB6419AD7AECF1FCB19A063B /* UIView+TZLayout.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 0F309700BE30714E7E9BE61ED46EC7AC /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 79AF540873E2761FA3E85C022D01098A /* TZImagePickerController.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/TZImagePickerController/TZImagePickerController-prefix.pch"; INFOPLIST_FILE = "Target Support Files/TZImagePickerController/TZImagePickerController-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/TZImagePickerController/TZImagePickerController.modulemap"; PRODUCT_MODULE_NAME = TZImagePickerController; PRODUCT_NAME = TZImagePickerController; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 2F9A407B49CF70DB83DD6D529EA937E6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 5773AB3F33F744AF16F1FB0119F12533 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; E9138ABF59EA2365B795302D4A6C8250 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 404028CEE6475EEF443DBCE7A1361E26 /* TZImagePickerController.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/TZImagePickerController/TZImagePickerController-prefix.pch"; INFOPLIST_FILE = "Target Support Files/TZImagePickerController/TZImagePickerController-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/TZImagePickerController/TZImagePickerController.modulemap"; PRODUCT_MODULE_NAME = TZImagePickerController; PRODUCT_NAME = TZImagePickerController; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ A085F08C446C82E654931299CE861F77 /* Build configuration list for PBXProject "TZImagePickerController" */ = { isa = XCConfigurationList; buildConfigurations = ( 2F9A407B49CF70DB83DD6D529EA937E6 /* Debug */, 5773AB3F33F744AF16F1FB0119F12533 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; DEF28D4F6A69F3722F75FC0F04BA6086 /* Build configuration list for PBXNativeTarget "TZImagePickerController" */ = { isa = XCConfigurationList; buildConfigurations = ( E9138ABF59EA2365B795302D4A6C8250 /* Debug */, 0F309700BE30714E7E9BE61ED46EC7AC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 5B50B2C00161813BCE250E120E9466A2 /* Project object */; } ================================================ FILE: JetChat/Pods/Target Support Files/Alamofire/Alamofire-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 5.6.1 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/Alamofire/Alamofire-dummy.m ================================================ #import @interface PodsDummy_Alamofire : NSObject @end @implementation PodsDummy_Alamofire @end ================================================ FILE: JetChat/Pods/Target Support Files/Alamofire/Alamofire-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/Alamofire/Alamofire-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double AlamofireVersionNumber; FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/Alamofire/Alamofire.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alamofire GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "CFNetwork" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/Alamofire/Alamofire.modulemap ================================================ framework module Alamofire { umbrella header "Alamofire-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/Alamofire/Alamofire.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alamofire GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "CFNetwork" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.1.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture-dummy.m ================================================ #import @interface PodsDummy_FDFullscreenPopGesture : NSObject @end @implementation PodsDummy_FDFullscreenPopGesture @end ================================================ FILE: JetChat/Pods/Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "UINavigationController+FDFullscreenPopGesture.h" FOUNDATION_EXPORT double FDFullscreenPopGestureVersionNumber; FOUNDATION_EXPORT const unsigned char FDFullscreenPopGestureVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FDFullscreenPopGesture GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/FDFullscreenPopGesture PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture.modulemap ================================================ framework module FDFullscreenPopGesture { umbrella header "FDFullscreenPopGesture-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/FDFullscreenPopGesture/FDFullscreenPopGesture.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FDFullscreenPopGesture GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/FDFullscreenPopGesture PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/HandyJSON/HandyJSON-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 5.0.2 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/HandyJSON/HandyJSON-dummy.m ================================================ #import @interface PodsDummy_HandyJSON : NSObject @end @implementation PodsDummy_HandyJSON @end ================================================ FILE: JetChat/Pods/Target Support Files/HandyJSON/HandyJSON-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/HandyJSON/HandyJSON-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "HandyJSON.h" FOUNDATION_EXPORT double HandyJSONVersionNumber; FOUNDATION_EXPORT const unsigned char HandyJSONVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/HandyJSON/HandyJSON.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/HandyJSON GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/HandyJSON PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES SWIFT_VERSION = 5.0 USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/HandyJSON/HandyJSON.modulemap ================================================ framework module HandyJSON { umbrella header "HandyJSON-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/HandyJSON/HandyJSON.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/HandyJSON GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/HandyJSON PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES SWIFT_VERSION = 5.0 USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/IGListDiffKit/IGListDiffKit-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 4.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/IGListDiffKit/IGListDiffKit-dummy.m ================================================ #import @interface PodsDummy_IGListDiffKit : NSObject @end @implementation PodsDummy_IGListDiffKit @end ================================================ FILE: JetChat/Pods/Target Support Files/IGListDiffKit/IGListDiffKit-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/IGListDiffKit/IGListDiffKit-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "IGListAssert.h" #import "IGListBatchUpdateData.h" #import "IGListCompatibility.h" #import "IGListDiff.h" #import "IGListDiffable.h" #import "IGListDiffKit.h" #import "IGListExperiments.h" #import "IGListIndexPathResult.h" #import "IGListIndexSetResult.h" #import "IGListMacros.h" #import "IGListMoveIndex.h" #import "IGListMoveIndexPath.h" #import "NSNumber+IGListDiffable.h" #import "NSString+IGListDiffable.h" FOUNDATION_EXPORT double IGListDiffKitVersionNumber; FOUNDATION_EXPORT const unsigned char IGListDiffKitVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/IGListDiffKit/IGListDiffKit.debug.xcconfig ================================================ CLANG_CXX_LANGUAGE_STANDARD = c++11 CLANG_CXX_LIBRARY = libc++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/IGListDiffKit GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -l"c++" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/IGListDiffKit PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/IGListDiffKit/IGListDiffKit.modulemap ================================================ framework module IGListDiffKit { umbrella header "IGListDiffKit-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/IGListDiffKit/IGListDiffKit.release.xcconfig ================================================ CLANG_CXX_LANGUAGE_STANDARD = c++11 CLANG_CXX_LIBRARY = libc++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/IGListDiffKit GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -l"c++" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/IGListDiffKit PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/IGListKit/IGListKit-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 4.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/IGListKit/IGListKit-dummy.m ================================================ #import @interface PodsDummy_IGListKit : NSObject @end @implementation PodsDummy_IGListKit @end ================================================ FILE: JetChat/Pods/Target Support Files/IGListKit/IGListKit-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/IGListKit/IGListKit-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "IGListAdapter.h" #import "IGListAdapterDataSource.h" #import "IGListAdapterDelegate.h" #import "IGListAdapterMoveDelegate.h" #import "IGListAdapterPerformanceDelegate.h" #import "IGListAdapterUpdateListener.h" #import "IGListAdapterUpdater.h" #import "IGListAdapterUpdaterDelegate.h" #import "IGListBatchContext.h" #import "IGListBindable.h" #import "IGListBindingSectionController.h" #import "IGListBindingSectionControllerDataSource.h" #import "IGListBindingSectionControllerSelectionDelegate.h" #import "IGListCollectionContext.h" #import "IGListCollectionScrollingTraits.h" #import "IGListCollectionView.h" #import "IGListCollectionViewDelegateLayout.h" #import "IGListCollectionViewLayout.h" #import "IGListCollectionViewLayoutCompatible.h" #import "IGListDisplayDelegate.h" #import "IGListGenericSectionController.h" #import "IGListKit.h" #import "IGListReloadDataUpdater.h" #import "IGListScrollDelegate.h" #import "IGListSectionController.h" #import "IGListSingleSectionController.h" #import "IGListSupplementaryViewSource.h" #import "IGListTransitionDelegate.h" #import "IGListUpdatingDelegate.h" #import "IGListWorkingRangeDelegate.h" FOUNDATION_EXPORT double IGListKitVersionNumber; FOUNDATION_EXPORT const unsigned char IGListKitVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/IGListKit/IGListKit.debug.xcconfig ================================================ CLANG_CXX_LANGUAGE_STANDARD = c++11 CLANG_CXX_LIBRARY = libc++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/IGListKit FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IGListDiffKit" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -l"c++" -framework "IGListDiffKit" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/IGListKit PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/IGListKit/IGListKit.modulemap ================================================ framework module IGListKit { umbrella header "IGListKit-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/IGListKit/IGListKit.release.xcconfig ================================================ CLANG_CXX_LANGUAGE_STANDARD = c++11 CLANG_CXX_LIBRARY = libc++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/IGListKit FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/IGListDiffKit" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -l"c++" -framework "IGListDiffKit" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/IGListKit PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 6.5.9 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-dummy.m ================================================ #import @interface PodsDummy_IQKeyboardManagerSwift : NSObject @end @implementation PodsDummy_IQKeyboardManagerSwift @end ================================================ FILE: JetChat/Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double IQKeyboardManagerSwiftVersionNumber; FOUNDATION_EXPORT const unsigned char IQKeyboardManagerSwiftVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Foundation" -framework "QuartzCore" -framework "UIKit" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/IQKeyboardManagerSwift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift.modulemap ================================================ framework module IQKeyboardManagerSwift { umbrella header "IQKeyboardManagerSwift-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/IQKeyboardManagerSwift/IQKeyboardManagerSwift.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Foundation" -framework "QuartzCore" -framework "UIKit" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/IQKeyboardManagerSwift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/Kingfisher/Kingfisher-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 6.3.1 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/Kingfisher/Kingfisher-dummy.m ================================================ #import @interface PodsDummy_Kingfisher : NSObject @end @implementation PodsDummy_Kingfisher @end ================================================ FILE: JetChat/Pods/Target Support Files/Kingfisher/Kingfisher-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/Kingfisher/Kingfisher-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double KingfisherVersionNumber; FOUNDATION_EXPORT const unsigned char KingfisherVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/Kingfisher/Kingfisher.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "CFNetwork" -weak_framework "Combine" -weak_framework "SwiftUI" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Kingfisher PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/Kingfisher/Kingfisher.modulemap ================================================ framework module Kingfisher { umbrella header "Kingfisher-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/Kingfisher/Kingfisher.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "CFNetwork" -weak_framework "Combine" -weak_framework "SwiftUI" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Kingfisher PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/Localize-Swift/Localize-Swift-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 3.2.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/Localize-Swift/Localize-Swift-dummy.m ================================================ #import @interface PodsDummy_Localize_Swift : NSObject @end @implementation PodsDummy_Localize_Swift @end ================================================ FILE: JetChat/Pods/Target Support Files/Localize-Swift/Localize-Swift-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/Localize-Swift/Localize-Swift-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "Localize_Swift.h" FOUNDATION_EXPORT double Localize_SwiftVersionNumber; FOUNDATION_EXPORT const unsigned char Localize_SwiftVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/Localize-Swift/Localize-Swift.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Localize-Swift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES SWIFT_VERSION = 5.3 USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/Localize-Swift/Localize-Swift.modulemap ================================================ framework module Localize_Swift { umbrella header "Localize-Swift-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/Localize-Swift/Localize-Swift.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Localize-Swift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES SWIFT_VERSION = 5.3 USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/LookinServer/LookinServer-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.5 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/LookinServer/LookinServer-dummy.m ================================================ #import @interface PodsDummy_LookinServer : NSObject @end @implementation PodsDummy_LookinServer @end ================================================ FILE: JetChat/Pods/Target Support Files/LookinServer/LookinServer-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/LookinServer/LookinServer-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "CALayer+LookinServer.h" #import "NSObject+LookinServer.h" #import "UIBlurEffect+LookinServer.h" #import "UIColor+LookinServer.h" #import "UIGestureRecognizer+LookinServer.h" #import "UIImage+LookinServer.h" #import "UIImageView+LookinServer.h" #import "UILabel+LookinServer.h" #import "UITableView+LookinServer.h" #import "UITextField+LookinServer.h" #import "UITextView+LookinServer.h" #import "UIView+LookinServer.h" #import "UIViewController+LookinServer.h" #import "UIVisualEffectView+LookinServer.h" #import "LKS_ConnectionManager.h" #import "LKS_RequestHandler.h" #import "LKS_AttrModificationHandler.h" #import "LKS_AttrModificationPatchHandler.h" #import "LKS_HierarchyDetailsHandler.h" #import "LKS_LocalInspectManager.h" #import "LKS_LocalInspectPanelLabelView.h" #import "LKS_LocalInspectViewController.h" #import "LookinServer.h" #import "LKS_AttrGroupsMaker.h" #import "LKS_EventHandlerMaker.h" #import "LKS_ExportManager.h" #import "LKS_Helper.h" #import "LKS_HierarchyDisplayItemsMaker.h" #import "LKS_MethodTraceManager.h" #import "LKS_ObjectRegistry.h" #import "LKS_TraceManager.h" #import "LookinServerDefines.h" #import "LKS_PerspectiveDataSource.h" #import "LKS_PerspectiveHierarchyCell.h" #import "LKS_PerspectiveHierarchyView.h" #import "LKS_PerspectiveItemLayer.h" #import "LKS_PerspectiveLayer.h" #import "LKS_PerspectiveManager.h" #import "LKS_PerspectiveToolbarButtons.h" #import "LKS_PerspectiveViewController.h" #import "CALayer+Lookin.h" #import "NSArray+Lookin.h" #import "NSObject+Lookin.h" #import "NSSet+Lookin.h" #import "NSString+Lookin.h" #import "LookinAppInfo.h" #import "LookinAttribute.h" #import "LookinAttributeModification.h" #import "LookinAttributesGroup.h" #import "LookinAttributesSection.h" #import "LookinAttrIdentifiers.h" #import "LookinAttrType.h" #import "LookinAutoLayoutConstraint.h" #import "LookinCodingValueType.h" #import "LookinConnectionAttachment.h" #import "LookinConnectionResponseAttachment.h" #import "LookinDashboardBlueprint.h" #import "LookinDefines.h" #import "LookinDisplayItem.h" #import "LookinDisplayItemDetail.h" #import "LookinEventHandler.h" #import "LookinHierarchyFile.h" #import "LookinHierarchyInfo.h" #import "LookinIvarTrace.h" #import "LookinMethodTraceRecord.h" #import "LookinObject.h" #import "LookinScreenshotFetchManager.h" #import "LookinStaticAsyncUpdateTask.h" #import "LookinTuple.h" #import "LookinWeakContainer.h" #import "LookinMsgAttribute.h" #import "LookinMsgTargetAction.h" #import "Lookin_PTChannel.h" #import "Lookin_PTPrivate.h" #import "Lookin_PTProtocol.h" #import "Lookin_PTUSBHub.h" #import "Peertalk.h" FOUNDATION_EXPORT double LookinServerVersionNumber; FOUNDATION_EXPORT const unsigned char LookinServerVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/LookinServer/LookinServer.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/LookinServer GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/LookinServer PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/LookinServer/LookinServer.modulemap ================================================ framework module LookinServer { umbrella header "LookinServer-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/LookinServer/LookinServer.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/LookinServer GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/LookinServer PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/MBProgressHUD/MBProgressHUD-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.2.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/MBProgressHUD/MBProgressHUD-dummy.m ================================================ #import @interface PodsDummy_MBProgressHUD : NSObject @end @implementation PodsDummy_MBProgressHUD @end ================================================ FILE: JetChat/Pods/Target Support Files/MBProgressHUD/MBProgressHUD-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/MBProgressHUD/MBProgressHUD-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "MBProgressHUD.h" FOUNDATION_EXPORT double MBProgressHUDVersionNumber; FOUNDATION_EXPORT const unsigned char MBProgressHUDVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/MBProgressHUD/MBProgressHUD.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "QuartzCore" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/MBProgressHUD PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/MBProgressHUD/MBProgressHUD.modulemap ================================================ framework module MBProgressHUD { umbrella header "MBProgressHUD-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/MBProgressHUD/MBProgressHUD.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "QuartzCore" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/MBProgressHUD PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/MJRefresh/MJRefresh-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 3.7.5 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/MJRefresh/MJRefresh-dummy.m ================================================ #import @interface PodsDummy_MJRefresh : NSObject @end @implementation PodsDummy_MJRefresh @end ================================================ FILE: JetChat/Pods/Target Support Files/MJRefresh/MJRefresh-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/MJRefresh/MJRefresh-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "MJRefreshAutoFooter.h" #import "MJRefreshBackFooter.h" #import "MJRefreshComponent.h" #import "MJRefreshFooter.h" #import "MJRefreshHeader.h" #import "MJRefreshTrailer.h" #import "MJRefreshAutoGifFooter.h" #import "MJRefreshAutoNormalFooter.h" #import "MJRefreshAutoStateFooter.h" #import "MJRefreshBackGifFooter.h" #import "MJRefreshBackNormalFooter.h" #import "MJRefreshBackStateFooter.h" #import "MJRefreshGifHeader.h" #import "MJRefreshNormalHeader.h" #import "MJRefreshStateHeader.h" #import "MJRefreshNormalTrailer.h" #import "MJRefreshStateTrailer.h" #import "MJRefresh.h" #import "MJRefreshConfig.h" #import "MJRefreshConst.h" #import "NSBundle+MJRefresh.h" #import "UICollectionViewLayout+MJRefresh.h" #import "UIScrollView+MJExtension.h" #import "UIScrollView+MJRefresh.h" #import "UIView+MJExtension.h" FOUNDATION_EXPORT double MJRefreshVersionNumber; FOUNDATION_EXPORT const unsigned char MJRefreshVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/MJRefresh/MJRefresh.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/MJRefresh PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/MJRefresh/MJRefresh.modulemap ================================================ framework module MJRefresh { umbrella header "MJRefresh-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/MJRefresh/MJRefresh.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/MJRefresh PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/Moya/Moya-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 15.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/Moya/Moya-dummy.m ================================================ #import @interface PodsDummy_Moya : NSObject @end @implementation PodsDummy_Moya @end ================================================ FILE: JetChat/Pods/Target Support Files/Moya/Moya-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/Moya/Moya-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double MoyaVersionNumber; FOUNDATION_EXPORT const unsigned char MoyaVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/Moya/Moya.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Moya FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "CFNetwork" -framework "Foundation" -framework "RxSwift" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Moya PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/Moya/Moya.modulemap ================================================ framework module Moya { umbrella header "Moya-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/Moya/Moya.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Moya FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "CFNetwork" -framework "Foundation" -framework "RxSwift" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/Moya PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/NSObject+Rx/NSObject+Rx-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 5.2.2 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/NSObject+Rx/NSObject+Rx-dummy.m ================================================ #import @interface PodsDummy_NSObject_Rx : NSObject @end @implementation PodsDummy_NSObject_Rx @end ================================================ FILE: JetChat/Pods/Target Support Files/NSObject+Rx/NSObject+Rx-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/NSObject+Rx/NSObject+Rx-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double NSObject_RxVersionNumber; FOUNDATION_EXPORT const unsigned char NSObject_RxVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/NSObject+Rx/NSObject+Rx.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/NSObject+Rx FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "RxSwift" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/NSObject+Rx PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/NSObject+Rx/NSObject+Rx.modulemap ================================================ framework module NSObject_Rx { umbrella header "NSObject+Rx-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/NSObject+Rx/NSObject+Rx.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/NSObject+Rx FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "RxSwift" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/NSObject+Rx PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat-acknowledgements.markdown ================================================ # Acknowledgements This application makes use of the following third party libraries: ## Alamofire Copyright (c) 2014-2022 Alamofire Software Foundation (http://alamofire.org/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## FDFullscreenPopGesture The MIT License (MIT) Copyright (c) 2015 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## HandyJSON Copyright 1999-2016 Alibaba Group Holding Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 1. reflection The MIT License (MIT) Copyright (c) 2016 Brad Hilton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 2. ObjectMapper The MIT License (MIT) Copyright (c) 2014 Hearst Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## IGListDiffKit MIT License Copyright (c) Facebook, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## IGListKit MIT License Copyright (c) Facebook, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## IQKeyboardManagerSwift MIT License Copyright (c) 2013-2017 Iftekhar Qurashi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## Kingfisher The MIT License (MIT) Copyright (c) 2019 Wei Wang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## Localize-Swift Copyright (c) 2015 Roy Marmelstein (http://roysapps.com/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## LookinServer GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ## MBProgressHUD Copyright © 2009-2020 Matej Bukovinski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## MJRefresh Copyright (c) 2013-2015 MJRefresh (https://github.com/CoderMJLee/MJRefresh) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## Moya The MIT License (MIT) Copyright (c) 2014-present Artsy, Ash Furrow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## NSObject+Rx The MIT License (MIT) Copyright (c) 2015 Ash Furrow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## R.swift The MIT License (MIT) Copyright (c) 2014-2020 Mathijs Kadijk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## R.swift.Library The MIT License (MIT) Copyright (c) 2015 Mathijs Kadijk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## ReachabilitySwift Copyright (c) 2016 Ashley Mills Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## RxCocoa **The MIT License** **Copyright © 2015 Krunoslav Zaher, Shai Mishali** **All rights reserved.** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## RxRelay **The MIT License** **Copyright © 2015 Krunoslav Zaher, Shai Mishali** **All rights reserved.** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## RxSwift **The MIT License** **Copyright © 2015 Krunoslav Zaher, Shai Mishali** **All rights reserved.** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## RxTheme Copyright (c) 2018 RxSwiftCommunity Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## SDWebImage Copyright (c) 2009-2020 Olivier Poitrey rs@dailymotion.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## SQLiteRepairKit Tencent is pleased to support the open source community by making WCDB available.  Copyright (C) 2017 THL A29 Limited, a Tencent company.  All rights reserved. If you have downloaded a copy of the WCDB binary from Tencent, please note that the WCDB binary is licensed under the BSD 3-Clause License. If you have downloaded a copy of the WCDB source code from Tencent, please note that WCDB source code is licensed under the BSD 3-Clause License, except for the third-party components listed below which are subject to different license terms.  Your integration of WCDB into your own projects may require compliance with the BSD 3-Clause License, as well as the other licenses applicable to the third-party components included within WCDB. A copy of the BSD 3-Clause License is included in this file. Other dependencies and licenses:   Open Source Software Licensed Under the Apache License, Version 2.0: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. Android Source Code  4.3 Copyright (C) 2006-2011 The Android Open Source Project     Terms of the Apache License, Version 2.0: --------------------------------------------------- Apache License Version 2.0, January 2004 http://www.apache.org/licenses/   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION   1. Definitions.   “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.   “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.   “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.   “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.   “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.   “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.   “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).   “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.   “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.”   “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.   2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.   3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.   4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:   a) You must give any other recipients of the Work or Derivative Works a copy of this License; and   b) You must cause any modified files to carry prominent notices stating that You changed the files; and   c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and   d) If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.   You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.   5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.   6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.   7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.   8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.   END OF TERMS AND CONDITIONS   APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.   Copyright [yyyy] [name of copyright owner]   Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.       Open Source Software Licensed Under Public Domain: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. SQLite  3.11.0       Open Source Software Licensed Under the OpenSSL License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. OpenSSL  1.0.2j Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved. Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved.       Terms of the OpenSSL License: --------------------------------------------------- LICENSE ISSUES: --------------------------------------------------------------------   The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit. See below for the actual license texts.   OpenSSL License: -------------------------------------------------------------------- Copyright (c) 1998-2016 The OpenSSL Project.  All rights reserved.   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:   1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.   3. All advertising materials mentioning features or use of this software must display the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)"   4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org.   5. Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project.   6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)"   THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================== * This product includes cryptographic software written by Eric Young (eay@cryptsoft.com).  This product includes software written by Tim Hudson (tjh@cryptsoft.com).     Original SSLeay License: -------------------------------------------------------------------- Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved.   This package is an SSL implementation written by Eric Young (eay@cryptsoft.com). The implementation was written so as to conform with Netscapes SSL.   This library is free for commercial and non-commercial use as long as the following conditions are aheared to. The following conditions apply to all code found in this distribution, be it the RC4, RSA, lhash, DES, etc., code; not just the SSL code. The SSL documentation included with this distribution is covered by the same copyright terms except that the holder is Tim Hudson (tjh@cryptsoft.com).   Copyright remains Eric Young's, and as such any Copyright notices in the code are not to be removed.  If this package is used in a product, Eric Young should be given attribution as the author of the parts of the library used. This can be in the form of a textual message at program startup or in documentation (online or textual) provided with the package.   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement:" This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)" The word 'cryptographic' can be left out if the rouines from the library being used are not cryptographic related :-). 4. If you include any Windows specific code (or a derivative thereof) from the apps directory (application code) you must include an acknowledgement: "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"   THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   The licence and distribution terms for any publically available version or derivative of this code cannot be changed.  i.e. this code cannot simply be copied and put under another distribution licence [including the GNU Public Licence.]       Open Source Software Licensed Under the ICU License: ---------------------------------------------------------------------------------------- 1. ICU4C  50.1 Copyright (c) 1995-2012 International Business Machines Corporation and others All rights reserved.     Terms of the ICU License: -------------------------------------------------------------------- ICU License - ICU 1.8.1 and later   COPYRIGHT AND PERMISSION NOTICE   Copyright (c) 1995-2012 International Business Machines Corporation and others   All rights reserved.   Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation.   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.   Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder.   All trademarks and registered trademarks mentioned herein are the property of their respective owners.     Open Source Software Licensed Under the BSD 3-Clause License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. SQLCipher  3.4.0 Copyright (c) 2008, ZETETIC LLC All rights reserved.       Terms of the BSD 3-Clause License: --------------------------------------------------------------------   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: l  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. l  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. l  Neither the name of [copyright holder] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## SnapKit Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## SwiftDate The MIT License (MIT) Copyright (c) 2018 Daniele Margutti Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## SwifterSwift MIT License Copyright (c) 2015-2018 SwifterSwift (https://github.com/swifterswift) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## SwiftyJSON The MIT License (MIT) Copyright (c) 2017 Ruoyu Fu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## TZImagePickerController The MIT License (MIT) Copyright (c) 2016 Zhen Tan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## UITableView+FDTemplateLayoutCell The MIT License (MIT) Copyright (c) 2015 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## UIView+FDCollapsibleConstraints The MIT License (MIT) Copyright (c) 2015 forkingdog Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## WCDB.swift Tencent is pleased to support the open source community by making WCDB available.  Copyright (C) 2017 THL A29 Limited, a Tencent company.  All rights reserved. If you have downloaded a copy of the WCDB binary from Tencent, please note that the WCDB binary is licensed under the BSD 3-Clause License. If you have downloaded a copy of the WCDB source code from Tencent, please note that WCDB source code is licensed under the BSD 3-Clause License, except for the third-party components listed below which are subject to different license terms.  Your integration of WCDB into your own projects may require compliance with the BSD 3-Clause License, as well as the other licenses applicable to the third-party components included within WCDB. A copy of the BSD 3-Clause License is included in this file. Other dependencies and licenses:   Open Source Software Licensed Under the Apache License, Version 2.0: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. Android Source Code  4.3 Copyright (C) 2006-2011 The Android Open Source Project     Terms of the Apache License, Version 2.0: --------------------------------------------------- Apache License Version 2.0, January 2004 http://www.apache.org/licenses/   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION   1. Definitions.   “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.   “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.   “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.   “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.   “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.   “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.   “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).   “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.   “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.”   “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.   2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.   3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.   4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:   a) You must give any other recipients of the Work or Derivative Works a copy of this License; and   b) You must cause any modified files to carry prominent notices stating that You changed the files; and   c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and   d) If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.   You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.   5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.   6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.   7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.   8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.   END OF TERMS AND CONDITIONS   APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.   Copyright [yyyy] [name of copyright owner]   Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.       Open Source Software Licensed Under Public Domain: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. SQLite  3.11.0       Open Source Software Licensed Under the OpenSSL License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. OpenSSL  1.0.2j Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved. Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved.       Terms of the OpenSSL License: --------------------------------------------------- LICENSE ISSUES: --------------------------------------------------------------------   The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit. See below for the actual license texts.   OpenSSL License: -------------------------------------------------------------------- Copyright (c) 1998-2016 The OpenSSL Project.  All rights reserved.   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:   1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.   3. All advertising materials mentioning features or use of this software must display the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)"   4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org.   5. Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project.   6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)"   THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================== * This product includes cryptographic software written by Eric Young (eay@cryptsoft.com).  This product includes software written by Tim Hudson (tjh@cryptsoft.com).     Original SSLeay License: -------------------------------------------------------------------- Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved.   This package is an SSL implementation written by Eric Young (eay@cryptsoft.com). The implementation was written so as to conform with Netscapes SSL.   This library is free for commercial and non-commercial use as long as the following conditions are aheared to. The following conditions apply to all code found in this distribution, be it the RC4, RSA, lhash, DES, etc., code; not just the SSL code. The SSL documentation included with this distribution is covered by the same copyright terms except that the holder is Tim Hudson (tjh@cryptsoft.com).   Copyright remains Eric Young's, and as such any Copyright notices in the code are not to be removed.  If this package is used in a product, Eric Young should be given attribution as the author of the parts of the library used. This can be in the form of a textual message at program startup or in documentation (online or textual) provided with the package.   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement:" This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)" The word 'cryptographic' can be left out if the rouines from the library being used are not cryptographic related :-). 4. If you include any Windows specific code (or a derivative thereof) from the apps directory (application code) you must include an acknowledgement: "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"   THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   The licence and distribution terms for any publically available version or derivative of this code cannot be changed.  i.e. this code cannot simply be copied and put under another distribution licence [including the GNU Public Licence.]       Open Source Software Licensed Under the ICU License: ---------------------------------------------------------------------------------------- 1. ICU4C  50.1 Copyright (c) 1995-2012 International Business Machines Corporation and others All rights reserved.     Terms of the ICU License: -------------------------------------------------------------------- ICU License - ICU 1.8.1 and later   COPYRIGHT AND PERMISSION NOTICE   Copyright (c) 1995-2012 International Business Machines Corporation and others   All rights reserved.   Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation.   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.   Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder.   All trademarks and registered trademarks mentioned herein are the property of their respective owners.     Open Source Software Licensed Under the BSD 3-Clause License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. SQLCipher  3.4.0 Copyright (c) 2008, ZETETIC LLC All rights reserved.       Terms of the BSD 3-Clause License: --------------------------------------------------------------------   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: l  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. l  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. l  Neither the name of [copyright holder] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## WCDBOptimizedSQLCipher Copyright (c) 2008, ZETETIC LLC All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the ZETETIC LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## YBImageBrowser MIT License Copyright (c) 2019 杨波 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## YYImage The MIT License (MIT) Copyright (c) 2015 ibireme Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## YYText The MIT License (MIT) Copyright (c) 2015 ibireme Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Generated by CocoaPods - https://cocoapods.org ================================================ FILE: JetChat/Pods/Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat-acknowledgements.plist ================================================ PreferenceSpecifiers FooterText This application makes use of the following third party libraries: Title Acknowledgements Type PSGroupSpecifier FooterText Copyright (c) 2014-2022 Alamofire Software Foundation (http://alamofire.org/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title Alamofire Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2015 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title FDFullscreenPopGesture Type PSGroupSpecifier FooterText Copyright 1999-2016 Alibaba Group Holding Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 1. reflection The MIT License (MIT) Copyright (c) 2016 Brad Hilton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 2. ObjectMapper The MIT License (MIT) Copyright (c) 2014 Hearst Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License Apache License 2.0 Title HandyJSON Type PSGroupSpecifier FooterText MIT License Copyright (c) Facebook, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title IGListDiffKit Type PSGroupSpecifier FooterText MIT License Copyright (c) Facebook, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title IGListKit Type PSGroupSpecifier FooterText MIT License Copyright (c) 2013-2017 Iftekhar Qurashi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title IQKeyboardManagerSwift Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2019 Wei Wang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title Kingfisher Type PSGroupSpecifier FooterText Copyright (c) 2015 Roy Marmelstein (http://roysapps.com/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title Localize-Swift Type PSGroupSpecifier FooterText GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: <program> Copyright (C) <year> <name of author> This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <https://www.gnu.org/licenses/why-not-lgpl.html>. License GPL-3.0 Title LookinServer Type PSGroupSpecifier FooterText Copyright © 2009-2020 Matej Bukovinski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title MBProgressHUD Type PSGroupSpecifier FooterText Copyright (c) 2013-2015 MJRefresh (https://github.com/CoderMJLee/MJRefresh) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title MJRefresh Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2014-present Artsy, Ash Furrow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title Moya Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2015 Ash Furrow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title NSObject+Rx Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2014-2020 Mathijs Kadijk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title R.swift Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2015 Mathijs Kadijk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title R.swift.Library Type PSGroupSpecifier FooterText Copyright (c) 2016 Ashley Mills Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title ReachabilitySwift Type PSGroupSpecifier FooterText **The MIT License** **Copyright © 2015 Krunoslav Zaher, Shai Mishali** **All rights reserved.** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title RxCocoa Type PSGroupSpecifier FooterText **The MIT License** **Copyright © 2015 Krunoslav Zaher, Shai Mishali** **All rights reserved.** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title RxRelay Type PSGroupSpecifier FooterText **The MIT License** **Copyright © 2015 Krunoslav Zaher, Shai Mishali** **All rights reserved.** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title RxSwift Type PSGroupSpecifier FooterText Copyright (c) 2018 RxSwiftCommunity Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title RxTheme Type PSGroupSpecifier FooterText Copyright (c) 2009-2020 Olivier Poitrey rs@dailymotion.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title SDWebImage Type PSGroupSpecifier FooterText Tencent is pleased to support the open source community by making WCDB available.  Copyright (C) 2017 THL A29 Limited, a Tencent company.  All rights reserved. If you have downloaded a copy of the WCDB binary from Tencent, please note that the WCDB binary is licensed under the BSD 3-Clause License. If you have downloaded a copy of the WCDB source code from Tencent, please note that WCDB source code is licensed under the BSD 3-Clause License, except for the third-party components listed below which are subject to different license terms.  Your integration of WCDB into your own projects may require compliance with the BSD 3-Clause License, as well as the other licenses applicable to the third-party components included within WCDB. A copy of the BSD 3-Clause License is included in this file. Other dependencies and licenses:   Open Source Software Licensed Under the Apache License, Version 2.0: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. Android Source Code  4.3 Copyright (C) 2006-2011 The Android Open Source Project     Terms of the Apache License, Version 2.0: --------------------------------------------------- Apache License Version 2.0, January 2004 http://www.apache.org/licenses/   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION   1. Definitions.   “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.   “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.   “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.   “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.   “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.   “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.   “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).   “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.   “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.”   “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.   2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.   3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.   4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:   a) You must give any other recipients of the Work or Derivative Works a copy of this License; and   b) You must cause any modified files to carry prominent notices stating that You changed the files; and   c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and   d) If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.   You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.   5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.   6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.   7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.   8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.   END OF TERMS AND CONDITIONS   APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.   Copyright [yyyy] [name of copyright owner]   Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.       Open Source Software Licensed Under Public Domain: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. SQLite  3.11.0       Open Source Software Licensed Under the OpenSSL License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. OpenSSL  1.0.2j Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved. Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved.       Terms of the OpenSSL License: --------------------------------------------------- LICENSE ISSUES: --------------------------------------------------------------------   The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit. See below for the actual license texts.   OpenSSL License: -------------------------------------------------------------------- Copyright (c) 1998-2016 The OpenSSL Project.  All rights reserved.   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:   1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.   3. All advertising materials mentioning features or use of this software must display the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)"   4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org.   5. Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project.   6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)"   THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================== * This product includes cryptographic software written by Eric Young (eay@cryptsoft.com).  This product includes software written by Tim Hudson (tjh@cryptsoft.com).     Original SSLeay License: -------------------------------------------------------------------- Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved.   This package is an SSL implementation written by Eric Young (eay@cryptsoft.com). The implementation was written so as to conform with Netscapes SSL.   This library is free for commercial and non-commercial use as long as the following conditions are aheared to. The following conditions apply to all code found in this distribution, be it the RC4, RSA, lhash, DES, etc., code; not just the SSL code. The SSL documentation included with this distribution is covered by the same copyright terms except that the holder is Tim Hudson (tjh@cryptsoft.com).   Copyright remains Eric Young's, and as such any Copyright notices in the code are not to be removed.  If this package is used in a product, Eric Young should be given attribution as the author of the parts of the library used. This can be in the form of a textual message at program startup or in documentation (online or textual) provided with the package.   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement:" This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)" The word 'cryptographic' can be left out if the rouines from the library being used are not cryptographic related :-). 4. If you include any Windows specific code (or a derivative thereof) from the apps directory (application code) you must include an acknowledgement: "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"   THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   The licence and distribution terms for any publically available version or derivative of this code cannot be changed.  i.e. this code cannot simply be copied and put under another distribution licence [including the GNU Public Licence.]       Open Source Software Licensed Under the ICU License: ---------------------------------------------------------------------------------------- 1. ICU4C  50.1 Copyright (c) 1995-2012 International Business Machines Corporation and others All rights reserved.     Terms of the ICU License: -------------------------------------------------------------------- ICU License - ICU 1.8.1 and later   COPYRIGHT AND PERMISSION NOTICE   Copyright (c) 1995-2012 International Business Machines Corporation and others   All rights reserved.   Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation.   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.   Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder.   All trademarks and registered trademarks mentioned herein are the property of their respective owners.     Open Source Software Licensed Under the BSD 3-Clause License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. SQLCipher  3.4.0 Copyright (c) 2008, ZETETIC LLC All rights reserved.       Terms of the BSD 3-Clause License: --------------------------------------------------------------------   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: l  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. l  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. l  Neither the name of [copyright holder] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. License BSD Title SQLiteRepairKit Type PSGroupSpecifier FooterText Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title SnapKit Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2018 Daniele Margutti Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title SwiftDate Type PSGroupSpecifier FooterText MIT License Copyright (c) 2015-2018 SwifterSwift (https://github.com/swifterswift) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title SwifterSwift Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2017 Ruoyu Fu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title SwiftyJSON Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2016 Zhen Tan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title TZImagePickerController Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2015 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title UITableView+FDTemplateLayoutCell Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2015 forkingdog Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title UIView+FDCollapsibleConstraints Type PSGroupSpecifier FooterText Tencent is pleased to support the open source community by making WCDB available.  Copyright (C) 2017 THL A29 Limited, a Tencent company.  All rights reserved. If you have downloaded a copy of the WCDB binary from Tencent, please note that the WCDB binary is licensed under the BSD 3-Clause License. If you have downloaded a copy of the WCDB source code from Tencent, please note that WCDB source code is licensed under the BSD 3-Clause License, except for the third-party components listed below which are subject to different license terms.  Your integration of WCDB into your own projects may require compliance with the BSD 3-Clause License, as well as the other licenses applicable to the third-party components included within WCDB. A copy of the BSD 3-Clause License is included in this file. Other dependencies and licenses:   Open Source Software Licensed Under the Apache License, Version 2.0: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. Android Source Code  4.3 Copyright (C) 2006-2011 The Android Open Source Project     Terms of the Apache License, Version 2.0: --------------------------------------------------- Apache License Version 2.0, January 2004 http://www.apache.org/licenses/   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION   1. Definitions.   “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.   “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.   “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.   “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.   “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.   “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.   “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).   “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.   “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.”   “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.   2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.   3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.   4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:   a) You must give any other recipients of the Work or Derivative Works a copy of this License; and   b) You must cause any modified files to carry prominent notices stating that You changed the files; and   c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and   d) If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.   You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.   5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.   6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.   7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.   8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.   END OF TERMS AND CONDITIONS   APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.   Copyright [yyyy] [name of copyright owner]   Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.       Open Source Software Licensed Under Public Domain: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. SQLite  3.11.0       Open Source Software Licensed Under the OpenSSL License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. OpenSSL  1.0.2j Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved. Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved.       Terms of the OpenSSL License: --------------------------------------------------- LICENSE ISSUES: --------------------------------------------------------------------   The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit. See below for the actual license texts.   OpenSSL License: -------------------------------------------------------------------- Copyright (c) 1998-2016 The OpenSSL Project.  All rights reserved.   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:   1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.   3. All advertising materials mentioning features or use of this software must display the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)"   4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org.   5. Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project.   6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)"   THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================== * This product includes cryptographic software written by Eric Young (eay@cryptsoft.com).  This product includes software written by Tim Hudson (tjh@cryptsoft.com).     Original SSLeay License: -------------------------------------------------------------------- Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved.   This package is an SSL implementation written by Eric Young (eay@cryptsoft.com). The implementation was written so as to conform with Netscapes SSL.   This library is free for commercial and non-commercial use as long as the following conditions are aheared to. The following conditions apply to all code found in this distribution, be it the RC4, RSA, lhash, DES, etc., code; not just the SSL code. The SSL documentation included with this distribution is covered by the same copyright terms except that the holder is Tim Hudson (tjh@cryptsoft.com).   Copyright remains Eric Young's, and as such any Copyright notices in the code are not to be removed.  If this package is used in a product, Eric Young should be given attribution as the author of the parts of the library used. This can be in the form of a textual message at program startup or in documentation (online or textual) provided with the package.   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement:" This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)" The word 'cryptographic' can be left out if the rouines from the library being used are not cryptographic related :-). 4. If you include any Windows specific code (or a derivative thereof) from the apps directory (application code) you must include an acknowledgement: "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"   THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   The licence and distribution terms for any publically available version or derivative of this code cannot be changed.  i.e. this code cannot simply be copied and put under another distribution licence [including the GNU Public Licence.]       Open Source Software Licensed Under the ICU License: ---------------------------------------------------------------------------------------- 1. ICU4C  50.1 Copyright (c) 1995-2012 International Business Machines Corporation and others All rights reserved.     Terms of the ICU License: -------------------------------------------------------------------- ICU License - ICU 1.8.1 and later   COPYRIGHT AND PERMISSION NOTICE   Copyright (c) 1995-2012 International Business Machines Corporation and others   All rights reserved.   Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation.   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.   Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder.   All trademarks and registered trademarks mentioned herein are the property of their respective owners.     Open Source Software Licensed Under the BSD 3-Clause License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. SQLCipher  3.4.0 Copyright (c) 2008, ZETETIC LLC All rights reserved.       Terms of the BSD 3-Clause License: --------------------------------------------------------------------   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: l  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. l  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. l  Neither the name of [copyright holder] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. License BSD Title WCDB.swift Type PSGroupSpecifier FooterText Copyright (c) 2008, ZETETIC LLC All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the ZETETIC LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. License BSD Title WCDBOptimizedSQLCipher Type PSGroupSpecifier FooterText MIT License Copyright (c) 2019 杨波 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title YBImageBrowser Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2015 ibireme <ibireme@gmail.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title YYImage Type PSGroupSpecifier FooterText The MIT License (MIT) Copyright (c) 2015 ibireme <ibireme@gmail.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License MIT Title YYText Type PSGroupSpecifier FooterText Generated by CocoaPods - https://cocoapods.org Title Type PSGroupSpecifier StringsTable Acknowledgements Title Acknowledgements ================================================ FILE: JetChat/Pods/Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat-dummy.m ================================================ #import @interface PodsDummy_Pods_FY_IMChat : NSObject @end @implementation PodsDummy_Pods_FY_IMChat @end ================================================ FILE: JetChat/Pods/Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat-frameworks.sh ================================================ #!/bin/sh set -e set -u set -o pipefail function on_error { echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" } trap 'on_error $LINENO' ERR if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy # frameworks to, so exit 0 (signalling the script phase was successful). exit 0 fi echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" SWIFT_STDLIB_PATH="${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" BCSYMBOLMAP_DIR="BCSymbolMaps" # This protects against multiple targets copying the same framework dependency at the same time. The solution # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") # Copies and strips a vendored framework install_framework() { if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then local source="${BUILT_PRODUCTS_DIR}/$1" elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" elif [ -r "$1" ]; then local source="$1" fi local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" if [ -L "${source}" ]; then echo "Symlinked..." source="$(readlink -f "${source}")" fi if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do echo "Installing $f" install_bcsymbolmap "$f" "$destination" rm "$f" done rmdir "${source}/${BCSYMBOLMAP_DIR}" fi # Use filter instead of exclude so missing patterns don't throw errors. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" local basename basename="$(basename -s .framework "$1")" binary="${destination}/${basename}.framework/${basename}" if ! [ -r "$binary" ]; then binary="${destination}/${basename}" elif [ -L "${binary}" ]; then echo "Destination binary is symlinked..." dirname="$(dirname "${binary}")" binary="${dirname}/$(readlink "${binary}")" fi # Strip invalid architectures so "fat" simulator / device frameworks work on device if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then strip_invalid_archs "$binary" fi # Resign the code if required by the build settings to avoid unstable apps code_sign_if_enabled "${destination}/$(basename "$1")" # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then local swift_runtime_libs swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) for lib in $swift_runtime_libs; do echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" code_sign_if_enabled "${destination}/${lib}" done fi } # Copies and strips a vendored dSYM install_dsym() { local source="$1" warn_missing_arch=${2:-true} if [ -r "$source" ]; then # Copy the dSYM into the targets temp dir. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" local basename basename="$(basename -s .dSYM "$source")" binary_name="$(ls "$source/Contents/Resources/DWARF")" binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" # Strip invalid architectures from the dSYM. if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then strip_invalid_archs "$binary" "$warn_missing_arch" fi if [[ $STRIP_BINARY_RETVAL == 0 ]]; then # Move the stripped file into its final destination. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" else # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. mkdir -p "${DWARF_DSYM_FOLDER_PATH}" touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" fi fi } # Used as a return value for each invocation of `strip_invalid_archs` function. STRIP_BINARY_RETVAL=0 # Strip invalid architectures strip_invalid_archs() { binary="$1" warn_missing_arch=${2:-true} # Get architectures for current target binary binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" # Intersect them with the architectures we are building for intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" # If there are no archs supported by this binary then warn the user if [[ -z "$intersected_archs" ]]; then if [[ "$warn_missing_arch" == "true" ]]; then echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." fi STRIP_BINARY_RETVAL=1 return fi stripped="" for arch in $binary_archs; do if ! [[ "${ARCHS}" == *"$arch"* ]]; then # Strip non-valid architectures in-place lipo -remove "$arch" -output "$binary" "$binary" stripped="$stripped $arch" fi done if [[ "$stripped" ]]; then echo "Stripped $binary of architectures:$stripped" fi STRIP_BINARY_RETVAL=0 } # Copies the bcsymbolmap files of a vendored framework install_bcsymbolmap() { local bcsymbolmap_path="$1" local destination="${BUILT_PRODUCTS_DIR}" echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" } # Signs a framework with the provided identity code_sign_if_enabled() { if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then # Use the current code_sign_identity echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then code_sign_cmd="$code_sign_cmd &" fi echo "$code_sign_cmd" eval "$code_sign_cmd" fi } if [[ "$CONFIGURATION" == "Debug" ]]; then install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" install_framework "${BUILT_PRODUCTS_DIR}/FDFullscreenPopGesture/FDFullscreenPopGesture.framework" install_framework "${BUILT_PRODUCTS_DIR}/HandyJSON/HandyJSON.framework" install_framework "${BUILT_PRODUCTS_DIR}/IGListDiffKit/IGListDiffKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/IGListKit/IGListKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework" install_framework "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework" install_framework "${BUILT_PRODUCTS_DIR}/Localize-Swift/Localize_Swift.framework" install_framework "${BUILT_PRODUCTS_DIR}/LookinServer/LookinServer.framework" install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework" install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework" install_framework "${BUILT_PRODUCTS_DIR}/Moya/Moya.framework" install_framework "${BUILT_PRODUCTS_DIR}/NSObject+Rx/NSObject_Rx.framework" install_framework "${BUILT_PRODUCTS_DIR}/R.swift.Library/Rswift.framework" install_framework "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework" install_framework "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework" install_framework "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework" install_framework "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework" install_framework "${BUILT_PRODUCTS_DIR}/RxTheme/RxTheme.framework" install_framework "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework" install_framework "${BUILT_PRODUCTS_DIR}/SQLiteRepairKit/sqliterk.framework" install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework" install_framework "${BUILT_PRODUCTS_DIR}/SwifterSwift/SwifterSwift.framework" install_framework "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework" install_framework "${BUILT_PRODUCTS_DIR}/TZImagePickerController/TZImagePickerController.framework" install_framework "${BUILT_PRODUCTS_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework" install_framework "${BUILT_PRODUCTS_DIR}/UIView+FDCollapsibleConstraints/UIView_FDCollapsibleConstraints.framework" install_framework "${BUILT_PRODUCTS_DIR}/WCDB.swift/WCDBSwift.framework" install_framework "${BUILT_PRODUCTS_DIR}/WCDBOptimizedSQLCipher/sqlcipher.framework" install_framework "${BUILT_PRODUCTS_DIR}/YBImageBrowser/YBImageBrowser.framework" install_framework "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework" install_framework "${BUILT_PRODUCTS_DIR}/YYText/YYText.framework" fi if [[ "$CONFIGURATION" == "Release" ]]; then install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" install_framework "${BUILT_PRODUCTS_DIR}/FDFullscreenPopGesture/FDFullscreenPopGesture.framework" install_framework "${BUILT_PRODUCTS_DIR}/HandyJSON/HandyJSON.framework" install_framework "${BUILT_PRODUCTS_DIR}/IGListDiffKit/IGListDiffKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/IGListKit/IGListKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework" install_framework "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework" install_framework "${BUILT_PRODUCTS_DIR}/Localize-Swift/Localize_Swift.framework" install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework" install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework" install_framework "${BUILT_PRODUCTS_DIR}/Moya/Moya.framework" install_framework "${BUILT_PRODUCTS_DIR}/NSObject+Rx/NSObject_Rx.framework" install_framework "${BUILT_PRODUCTS_DIR}/R.swift.Library/Rswift.framework" install_framework "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework" install_framework "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework" install_framework "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework" install_framework "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework" install_framework "${BUILT_PRODUCTS_DIR}/RxTheme/RxTheme.framework" install_framework "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework" install_framework "${BUILT_PRODUCTS_DIR}/SQLiteRepairKit/sqliterk.framework" install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework" install_framework "${BUILT_PRODUCTS_DIR}/SwifterSwift/SwifterSwift.framework" install_framework "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework" install_framework "${BUILT_PRODUCTS_DIR}/TZImagePickerController/TZImagePickerController.framework" install_framework "${BUILT_PRODUCTS_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework" install_framework "${BUILT_PRODUCTS_DIR}/UIView+FDCollapsibleConstraints/UIView_FDCollapsibleConstraints.framework" install_framework "${BUILT_PRODUCTS_DIR}/WCDB.swift/WCDBSwift.framework" install_framework "${BUILT_PRODUCTS_DIR}/WCDBOptimizedSQLCipher/sqlcipher.framework" install_framework "${BUILT_PRODUCTS_DIR}/YBImageBrowser/YBImageBrowser.framework" install_framework "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework" install_framework "${BUILT_PRODUCTS_DIR}/YYText/YYText.framework" fi if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then wait fi ================================================ FILE: JetChat/Pods/Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double Pods_FY_IMChatVersionNumber; FOUNDATION_EXPORT const unsigned char Pods_FY_IMChatVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat.debug.xcconfig ================================================ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/FDFullscreenPopGesture" "${PODS_CONFIGURATION_BUILD_DIR}/HandyJSON" "${PODS_CONFIGURATION_BUILD_DIR}/IGListDiffKit" "${PODS_CONFIGURATION_BUILD_DIR}/IGListKit" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift" "${PODS_CONFIGURATION_BUILD_DIR}/LookinServer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Moya" "${PODS_CONFIGURATION_BUILD_DIR}/NSObject+Rx" "${PODS_CONFIGURATION_BUILD_DIR}/R.swift.Library" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/RxCocoa" "${PODS_CONFIGURATION_BUILD_DIR}/RxRelay" "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" "${PODS_CONFIGURATION_BUILD_DIR}/RxTheme" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SQLiteRepairKit" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/SwifterSwift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON" "${PODS_CONFIGURATION_BUILD_DIR}/TZImagePickerController" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" "${PODS_CONFIGURATION_BUILD_DIR}/UIView+FDCollapsibleConstraints" "${PODS_CONFIGURATION_BUILD_DIR}/WCDB.swift" "${PODS_CONFIGURATION_BUILD_DIR}/WCDBOptimizedSQLCipher" "${PODS_CONFIGURATION_BUILD_DIR}/YBImageBrowser" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYText" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FDFullscreenPopGesture/FDFullscreenPopGesture.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/HandyJSON/HandyJSON.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IGListDiffKit/IGListDiffKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IGListKit/IGListKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift/Localize_Swift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/LookinServer/LookinServer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Moya/Moya.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/NSObject+Rx/NSObject_Rx.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/R.swift.Library/Rswift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RxCocoa/RxCocoa.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RxRelay/RxRelay.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift/RxSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RxTheme/RxTheme.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SQLiteRepairKit/sqliterk.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwifterSwift/SwifterSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON/SwiftyJSON.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/TZImagePickerController/TZImagePickerController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UIView+FDCollapsibleConstraints/UIView_FDCollapsibleConstraints.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/WCDB.swift/WCDBSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/WCDBOptimizedSQLCipher/sqlcipher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YBImageBrowser/YBImageBrowser.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYText/YYText.framework/Headers" LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -l"c++" -l"z" -framework "Accelerate" -framework "Alamofire" -framework "AssetsLibrary" -framework "CFNetwork" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreTelephony" -framework "CoreText" -framework "FDFullscreenPopGesture" -framework "Foundation" -framework "HandyJSON" -framework "IGListDiffKit" -framework "IGListKit" -framework "IQKeyboardManagerSwift" -framework "ImageIO" -framework "Kingfisher" -framework "Localize_Swift" -framework "LookinServer" -framework "MBProgressHUD" -framework "MJRefresh" -framework "MobileCoreServices" -framework "Moya" -framework "NSObject_Rx" -framework "Photos" -framework "PhotosUI" -framework "QuartzCore" -framework "Reachability" -framework "Rswift" -framework "RxCocoa" -framework "RxRelay" -framework "RxSwift" -framework "RxTheme" -framework "SDWebImage" -framework "Security" -framework "SnapKit" -framework "SwiftDate" -framework "SwifterSwift" -framework "SwiftyJSON" -framework "SystemConfiguration" -framework "TZImagePickerController" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -framework "UIView_FDCollapsibleConstraints" -framework "WCDBSwift" -framework "YBImageBrowser" -framework "YYImage" -framework "YYText" -framework "sqlcipher" -framework "sqliterk" -weak_framework "Combine" -weak_framework "SwiftUI" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_ROOT = ${SRCROOT}/Pods PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat.modulemap ================================================ framework module Pods_FY_IMChat { umbrella header "Pods-FY-IMChat-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/Pods-FY-IMChat/Pods-FY-IMChat.release.xcconfig ================================================ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/FDFullscreenPopGesture" "${PODS_CONFIGURATION_BUILD_DIR}/HandyJSON" "${PODS_CONFIGURATION_BUILD_DIR}/IGListDiffKit" "${PODS_CONFIGURATION_BUILD_DIR}/IGListKit" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Moya" "${PODS_CONFIGURATION_BUILD_DIR}/NSObject+Rx" "${PODS_CONFIGURATION_BUILD_DIR}/R.swift.Library" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/RxCocoa" "${PODS_CONFIGURATION_BUILD_DIR}/RxRelay" "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" "${PODS_CONFIGURATION_BUILD_DIR}/RxTheme" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SQLiteRepairKit" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/SwifterSwift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON" "${PODS_CONFIGURATION_BUILD_DIR}/TZImagePickerController" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" "${PODS_CONFIGURATION_BUILD_DIR}/UIView+FDCollapsibleConstraints" "${PODS_CONFIGURATION_BUILD_DIR}/WCDB.swift" "${PODS_CONFIGURATION_BUILD_DIR}/WCDBOptimizedSQLCipher" "${PODS_CONFIGURATION_BUILD_DIR}/YBImageBrowser" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYText" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FDFullscreenPopGesture/FDFullscreenPopGesture.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/HandyJSON/HandyJSON.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IGListDiffKit/IGListDiffKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IGListKit/IGListKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift/Localize_Swift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Moya/Moya.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/NSObject+Rx/NSObject_Rx.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/R.swift.Library/Rswift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RxCocoa/RxCocoa.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RxRelay/RxRelay.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift/RxSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RxTheme/RxTheme.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SQLiteRepairKit/sqliterk.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwifterSwift/SwifterSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON/SwiftyJSON.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/TZImagePickerController/TZImagePickerController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UIView+FDCollapsibleConstraints/UIView_FDCollapsibleConstraints.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/WCDB.swift/WCDBSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/WCDBOptimizedSQLCipher/sqlcipher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YBImageBrowser/YBImageBrowser.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYText/YYText.framework/Headers" LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -l"c++" -l"z" -framework "Accelerate" -framework "Alamofire" -framework "AssetsLibrary" -framework "CFNetwork" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreTelephony" -framework "CoreText" -framework "FDFullscreenPopGesture" -framework "Foundation" -framework "HandyJSON" -framework "IGListDiffKit" -framework "IGListKit" -framework "IQKeyboardManagerSwift" -framework "ImageIO" -framework "Kingfisher" -framework "Localize_Swift" -framework "MBProgressHUD" -framework "MJRefresh" -framework "MobileCoreServices" -framework "Moya" -framework "NSObject_Rx" -framework "Photos" -framework "PhotosUI" -framework "QuartzCore" -framework "Reachability" -framework "Rswift" -framework "RxCocoa" -framework "RxRelay" -framework "RxSwift" -framework "RxTheme" -framework "SDWebImage" -framework "Security" -framework "SnapKit" -framework "SwiftDate" -framework "SwifterSwift" -framework "SwiftyJSON" -framework "SystemConfiguration" -framework "TZImagePickerController" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -framework "UIView_FDCollapsibleConstraints" -framework "WCDBSwift" -framework "YBImageBrowser" -framework "YYImage" -framework "YYText" -framework "sqlcipher" -framework "sqliterk" -weak_framework "Combine" -weak_framework "SwiftUI" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_ROOT = ${SRCROOT}/Pods PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/R.swift/R.swift.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/R.swift FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/R.swift.Library" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/R.swift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/R.swift/R.swift.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/R.swift FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/R.swift.Library" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/R.swift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/R.swift.Library/R.swift.Library-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 5.3.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/R.swift.Library/R.swift.Library-dummy.m ================================================ #import @interface PodsDummy_R_swift_Library : NSObject @end @implementation PodsDummy_R_swift_Library @end ================================================ FILE: JetChat/Pods/Target Support Files/R.swift.Library/R.swift.Library-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/R.swift.Library/R.swift.Library-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double RswiftVersionNumber; FOUNDATION_EXPORT const unsigned char RswiftVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/R.swift.Library/R.swift.Library.debug.xcconfig ================================================ APPLICATION_EXTENSION_API_ONLY = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/R.swift.Library GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/R.swift.Library PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/R.swift.Library/R.swift.Library.modulemap ================================================ framework module Rswift { umbrella header "R.swift.Library-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/R.swift.Library/R.swift.Library.release.xcconfig ================================================ APPLICATION_EXTENSION_API_ONLY = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/R.swift.Library GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/R.swift.Library PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 5.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift-dummy.m ================================================ #import @interface PodsDummy_ReachabilitySwift : NSObject @end @implementation PodsDummy_ReachabilitySwift @end ================================================ FILE: JetChat/Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double ReachabilityVersionNumber; FOUNDATION_EXPORT const unsigned char ReachabilityVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "CoreTelephony" -framework "SystemConfiguration" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/ReachabilitySwift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift.modulemap ================================================ framework module Reachability { umbrella header "ReachabilitySwift-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "CoreTelephony" -framework "SystemConfiguration" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/ReachabilitySwift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/RxCocoa/RxCocoa-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 6.5.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/RxCocoa/RxCocoa-dummy.m ================================================ #import @interface PodsDummy_RxCocoa : NSObject @end @implementation PodsDummy_RxCocoa @end ================================================ FILE: JetChat/Pods/Target Support Files/RxCocoa/RxCocoa-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/RxCocoa/RxCocoa-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "RxCocoaRuntime.h" #import "_RX.h" #import "_RXDelegateProxy.h" #import "_RXKVOObserver.h" #import "_RXObjCRuntime.h" #import "RxCocoa.h" FOUNDATION_EXPORT double RxCocoaVersionNumber; FOUNDATION_EXPORT const unsigned char RxCocoaVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/RxCocoa/RxCocoa.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RxCocoa FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RxRelay" "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "RxRelay" -framework "RxSwift" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/RxCocoa PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/RxCocoa/RxCocoa.modulemap ================================================ framework module RxCocoa { umbrella header "RxCocoa-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/RxCocoa/RxCocoa.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RxCocoa FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RxRelay" "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "RxRelay" -framework "RxSwift" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/RxCocoa PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/RxRelay/RxRelay-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 6.5.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/RxRelay/RxRelay-dummy.m ================================================ #import @interface PodsDummy_RxRelay : NSObject @end @implementation PodsDummy_RxRelay @end ================================================ FILE: JetChat/Pods/Target Support Files/RxRelay/RxRelay-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/RxRelay/RxRelay-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double RxRelayVersionNumber; FOUNDATION_EXPORT const unsigned char RxRelayVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/RxRelay/RxRelay.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RxRelay FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "RxSwift" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/RxRelay PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/RxRelay/RxRelay.modulemap ================================================ framework module RxRelay { umbrella header "RxRelay-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/RxRelay/RxRelay.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RxRelay FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "RxSwift" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/RxRelay PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/RxSwift/RxSwift-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 6.5.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/RxSwift/RxSwift-dummy.m ================================================ #import @interface PodsDummy_RxSwift : NSObject @end @implementation PodsDummy_RxSwift @end ================================================ FILE: JetChat/Pods/Target Support Files/RxSwift/RxSwift-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/RxSwift/RxSwift-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double RxSwiftVersionNumber; FOUNDATION_EXPORT const unsigned char RxSwiftVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/RxSwift/RxSwift.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RxSwift GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/RxSwift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/RxSwift/RxSwift.modulemap ================================================ framework module RxSwift { umbrella header "RxSwift-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/RxSwift/RxSwift.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RxSwift GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/RxSwift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/RxTheme/RxTheme-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 6.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/RxTheme/RxTheme-dummy.m ================================================ #import @interface PodsDummy_RxTheme : NSObject @end @implementation PodsDummy_RxTheme @end ================================================ FILE: JetChat/Pods/Target Support Files/RxTheme/RxTheme-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/RxTheme/RxTheme-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double RxThemeVersionNumber; FOUNDATION_EXPORT const unsigned char RxThemeVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/RxTheme/RxTheme.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RxTheme FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RxCocoa" "${PODS_CONFIGURATION_BUILD_DIR}/RxRelay" "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "RxCocoa" -framework "RxSwift" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/RxTheme PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/RxTheme/RxTheme.modulemap ================================================ framework module RxTheme { umbrella header "RxTheme-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/RxTheme/RxTheme.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RxTheme FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/RxCocoa" "${PODS_CONFIGURATION_BUILD_DIR}/RxRelay" "${PODS_CONFIGURATION_BUILD_DIR}/RxSwift" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "RxCocoa" -framework "RxSwift" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/RxTheme PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SDWebImage/SDWebImage-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 5.12.5 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/SDWebImage/SDWebImage-dummy.m ================================================ #import @interface PodsDummy_SDWebImage : NSObject @end @implementation PodsDummy_SDWebImage @end ================================================ FILE: JetChat/Pods/Target Support Files/SDWebImage/SDWebImage-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/SDWebImage/SDWebImage-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "NSButton+WebCache.h" #import "NSData+ImageContentType.h" #import "NSImage+Compatibility.h" #import "SDAnimatedImage.h" #import "SDAnimatedImagePlayer.h" #import "SDAnimatedImageRep.h" #import "SDAnimatedImageView+WebCache.h" #import "SDAnimatedImageView.h" #import "SDDiskCache.h" #import "SDGraphicsImageRenderer.h" #import "SDImageAPNGCoder.h" #import "SDImageAWebPCoder.h" #import "SDImageCache.h" #import "SDImageCacheConfig.h" #import "SDImageCacheDefine.h" #import "SDImageCachesManager.h" #import "SDImageCoder.h" #import "SDImageCoderHelper.h" #import "SDImageCodersManager.h" #import "SDImageFrame.h" #import "SDImageGIFCoder.h" #import "SDImageGraphics.h" #import "SDImageHEICCoder.h" #import "SDImageIOAnimatedCoder.h" #import "SDImageIOCoder.h" #import "SDImageLoader.h" #import "SDImageLoadersManager.h" #import "SDImageTransformer.h" #import "SDMemoryCache.h" #import "SDWebImageCacheKeyFilter.h" #import "SDWebImageCacheSerializer.h" #import "SDWebImageCompat.h" #import "SDWebImageDefine.h" #import "SDWebImageDownloader.h" #import "SDWebImageDownloaderConfig.h" #import "SDWebImageDownloaderDecryptor.h" #import "SDWebImageDownloaderOperation.h" #import "SDWebImageDownloaderRequestModifier.h" #import "SDWebImageDownloaderResponseModifier.h" #import "SDWebImageError.h" #import "SDWebImageIndicator.h" #import "SDWebImageManager.h" #import "SDWebImageOperation.h" #import "SDWebImageOptionsProcessor.h" #import "SDWebImagePrefetcher.h" #import "SDWebImageTransition.h" #import "UIButton+WebCache.h" #import "UIImage+ExtendedCacheData.h" #import "UIImage+ForceDecode.h" #import "UIImage+GIF.h" #import "UIImage+MemoryCacheCost.h" #import "UIImage+Metadata.h" #import "UIImage+MultiFormat.h" #import "UIImage+Transform.h" #import "UIImageView+HighlightedWebCache.h" #import "UIImageView+WebCache.h" #import "UIView+WebCache.h" #import "UIView+WebCacheOperation.h" #import "SDWebImage.h" FOUNDATION_EXPORT double SDWebImageVersionNumber; FOUNDATION_EXPORT const unsigned char SDWebImageVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/SDWebImage/SDWebImage.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = NO GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "ImageIO" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SDWebImage PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES SUPPORTS_MACCATALYST = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SDWebImage/SDWebImage.modulemap ================================================ framework module SDWebImage { umbrella header "SDWebImage-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/SDWebImage/SDWebImage.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = NO GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "ImageIO" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SDWebImage PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES SUPPORTS_MACCATALYST = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SQLiteRepairKit/SQLiteRepairKit-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.2.2 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/SQLiteRepairKit/SQLiteRepairKit-dummy.m ================================================ #import @interface PodsDummy_SQLiteRepairKit : NSObject @end @implementation PodsDummy_SQLiteRepairKit @end ================================================ FILE: JetChat/Pods/Target Support Files/SQLiteRepairKit/SQLiteRepairKit-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/SQLiteRepairKit/SQLiteRepairKit-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "SQLiteRepairKit.h" FOUNDATION_EXPORT double sqliterkVersionNumber; FOUNDATION_EXPORT const unsigned char sqliterkVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/SQLiteRepairKit/SQLiteRepairKit.debug.xcconfig ================================================ APPLICATION_EXTENSION_API_ONLY = YES CLANG_CXX_LANGUAGE_STANDARD = gnu++0x CLANG_CXX_LIBRARY = libc++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SQLiteRepairKit FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/WCDBOptimizedSQLCipher" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 SQLITE_HAS_CODEC WCDB_BUILTIN_SQLCIPHER LIBRARY_SEARCH_PATHS[sdk=macosx*] = $(SDKROOT)/usr/lib/system OTHER_LDFLAGS = $(inherited) -l"c++" -l"z" -framework "Foundation" -framework "Security" -framework "sqlcipher" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SQLiteRepairKit PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SQLiteRepairKit/SQLiteRepairKit.modulemap ================================================ framework module sqliterk { umbrella header "SQLiteRepairKit-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/SQLiteRepairKit/SQLiteRepairKit.release.xcconfig ================================================ APPLICATION_EXTENSION_API_ONLY = YES CLANG_CXX_LANGUAGE_STANDARD = gnu++0x CLANG_CXX_LIBRARY = libc++ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SQLiteRepairKit FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/WCDBOptimizedSQLCipher" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 SQLITE_HAS_CODEC WCDB_BUILTIN_SQLCIPHER LIBRARY_SEARCH_PATHS[sdk=macosx*] = $(SDKROOT)/usr/lib/system OTHER_LDFLAGS = $(inherited) -l"c++" -l"z" -framework "Foundation" -framework "Security" -framework "sqlcipher" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SQLiteRepairKit PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SnapKit/SnapKit-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 5.6.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/SnapKit/SnapKit-dummy.m ================================================ #import @interface PodsDummy_SnapKit : NSObject @end @implementation PodsDummy_SnapKit @end ================================================ FILE: JetChat/Pods/Target Support Files/SnapKit/SnapKit-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/SnapKit/SnapKit-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double SnapKitVersionNumber; FOUNDATION_EXPORT const unsigned char SnapKitVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/SnapKit/SnapKit.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SnapKit GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SnapKit PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SnapKit/SnapKit.modulemap ================================================ framework module SnapKit { umbrella header "SnapKit-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/SnapKit/SnapKit.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SnapKit GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SnapKit PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SwiftDate/SwiftDate-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 6.3.1 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/SwiftDate/SwiftDate-dummy.m ================================================ #import @interface PodsDummy_SwiftDate : NSObject @end @implementation PodsDummy_SwiftDate @end ================================================ FILE: JetChat/Pods/Target Support Files/SwiftDate/SwiftDate-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/SwiftDate/SwiftDate-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double SwiftDateVersionNumber; FOUNDATION_EXPORT const unsigned char SwiftDateVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/SwiftDate/SwiftDate.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "Foundation" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftDate PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SwiftDate/SwiftDate.modulemap ================================================ framework module SwiftDate { umbrella header "SwiftDate-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/SwiftDate/SwiftDate.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_LDFLAGS = $(inherited) -framework "Foundation" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftDate PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SwifterSwift/SwifterSwift-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 5.2.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/SwifterSwift/SwifterSwift-dummy.m ================================================ #import @interface PodsDummy_SwifterSwift : NSObject @end @implementation PodsDummy_SwifterSwift @end ================================================ FILE: JetChat/Pods/Target Support Files/SwifterSwift/SwifterSwift-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/SwifterSwift/SwifterSwift-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double SwifterSwiftVersionNumber; FOUNDATION_EXPORT const unsigned char SwifterSwiftVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/SwifterSwift/SwifterSwift.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwifterSwift GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwifterSwift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SwifterSwift/SwifterSwift.modulemap ================================================ framework module SwifterSwift { umbrella header "SwifterSwift-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/SwifterSwift/SwifterSwift.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwifterSwift GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwifterSwift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 5.0.1 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-dummy.m ================================================ #import @interface PodsDummy_SwiftyJSON : NSObject @end @implementation PodsDummy_SwiftyJSON @end ================================================ FILE: JetChat/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/SwiftyJSON/SwiftyJSON-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double SwiftyJSONVersionNumber; FOUNDATION_EXPORT const unsigned char SwiftyJSONVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftyJSON PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.modulemap ================================================ framework module SwiftyJSON { umbrella header "SwiftyJSON-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/SwiftyJSON/SwiftyJSON.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftyJSON GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftyJSON PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/TZImagePickerController/TZImagePickerController-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 3.8.1 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/TZImagePickerController/TZImagePickerController-dummy.m ================================================ #import @interface PodsDummy_TZImagePickerController : NSObject @end @implementation PodsDummy_TZImagePickerController @end ================================================ FILE: JetChat/Pods/Target Support Files/TZImagePickerController/TZImagePickerController-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/TZImagePickerController/TZImagePickerController-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "NSBundle+TZImagePicker.h" #import "TZAssetCell.h" #import "TZAssetModel.h" #import "TZAuthLimitedFooterTipView.h" #import "TZGifPhotoPreviewController.h" #import "TZImageCropManager.h" #import "TZImageManager.h" #import "TZImagePickerController.h" #import "TZImageRequestOperation.h" #import "TZLocationManager.h" #import "TZPhotoPickerController.h" #import "TZPhotoPreviewCell.h" #import "TZPhotoPreviewController.h" #import "TZProgressView.h" #import "TZVideoCropController.h" #import "TZVideoEditedPreviewController.h" #import "TZVideoPlayerController.h" #import "UIView+TZLayout.h" FOUNDATION_EXPORT double TZImagePickerControllerVersionNumber; FOUNDATION_EXPORT const unsigned char TZImagePickerControllerVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/TZImagePickerController/TZImagePickerController.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/TZImagePickerController GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "Photos" -framework "PhotosUI" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/TZImagePickerController PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/TZImagePickerController/TZImagePickerController.modulemap ================================================ framework module TZImagePickerController { umbrella header "TZImagePickerController-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/TZImagePickerController/TZImagePickerController.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/TZImagePickerController GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "Photos" -framework "PhotosUI" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/TZImagePickerController PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.6.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell-dummy.m ================================================ #import @interface PodsDummy_UITableView_FDTemplateLayoutCell : NSObject @end @implementation PodsDummy_UITableView_FDTemplateLayoutCell @end ================================================ FILE: JetChat/Pods/Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "UITableView+FDIndexPathHeightCache.h" #import "UITableView+FDKeyedHeightCache.h" #import "UITableView+FDTemplateLayoutCell.h" #import "UITableView+FDTemplateLayoutCellDebug.h" FOUNDATION_EXPORT double UITableView_FDTemplateLayoutCellVersionNumber; FOUNDATION_EXPORT const unsigned char UITableView_FDTemplateLayoutCellVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/UITableView+FDTemplateLayoutCell PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell.modulemap ================================================ framework module UITableView_FDTemplateLayoutCell { umbrella header "UITableView+FDTemplateLayoutCell-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/UITableView+FDTemplateLayoutCell PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.1.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints-dummy.m ================================================ #import @interface PodsDummy_UIView_FDCollapsibleConstraints : NSObject @end @implementation PodsDummy_UIView_FDCollapsibleConstraints @end ================================================ FILE: JetChat/Pods/Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "UIView+FDCollapsibleConstraints.h" FOUNDATION_EXPORT double UIView_FDCollapsibleConstraintsVersionNumber; FOUNDATION_EXPORT const unsigned char UIView_FDCollapsibleConstraintsVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/UIView+FDCollapsibleConstraints GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/UIView+FDCollapsibleConstraints PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints.modulemap ================================================ framework module UIView_FDCollapsibleConstraints { umbrella header "UIView+FDCollapsibleConstraints-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/UIView+FDCollapsibleConstraints GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/UIView+FDCollapsibleConstraints PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/WCDB.swift/WCDB.swift-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.8 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/WCDB.swift/WCDB.swift-dummy.m ================================================ #import @interface PodsDummy_WCDB_swift : NSObject @end @implementation PodsDummy_WCDB_swift @end ================================================ FILE: JetChat/Pods/Target Support Files/WCDB.swift/WCDB.swift-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/WCDB.swift/WCDB.swift-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "SQLite-Bridging.h" #import "WCDB-Bridging.h" FOUNDATION_EXPORT double WCDBSwiftVersionNumber; FOUNDATION_EXPORT const unsigned char WCDBSwiftVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/WCDB.swift/WCDB.swift.debug.xcconfig ================================================ APPLICATION_EXTENSION_API_ONLY = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/WCDB.swift FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SQLiteRepairKit" "${PODS_CONFIGURATION_BUILD_DIR}/WCDBOptimizedSQLCipher" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 SQLITE_HAS_CODEC WCDB_BUILTIN_SQLCIPHER HEADER_SEARCH_PATHS = $(inherited) ${PODS_ROOT}/WCDBSwift LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift LIBRARY_SEARCH_PATHS[sdk=macosx*] = $(SDKROOT)/usr/lib/system OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "Security" -framework "sqlcipher" -framework "sqliterk" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS OTHER_SWIFT_FLAGS[config=Debug] = -D DEBUG OTHER_SWIFT_FLAGS[config=Debug][sdk=iphoneos*] = -D WCDB_IOS -D DEBUG OTHER_SWIFT_FLAGS[config=Debug][sdk=iphonesimulator*] = -D WCDB_IOS -D DEBUG OTHER_SWIFT_FLAGS[config=Release][sdk=iphoneos*] = -D WCDB_IOS OTHER_SWIFT_FLAGS[config=Release][sdk=iphonesimulator*] = -D WCDB_IOS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/WCDB.swift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES SWIFT_WHOLE_MODULE_OPTIMIZATION = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/WCDB.swift/WCDB.swift.modulemap ================================================ framework module WCDBSwift { umbrella header "WCDB.swift-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/WCDB.swift/WCDB.swift.release.xcconfig ================================================ APPLICATION_EXTENSION_API_ONLY = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/WCDB.swift FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SQLiteRepairKit" "${PODS_CONFIGURATION_BUILD_DIR}/WCDBOptimizedSQLCipher" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 SQLITE_HAS_CODEC WCDB_BUILTIN_SQLCIPHER HEADER_SEARCH_PATHS = $(inherited) ${PODS_ROOT}/WCDBSwift LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift LIBRARY_SEARCH_PATHS[sdk=macosx*] = $(SDKROOT)/usr/lib/system OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "Security" -framework "sqlcipher" -framework "sqliterk" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS OTHER_SWIFT_FLAGS[config=Debug] = -D DEBUG OTHER_SWIFT_FLAGS[config=Debug][sdk=iphoneos*] = -D WCDB_IOS -D DEBUG OTHER_SWIFT_FLAGS[config=Debug][sdk=iphonesimulator*] = -D WCDB_IOS -D DEBUG OTHER_SWIFT_FLAGS[config=Release][sdk=iphoneos*] = -D WCDB_IOS OTHER_SWIFT_FLAGS[config=Release][sdk=iphonesimulator*] = -D WCDB_IOS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/WCDB.swift PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES SWIFT_WHOLE_MODULE_OPTIMIZATION = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/WCDBOptimizedSQLCipher/WCDBOptimizedSQLCipher-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.2.1 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/WCDBOptimizedSQLCipher/WCDBOptimizedSQLCipher-dummy.m ================================================ #import @interface PodsDummy_WCDBOptimizedSQLCipher : NSObject @end @implementation PodsDummy_WCDBOptimizedSQLCipher @end ================================================ FILE: JetChat/Pods/Target Support Files/WCDBOptimizedSQLCipher/WCDBOptimizedSQLCipher-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/WCDBOptimizedSQLCipher/WCDBOptimizedSQLCipher-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "sqlite3.h" #import "fts3_tokenizer.h" FOUNDATION_EXPORT double sqlcipherVersionNumber; FOUNDATION_EXPORT const unsigned char sqlcipherVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/WCDBOptimizedSQLCipher/WCDBOptimizedSQLCipher.debug.xcconfig ================================================ APPLICATION_EXTENSION_API_ONLY = YES CLANG_WARN_COMMA = NO CLANG_WARN_CONSTANT_CONVERSION = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CLANG_WARN_STRICT_PROTOTYPES = NO CLANG_WARN_UNREACHABLE_CODE = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/WCDBOptimizedSQLCipher GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 SQLITE_ENABLE_FTS3 SQLITE_ENABLE_FTS3_PARENTHESIS SQLITE_ENABLE_API_ARMOR SQLITE_OMIT_BUILTIN_TEST SQLITE_OMIT_AUTORESET SQLITE_ENABLE_UPDATE_DELETE_LIMIT SQLITE_ENABLE_RTREE SQLITE_ENABLE_LOCKING_STYLE=1 SQLITE_SYSTEM_MALLOC SQLITE_OMIT_LOAD_EXTENSION SQLITE_CORE SQLITE_THREADSAFE=2 SQLITE_DEFAULT_CACHE_SIZE=250 SQLITE_DEFAULT_CKPTFULLFSYNC=1 SQLITE_DEFAULT_PAGE_SIZE=4096 SQLITE_OMIT_SHARED_CACHE SQLITE_HAS_CODEC SQLCIPHER_CRYPTO_CC USE_PREAD=1 SQLITE_TEMP_STORE=2 SQLCIPHER_PREPROCESSED HAVE_USLEEP SQLITE_MALLOC_SOFT_LIMIT=0 SQLITE_WCDB_SIGNAL_RETRY=1 SQLITE_DEFAULT_MEMSTATUS=0 SQLITE_ENABLE_COLUMN_METADATA SQLITE_DEFAULT_WAL_SYNCHRONOUS=1 SQLITE_LIKE_DOESNT_MATCH_BLOBS SQLITE_MAX_EXPR_DEPTH=0 SQLITE_OMIT_DEPRECATED SQLITE_OMIT_PROGRESS_CALLBACK SQLITE_OMIT_SHARED_CACHE OMIT_CONSTTIME_MEM OMIT_MEMLOCK SQLITE_ENABLE_FTS3_TOKENIZER SQLITE_WCDB_CHECKPOINT_HANDLER SQLITE_MMAP_READWRITE SQLITE_ENABLE_DBSTAT_VTAB SQLITE_ENABLE_FTS5 GCC_WARN_64_TO_32_BIT_CONVERSION = NO GCC_WARN_UNUSED_FUNCTION = NO GCC_WARN_UNUSED_VARIABLE = NO OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "Security" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/WCDBOptimizedSQLCipher PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/WCDBOptimizedSQLCipher/WCDBOptimizedSQLCipher.modulemap ================================================ framework module sqlcipher { umbrella header "WCDBOptimizedSQLCipher-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/WCDBOptimizedSQLCipher/WCDBOptimizedSQLCipher.release.xcconfig ================================================ APPLICATION_EXTENSION_API_ONLY = YES CLANG_WARN_COMMA = NO CLANG_WARN_CONSTANT_CONVERSION = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CLANG_WARN_STRICT_PROTOTYPES = NO CLANG_WARN_UNREACHABLE_CODE = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/WCDBOptimizedSQLCipher GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 SQLITE_ENABLE_FTS3 SQLITE_ENABLE_FTS3_PARENTHESIS SQLITE_ENABLE_API_ARMOR SQLITE_OMIT_BUILTIN_TEST SQLITE_OMIT_AUTORESET SQLITE_ENABLE_UPDATE_DELETE_LIMIT SQLITE_ENABLE_RTREE SQLITE_ENABLE_LOCKING_STYLE=1 SQLITE_SYSTEM_MALLOC SQLITE_OMIT_LOAD_EXTENSION SQLITE_CORE SQLITE_THREADSAFE=2 SQLITE_DEFAULT_CACHE_SIZE=250 SQLITE_DEFAULT_CKPTFULLFSYNC=1 SQLITE_DEFAULT_PAGE_SIZE=4096 SQLITE_OMIT_SHARED_CACHE SQLITE_HAS_CODEC SQLCIPHER_CRYPTO_CC USE_PREAD=1 SQLITE_TEMP_STORE=2 SQLCIPHER_PREPROCESSED HAVE_USLEEP SQLITE_MALLOC_SOFT_LIMIT=0 SQLITE_WCDB_SIGNAL_RETRY=1 SQLITE_DEFAULT_MEMSTATUS=0 SQLITE_ENABLE_COLUMN_METADATA SQLITE_DEFAULT_WAL_SYNCHRONOUS=1 SQLITE_LIKE_DOESNT_MATCH_BLOBS SQLITE_MAX_EXPR_DEPTH=0 SQLITE_OMIT_DEPRECATED SQLITE_OMIT_PROGRESS_CALLBACK SQLITE_OMIT_SHARED_CACHE OMIT_CONSTTIME_MEM OMIT_MEMLOCK SQLITE_ENABLE_FTS3_TOKENIZER SQLITE_WCDB_CHECKPOINT_HANDLER SQLITE_MMAP_READWRITE SQLITE_ENABLE_DBSTAT_VTAB SQLITE_ENABLE_FTS5 GCC_WARN_64_TO_32_BIT_CONVERSION = NO GCC_WARN_UNUSED_FUNCTION = NO GCC_WARN_UNUSED_VARIABLE = NO OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "Security" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/WCDBOptimizedSQLCipher PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/YBImageBrowser/YBImageBrowser-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 3.0.9 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/YBImageBrowser/YBImageBrowser-dummy.m ================================================ #import @interface PodsDummy_YBImageBrowser : NSObject @end @implementation PodsDummy_YBImageBrowser @end ================================================ FILE: JetChat/Pods/Target Support Files/YBImageBrowser/YBImageBrowser-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/YBImageBrowser/YBImageBrowser-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "YBIBAuxiliaryViewHandler.h" #import "YBIBLoadingView.h" #import "YBIBToastView.h" #import "NSObject+YBImageBrowser.h" #import "YBIBAnimatedTransition.h" #import "YBIBCollectionView.h" #import "YBIBCollectionViewLayout.h" #import "YBIBContainerView.h" #import "YBIBDataMediator.h" #import "YBIBScreenRotationHandler.h" #import "YBImageBrowser+Internal.h" #import "YBIBCopywriter.h" #import "YBIBIconManager.h" #import "YBIBPhotoAlbumManager.h" #import "YBIBSentinel.h" #import "YBIBUtilities.h" #import "YBIBImageCache+Internal.h" #import "YBIBImageCache.h" #import "YBIBImageCell+Internal.h" #import "YBIBImageCell.h" #import "YBIBImageData+Internal.h" #import "YBIBImageData.h" #import "YBIBImageLayout.h" #import "YBIBImageScrollView.h" #import "YBIBInteractionProfile.h" #import "YBImage.h" #import "YBIBCellProtocol.h" #import "YBIBDataProtocol.h" #import "YBIBGetBaseInfoProtocol.h" #import "YBIBOperateBrowserProtocol.h" #import "YBIBOrientationReceiveProtocol.h" #import "YBImageBrowserDataSource.h" #import "YBImageBrowserDelegate.h" #import "YBIBSheetView.h" #import "YBIBToolViewHandler.h" #import "YBIBTopView.h" #import "YBIBDefaultWebImageMediator.h" #import "YBIBWebImageMediator.h" #import "YBImageBrowser.h" #import "YBIBVideoActionBar.h" #import "YBIBVideoCell+Internal.h" #import "YBIBVideoCell.h" #import "YBIBVideoData+Internal.h" #import "YBIBVideoData.h" #import "YBIBVideoTopBar.h" #import "YBIBVideoView.h" FOUNDATION_EXPORT double YBImageBrowserVersionNumber; FOUNDATION_EXPORT const unsigned char YBImageBrowserVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/YBImageBrowser/YBImageBrowser.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YBImageBrowser FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "AssetsLibrary" -framework "CoreFoundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "QuartzCore" -framework "SDWebImage" -framework "UIKit" -framework "YYImage" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/YBImageBrowser PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/YBImageBrowser/YBImageBrowser.modulemap ================================================ framework module YBImageBrowser { umbrella header "YBImageBrowser-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/YBImageBrowser/YBImageBrowser.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YBImageBrowser FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "AssetsLibrary" -framework "CoreFoundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "QuartzCore" -framework "SDWebImage" -framework "UIKit" -framework "YYImage" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/YBImageBrowser PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/YYImage/YYImage-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.4 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/YYImage/YYImage-dummy.m ================================================ #import @interface PodsDummy_YYImage : NSObject @end @implementation PodsDummy_YYImage @end ================================================ FILE: JetChat/Pods/Target Support Files/YYImage/YYImage-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/YYImage/YYImage-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "YYAnimatedImageView.h" #import "YYFrameImage.h" #import "YYImage.h" #import "YYImageCoder.h" #import "YYSpriteSheetImage.h" FOUNDATION_EXPORT double YYImageVersionNumber; FOUNDATION_EXPORT const unsigned char YYImageVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/YYImage/YYImage.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YYImage GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -l"z" -framework "Accelerate" -framework "AssetsLibrary" -framework "CoreFoundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "QuartzCore" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/YYImage PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/YYImage/YYImage.modulemap ================================================ framework module YYImage { umbrella header "YYImage-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/YYImage/YYImage.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YYImage GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -l"z" -framework "Accelerate" -framework "AssetsLibrary" -framework "CoreFoundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "QuartzCore" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/YYImage PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/YYText/YYText-Info.plist ================================================ CFBundleDevelopmentRegion ${PODS_DEVELOPMENT_LANGUAGE} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.7 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: JetChat/Pods/Target Support Files/YYText/YYText-dummy.m ================================================ #import @interface PodsDummy_YYText : NSObject @end @implementation PodsDummy_YYText @end ================================================ FILE: JetChat/Pods/Target Support Files/YYText/YYText-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: JetChat/Pods/Target Support Files/YYText/YYText-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "YYTextContainerView.h" #import "YYTextDebugOption.h" #import "YYTextEffectWindow.h" #import "YYTextInput.h" #import "YYTextKeyboardManager.h" #import "YYTextLayout.h" #import "YYTextLine.h" #import "YYTextMagnifier.h" #import "YYTextSelectionView.h" #import "YYTextArchiver.h" #import "YYTextAttribute.h" #import "YYTextParser.h" #import "YYTextRubyAnnotation.h" #import "YYTextRunDelegate.h" #import "NSAttributedString+YYText.h" #import "NSParagraphStyle+YYText.h" #import "UIPasteboard+YYText.h" #import "UIView+YYText.h" #import "YYTextAsyncLayer.h" #import "YYTextTransaction.h" #import "YYTextUtilities.h" #import "YYTextWeakProxy.h" #import "YYLabel.h" #import "YYText.h" #import "YYTextView.h" FOUNDATION_EXPORT double YYTextVersionNumber; FOUNDATION_EXPORT const unsigned char YYTextVersionString[]; ================================================ FILE: JetChat/Pods/Target Support Files/YYText/YYText.debug.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YYText GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "CoreFoundation" -framework "CoreText" -framework "MobileCoreServices" -framework "QuartzCore" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/YYText PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/Target Support Files/YYText/YYText.modulemap ================================================ framework module YYText { umbrella header "YYText-umbrella.h" export * module * { export * } } ================================================ FILE: JetChat/Pods/Target Support Files/YYText/YYText.release.xcconfig ================================================ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YYText GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "CoreFoundation" -framework "CoreText" -framework "MobileCoreServices" -framework "QuartzCore" -framework "UIKit" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/YYText PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell/Classes/UITableView+FDIndexPathHeightCache.h ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import @interface FDIndexPathHeightCache : NSObject // Enable automatically if you're using index path driven height cache @property (nonatomic, assign) BOOL automaticallyInvalidateEnabled; // Height cache - (BOOL)existsHeightAtIndexPath:(NSIndexPath *)indexPath; - (void)cacheHeight:(CGFloat)height byIndexPath:(NSIndexPath *)indexPath; - (CGFloat)heightForIndexPath:(NSIndexPath *)indexPath; - (void)invalidateHeightAtIndexPath:(NSIndexPath *)indexPath; - (void)invalidateAllHeightCache; @end @interface UITableView (FDIndexPathHeightCache) /// Height cache by index path. Generally, you don't need to use it directly. @property (nonatomic, strong, readonly) FDIndexPathHeightCache *fd_indexPathHeightCache; @end @interface UITableView (FDIndexPathHeightCacheInvalidation) /// Call this method when you want to reload data but don't want to invalidate /// all height cache by index path, for example, load more data at the bottom of /// table view. - (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache; @end ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell/Classes/UITableView+FDIndexPathHeightCache.m ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import "UITableView+FDIndexPathHeightCache.h" #import typedef NSMutableArray *> FDIndexPathHeightsBySection; @interface FDIndexPathHeightCache () @property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForPortrait; @property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForLandscape; @end @implementation FDIndexPathHeightCache - (instancetype)init { self = [super init]; if (self) { _heightsBySectionForPortrait = [NSMutableArray array]; _heightsBySectionForLandscape = [NSMutableArray array]; } return self; } - (FDIndexPathHeightsBySection *)heightsBySectionForCurrentOrientation { return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation) ? self.heightsBySectionForPortrait: self.heightsBySectionForLandscape; } - (void)enumerateAllOrientationsUsingBlock:(void (^)(FDIndexPathHeightsBySection *heightsBySection))block { block(self.heightsBySectionForPortrait); block(self.heightsBySectionForLandscape); } - (BOOL)existsHeightAtIndexPath:(NSIndexPath *)indexPath { [self buildCachesAtIndexPathsIfNeeded:@[indexPath]]; NSNumber *number = self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row]; return ![number isEqualToNumber:@-1]; } - (void)cacheHeight:(CGFloat)height byIndexPath:(NSIndexPath *)indexPath { self.automaticallyInvalidateEnabled = YES; [self buildCachesAtIndexPathsIfNeeded:@[indexPath]]; self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row] = @(height); } - (CGFloat)heightForIndexPath:(NSIndexPath *)indexPath { [self buildCachesAtIndexPathsIfNeeded:@[indexPath]]; NSNumber *number = self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row]; #if CGFLOAT_IS_DOUBLE return number.doubleValue; #else return number.floatValue; #endif } - (void)invalidateHeightAtIndexPath:(NSIndexPath *)indexPath { [self buildCachesAtIndexPathsIfNeeded:@[indexPath]]; [self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { heightsBySection[indexPath.section][indexPath.row] = @-1; }]; } - (void)invalidateAllHeightCache { [self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { [heightsBySection removeAllObjects]; }]; } - (void)buildCachesAtIndexPathsIfNeeded:(NSArray *)indexPaths { // Build every section array or row array which is smaller than given index path. [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { [self buildSectionsIfNeeded:indexPath.section]; [self buildRowsIfNeeded:indexPath.row inExistSection:indexPath.section]; }]; } - (void)buildSectionsIfNeeded:(NSInteger)targetSection { [self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { for (NSInteger section = 0; section <= targetSection; ++section) { if (section >= heightsBySection.count) { heightsBySection[section] = [NSMutableArray array]; } } }]; } - (void)buildRowsIfNeeded:(NSInteger)targetRow inExistSection:(NSInteger)section { [self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { NSMutableArray *heightsByRow = heightsBySection[section]; for (NSInteger row = 0; row <= targetRow; ++row) { if (row >= heightsByRow.count) { heightsByRow[row] = @-1; } } }]; } @end @implementation UITableView (FDIndexPathHeightCache) - (FDIndexPathHeightCache *)fd_indexPathHeightCache { FDIndexPathHeightCache *cache = objc_getAssociatedObject(self, _cmd); if (!cache) { cache = [FDIndexPathHeightCache new]; objc_setAssociatedObject(self, _cmd, cache, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return cache; } @end // We just forward primary call, in crash report, top most method in stack maybe FD's, // but it's really not our bug, you should check whether your table view's data source and // displaying cells are not matched when reloading. static void __FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(void (^callout)(void)) { callout(); } #define FDPrimaryCall(...) do {__FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(^{__VA_ARGS__});} while(0) @implementation UITableView (FDIndexPathHeightCacheInvalidation) - (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache { FDPrimaryCall([self fd_reloadData];); } + (void)load { // All methods that trigger height cache's invalidation SEL selectors[] = { @selector(reloadData), @selector(insertSections:withRowAnimation:), @selector(deleteSections:withRowAnimation:), @selector(reloadSections:withRowAnimation:), @selector(moveSection:toSection:), @selector(insertRowsAtIndexPaths:withRowAnimation:), @selector(deleteRowsAtIndexPaths:withRowAnimation:), @selector(reloadRowsAtIndexPaths:withRowAnimation:), @selector(moveRowAtIndexPath:toIndexPath:) }; for (NSUInteger index = 0; index < sizeof(selectors) / sizeof(SEL); ++index) { SEL originalSelector = selectors[index]; SEL swizzledSelector = NSSelectorFromString([@"fd_" stringByAppendingString:NSStringFromSelector(originalSelector)]); Method originalMethod = class_getInstanceMethod(self, originalSelector); Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod); } } - (void)fd_reloadData { if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) { [self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { [heightsBySection removeAllObjects]; }]; } FDPrimaryCall([self fd_reloadData];); } - (void)fd_insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) { [sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL *stop) { [self.fd_indexPathHeightCache buildSectionsIfNeeded:section]; [self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { [heightsBySection insertObject:[NSMutableArray array] atIndex:section]; }]; }]; } FDPrimaryCall([self fd_insertSections:sections withRowAnimation:animation];); } - (void)fd_deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) { [sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL *stop) { [self.fd_indexPathHeightCache buildSectionsIfNeeded:section]; [self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { [heightsBySection removeObjectAtIndex:section]; }]; }]; } FDPrimaryCall([self fd_deleteSections:sections withRowAnimation:animation];); } - (void)fd_reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) { [sections enumerateIndexesUsingBlock: ^(NSUInteger section, BOOL *stop) { [self.fd_indexPathHeightCache buildSectionsIfNeeded:section]; [self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { [heightsBySection[section] removeAllObjects]; }]; }]; } FDPrimaryCall([self fd_reloadSections:sections withRowAnimation:animation];); } - (void)fd_moveSection:(NSInteger)section toSection:(NSInteger)newSection { if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) { [self.fd_indexPathHeightCache buildSectionsIfNeeded:section]; [self.fd_indexPathHeightCache buildSectionsIfNeeded:newSection]; [self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { [heightsBySection exchangeObjectAtIndex:section withObjectAtIndex:newSection]; }]; } FDPrimaryCall([self fd_moveSection:section toSection:newSection];); } - (void)fd_insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) { [self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:indexPaths]; [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { [self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { [heightsBySection[indexPath.section] insertObject:@-1 atIndex:indexPath.row]; }]; }]; } FDPrimaryCall([self fd_insertRowsAtIndexPaths:indexPaths withRowAnimation:animation];); } - (void)fd_deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) { [self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:indexPaths]; NSMutableDictionary *mutableIndexSetsToRemove = [NSMutableDictionary dictionary]; [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { NSMutableIndexSet *mutableIndexSet = mutableIndexSetsToRemove[@(indexPath.section)]; if (!mutableIndexSet) { mutableIndexSet = [NSMutableIndexSet indexSet]; mutableIndexSetsToRemove[@(indexPath.section)] = mutableIndexSet; } [mutableIndexSet addIndex:indexPath.row]; }]; [mutableIndexSetsToRemove enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSIndexSet *indexSet, BOOL *stop) { [self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { [heightsBySection[key.integerValue] removeObjectsAtIndexes:indexSet]; }]; }]; } FDPrimaryCall([self fd_deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation];); } - (void)fd_reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) { [self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:indexPaths]; [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { [self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { heightsBySection[indexPath.section][indexPath.row] = @-1; }]; }]; } FDPrimaryCall([self fd_reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation];); } - (void)fd_moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath { if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) { [self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:@[sourceIndexPath, destinationIndexPath]]; [self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) { NSMutableArray *sourceRows = heightsBySection[sourceIndexPath.section]; NSMutableArray *destinationRows = heightsBySection[destinationIndexPath.section]; NSNumber *sourceValue = sourceRows[sourceIndexPath.row]; NSNumber *destinationValue = destinationRows[destinationIndexPath.row]; sourceRows[sourceIndexPath.row] = destinationValue; destinationRows[destinationIndexPath.row] = sourceValue; }]; } FDPrimaryCall([self fd_moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];); } @end ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell/Classes/UITableView+FDKeyedHeightCache.h ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import @interface FDKeyedHeightCache : NSObject - (BOOL)existsHeightForKey:(id)key; - (void)cacheHeight:(CGFloat)height byKey:(id)key; - (CGFloat)heightForKey:(id)key; // Invalidation - (void)invalidateHeightForKey:(id)key; - (void)invalidateAllHeightCache; @end @interface UITableView (FDKeyedHeightCache) /// Height cache by key. Generally, you don't need to use it directly. @property (nonatomic, strong, readonly) FDKeyedHeightCache *fd_keyedHeightCache; @end ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell/Classes/UITableView+FDKeyedHeightCache.m ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import "UITableView+FDKeyedHeightCache.h" #import @interface FDKeyedHeightCache () @property (nonatomic, strong) NSMutableDictionary, NSNumber *> *mutableHeightsByKeyForPortrait; @property (nonatomic, strong) NSMutableDictionary, NSNumber *> *mutableHeightsByKeyForLandscape; @end @implementation FDKeyedHeightCache - (instancetype)init { self = [super init]; if (self) { _mutableHeightsByKeyForPortrait = [NSMutableDictionary dictionary]; _mutableHeightsByKeyForLandscape = [NSMutableDictionary dictionary]; } return self; } - (NSMutableDictionary, NSNumber *> *)mutableHeightsByKeyForCurrentOrientation { return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation) ? self.mutableHeightsByKeyForPortrait: self.mutableHeightsByKeyForLandscape; } - (BOOL)existsHeightForKey:(id)key { NSNumber *number = self.mutableHeightsByKeyForCurrentOrientation[key]; return number && ![number isEqualToNumber:@-1]; } - (void)cacheHeight:(CGFloat)height byKey:(id)key { self.mutableHeightsByKeyForCurrentOrientation[key] = @(height); } - (CGFloat)heightForKey:(id)key { #if CGFLOAT_IS_DOUBLE return [self.mutableHeightsByKeyForCurrentOrientation[key] doubleValue]; #else return [self.mutableHeightsByKeyForCurrentOrientation[key] floatValue]; #endif } - (void)invalidateHeightForKey:(id)key { [self.mutableHeightsByKeyForPortrait removeObjectForKey:key]; [self.mutableHeightsByKeyForLandscape removeObjectForKey:key]; } - (void)invalidateAllHeightCache { [self.mutableHeightsByKeyForPortrait removeAllObjects]; [self.mutableHeightsByKeyForLandscape removeAllObjects]; } @end @implementation UITableView (FDKeyedHeightCache) - (FDKeyedHeightCache *)fd_keyedHeightCache { FDKeyedHeightCache *cache = objc_getAssociatedObject(self, _cmd); if (!cache) { cache = [FDKeyedHeightCache new]; objc_setAssociatedObject(self, _cmd, cache, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return cache; } @end ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell/Classes/UITableView+FDTemplateLayoutCell.h ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import #import "UITableView+FDKeyedHeightCache.h" #import "UITableView+FDIndexPathHeightCache.h" #import "UITableView+FDTemplateLayoutCellDebug.h" @interface UITableView (FDTemplateLayoutCell) /// Access to internal template layout cell for given reuse identifier. /// Generally, you don't need to know these template layout cells. /// /// @param identifier Reuse identifier for cell which must be registered. /// - (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier; /// Returns height of cell of type specifed by a reuse identifier and configured /// by the configuration block. /// /// The cell would be layed out on a fixed-width, vertically expanding basis with /// respect to its dynamic content, using auto layout. Thus, it is imperative that /// the cell was set up to be self-satisfied, i.e. its content always determines /// its height given the width is equal to the tableview's. /// /// @param identifier A string identifier for retrieving and maintaining template /// cells with system's "-dequeueReusableCellWithIdentifier:" call. /// @param configuration An optional block for configuring and providing content /// to the template cell. The configuration should be minimal for scrolling /// performance yet sufficient for calculating cell's height. /// - (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id cell))configuration; /// This method does what "-fd_heightForCellWithIdentifier:configuration" does, and /// calculated height will be cached by its index path, returns a cached height /// when needed. Therefore lots of extra height calculations could be saved. /// /// No need to worry about invalidating cached heights when data source changes, it /// will be done automatically when you call "-reloadData" or any method that triggers /// UITableView's reloading. /// /// @param indexPath where this cell's height cache belongs. /// - (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByIndexPath:(NSIndexPath *)indexPath configuration:(void (^)(id cell))configuration; /// This method caches height by your model entity's identifier. /// If your model's changed, call "-invalidateHeightForKey:(id )key" to /// invalidate cache and re-calculate, it's much cheaper and effective than "cacheByIndexPath". /// /// @param key model entity's identifier whose data configures a cell. /// - (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByKey:(id)key configuration:(void (^)(id cell))configuration; @end @interface UITableView (FDTemplateLayoutHeaderFooterView) /// Returns header or footer view's height that registered in table view with reuse identifier. /// /// Use it after calling "-[UITableView registerNib/Class:forHeaderFooterViewReuseIdentifier]", /// same with "-fd_heightForCellWithIdentifier:configuration:", it will call "-sizeThatFits:" for /// subclass of UITableViewHeaderFooterView which is not using Auto Layout. /// - (CGFloat)fd_heightForHeaderFooterViewWithIdentifier:(NSString *)identifier configuration:(void (^)(id headerFooterView))configuration; @end @interface UITableViewCell (FDTemplateLayoutCell) /// Indicate this is a template layout cell for calculation only. /// You may need this when there are non-UI side effects when configure a cell. /// Like: /// - (void)configureCell:(FooCell *)cell atIndexPath:(NSIndexPath *)indexPath { /// cell.entity = [self entityAtIndexPath:indexPath]; /// if (!cell.fd_isTemplateLayoutCell) { /// [self notifySomething]; // non-UI side effects /// } /// } /// @property (nonatomic, assign) BOOL fd_isTemplateLayoutCell; /// Enable to enforce this template layout cell to use "frame layout" rather than "auto layout", /// and will ask cell's height by calling "-sizeThatFits:", so you must override this method. /// Use this property only when you want to manually control this template layout cell's height /// calculation mode, default to NO. /// @property (nonatomic, assign) BOOL fd_enforceFrameLayout; @end ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell/Classes/UITableView+FDTemplateLayoutCell.m ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import "UITableView+FDTemplateLayoutCell.h" #import @implementation UITableView (FDTemplateLayoutCell) - (CGFloat)fd_systemFittingHeightForConfiguratedCell:(UITableViewCell *)cell { CGFloat contentViewWidth = CGRectGetWidth(self.frame); // If a cell has accessory view or system accessory type, its content view's width is smaller // than cell's by some fixed values. if (cell.accessoryView) { contentViewWidth -= 16 + CGRectGetWidth(cell.accessoryView.frame); } else { static const CGFloat systemAccessoryWidths[] = { [UITableViewCellAccessoryNone] = 0, [UITableViewCellAccessoryDisclosureIndicator] = 34, [UITableViewCellAccessoryDetailDisclosureButton] = 68, [UITableViewCellAccessoryCheckmark] = 40, [UITableViewCellAccessoryDetailButton] = 48 }; contentViewWidth -= systemAccessoryWidths[cell.accessoryType]; } // If not using auto layout, you have to override "-sizeThatFits:" to provide a fitting size by yourself. // This is the same height calculation passes used in iOS8 self-sizing cell's implementation. // // 1. Try "- systemLayoutSizeFittingSize:" first. (skip this step if 'fd_enforceFrameLayout' set to YES.) // 2. Warning once if step 1 still returns 0 when using AutoLayout // 3. Try "- sizeThatFits:" if step 1 returns 0 // 4. Use a valid height or default row height (44) if not exist one CGFloat fittingHeight = 0; if (!cell.fd_enforceFrameLayout && contentViewWidth > 0) { // Add a hard width constraint to make dynamic content views (like labels) expand vertically instead // of growing horizontally, in a flow-layout manner. NSLayoutConstraint *widthFenceConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:contentViewWidth]; // [bug fix] after iOS 10.3, Auto Layout engine will add an additional 0 width constraint onto cell's content view, to avoid that, we add constraints to content view's left, right, top and bottom. static BOOL isSystemVersionEqualOrGreaterThen10_2 = NO; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ isSystemVersionEqualOrGreaterThen10_2 = [UIDevice.currentDevice.systemVersion compare:@"10.2" options:NSNumericSearch] != NSOrderedAscending; }); NSArray *edgeConstraints; if (isSystemVersionEqualOrGreaterThen10_2) { // To avoid confilicts, make width constraint softer than required (1000) widthFenceConstraint.priority = UILayoutPriorityRequired - 1; // Build edge constraints NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:cell attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]; NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:cell attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]; NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:cell attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:cell attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]; edgeConstraints = @[leftConstraint, rightConstraint, topConstraint, bottomConstraint]; [cell addConstraints:edgeConstraints]; } [cell.contentView addConstraint:widthFenceConstraint]; // Auto layout engine does its math fittingHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; // Clean-ups [cell.contentView removeConstraint:widthFenceConstraint]; if (isSystemVersionEqualOrGreaterThen10_2) { [cell removeConstraints:edgeConstraints]; } [self fd_debugLog:[NSString stringWithFormat:@"calculate using system fitting size (AutoLayout) - %@", @(fittingHeight)]]; } if (fittingHeight == 0) { #if DEBUG // Warn if using AutoLayout but get zero height. if (cell.contentView.constraints.count > 0) { if (!objc_getAssociatedObject(self, _cmd)) { NSLog(@"[FDTemplateLayoutCell] Warning once only: Cannot get a proper cell height (now 0) from '- systemFittingSize:'(AutoLayout). You should check how constraints are built in cell, making it into 'self-sizing' cell."); objc_setAssociatedObject(self, _cmd, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } #endif // Try '- sizeThatFits:' for frame layout. // Note: fitting height should not include separator view. fittingHeight = [cell sizeThatFits:CGSizeMake(contentViewWidth, 0)].height; [self fd_debugLog:[NSString stringWithFormat:@"calculate using sizeThatFits - %@", @(fittingHeight)]]; } // Still zero height after all above. if (fittingHeight == 0) { // Use default row height. fittingHeight = 44; } // Add 1px extra space for separator line if needed, simulating default UITableViewCell. if (self.separatorStyle != UITableViewCellSeparatorStyleNone) { fittingHeight += 1.0 / [UIScreen mainScreen].scale; } return fittingHeight; } - (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier { NSAssert(identifier.length > 0, @"Expect a valid identifier - %@", identifier); NSMutableDictionary *templateCellsByIdentifiers = objc_getAssociatedObject(self, _cmd); if (!templateCellsByIdentifiers) { templateCellsByIdentifiers = @{}.mutableCopy; objc_setAssociatedObject(self, _cmd, templateCellsByIdentifiers, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } UITableViewCell *templateCell = templateCellsByIdentifiers[identifier]; if (!templateCell) { templateCell = [self dequeueReusableCellWithIdentifier:identifier]; NSAssert(templateCell != nil, @"Cell must be registered to table view for identifier - %@", identifier); templateCell.fd_isTemplateLayoutCell = YES; templateCell.contentView.translatesAutoresizingMaskIntoConstraints = NO; templateCellsByIdentifiers[identifier] = templateCell; [self fd_debugLog:[NSString stringWithFormat:@"layout cell created - %@", identifier]]; } return templateCell; } - (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id cell))configuration { if (!identifier) { return 0; } UITableViewCell *templateLayoutCell = [self fd_templateCellForReuseIdentifier:identifier]; // Manually calls to ensure consistent behavior with actual cells. (that are displayed on screen) [templateLayoutCell prepareForReuse]; // Customize and provide content for our template cell. if (configuration) { configuration(templateLayoutCell); } return [self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell]; } - (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByIndexPath:(NSIndexPath *)indexPath configuration:(void (^)(id cell))configuration { if (!identifier || !indexPath) { return 0; } // Hit cache if ([self.fd_indexPathHeightCache existsHeightAtIndexPath:indexPath]) { [self fd_debugLog:[NSString stringWithFormat:@"hit cache by index path[%@:%@] - %@", @(indexPath.section), @(indexPath.row), @([self.fd_indexPathHeightCache heightForIndexPath:indexPath])]]; return [self.fd_indexPathHeightCache heightForIndexPath:indexPath]; } CGFloat height = [self fd_heightForCellWithIdentifier:identifier configuration:configuration]; [self.fd_indexPathHeightCache cacheHeight:height byIndexPath:indexPath]; [self fd_debugLog:[NSString stringWithFormat: @"cached by index path[%@:%@] - %@", @(indexPath.section), @(indexPath.row), @(height)]]; return height; } - (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByKey:(id)key configuration:(void (^)(id cell))configuration { if (!identifier || !key) { return 0; } // Hit cache if ([self.fd_keyedHeightCache existsHeightForKey:key]) { CGFloat cachedHeight = [self.fd_keyedHeightCache heightForKey:key]; [self fd_debugLog:[NSString stringWithFormat:@"hit cache by key[%@] - %@", key, @(cachedHeight)]]; return cachedHeight; } CGFloat height = [self fd_heightForCellWithIdentifier:identifier configuration:configuration]; [self.fd_keyedHeightCache cacheHeight:height byKey:key]; [self fd_debugLog:[NSString stringWithFormat:@"cached by key[%@] - %@", key, @(height)]]; return height; } @end @implementation UITableView (FDTemplateLayoutHeaderFooterView) - (__kindof UITableViewHeaderFooterView *)fd_templateHeaderFooterViewForReuseIdentifier:(NSString *)identifier { NSAssert(identifier.length > 0, @"Expect a valid identifier - %@", identifier); NSMutableDictionary *templateHeaderFooterViews = objc_getAssociatedObject(self, _cmd); if (!templateHeaderFooterViews) { templateHeaderFooterViews = @{}.mutableCopy; objc_setAssociatedObject(self, _cmd, templateHeaderFooterViews, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } UITableViewHeaderFooterView *templateHeaderFooterView = templateHeaderFooterViews[identifier]; if (!templateHeaderFooterView) { templateHeaderFooterView = [self dequeueReusableHeaderFooterViewWithIdentifier:identifier]; NSAssert(templateHeaderFooterView != nil, @"HeaderFooterView must be registered to table view for identifier - %@", identifier); templateHeaderFooterView.contentView.translatesAutoresizingMaskIntoConstraints = NO; templateHeaderFooterViews[identifier] = templateHeaderFooterView; [self fd_debugLog:[NSString stringWithFormat:@"layout header footer view created - %@", identifier]]; } return templateHeaderFooterView; } - (CGFloat)fd_heightForHeaderFooterViewWithIdentifier:(NSString *)identifier configuration:(void (^)(id))configuration { UITableViewHeaderFooterView *templateHeaderFooterView = [self fd_templateHeaderFooterViewForReuseIdentifier:identifier]; NSLayoutConstraint *widthFenceConstraint = [NSLayoutConstraint constraintWithItem:templateHeaderFooterView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:CGRectGetWidth(self.frame)]; [templateHeaderFooterView addConstraint:widthFenceConstraint]; CGFloat fittingHeight = [templateHeaderFooterView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; [templateHeaderFooterView removeConstraint:widthFenceConstraint]; if (fittingHeight == 0) { fittingHeight = [templateHeaderFooterView sizeThatFits:CGSizeMake(CGRectGetWidth(self.frame), 0)].height; } return fittingHeight; } @end @implementation UITableViewCell (FDTemplateLayoutCell) - (BOOL)fd_isTemplateLayoutCell { return [objc_getAssociatedObject(self, _cmd) boolValue]; } - (void)setFd_isTemplateLayoutCell:(BOOL)isTemplateLayoutCell { objc_setAssociatedObject(self, @selector(fd_isTemplateLayoutCell), @(isTemplateLayoutCell), OBJC_ASSOCIATION_RETAIN); } - (BOOL)fd_enforceFrameLayout { return [objc_getAssociatedObject(self, _cmd) boolValue]; } - (void)setFd_enforceFrameLayout:(BOOL)enforceFrameLayout { objc_setAssociatedObject(self, @selector(fd_enforceFrameLayout), @(enforceFrameLayout), OBJC_ASSOCIATION_RETAIN); } @end ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell/Classes/UITableView+FDTemplateLayoutCellDebug.h ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import @interface UITableView (FDTemplateLayoutCellDebug) /// Helps to debug or inspect what is this "FDTemplateLayoutCell" extention doing, /// turning on to print logs when "creating", "calculating", "precaching" or "hitting cache". /// /// Default to NO, log by NSLog. /// @property (nonatomic, assign) BOOL fd_debugLogEnabled; /// Debug log controlled by "fd_debugLogEnabled". - (void)fd_debugLog:(NSString *)message; @end ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell/Classes/UITableView+FDTemplateLayoutCellDebug.m ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import "UITableView+FDTemplateLayoutCellDebug.h" #import @implementation UITableView (FDTemplateLayoutCellDebug) - (BOOL)fd_debugLogEnabled { return [objc_getAssociatedObject(self, _cmd) boolValue]; } - (void)setFd_debugLogEnabled:(BOOL)debugLogEnabled { objc_setAssociatedObject(self, @selector(fd_debugLogEnabled), @(debugLogEnabled), OBJC_ASSOCIATION_RETAIN); } - (void)fd_debugLog:(NSString *)message { if (self.fd_debugLogEnabled) { NSLog(@"** FDTemplateLayoutCell ** %@", message); } } @end ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell/README.md ================================================ # UITableView-FDTemplateLayoutCell ## Overview Template auto layout cell for **automatically** UITableViewCell height calculating. ![Demo Overview](https://github.com/forkingdog/UITableView-FDTemplateLayoutCell/blob/master/Sceenshots/screenshot2.gif) ## Basic usage If you have a **self-satisfied** cell, then all you have to do is: ``` objc #import "UITableView+FDTemplateLayoutCell.h" - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return [tableView fd_heightForCellWithIdentifier:@"reuse identifer" configuration:^(id cell) { // Configure this cell with data, same as what you've done in "-tableView:cellForRowAtIndexPath:" // Like: // cell.entity = self.feedEntities[indexPath.row]; }]; } ``` ## Height Caching API Since iOS8, `-tableView:heightForRowAtIndexPath:` will be called more times than we expect, we can feel these extra calculations when scrolling. So we provide another API with cache by index path: ``` objc - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByIndexPath:indexPath configuration:^(id cell) { // configurations }]; } ``` Or, if your entity has an unique identifier, use cache by key API: ``` objc - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { Entity *entity = self.entities[indexPath.row]; return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByKey:entity.uid configuration:^(id cell) { // configurations }]; } ``` ## Frame layout mode `FDTemplateLayoutCell` offers 2 modes for asking cell's height. 1. Auto layout mode using "-systemLayoutSizeFittingSize:" 2. Frame layout mode using "-sizeThatFits:" Generally, no need to care about modes, it will **automatically** choose a proper mode by whether you have set auto layout constrants on cell's content view. If you want to enforce frame layout mode, enable this property in your cell's configuration block: ``` objc cell.fd_enforceFrameLayout = YES; ``` And if you're using frame layout mode, you must override `-sizeThatFits:` in your customized cell and return content view's height (separator excluded) ``` - (CGSize)sizeThatFits:(CGSize)size { return CGSizeMake(size.width, A+B+C+D+E+....); } ``` ## Debug log Debug log helps to debug or inspect what is this "FDTemplateLayoutCell" extention doing, turning on to print logs when "calculating", "precaching" or "hitting cache".Default to "NO", log by "NSLog". ``` objc self.tableView.fd_debugLogEnabled = YES; ``` It will print like this: ``` objc ** FDTemplateLayoutCell ** layout cell created - FDFeedCell ** FDTemplateLayoutCell ** calculate - [0:0] 233.5 ** FDTemplateLayoutCell ** calculate - [0:1] 155.5 ** FDTemplateLayoutCell ** calculate - [0:2] 258 ** FDTemplateLayoutCell ** calculate - [0:3] 284 ** FDTemplateLayoutCell ** precached - [0:3] 284 ** FDTemplateLayoutCell ** calculate - [0:4] 278.5 ** FDTemplateLayoutCell ** precached - [0:4] 278.5 ** FDTemplateLayoutCell ** hit cache - [0:3] 284 ** FDTemplateLayoutCell ** hit cache - [0:4] 278.5 ** FDTemplateLayoutCell ** hit cache - [0:5] 156 ** FDTemplateLayoutCell ** hit cache - [0:6] 165 ``` ## About self-satisfied cell a fully **self-satisfied** cell is constrainted by auto layout and each edge("top", "left", "bottom", "right") has at least one layout constraint against it. It's the same concept introduced as "self-sizing cell" in iOS8 using auto layout. A bad one :( - missing right and bottom ![non-self-satisfied](https://github.com/forkingdog/UITableView-FDTemplateLayoutCell/blob/master/Sceenshots/screenshot0.png) A good one :) ![self-satisfied](https://github.com/forkingdog/UITableView-FDTemplateLayoutCell/blob/master/Sceenshots/screenshot1.png) ## Notes A template layout cell is created by `-dequeueReusableCellWithIdentifier:` method, it means that you MUST have registered this cell reuse identifier by one of: - A prototype cell of UITableView in storyboard. - Use `-registerNib:forCellReuseIdentifier:` - Use `-registerClass:forCellReuseIdentifier:` ## 如果你在天朝 可以看这篇中文博客: [http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/](http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/) ## Installation Latest version: **1.6** ``` pod search UITableView+FDTemplateLayoutCell ``` If you cannot search out the latest version, try: ``` pod setup ``` ## Release Notes We recommend to use the latest release in cocoapods. - 1.6 fix bug in iOS 10 - 1.4 Refactor, add "cacheByKey" mode, bug fixed - 1.3 Frame layout mode, handle cell's accessory view/type - 1.2 Precache and auto cache invalidation - 1.1 Height cache - 1.0 Basic automatically height calculation ## License MIT ================================================ FILE: JetChat/Pods/UITableView+FDTemplateLayoutCell.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 1E09C004B341E59C2E6EF2E668A76518 /* UITableView+FDKeyedHeightCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 82452C3953316DA0768AA0BAF17B2594 /* UITableView+FDKeyedHeightCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 36673590C95D380C6403B236780BFC31 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E5FAAA3A51B06A67C696ED935E41340 /* Foundation.framework */; }; 3CBF43EEF83DE8194D18E099C7FC3F40 /* UITableView+FDIndexPathHeightCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C352D7B5B70A4043D58144481D92AAB /* UITableView+FDIndexPathHeightCache.m */; }; 49945F48ED36F3CF1A80461A82937029 /* UITableView+FDKeyedHeightCache.m in Sources */ = {isa = PBXBuildFile; fileRef = A5A234572B49383DB54892019DA35358 /* UITableView+FDKeyedHeightCache.m */; }; 6DB5E67073D5EC2B3198571FD2ACAB5C /* UITableView+FDIndexPathHeightCache.h in Headers */ = {isa = PBXBuildFile; fileRef = E75BFDB8EB235D84AE79D0C5F196B0DB /* UITableView+FDIndexPathHeightCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; A9866FC40D8475797539209E1B2A31C9 /* UITableView+FDTemplateLayoutCellDebug.h in Headers */ = {isa = PBXBuildFile; fileRef = 27E34899E89FFA5BDA253EA7EBCA6431 /* UITableView+FDTemplateLayoutCellDebug.h */; settings = {ATTRIBUTES = (Public, ); }; }; B4D47C5DC33D36F28A5A788799CD1EF3 /* UITableView+FDTemplateLayoutCellDebug.m in Sources */ = {isa = PBXBuildFile; fileRef = D7F7D93332977D41F9E7CE0659CCE4F3 /* UITableView+FDTemplateLayoutCellDebug.m */; }; CE8464EAFE92933007BFB92179C05E0F /* UITableView+FDTemplateLayoutCell-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = F7307175311F84F3B2DADC2BDFFB1005 /* UITableView+FDTemplateLayoutCell-dummy.m */; }; E14C8778B3CBD5CD676EF70FAE924AFA /* UITableView+FDTemplateLayoutCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 52A9A52A7A63F3F47F30FEB0E923CEAF /* UITableView+FDTemplateLayoutCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; E48801A9C8E280C934BE36571D92D983 /* UITableView+FDTemplateLayoutCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 806B76FC2A5B40189B487760462DC49B /* UITableView+FDTemplateLayoutCell.m */; }; F6469A1F27036B5F097C0BCA7B7B394A /* UITableView+FDTemplateLayoutCell-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = F130788D63F6973346E8FC80C218AA06 /* UITableView+FDTemplateLayoutCell-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 068521301BA74AE01BBC3D107892363E /* UITableView+FDTemplateLayoutCell */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "UITableView+FDTemplateLayoutCell"; path = UITableView_FDTemplateLayoutCell.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 100476275E9B228EDB05DEED197A76AC /* UITableView+FDTemplateLayoutCell-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "UITableView+FDTemplateLayoutCell-Info.plist"; sourceTree = ""; }; 27E34899E89FFA5BDA253EA7EBCA6431 /* UITableView+FDTemplateLayoutCellDebug.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UITableView+FDTemplateLayoutCellDebug.h"; path = "Classes/UITableView+FDTemplateLayoutCellDebug.h"; sourceTree = ""; }; 2DC094677C8CD69ECB23037D1D0D499D /* UITableView+FDTemplateLayoutCell-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "UITableView+FDTemplateLayoutCell-prefix.pch"; sourceTree = ""; }; 3B587664BC6B196E21406A09D95CC203 /* UITableView+FDTemplateLayoutCell.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "UITableView+FDTemplateLayoutCell.debug.xcconfig"; sourceTree = ""; }; 4D50C7257CD7023EAE084324F901DBE4 /* UITableView+FDTemplateLayoutCell.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "UITableView+FDTemplateLayoutCell.release.xcconfig"; sourceTree = ""; }; 52A9A52A7A63F3F47F30FEB0E923CEAF /* UITableView+FDTemplateLayoutCell.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UITableView+FDTemplateLayoutCell.h"; path = "Classes/UITableView+FDTemplateLayoutCell.h"; sourceTree = ""; }; 5C352D7B5B70A4043D58144481D92AAB /* UITableView+FDIndexPathHeightCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UITableView+FDIndexPathHeightCache.m"; path = "Classes/UITableView+FDIndexPathHeightCache.m"; sourceTree = ""; }; 6E5FAAA3A51B06A67C696ED935E41340 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 7814BB124FC91EB45C81F09E705006FA /* UITableView+FDTemplateLayoutCell.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "UITableView+FDTemplateLayoutCell.modulemap"; sourceTree = ""; }; 806B76FC2A5B40189B487760462DC49B /* UITableView+FDTemplateLayoutCell.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UITableView+FDTemplateLayoutCell.m"; path = "Classes/UITableView+FDTemplateLayoutCell.m"; sourceTree = ""; }; 82452C3953316DA0768AA0BAF17B2594 /* UITableView+FDKeyedHeightCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UITableView+FDKeyedHeightCache.h"; path = "Classes/UITableView+FDKeyedHeightCache.h"; sourceTree = ""; }; A5A234572B49383DB54892019DA35358 /* UITableView+FDKeyedHeightCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UITableView+FDKeyedHeightCache.m"; path = "Classes/UITableView+FDKeyedHeightCache.m"; sourceTree = ""; }; D7F7D93332977D41F9E7CE0659CCE4F3 /* UITableView+FDTemplateLayoutCellDebug.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UITableView+FDTemplateLayoutCellDebug.m"; path = "Classes/UITableView+FDTemplateLayoutCellDebug.m"; sourceTree = ""; }; E75BFDB8EB235D84AE79D0C5F196B0DB /* UITableView+FDIndexPathHeightCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UITableView+FDIndexPathHeightCache.h"; path = "Classes/UITableView+FDIndexPathHeightCache.h"; sourceTree = ""; }; F130788D63F6973346E8FC80C218AA06 /* UITableView+FDTemplateLayoutCell-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "UITableView+FDTemplateLayoutCell-umbrella.h"; sourceTree = ""; }; F7307175311F84F3B2DADC2BDFFB1005 /* UITableView+FDTemplateLayoutCell-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "UITableView+FDTemplateLayoutCell-dummy.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ A93DD8EE5D95222C2CDC9C3A8F7DED2D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 36673590C95D380C6403B236780BFC31 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 06CF3381C5623FFB8E280DD3F61CF33F /* Support Files */ = { isa = PBXGroup; children = ( 7814BB124FC91EB45C81F09E705006FA /* UITableView+FDTemplateLayoutCell.modulemap */, F7307175311F84F3B2DADC2BDFFB1005 /* UITableView+FDTemplateLayoutCell-dummy.m */, 100476275E9B228EDB05DEED197A76AC /* UITableView+FDTemplateLayoutCell-Info.plist */, 2DC094677C8CD69ECB23037D1D0D499D /* UITableView+FDTemplateLayoutCell-prefix.pch */, F130788D63F6973346E8FC80C218AA06 /* UITableView+FDTemplateLayoutCell-umbrella.h */, 3B587664BC6B196E21406A09D95CC203 /* UITableView+FDTemplateLayoutCell.debug.xcconfig */, 4D50C7257CD7023EAE084324F901DBE4 /* UITableView+FDTemplateLayoutCell.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/UITableView+FDTemplateLayoutCell"; sourceTree = ""; }; 1DB0E2E5E37A69D0D714DEAD78CB474B /* iOS */ = { isa = PBXGroup; children = ( 6E5FAAA3A51B06A67C696ED935E41340 /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; 4E7E6A26306312417BE85B6DB7307A41 = { isa = PBXGroup; children = ( B53F512F4B0151D43F3B3E5D2FDC2678 /* Frameworks */, DA533AEF37E42061E1E3740E63232ACB /* Products */, F56C79E4E6F8B2B5795128785201D960 /* UITableView+FDTemplateLayoutCell */, ); sourceTree = ""; }; B53F512F4B0151D43F3B3E5D2FDC2678 /* Frameworks */ = { isa = PBXGroup; children = ( 1DB0E2E5E37A69D0D714DEAD78CB474B /* iOS */, ); name = Frameworks; sourceTree = ""; }; DA533AEF37E42061E1E3740E63232ACB /* Products */ = { isa = PBXGroup; children = ( 068521301BA74AE01BBC3D107892363E /* UITableView+FDTemplateLayoutCell */, ); name = Products; sourceTree = ""; }; F56C79E4E6F8B2B5795128785201D960 /* UITableView+FDTemplateLayoutCell */ = { isa = PBXGroup; children = ( E75BFDB8EB235D84AE79D0C5F196B0DB /* UITableView+FDIndexPathHeightCache.h */, 5C352D7B5B70A4043D58144481D92AAB /* UITableView+FDIndexPathHeightCache.m */, 82452C3953316DA0768AA0BAF17B2594 /* UITableView+FDKeyedHeightCache.h */, A5A234572B49383DB54892019DA35358 /* UITableView+FDKeyedHeightCache.m */, 52A9A52A7A63F3F47F30FEB0E923CEAF /* UITableView+FDTemplateLayoutCell.h */, 806B76FC2A5B40189B487760462DC49B /* UITableView+FDTemplateLayoutCell.m */, 27E34899E89FFA5BDA253EA7EBCA6431 /* UITableView+FDTemplateLayoutCellDebug.h */, D7F7D93332977D41F9E7CE0659CCE4F3 /* UITableView+FDTemplateLayoutCellDebug.m */, 06CF3381C5623FFB8E280DD3F61CF33F /* Support Files */, ); name = "UITableView+FDTemplateLayoutCell"; path = "UITableView+FDTemplateLayoutCell"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ B1B4A58DDBE40851EC0B667E6FE7AC95 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 6DB5E67073D5EC2B3198571FD2ACAB5C /* UITableView+FDIndexPathHeightCache.h in Headers */, 1E09C004B341E59C2E6EF2E668A76518 /* UITableView+FDKeyedHeightCache.h in Headers */, E14C8778B3CBD5CD676EF70FAE924AFA /* UITableView+FDTemplateLayoutCell.h in Headers */, F6469A1F27036B5F097C0BCA7B7B394A /* UITableView+FDTemplateLayoutCell-umbrella.h in Headers */, A9866FC40D8475797539209E1B2A31C9 /* UITableView+FDTemplateLayoutCellDebug.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ A5CF12962AE63218F7BCE07569CE36DE /* UITableView+FDTemplateLayoutCell */ = { isa = PBXNativeTarget; buildConfigurationList = 1FB8745A3A96FE1E69EFA76886AC9A9F /* Build configuration list for PBXNativeTarget "UITableView+FDTemplateLayoutCell" */; buildPhases = ( B1B4A58DDBE40851EC0B667E6FE7AC95 /* Headers */, 2C15DE9B947A05D222094C721939D1C2 /* Sources */, A93DD8EE5D95222C2CDC9C3A8F7DED2D /* Frameworks */, C2C1F6CFE3489C83A3173E510AF21D4A /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "UITableView+FDTemplateLayoutCell"; productName = UITableView_FDTemplateLayoutCell; productReference = 068521301BA74AE01BBC3D107892363E /* UITableView+FDTemplateLayoutCell */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2EA7F5E9F6238B172DAFD82B3E4F0670 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = 9E7FFA1E60A4850A886DE5D7EBAA2F29 /* Build configuration list for PBXProject "UITableView+FDTemplateLayoutCell" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 4E7E6A26306312417BE85B6DB7307A41; productRefGroup = DA533AEF37E42061E1E3740E63232ACB /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( A5CF12962AE63218F7BCE07569CE36DE /* UITableView+FDTemplateLayoutCell */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ C2C1F6CFE3489C83A3173E510AF21D4A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2C15DE9B947A05D222094C721939D1C2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3CBF43EEF83DE8194D18E099C7FC3F40 /* UITableView+FDIndexPathHeightCache.m in Sources */, 49945F48ED36F3CF1A80461A82937029 /* UITableView+FDKeyedHeightCache.m in Sources */, E48801A9C8E280C934BE36571D92D983 /* UITableView+FDTemplateLayoutCell.m in Sources */, CE8464EAFE92933007BFB92179C05E0F /* UITableView+FDTemplateLayoutCell-dummy.m in Sources */, B4D47C5DC33D36F28A5A788799CD1EF3 /* UITableView+FDTemplateLayoutCellDebug.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 4EEE22928CE06EB3E69ABD876122F954 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4D50C7257CD7023EAE084324F901DBE4 /* UITableView+FDTemplateLayoutCell.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell-prefix.pch"; INFOPLIST_FILE = "Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell.modulemap"; PRODUCT_MODULE_NAME = UITableView_FDTemplateLayoutCell; PRODUCT_NAME = UITableView_FDTemplateLayoutCell; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 7A064B421160F133E07EFDF298842928 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; 7ED7D111F2FDBD39CD2026C3A93239C1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; EB2F6309973EFAA2CA54B5833F3DE3FA /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3B587664BC6B196E21406A09D95CC203 /* UITableView+FDTemplateLayoutCell.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell-prefix.pch"; INFOPLIST_FILE = "Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/UITableView+FDTemplateLayoutCell/UITableView+FDTemplateLayoutCell.modulemap"; PRODUCT_MODULE_NAME = UITableView_FDTemplateLayoutCell; PRODUCT_NAME = UITableView_FDTemplateLayoutCell; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1FB8745A3A96FE1E69EFA76886AC9A9F /* Build configuration list for PBXNativeTarget "UITableView+FDTemplateLayoutCell" */ = { isa = XCConfigurationList; buildConfigurations = ( EB2F6309973EFAA2CA54B5833F3DE3FA /* Debug */, 4EEE22928CE06EB3E69ABD876122F954 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 9E7FFA1E60A4850A886DE5D7EBAA2F29 /* Build configuration list for PBXProject "UITableView+FDTemplateLayoutCell" */ = { isa = XCConfigurationList; buildConfigurations = ( 7ED7D111F2FDBD39CD2026C3A93239C1 /* Debug */, 7A064B421160F133E07EFDF298842928 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 2EA7F5E9F6238B172DAFD82B3E4F0670 /* Project object */; } ================================================ FILE: JetChat/Pods/UIView+FDCollapsibleConstraints/Classes/UIView+FDCollapsibleConstraints.h ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import /// "UIView+FDCollapsibleConstraints" helps to implement flow-layout-like behavior /// in Auto Layout (with Interface Builder). /// /// Normally, when a view is to be hidden (which is common in type multiplexing /// views or cells), some related constraints in horizontal or vertical direction /// should also be disabled (by setting the "constant" property to zero as we might /// want to reuse the view later) to avoid double margin. /// /// Now with this extension, you could easily achieve this by specifying constraints /// to be set to zero when the view is "collapsed", as well as setting the /// "fd_collapsed" property when you set the view hidden or with empty content. /// /// For UIView's with fixed height constraint, in most cases you should use it /// with its height constraint and top (or bottom) margin constraint connected /// to fd_collapsibleConstraints IBOutletCollection. For UILabel-like intrinsic /// content size based views, you typically don't have a height constraint, thus /// only need to connect its margin constraint. You'll get the label totally /// disappeared by setting its "title" to nil and "fd_collapsed" to YES. @interface UIView (FDCollapsibleConstraints) /// Assigning this property immediately disables the view's collapsible constraints' /// by setting their constants to zero. @property (nonatomic, assign) BOOL fd_collapsed; /// Specify constraints to be affected by "fd_collapsed" property by connecting in /// Interface Builder. @property (nonatomic, copy) IBOutletCollection(NSLayoutConstraint) NSArray *fd_collapsibleConstraints; @end @interface UIView (FDAutomaticallyCollapseByIntrinsicContentSize) /// Enable to automatically collapse constraints in "fd_collapsibleConstraints" when /// you set or indirectly set this view's "intrinsicContentSize" to {0, 0} or absent. /// /// For example: /// imageView.image = nil; /// label.text = nil, label.text = @""; /// /// "NO" by default, you may enable it by codes. @property (nonatomic, assign) BOOL fd_autoCollapse; /// "IBInspectable" property, more friendly to Interface Builder. /// You gonna find this attribute in "Attribute Inspector", toggle "On" to enable. /// Why not a "fd_" prefix? Xcode Attribute Inspector will clip it like a shit. /// You should not assgin this property directly by code, use "fd_autoCollapse" instead. @property (nonatomic, assign, getter=fd_autoCollapse) IBInspectable BOOL autoCollapse; @end ================================================ FILE: JetChat/Pods/UIView+FDCollapsibleConstraints/Classes/UIView+FDCollapsibleConstraints.m ================================================ // The MIT License (MIT) // // Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import "UIView+FDCollapsibleConstraints.h" #import /// A stored property extension for NSLayoutConstraint's original constant. @implementation NSLayoutConstraint (_FDOriginalConstantStorage) - (void)setFd_originalConstant:(CGFloat)originalConstant { objc_setAssociatedObject(self, @selector(fd_originalConstant), @(originalConstant), OBJC_ASSOCIATION_RETAIN); } - (CGFloat)fd_originalConstant { #if CGFLOAT_IS_DOUBLE return [objc_getAssociatedObject(self, _cmd) doubleValue]; #else return [objc_getAssociatedObject(self, _cmd) floatValue]; #endif } @end @implementation UIView (FDCollapsibleConstraints) #pragma mark - Hacking KVC + (void)load { // Swizzle setValue:forKey: to intercept assignments to `fd_collapsibleConstraints` // from Interface Builder. We should not do so by overriding setvalue:forKey: // as the primary class implementation would be bypassed. SEL originalSelector = @selector(setValue:forKey:); SEL swizzledSelector = @selector(fd_setValue:forKey:); Class class = UIView.class; Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod); } - (void)fd_setValue:(id)value forKey:(NSString *)key { NSString *injectedKey = [NSString stringWithUTF8String:sel_getName(@selector(fd_collapsibleConstraints))]; if ([key isEqualToString:injectedKey]) { // This kind of IBOutlet won't trigger property's setter, so we forward it. self.fd_collapsibleConstraints = value; } else { // Forward the rest of KVC's to original implementation. [self fd_setValue:value forKey:key]; } } #pragma mark - Dynamic Properties - (void)setFd_collapsed:(BOOL)collapsed { [self.fd_collapsibleConstraints enumerateObjectsUsingBlock: ^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) { if (collapsed) { constraint.constant = 0; } else { constraint.constant = constraint.fd_originalConstant; } }]; objc_setAssociatedObject(self, @selector(fd_collapsed), @(collapsed), OBJC_ASSOCIATION_RETAIN); } - (BOOL)fd_collapsed { return [objc_getAssociatedObject(self, _cmd) boolValue]; } - (NSMutableArray *)fd_collapsibleConstraints { NSMutableArray *constraints = objc_getAssociatedObject(self, _cmd); if (!constraints) { constraints = @[].mutableCopy; objc_setAssociatedObject(self, _cmd, constraints, OBJC_ASSOCIATION_RETAIN); } return constraints; } - (void)setFd_collapsibleConstraints:(NSArray *)fd_collapsibleConstraints { // Hook assignments to our custom `fd_collapsibleConstraints` property. NSMutableArray *constraints = (NSMutableArray *)self.fd_collapsibleConstraints; [fd_collapsibleConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) { // Store original constant value constraint.fd_originalConstant = constraint.constant; [constraints addObject:constraint]; }]; } @end @implementation UIView (FDAutomaticallyCollapseByIntrinsicContentSize) #pragma mark - Hacking "-updateConstraints" + (void)load { // Swizzle to hack "-updateConstraints" method SEL originalSelector = @selector(updateConstraints); SEL swizzledSelector = @selector(fd_updateConstraints); Class class = UIView.class; Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod); } - (void)fd_updateConstraints { // Call primary method's implementation [self fd_updateConstraints]; if (self.fd_autoCollapse && self.fd_collapsibleConstraints.count > 0) { // "Absent" means this view doesn't have an intrinsic content size, {-1, -1} actually. const CGSize absentIntrinsicContentSize = CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric); // Calculated intrinsic content size const CGSize contentSize = [self intrinsicContentSize]; // When this view doesn't have one, or has no intrinsic content size after calculating, // it going to be collapsed. if (CGSizeEqualToSize(contentSize, absentIntrinsicContentSize) || CGSizeEqualToSize(contentSize, CGSizeZero)) { self.fd_collapsed = YES; } else { self.fd_collapsed = NO; } } } #pragma mark - Dynamic Properties - (BOOL)fd_autoCollapse { return [objc_getAssociatedObject(self, _cmd) boolValue]; } - (void)setFd_autoCollapse:(BOOL)autoCollapse { objc_setAssociatedObject(self, @selector(fd_autoCollapse), @(autoCollapse), OBJC_ASSOCIATION_RETAIN); } - (void)setAutoCollapse:(BOOL)collapse { // Just forwarding self.fd_autoCollapse = collapse; } @end ================================================ FILE: JetChat/Pods/UIView+FDCollapsibleConstraints/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 forkingdog Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: JetChat/Pods/UIView+FDCollapsibleConstraints/README.md ================================================ # UIView-FDCollapsibleConstraints ![forkingdog](https://cloud.githubusercontent.com/assets/219689/7244961/4209de32-e816-11e4-87bc-b161c442d348.png) # Overview `UIView+FDCollapsibleConstraints` builds to **collapse** a view and its relevant layout constraints. ## Demo 1 This demo collapses the `forkingdog` image view and its bottom margin constraint. ![view demo](https://github.com/forkingdog/UIView-FDCollapsibleConstraints/blob/master/Sceenshots/screenshot0.gif) ## Demo 2 This demo collapses diffent components in cell, according to its data entity, each margin handles right as well. ![cell demo](https://github.com/forkingdog/UIView-FDCollapsibleConstraints/blob/master/Sceenshots/screenshot1.gif) # Basic usage ## 1. Select constraints to collapse First, tell which constraints will be collapsed when the view collapses. We provide a `IBOutletCollection` to make it easier in Interface Builder: ``` @property (nonatomic, copy) IBOutletCollection(NSLayoutConstraint) NSArray *fd_collapsibleConstraints; ``` You can assgin it by codes, but it's better to **"connect lines"** in Interface Builder: ![cell demo](https://github.com/forkingdog/UIView-FDCollapsibleConstraints/blob/master/Sceenshots/screenshot2.gif) ## 2. Collapse a view Selected constraints will collapse: ``` view.fd_collapsed = YES; ``` And recover back properly: ``` view.fd_collapsed = NO; ``` # Auto collapse # License MIT ================================================ FILE: JetChat/Pods/UIView+FDCollapsibleConstraints.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 1C010105DDAEBDED0903128348301D7A /* UIView+FDCollapsibleConstraints.h in Headers */ = {isa = PBXBuildFile; fileRef = 969DA39AF015BA72212F9C847A40C920 /* UIView+FDCollapsibleConstraints.h */; settings = {ATTRIBUTES = (Public, ); }; }; 47AD5553090FFCD2CE658D3E3DEBF6E5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E7E286CA4A5F14B5DD9A04F50E0C35F /* Foundation.framework */; }; 972532C9608F31B1987A4AA7487C8598 /* UIView+FDCollapsibleConstraints.m in Sources */ = {isa = PBXBuildFile; fileRef = E5532F5D66F8DFBA9C87DBFBD3D03DBE /* UIView+FDCollapsibleConstraints.m */; }; 988C9CD86D4AA046390244E14CEB241E /* UIView+FDCollapsibleConstraints-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AC1654DFB40505EC0B615B2275AB355 /* UIView+FDCollapsibleConstraints-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; B740C40417952E3870D3F9739BE95A85 /* UIView+FDCollapsibleConstraints-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 46F26C23D78D58B521829731B4FF78FB /* UIView+FDCollapsibleConstraints-dummy.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 36609745833675C157CB207E2D81E252 /* UIView+FDCollapsibleConstraints-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "UIView+FDCollapsibleConstraints-Info.plist"; sourceTree = ""; }; 46F26C23D78D58B521829731B4FF78FB /* UIView+FDCollapsibleConstraints-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "UIView+FDCollapsibleConstraints-dummy.m"; sourceTree = ""; }; 4AC1654DFB40505EC0B615B2275AB355 /* UIView+FDCollapsibleConstraints-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "UIView+FDCollapsibleConstraints-umbrella.h"; sourceTree = ""; }; 5384065D3B35C59943B009536B20AE94 /* UIView+FDCollapsibleConstraints.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "UIView+FDCollapsibleConstraints.modulemap"; sourceTree = ""; }; 6E7E286CA4A5F14B5DD9A04F50E0C35F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 7EF4ED1716912F813C6E4BE0C2C44C96 /* UIView+FDCollapsibleConstraints.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "UIView+FDCollapsibleConstraints.release.xcconfig"; sourceTree = ""; }; 8DAFC143C480F429F6084E7122AFF71F /* UIView+FDCollapsibleConstraints */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "UIView+FDCollapsibleConstraints"; path = UIView_FDCollapsibleConstraints.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 969DA39AF015BA72212F9C847A40C920 /* UIView+FDCollapsibleConstraints.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+FDCollapsibleConstraints.h"; path = "Classes/UIView+FDCollapsibleConstraints.h"; sourceTree = ""; }; B7FD4CEFB45302D8C100BA871F0FE618 /* UIView+FDCollapsibleConstraints.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "UIView+FDCollapsibleConstraints.debug.xcconfig"; sourceTree = ""; }; DB504BB8244B74D2F0261A68F5F718FA /* UIView+FDCollapsibleConstraints-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "UIView+FDCollapsibleConstraints-prefix.pch"; sourceTree = ""; }; E5532F5D66F8DFBA9C87DBFBD3D03DBE /* UIView+FDCollapsibleConstraints.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+FDCollapsibleConstraints.m"; path = "Classes/UIView+FDCollapsibleConstraints.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 7717BDBAA85A3EAE87C403283B52AC97 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 47AD5553090FFCD2CE658D3E3DEBF6E5 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2D31B64AFA7CAE50B31E529CBC424368 = { isa = PBXGroup; children = ( D82E421E72FD536B3A6996C9C5BD708E /* Frameworks */, 2E2910AB20145C577898813637C3F217 /* Products */, E73E9CD5081BC073E14E88BB277DBCE9 /* UIView+FDCollapsibleConstraints */, ); sourceTree = ""; }; 2E2910AB20145C577898813637C3F217 /* Products */ = { isa = PBXGroup; children = ( 8DAFC143C480F429F6084E7122AFF71F /* UIView+FDCollapsibleConstraints */, ); name = Products; sourceTree = ""; }; 31AB1ACBAB796D939CCA0C500A74BC8D /* iOS */ = { isa = PBXGroup; children = ( 6E7E286CA4A5F14B5DD9A04F50E0C35F /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; D82E421E72FD536B3A6996C9C5BD708E /* Frameworks */ = { isa = PBXGroup; children = ( 31AB1ACBAB796D939CCA0C500A74BC8D /* iOS */, ); name = Frameworks; sourceTree = ""; }; E73E9CD5081BC073E14E88BB277DBCE9 /* UIView+FDCollapsibleConstraints */ = { isa = PBXGroup; children = ( 969DA39AF015BA72212F9C847A40C920 /* UIView+FDCollapsibleConstraints.h */, E5532F5D66F8DFBA9C87DBFBD3D03DBE /* UIView+FDCollapsibleConstraints.m */, FE78FC97C32F6FEF2BDE9F30E66D9464 /* Support Files */, ); name = "UIView+FDCollapsibleConstraints"; path = "UIView+FDCollapsibleConstraints"; sourceTree = ""; }; FE78FC97C32F6FEF2BDE9F30E66D9464 /* Support Files */ = { isa = PBXGroup; children = ( 5384065D3B35C59943B009536B20AE94 /* UIView+FDCollapsibleConstraints.modulemap */, 46F26C23D78D58B521829731B4FF78FB /* UIView+FDCollapsibleConstraints-dummy.m */, 36609745833675C157CB207E2D81E252 /* UIView+FDCollapsibleConstraints-Info.plist */, DB504BB8244B74D2F0261A68F5F718FA /* UIView+FDCollapsibleConstraints-prefix.pch */, 4AC1654DFB40505EC0B615B2275AB355 /* UIView+FDCollapsibleConstraints-umbrella.h */, B7FD4CEFB45302D8C100BA871F0FE618 /* UIView+FDCollapsibleConstraints.debug.xcconfig */, 7EF4ED1716912F813C6E4BE0C2C44C96 /* UIView+FDCollapsibleConstraints.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/UIView+FDCollapsibleConstraints"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 5A1C166569A71C45E808D9E1517E782D /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 1C010105DDAEBDED0903128348301D7A /* UIView+FDCollapsibleConstraints.h in Headers */, 988C9CD86D4AA046390244E14CEB241E /* UIView+FDCollapsibleConstraints-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ F254007166B3EB42A09DBC736355B678 /* UIView+FDCollapsibleConstraints */ = { isa = PBXNativeTarget; buildConfigurationList = 782693A7D7D8F003751D1FC794435C57 /* Build configuration list for PBXNativeTarget "UIView+FDCollapsibleConstraints" */; buildPhases = ( 5A1C166569A71C45E808D9E1517E782D /* Headers */, 3920B9A4BF7D0AE71208BE9E746B2FAC /* Sources */, 7717BDBAA85A3EAE87C403283B52AC97 /* Frameworks */, 55F99CE7046FC02DB5DCE0E47B16C40A /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "UIView+FDCollapsibleConstraints"; productName = UIView_FDCollapsibleConstraints; productReference = 8DAFC143C480F429F6084E7122AFF71F /* UIView+FDCollapsibleConstraints */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 5800FB358287256DB968AF2A1C0FDC49 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = A1ED46756FA660FF8A0800D053BEAB59 /* Build configuration list for PBXProject "UIView+FDCollapsibleConstraints" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 2D31B64AFA7CAE50B31E529CBC424368; productRefGroup = 2E2910AB20145C577898813637C3F217 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F254007166B3EB42A09DBC736355B678 /* UIView+FDCollapsibleConstraints */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 55F99CE7046FC02DB5DCE0E47B16C40A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 3920B9A4BF7D0AE71208BE9E746B2FAC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 972532C9608F31B1987A4AA7487C8598 /* UIView+FDCollapsibleConstraints.m in Sources */, B740C40417952E3870D3F9739BE95A85 /* UIView+FDCollapsibleConstraints-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 32E80501C4B1DA3FE4E7E4878C66D833 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = B7FD4CEFB45302D8C100BA871F0FE618 /* UIView+FDCollapsibleConstraints.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints-prefix.pch"; INFOPLIST_FILE = "Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints.modulemap"; PRODUCT_MODULE_NAME = UIView_FDCollapsibleConstraints; PRODUCT_NAME = UIView_FDCollapsibleConstraints; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 5256AA0DC59D0083CDE8726C8E8C26B6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 654552BF04C3A1793C8250C03820F4C3 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7EF4ED1716912F813C6E4BE0C2C44C96 /* UIView+FDCollapsibleConstraints.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints-prefix.pch"; INFOPLIST_FILE = "Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/UIView+FDCollapsibleConstraints/UIView+FDCollapsibleConstraints.modulemap"; PRODUCT_MODULE_NAME = UIView_FDCollapsibleConstraints; PRODUCT_NAME = UIView_FDCollapsibleConstraints; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 76EB699657929BE2312FC1BE51E58EB6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 782693A7D7D8F003751D1FC794435C57 /* Build configuration list for PBXNativeTarget "UIView+FDCollapsibleConstraints" */ = { isa = XCConfigurationList; buildConfigurations = ( 32E80501C4B1DA3FE4E7E4878C66D833 /* Debug */, 654552BF04C3A1793C8250C03820F4C3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; A1ED46756FA660FF8A0800D053BEAB59 /* Build configuration list for PBXProject "UIView+FDCollapsibleConstraints" */ = { isa = XCConfigurationList; buildConfigurations = ( 5256AA0DC59D0083CDE8726C8E8C26B6 /* Debug */, 76EB699657929BE2312FC1BE51E58EB6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 5800FB358287256DB968AF2A1C0FDC49 /* Project object */; } ================================================ FILE: JetChat/Pods/WCDB.swift/LICENSE ================================================ Tencent is pleased to support the open source community by making WCDB available.  Copyright (C) 2017 THL A29 Limited, a Tencent company.  All rights reserved. If you have downloaded a copy of the WCDB binary from Tencent, please note that the WCDB binary is licensed under the BSD 3-Clause License. If you have downloaded a copy of the WCDB source code from Tencent, please note that WCDB source code is licensed under the BSD 3-Clause License, except for the third-party components listed below which are subject to different license terms.  Your integration of WCDB into your own projects may require compliance with the BSD 3-Clause License, as well as the other licenses applicable to the third-party components included within WCDB. A copy of the BSD 3-Clause License is included in this file. Other dependencies and licenses:   Open Source Software Licensed Under the Apache License, Version 2.0: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. Android Source Code  4.3 Copyright (C) 2006-2011 The Android Open Source Project     Terms of the Apache License, Version 2.0: --------------------------------------------------- Apache License Version 2.0, January 2004 http://www.apache.org/licenses/   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION   1. Definitions.   “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.   “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.   “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.   “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.   “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.   “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.   “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).   “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.   “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.”   “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.   2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.   3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.   4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:   a) You must give any other recipients of the Work or Derivative Works a copy of this License; and   b) You must cause any modified files to carry prominent notices stating that You changed the files; and   c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and   d) If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.   You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.   5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.   6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.   7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.   8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.   END OF TERMS AND CONDITIONS   APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.   Copyright [yyyy] [name of copyright owner]   Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.       Open Source Software Licensed Under Public Domain: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. SQLite  3.11.0       Open Source Software Licensed Under the OpenSSL License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. OpenSSL  1.0.2j Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved. Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved.       Terms of the OpenSSL License: --------------------------------------------------- LICENSE ISSUES: --------------------------------------------------------------------   The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit. See below for the actual license texts.   OpenSSL License: -------------------------------------------------------------------- Copyright (c) 1998-2016 The OpenSSL Project.  All rights reserved.   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:   1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.   3. All advertising materials mentioning features or use of this software must display the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)"   4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org.   5. Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project.   6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)"   THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================== * This product includes cryptographic software written by Eric Young (eay@cryptsoft.com).  This product includes software written by Tim Hudson (tjh@cryptsoft.com).     Original SSLeay License: -------------------------------------------------------------------- Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved.   This package is an SSL implementation written by Eric Young (eay@cryptsoft.com). The implementation was written so as to conform with Netscapes SSL.   This library is free for commercial and non-commercial use as long as the following conditions are aheared to. The following conditions apply to all code found in this distribution, be it the RC4, RSA, lhash, DES, etc., code; not just the SSL code. The SSL documentation included with this distribution is covered by the same copyright terms except that the holder is Tim Hudson (tjh@cryptsoft.com).   Copyright remains Eric Young's, and as such any Copyright notices in the code are not to be removed.  If this package is used in a product, Eric Young should be given attribution as the author of the parts of the library used. This can be in the form of a textual message at program startup or in documentation (online or textual) provided with the package.   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement:" This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)" The word 'cryptographic' can be left out if the rouines from the library being used are not cryptographic related :-). 4. If you include any Windows specific code (or a derivative thereof) from the apps directory (application code) you must include an acknowledgement: "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"   THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   The licence and distribution terms for any publically available version or derivative of this code cannot be changed.  i.e. this code cannot simply be copied and put under another distribution licence [including the GNU Public Licence.]       Open Source Software Licensed Under the ICU License: ---------------------------------------------------------------------------------------- 1. ICU4C  50.1 Copyright (c) 1995-2012 International Business Machines Corporation and others All rights reserved.     Terms of the ICU License: -------------------------------------------------------------------- ICU License - ICU 1.8.1 and later   COPYRIGHT AND PERMISSION NOTICE   Copyright (c) 1995-2012 International Business Machines Corporation and others   All rights reserved.   Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation.   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.   Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder.   All trademarks and registered trademarks mentioned herein are the property of their respective owners.     Open Source Software Licensed Under the BSD 3-Clause License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2017 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. SQLCipher  3.4.0 Copyright (c) 2008, ZETETIC LLC All rights reserved.       Terms of the BSD 3-Clause License: --------------------------------------------------------------------   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: l  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. l  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. l  Neither the name of [copyright holder] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: JetChat/Pods/WCDB.swift/README.md ================================================ # WCDB [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Tencent/wcdb/pulls) [![Release Version](https://img.shields.io/badge/release-1.0.5-brightgreen.svg)](https://github.com/Tencent/wcdb/releases) [![WeChat Approved iOS](https://img.shields.io/badge/Wechat_Approved_iOS-1.0.5-brightgreen.svg)](https://github.com/Tencent/wcdb/blob/master/README.md#wcdb-for-iosmacos) [![WeChat Approved Android](https://img.shields.io/badge/Wechat_Approved_Android-1.0.5-brightgreen.svg)](https://github.com/Tencent/wcdb/blob/master/README.md#wcdb-for-android) [![Platform](https://img.shields.io/badge/Platform-%20iOS%20%7C%20macOS%20%7C%20Android-brightgreen.svg)](https://github.com/Tencent/wcdb/wiki) 中文版本请参看[这里][wcdb-wiki] WCDB is an **efficient**, **complete**, **easy-to-use** mobile database framework used in the WeChat application. It's currently available on iOS, macOS and Android. # WCDB for iOS/macOS ## Features * **Easy-to-use**. Through WCDB, you can get objects from database in one line code. * **WINQ** (WCDB language integrated query): WINQ is a native data querying capability which frees developers from writing glue code to concatenate SQL query strings. * **ORM** (Object Relational Mapping): WCDB provides a flexible, easy-to-use ORM for creating tables, indices and constraints, as well as CRUD through ObjC objects. ```objective-c [database getObjectsOfClass:WCTSampleConvenient.class fromTable:tableName where:WCTSampleConvenient.intValue>=10 limit:20]; ``` * **Efficient**. Through the framework layer and sqlcipher source optimization, WCDB have more efficient performance. * **Multi-threaded concurrency**: WCDB supports concurrent read-read and read-write access via connection pooling. * **Batch Write Performance Test**. ![](https://raw.githubusercontent.com/wiki/Tencent/wcdb/assets/benchmark/baseline_batch_write.png) For more benchmark data, please refer to [our benchmark][Benchmark-iOS]. * **Complete**. * **Encryption Support**: WCDB supports database encryption via [SQLCipher][sqlcipher]. * **Corruption recovery**: WCDB provides a built-in repair kit for database corruption recovery. * **Anti-injection**: WCDB provides a built-in protection from SQL injection. ## Getting Started ### Prerequisites * Apps using WCDB can target: iOS 7 or later, macOS 10.9 or later. * Xcode 8.0 or later required. * Objective-C++ required. ### Installation * **Via Cocoapods:** 1. Install [CocoaPods.](https://guides.cocoapods.org/using/getting-started.html) 2. Run `pod repo update` to make CocoaPods aware of the latest available WCDB versions. 3. In your Podfile, add `pod 'WCDB'` to your app target. 4. From the command line, run `pod install`. 5. Use the `.xcworkspace` file generated by CocoaPods to work on your project. 6. Add `#import ` at the top of your Objective-C++ source files and start your WCDB journey. 7. **Since WCDB is an Objective-C++ framework, for those files in your project that includes WCDB, you should rename their extension `.m` to `.mm`.** * **Via Carthage:** 1. Install [Carthage][install-carthage]. 2. Add `github "Tencent/WCDB"` to your Cartfile. 3. Run `carthage update`. 4. Drag `WCDB.framework` from the appropriate platform directory in `Carthage/Build/` to the `Linked Binary and Libraries` section of your Xcode project’s `Build Phases` settings. 5. On your application targets' `Build Phases` settings tab, click the "+" icon and choose `New Run Script Phase`. Create a Run Script with `carthage copy-frameworks` and add the paths to the frameworks under `Input Files`: `$(SRCROOT)/Carthage/Build/iOS/WCDB.framework` or `$(SRCROOT)/Carthage/Build/Mac/WCDB.framework`. 6. Add `#import ` at the top of your Objective-C++ source files and start your WCDB journey. 7. **Since WCDB is an Objective-C++ framework, for those files in your project that includes WCDB, you should rename their extension `.m` to `.mm`.** * **Via Dynamic Framework**: **Note that Dynamic frameworks are not compatible with iOS 7. See “Static Framework” for iOS 7 support.** 1. Getting source code from git repository and update the submodule of sqlcipher. - `git clone https://github.com/Tencent/wcdb.git` - `cd wcdb` - `git submodule update --init sqlcipher ` 2. Drag `WCDB.xcodeproj` in `wcdb/apple/` into your project. 3. Add `WCDB.framework` to the `Enbedded Binaries` section of your Xcode project's `General settings`. **Note that there are two frameworks here and the dynamic one should be chosen. You can check it at `Build Phases`->`Target Dependencies`. The right one is `WCDB` while `WCDB iOS Static is used for static lib.** 4. Add `#import ` at the top of your Objective-C++ source files and start your WCDB journey. 5. **Since WCDB is an Objective-C++ framework, for those files in your project that includes WCDB, you should rename their extension `.m` to `.mm`.** * **Via Static Framework:** 1. Getting source code from git repository and update the submodule of sqlcipher. - `git clone https://github.com/Tencent/wcdb.git` - `cd wcdb` - `git submodule update --init sqlcipher ` 2. Drag `WCDB.xcodeproj` in `wcdb/apple/` into your project. 3. Add `WCDB iOS Static` to the `Target Dependencies` section of your Xcode project's `Build Phases` settings. 4. Add `WCDB.framework`, `libz.tbd` to the `Linked Binary and Libraries` section of your Xcode project's `Build Phases` settings. Note that there are two `WCDB.framework`, you should choose the one from `WCDB iOS Static` target. 5. Add `-all_load` and `-ObjC` to the `Other Linker Flags` section of your Xcode project's `Build Settings`. 6. Add `#import ` at the top of your Objective-C++ source files and start your WCDB journey. 7. **Since WCDB is an Objective-C++ framework, for those files in your project that includes WCDB, you should rename their extension `.m` to `.mm`.** ## Tutorials Tutorials can be found [here][iOS-tutorial]. ## Documentations * Documentations can be found on [our Wiki][wcdb-wiki]. * API references for iOS/macOS can be found [here][wcdb-docs-ios]. * Performance data can be found on [our benchmark][Benchmark-iOS]. # WCDB for Android ## Features * Database encryption via [SQLCipher][sqlcipher]. * ORM/persistence solution via [Room][room] from Android Architecture Components. * Concurrent access via connection pooling from modern Android framework. * Repair toolkit for database corruption recovery. * Database backup and recovery utility optimized for small backup size. * Log redirection and various tracing facilities. * API 14 (Android 4.0) and above are supported. ## Getting Started To include WCDB to your project, choose either way: import via Maven or via AAR package. ### Import via Maven To import WCDB via Maven repositories, add the following lines to `build.gradle` on your app module: ```gradle dependencies { compile 'com.tencent.wcdb:wcdb-android:1.0.8' // Replace "1.0.8" to any available version. } ``` This will cause Gradle to download AAR package from JCenter while building your application. If you want to use Room persistence library, you need to add the Google Maven repository to `build.gradle` to your **root project**. ```gradle allprojects { repositories { jcenter() google() // Add this line } } ``` Also add dependencies to module `build.gradle`. ```gradle dependencies { compile 'com.tencent.wcdb:room:1.0.8' // Replace "1.0.8" to any available version. annotationProcessor 'android.arch.persistence.room:compiler:1.1.1' // Don't forget to include Room annotation compiler from Google. } ``` ### Import Prebuilt AAR Package   1. **Download AAR package from release page.**   2. **Import the AAR as new module.** In Android Studio, select `File -> New -> New Module...` menu and choose `"Import JAR/AAR Package"`.   3. **Add a dependency on the new module.** This can be done using `File -> Project Structure...` in Android Studio, or by adding following code to application's `build.gradle`: ```gradle dependencies { // Change "wcdb" to the actual module name specified in step 2. compile project(':wcdb') } ``` ### Migrate from Plain-text SQLite Databases WCDB has interfaces very similar to Android SQLite Database APIs. To migrate you application from AOSP API, change import path from `android.database.*` to `com.tencent.wcdb.*`, and `android.database.sqlite.*` to `com.tencent.wcdb.database.*`. After import path update, your application links to WCDB instead of AOSP API. To open or create an encrypted database, use with-password versions of `SQLiteDatabase.openOrCreateDatabase()`, `SQLiteOpenHelper.getWritableDatabase()`, or `Context.openOrCreateDatabase()`. *Note: WCDB uses `byte[]` for password instead of `String` in SQLCipher Android Binding.* ```java String password = "MyPassword"; SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase("/path/to/database", password.getBytes(), null, null); ``` See `sample-encryptdb` for sample for transferring data between plain-text and encrypted databases. ### Use WCDB with Room To use WCDB with Room library, follow the [Room instructions][room]. The only code to change is specifying `WCDBOpenHelperFactory` when getting instance of the database. ```java SQLiteCipherSpec cipherSpec = new SQLiteCipherSpec() .setPageSize(4096) .setKDFIteration(64000); WCDBOpenHelperFactory factory = new WCDBOpenHelperFactory() .passphrase("passphrase".getBytes()) // passphrase to the database, remove this line for plain-text .cipherSpec(cipherSpec) // cipher to use, remove for default settings .writeAheadLoggingEnabled(true) // enable WAL mode, remove if not needed .asyncCheckpointEnabled(true); // enable asynchronous checkpoint, remove if not needed AppDatabase db = Room.databaseBuilder(this, AppDatabase.class, "app-db") .allowMainThreadQueries() .openHelperFactory(factory) // specify WCDBOpenHelperFactory when opening database .build(); ``` See `sample-persistence` for samples using Room library with WCDB. See `sample-room-with-a-view` for samples using Room library with WCDB and other architecture components from Google. > `sample-room-with-a-view` comes from Google's CodeLabs with modification of just a few rows. Search for *\[WCDB\]* keyword for the modification. > > See [here][room-codelabs] for the original tutorial. ### Corruption Recovery See `sample-repairdb` for instructions how to recover corrupted databases using `RepairKit`. ### Redirect Log Output By default, WCDB prints its log message to system logcat. You may want to change this behavior in order to, for example, save logs for troubleshooting. WCDB can redirect all of its log outputs to user-defined routine using `Log.setLogger(LogCallback)` method. ## Build from Sources ### Build WCDB Android with Prebuilt Dependencies WCDB itself can be built apart from its dependencies using Gradle or Android Studio. To build WCDB Android library, run Gradle on `android` directory: ```bash $ cd android $ ./gradlew build ``` Building WCDB requires Android NDK installed. If Gradle failed to find your SDK and/or NDK, you may need to create a file named `local.properties` on the `android` directory with content: ``` sdk.dir=path/to/sdk ndk.dir=path/to/ndk ``` Android Studio will do this for you when the project is imported. ### Build Dependencies from Sources WCDB depends on OpenSSL crypto library and SQLCipher. You can rebuild all dependencies if you wish. In this case, a working C compiler on the host system, Perl 5, Tcl and a bash environment is needed to be installed on your system. To build dependencies, checkout all submodules, set `ANDROID_NDK_ROOT` environment variable to your NDK path, then run `build-depends-android.sh`: ```bash $ export ANDROID_NDK_ROOT=/path/to/ndk $ ./build-depends-android.sh ``` This will build OpenSSL crypto library and generate SQLCipher amalgamation sources and place them to proper locations suitable for WCDB library building. ## Documentations * Documentations can be found on [our Wiki][wcdb-wiki]. * API references for Android can be found [here][wcdb-docs-android]. ## Contributing If you are interested in contributing, check out the [CONTRIBUTING.md], also join our [Tencent OpenSource Plan](https://opensource.tencent.com/contribution). [install-carthage]: https://github.com/Carthage/Carthage#installing-carthage [wcdb-wiki]: https://github.com/Tencent/wcdb/wiki [wcdb-docs-ios]: https://tencent.github.io/wcdb/references/ios/index.html [wcdb-docs-android]: https://tencent.github.io/wcdb/references/android/index.html [sqlcipher]: https://github.com/sqlcipher/sqlcipher [room]: https://developer.android.com/topic/libraries/architecture/room [room-codelabs]: https://codelabs.developers.google.com/codelabs/android-room-with-a-view [iOS-tutorial]: https://github.com/Tencent/wcdb/wiki/iOS-macOS-Tutorial [Benchmark-iOS]: https://github.com/Tencent/wcdb/wiki/WCDB-iOS-benchmark ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Column.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct Column: Describable { public private(set) var description: String public static let all: Column = Column(named: "*") public static let rowid: Column = Column(named: "rowid") public init(named name: String) { description = name } public func `in`(table: String) -> Column { return Column(named: "\(table).\(description)") } } extension Column: ColumnConvertible, ExpressionOperable { public func asColumn() -> Column { return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/ColumnDef.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class ColumnDef: Describable { public private(set) var description: String public init(with columnConvertible: ColumnConvertible, and optionalType: ColumnType? = nil) { description = columnConvertible.asColumn().description if let type = optionalType { description.append(" \(type.description)") } } @discardableResult public func makePrimary(orderBy term: OrderTerm? = nil, isAutoIncrement: Bool = false, onConflict conflict: Conflict? = nil) -> ColumnDef { description.append(" PRIMARY KEY") if term != nil { description.append(" \(term!.description)") } if isAutoIncrement { description.append(" AUTOINCREMENT") } if conflict != nil { description.append(" ON CONFLICT \(conflict!.description)") } return self } public enum DefaultType: Describable { case null case int32(Int32) case int64(Int64) case bool(Bool) case text(String) case float(Double) case BLOB(Data) case expression(Expression) case currentTime case currentDate case currentTimestamp public var description: String { switch self { case .null: return "NULL" case .int32(let value): return LiteralValue(value).description case .int64(let value): return LiteralValue(value).description case .bool(let value): return LiteralValue(value).description case .text(let value): return LiteralValue(value).description case .float(let value): return LiteralValue(value).description case .BLOB(let value): return LiteralValue(value).description case .expression(let value): return value.description case .currentDate: return "CURRENT_DATE" case .currentTime: return "CURRENT_TIME" case .currentTimestamp: return "CURRENT_TIMESTAMP" } } } @discardableResult public func makeDefault(to defaultValue: DefaultType) -> ColumnDef { description.append(" DEFAULT \(defaultValue.description)") return self } @discardableResult public func makeNotNull() -> ColumnDef { description.append(" NOT NULL") return self } @discardableResult public func makeUnique() -> ColumnDef { description.append(" UNIQUE") return self } @discardableResult public func makeForeignKey(_ foreignKey: ForeignKey) -> ColumnDef { description.append(" \(foreignKey.description)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/ColumnIndex.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct ColumnIndex: Describable { public private(set) var description: String public init(with columnConvertible: ColumnConvertible, orderBy term: OrderTerm? = nil) { description = columnConvertible.asColumn().description if let wrappedTerm = term { description.append(" \(wrappedTerm.description)") } } public init(with expressionConvertible: ExpressionConvertible, orderBy term: OrderTerm? = nil) { description = expressionConvertible.asExpression().description if let wrappedTerm = term { description.append(" \(wrappedTerm.description)") } } } extension ColumnIndex: ColumnIndexConvertible { public func asIndex() -> ColumnIndex { return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/ColumnResult.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class ColumnResult: Describable { public private(set) var description: String public init(with expressionConvertible: ExpressionConvertible) { description = expressionConvertible.asExpression().description } @discardableResult public func `as`(_ alias: String) -> ColumnResult { description.append(" AS " + alias) return self } } extension ColumnResult: ColumnResultConvertible { public func asColumnResult() -> ColumnResult { return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/ColumnType.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public enum ColumnType: Describable { case integer32 case integer64 case text case float case BLOB case null public var description: String { switch self { case .integer32: fallthrough case .integer64: return "INTEGER" case .float: return "REAL" case .text: return "TEXT" case .BLOB: return "BLOB" case .null: return "NULL" } } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Conflict.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public enum Conflict: Describable { case rollback case abort case fail case ignore case replace public var description: String { switch self { case .rollback: return "ROLLBACK" case .abort: return "ABORT" case .fail: return "FAIL" case .ignore: return "IGNORE" case .replace: return "REPLACE" } } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Convertible.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public protocol OrderConvertible { func asOrder() -> Order } public protocol SpecificOrderConvertible: OrderConvertible { func asOrder(by term: OrderTerm?) -> Order } extension SpecificOrderConvertible { public func asOrder() -> Order { return asOrder(by: nil) } } public protocol SpecificColumnDefConvertible { func asDef(with columnType: ColumnType?) -> ColumnDef } public protocol ColumnIndexConvertible { func asIndex() -> ColumnIndex } public protocol SpecificColumnIndexConvertible: ColumnIndexConvertible { func asIndex(orderBy term: OrderTerm?) -> ColumnIndex } extension SpecificColumnIndexConvertible { public func asIndex() -> ColumnIndex { return asIndex(orderBy: nil) } } public protocol ColumnResultConvertible { func asColumnResult() -> ColumnResult } public protocol TableOrSubqueryConvertible { func asTableOrSubquery() -> Subquery } public protocol ExpressionConvertible: ColumnResultConvertible, SpecificOrderConvertible { func asExpression() -> Expression } extension ExpressionConvertible { public func asColumnResult() -> ColumnResult { return ColumnResult(with: asExpression()) } public func asOrder(by term: OrderTerm?) -> Order { return Order(with: asExpression(), by: term) } } public protocol ColumnConvertible: ExpressionConvertible, SpecificColumnIndexConvertible, SpecificColumnDefConvertible { func asColumn() -> Column func `in`(table: String) -> Column } extension ColumnConvertible { public func asExpression() -> Expression { return Expression(with: self) } public func asIndex(orderBy term: OrderTerm?) -> ColumnIndex { return ColumnIndex(with: self, orderBy: term) } public func asDef(with columnType: ColumnType? = nil) -> ColumnDef { return ColumnDef(with: self, and: columnType) } public func `in`(table: String) -> Column { return asColumn().in(table: table) } } public protocol LiteralValueConvertible: ExpressionConvertible { func asLiteralValue() -> LiteralValue } extension LiteralValueConvertible { public func asExpression() -> Expression { return Expression(with: self) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Describable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation //TODO: Refactor: change all winq classes into structs public typealias Describable = CustomStringConvertible ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Expression.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct Expression: Describable { public private(set) var description: String public static let bindParameter = Expression(withRaw: "?") public init(with columnConvertible: ColumnConvertible) { description = columnConvertible.asColumn().description } public init(with literalValueConvertible: LiteralValueConvertible) { description = literalValueConvertible.asLiteralValue().description } public init(with statementSelect: StatementSelect) { description = statementSelect.description } init(withRaw raw: String) { description = raw } } extension Expression: ExpressibleByNilLiteral { public init(nilLiteral value: ()) { self.init(with: LiteralValue(value)) } } extension Expression: ExpressibleByBooleanLiteral { public init(booleanLiteral value: Bool) { self.init(with: LiteralValue(value)) } } extension Expression: ExpressibleByIntegerLiteral { public init(integerLiteral value: Int) { self.init(with: LiteralValue(value)) } } extension Expression: ExpressibleByFloatLiteral { public init(floatLiteral value: Double) { self.init(with: LiteralValue(value)) } } extension Expression: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self.init(with: LiteralValue(value)) } } extension Expression: ExpressionOperable { public static func exists(_ statementSelect: StatementSelect) -> Expression { return Expression.operate(prefix: "EXISTS ", operand: statementSelect) } public static func notExists(_ statementSelect: StatementSelect) -> Expression { return Expression.operate(prefix: "NOT EXISTS ", operand: statementSelect) } public static func combine(_ expressionConvertibleList: ExpressionConvertible...) -> Expression { return combine(expressionConvertibleList) } public static func combine(_ expressionConvertibleList: [ExpressionConvertible]) -> Expression { return Expression(withRaw: "(\(expressionConvertibleList.joined()))") } //Function public static func function(named name: String, _ expressions: ExpressionConvertible..., isDistinct: Bool = false) -> Expression { return function(named: name, expressions, isDistinct: isDistinct) } public static func function(named name: String, _ expressions: [ExpressionConvertible], isDistinct: Bool = false) -> Expression { return Expression.operate(title: name, infix: isDistinct ? "DISTINCT" : nil, operands: expressions) } public static func `case`(_ expressionConvertible: ExpressionConvertible, _ flows: (when: ExpressionConvertible, then: ExpressionConvertible)..., `else`: ExpressionConvertible) -> Expression { return `case`(expressionConvertible.asExpression(), flows, else: `else`.asExpression()) } public static func `case`(_ `case`: ExpressionConvertible, _ flows: [(when: ExpressionConvertible, then: ExpressionConvertible)], `else`: ExpressionConvertible) -> Expression { var descrption = "CASE \(`case`.asExpression().description) " descrption.append(flows.joined({ "WHEN \($0.when) THEN \($0.then) " })) descrption.append("ELSE \(`else`.asExpression().description) END") return Expression(withRaw: descrption) } public func asExpression() -> Expression { return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/ForeignKey.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class ForeignKey: Describable { public private(set) var description: String public init(withForeignTable table: String, and columnConvertibleList: [ColumnConvertible]) { description = "REFERENCES \(table)" if !columnConvertibleList.isEmpty { description.append("(\(columnConvertibleList.joined(separateBy: ", ")))") } } public convenience init(withForeignTable table: String, and columnConvertibleList: ColumnConvertible...) { self.init(withForeignTable: table, and: columnConvertibleList) } public enum Action: Describable { case setNull case setDefault case cascade case restrict case noAction public var description: String { switch self { case .setNull: return "SET NULL" case .setDefault: return "SET DEFAULT" case .cascade: return "CASCADE" case .restrict: return "RESTRICT" case .noAction: return "NO ACTION" } } } @discardableResult public func onDelete(_ action: Action) -> ForeignKey { description.append(" ON DELETE \(action.description)") return self } @discardableResult public func onUpdate(_ action: Action) -> ForeignKey { description.append(" ON UPDATE \(action.description)") return self } @discardableResult public func match(name: String) -> ForeignKey { description.append(" MATCH \(name)") return self } public enum Deferrable: Describable { case deferred case immediate public var description: String { switch self { case .deferred: return "INITIALLY DEFERRED" case .immediate: return "INITIALLY IMMEDIATE" } } } @discardableResult public func deferrable(_ deferrable: Deferrable) -> ForeignKey { description.append(" DEFERRABLE \(deferrable.description)") return self } @discardableResult public func notDeferrable(_ deferrable: Deferrable) -> ForeignKey { description.append(" NOT DEFERRABLE \(deferrable.description)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/FundamentalValue.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation // nullable, Int32, Int64, Double, String, Data public struct FundamentalValue { private let base: Any? public let type: ColumnType public init(_ _: Void? = nil) { base = nil type = .null } public init(_ value: Int32) { base = value type = .integer32 } public init(_ value: Int64) { base = value type = .integer64 } public init(_ value: Double) { base = value type = .float } public init(_ value: String) { base = value type = .text } public init(_ value: Data) { base = value type = .BLOB } public init(_ encodedValue: T) { self = encodedValue.archivedValue() } public var int32Value: Int32 { switch type { case .integer32: return base as! Int32 case .integer64: return Int32(truncatingIfNeeded: base as! Int64) case .float: return Int32(base as! Double) case .text: return Int32(base as! String) ?? 0 default: return 0 } } public var int64Value: Int64 { switch type { case .integer32: return Int64(base as! Int32) case .integer64: return base as! Int64 case .float: return Int64(base as! Double) case .text: return Int64(base as! String) ?? 0 default: return 0 } } public var stringValue: String { switch type { case .integer32: return "\(base as! Int32)" case .integer64: return "\(base as! Int64)" case .float: return "\(base as! Double)" case .text: return base as! String case .BLOB: return String(data: base as! Data, encoding: .utf8) ?? "" default: return "" } } public var doubleValue: Double { switch type { case .integer32: return Double(base as! Int32) case .integer64: return Double(base as! Int64) case .float: return base as! Double case .text: return Double(base as! String) ?? 0 default: return 0 } } public var dataValue: Data { switch type { case .integer32: fallthrough case .integer64: fallthrough case .float: fallthrough case .text: return stringValue.data(using: .utf8) ?? Data() case .BLOB: return (base as? Data) ?? Data() default: return Data() } } } public typealias FundamentalColumn = [FundamentalValue] public typealias FundamentalRow = [FundamentalValue] public typealias FundamentalRowXColumn = [FundamentalRow] extension Array where Element==[FundamentalValue] { public subscript(row row: Array.Index, column column: Array.Index) -> FundamentalValue { return self[row][column] } public subscript(row row: Array.Index) -> FundamentalColumn { return self[row] } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Handle.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public typealias Tag = Int public final class Handle { private var handle: SQLite3? public let path: String public internal(set) var tag: Tag? = nil { didSet { tracer?.userInfo = tag } } typealias CommittedHook = (Handle, Int, Void?) -> Void private struct CommittedHookInfo { var onCommitted: CommittedHook weak var handle: Handle? } private var committedHookInfo: CommittedHookInfo? private var tracer: Tracer? init(withPath path: String) { DispatchQueue.once(name: "com.tencent.wcdb.handle") { sqlite3_config_multithread() sqlite3_config_memstatus(Int32(truncating: false)) sqlite3_config_log({ (_, code, message) in let msg = (message != nil) ? String(cString: message!) : "" Error.reportSQLiteGlobal(code: Int(code), message: msg) }, nil) } self.path = path } deinit { try? close() } func open() throws { let directory = URL(fileURLWithPath: path).deletingLastPathComponent().path try File.createDirectoryWithIntermediateDirectories(atPath: directory) let rc = sqlite3_open(path, &handle) guard rc == SQLITE_OK else { throw Error.reportSQLite(tag: tag, path: path, operation: .open, code: rc, message: String(cString: sqlite3_errmsg(handle)) ) } } func close() throws { let rc = sqlite3_close(handle) guard rc == SQLITE_OK else { throw Error.reportSQLite(tag: tag, path: path, operation: .close, code: rc, message: String(cString: sqlite3_errmsg(handle)) ) } handle = nil } public func prepare(_ statement: Statement) throws -> HandleStatement { assert(statement.statementType != .transaction, "[prepare] a transaction is not allowed, use [exec] instead") var stmt: OpaquePointer? = nil let rc = sqlite3_prepare_v2(handle, statement.description, -1, &stmt, nil) guard rc==SQLITE_OK else { throw Error.reportSQLite(tag: tag, path: path, operation: .prepare, extendedError: sqlite3_extended_errcode(handle), sql: statement.description, code: rc, message: String(cString: sqlite3_errmsg(handle)) ) } return HandleStatement(with: stmt!, and: self) } public func exec(_ statement: Statement) throws { let rc = sqlite3_exec(handle, statement.description, nil, nil, nil) let result = rc == SQLITE_OK if let tracer = self.tracer { if statement.statementType == .transaction, let statementTransaction = statement as? StatementTransaction, let transactionType = statementTransaction.transactionType { switch transactionType { case .begin: if result { tracer.shouldAggregation = true } case .commit: if result { tracer.shouldAggregation = false } case .rollback: tracer.shouldAggregation = false } } } guard result else { throw Error.reportSQLite(tag: tag, path: path, operation: .exec, extendedError: sqlite3_extended_errcode(handle), sql: statement.description, code: rc, message: String(cString: sqlite3_errmsg(handle)) ) } } public var changes: Int { return Int(sqlite3_changes(handle)) } public var isReadonly: Bool { return sqlite3_db_readonly(handle, nil)==1 } } //Cipher extension Handle { public func setCipher(key: Data) throws { let rc = key.withUnsafeBytes ({ (bytes: UnsafePointer) -> Int32 in return sqlite3_key(handle, bytes, Int32(key.count)) }) guard rc == SQLITE_OK else { throw Error.reportSQLite(tag: tag, path: path, operation: .setCipherKey, extendedError: sqlite3_extended_errcode(handle), code: rc, message: String(cString: sqlite3_errmsg(handle)) ) } } } //Repair extension Handle { public static let backupSubfix = "-backup" public var backupPath: String { return path+Handle.backupSubfix } public func backup(withKey optionalKey: Data? = nil) throws { var rc = SQLITE_OK if let key = optionalKey { key.withUnsafeBytes { (bytes: UnsafePointer) -> Void in rc = sqliterk_save_master(handle, backupPath, bytes, Int32(key.count)) } } else { rc = sqliterk_save_master(handle, backupPath, nil, 0) } guard rc == SQLITERK_OK else { throw Error.reportRepair(path: path, operation: .saveMaster, code: Int(rc)) } } public func recover(fromPath source: String, withPageSize pageSize: Int32, databaseKey optionalDatabaseKey: Data? = nil, backupKey optionalBackupKey: Data? = nil) throws { var rc = SQLITERK_OK let backupPath = source+Handle.backupSubfix let kdfSalt = UnsafeMutablePointer.allocate(capacity: 16) memset(kdfSalt, 0, 16) var info: OpaquePointer? = nil let backupSize: Int32 = Int32(optionalBackupKey?.count ?? 0) if let backupKey = optionalBackupKey { backupKey.withUnsafeBytes({ (bytes: UnsafePointer) -> Void in rc = sqliterk_load_master(backupPath, bytes, backupSize, nil, 0, &info, kdfSalt) }) }else { rc = sqliterk_load_master(backupPath, nil, backupSize, nil, 0, &info, kdfSalt) } guard rc == SQLITERK_OK else { throw Error.reportRepair(path: backupPath, operation: .repair, code: Int(rc)) } var conf = sqliterk_cipher_conf() conf.page_size = pageSize conf.kdf_salt = UnsafePointer(kdfSalt) conf.use_hmac = 1 typealias RepairKit = OpaquePointer var rk: RepairKit? = nil let databaseSize: Int32 = Int32(optionalDatabaseKey?.count ?? 0) if let databaseKey = optionalDatabaseKey { databaseKey.withUnsafeBytes({ (bytes: UnsafePointer) -> Void in sqliterk_cipher_conf_set_key(&conf, bytes, databaseSize) rc = sqliterk_open(source, &conf, &rk) }) }else { sqliterk_cipher_conf_set_key(&conf, nil, 0) rc = sqliterk_open(source, &conf, &rk) } guard rc == SQLITERK_OK else { throw Error.reportRepair(path: source, operation: .repair, code: Int(rc)) } rc = sqliterk_output(rk, handle, info, UInt32(SQLITERK_OUTPUT_ALL_TABLES)) guard rc == SQLITERK_OK else { throw Error.reportRepair(path: source, operation: .repair, code: Int(rc)) } } } extension Handle { public static let subfixs: [String] = ["", "-wal", "-journal", "-shm", Handle.backupSubfix] } extension Handle { public typealias SQLTracer = (String) -> Void func lazyTracer() -> Tracer? { if tracer == nil && handle != nil { tracer = Tracer(with: handle!) } return tracer } func trace(sql sqlTracer: @escaping SQLTracer) { lazyTracer()?.trace(sql: sqlTracer) } public typealias PerformanceTracer = (Tag?, [String: Int], Int64) -> Void // Tag?, (SQL, count), cost func trace(performance performanceTracer: @escaping PerformanceTracer) { lazyTracer()?.track(performance: { (sqls, cost, userInfo) in performanceTracer(userInfo as? Tag, sqls, cost) }) } } //Commit hook extension Handle { func register(onCommitted: @escaping CommittedHook) { committedHookInfo = CommittedHookInfo(onCommitted: onCommitted, handle: self) sqlite3_wal_hook(handle, { (pointer, _, _, pages) -> Int32 in let committedHookInfo = pointer!.assumingMemoryBound(to: CommittedHookInfo.self).pointee committedHookInfo.onCommitted(committedHookInfo.handle!, Int(pages), nil) return SQLITE_OK }, &committedHookInfo) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/HandleStatement.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class HandleStatement { private var stmt: SQLite3Statement? private let handle: Handle public var path: String { return handle.path } public var tag: Tag? { return handle.tag } init(with stmt: SQLite3Statement, and handle: Handle) { self.stmt = stmt self.handle = handle } deinit { try? finalize() } @discardableResult public func step() throws -> Bool { let rc = sqlite3_step(stmt) guard rc==SQLITE_OK || rc==SQLITE_DONE || rc==SQLITE_ROW else { let dbHandle = sqlite3_db_handle(stmt) throw Error.reportSQLite(tag: handle.tag, path: handle.path, operation: .step, extendedError: sqlite3_extended_errcode(dbHandle), sql: String(cString: sqlite3_sql(stmt)), code: rc, message: String(cString: sqlite3_errmsg(dbHandle)) ) } return rc==SQLITE_ROW } public func reset() throws { let rc = sqlite3_reset(stmt) guard rc==SQLITE_OK else { let dbHandle = sqlite3_db_handle(stmt) throw Error.reportSQLite(tag: handle.tag, path: handle.path, operation: .finalize, extendedError: sqlite3_extended_errcode(dbHandle), code: rc, message: String(cString: sqlite3_errmsg(dbHandle)) ) } } public var changes: Int { return Int(sqlite3_changes(sqlite3_db_handle(stmt))) } public func bind(_ value: FundamentalValue, toIndex index: Int) { switch value.type { case .integer32: sqlite3_bind_int(stmt, Int32(index), value.int32Value) case .integer64: sqlite3_bind_int64(stmt, Int32(index), value.int64Value) case .float: sqlite3_bind_double(stmt, Int32(index), value.doubleValue) case .text: sqlite3_bind_text_transient(stmt, Int32(index), value.stringValue, -1) case .BLOB: let data = value.dataValue data.withUnsafeBytes ({ (bytes: UnsafePointer) -> Void in sqlite3_bind_blob_transient(stmt, Int32(index), bytes, Int32(data.count)) }) case .null: sqlite3_bind_null(stmt, Int32(index)) } } public func bind(_ value: Int32, toIndex index: Int) { sqlite3_bind_int(stmt, Int32(index), value) } public func bind(_ value: Int64, toIndex index: Int) { sqlite3_bind_int64(stmt, Int32(index), value) } public func bind(_ value: Double, toIndex index: Int) { sqlite3_bind_double(stmt, Int32(index), value) } public func bind(_ value: String, toIndex index: Int) { sqlite3_bind_text_transient(stmt, Int32(index), value, -1) } public func bind(_ value: Data, toIndex index: Int) { value.withUnsafeBytes ({ (bytes: UnsafePointer) -> Void in sqlite3_bind_blob_transient(stmt, Int32(index), bytes, Int32(value.count)) }) } public func bind(_ _: Void?, toIndex index: Int) { sqlite3_bind_null(stmt, Int32(index)) } public func columnValue(atIndex index: Int) -> FundamentalValue { switch columnType(atIndex: index) { case .integer32: return FundamentalValue(columnValue(atIndex: index, of: Int32.self)) case .integer64: return FundamentalValue(columnValue(atIndex: index, of: Int64.self)) case .float: return FundamentalValue(columnValue(atIndex: index, of: Double.self)) case .text: return FundamentalValue(columnValue(atIndex: index, of: String.self)) case .BLOB: return FundamentalValue(columnValue(atIndex: index, of: Data.self)) case .null: return FundamentalValue(nil) } } public func columnValue(atIndex index: Int, of type: Int32.Type = Int32.self) -> Int32 { return sqlite3_column_int(stmt, Int32(index)) } public func columnValue(atIndex index: Int, of type: Int64.Type = Int64.self) -> Int64 { return sqlite3_column_int64(stmt, Int32(index)) } public func columnValue(atIndex index: Int, of type: Double.Type = Double.self) -> Double { return sqlite3_column_double(stmt, Int32(index)) } public func columnValue(atIndex index: Int, of type: String.Type = String.self) -> String { guard let cString = sqlite3_column_text(stmt, Int32(index)) else { return "" } return String(cString: cString) } public func columnValue(atIndex index: Int, of type: Data.Type = Data.self) -> Data { guard let bytes = sqlite3_column_blob(stmt, Int32(index)) else { return Data() } let count = Int(sqlite3_column_bytes(stmt, Int32(index))) return Data(bytes: bytes, count: count) } public func columnCount() -> Int { return Int(sqlite3_column_count(stmt)) } public func columnName(atIndex index: Int) -> String { return String(cString: sqlite3_column_name(stmt, Int32(index))) } public func columnTableName(atIndex index: Int) -> String { return String(cString: sqlite3_column_table_name(stmt, Int32(index))) } public func columnType(atIndex index: Int) -> ColumnType { switch sqlite3_column_type(stmt, Int32(index)) { case SQLITE_INTEGER: return ColumnType.integer64 case SQLITE_FLOAT: return ColumnType.float case SQLITE_BLOB: return ColumnType.BLOB case SQLITE_TEXT: return ColumnType.text default: return ColumnType.null } } public var lastInsertedRowID: Int64 { return sqlite3_last_insert_rowid(sqlite3_db_handle(stmt)) } public func finalize() throws { if stmt != nil { let dbHandle = sqlite3_db_handle(stmt) let rc = sqlite3_finalize(stmt) stmt = nil guard rc==SQLITE_OK else { throw Error.reportSQLite(tag: handle.tag, path: handle.path, operation: .finalize, extendedError: sqlite3_extended_errcode(dbHandle), code: rc, message: String(cString: sqlite3_errmsg(dbHandle)) ) } } } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/JoinClause.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class JoinClause: Describable { public private(set) var description: String public enum JoinClauseType: Describable { case left case leftOuter case inner case cross public var description: String { switch self { case .left: return "LEFT" case .leftOuter: return "LEFT OUTER" case .inner: return "INNER" case .cross: return "CROSS" } } } public init(with subqueryConvertible: TableOrSubqueryConvertible) { description = subqueryConvertible.asTableOrSubquery().description } @discardableResult private func join(_ subqueryConvertible: TableOrSubqueryConvertible, with type: JoinClauseType? = nil, isNatural: Bool = false) -> JoinClause { if isNatural { description.append(" NATURAL") } if type != nil { description.append(" \(type!.description)") } description.append(" JOIN \(subqueryConvertible.asTableOrSubquery().description)") return self } @discardableResult public func join(_ subqueryConvertible: TableOrSubqueryConvertible, with type: JoinClauseType? = nil) -> JoinClause { return self.join(subqueryConvertible, with: type, isNatural: false) } @discardableResult public func natureJoin(_ subqueryConvertible: TableOrSubqueryConvertible, with type: JoinClauseType? = nil) -> JoinClause { return self.join(subqueryConvertible, with: type, isNatural: true) } @discardableResult public func on(_ expressionConvertible: ExpressionConvertible) -> JoinClause { description.append(" ON \(expressionConvertible.asExpression().description)") return self } @discardableResult public func using(_ columnConvertibleList: ColumnConvertible...) -> JoinClause { return using(columnConvertibleList) } @discardableResult public func using(_ columnConvertibleList: [ColumnConvertible]) -> JoinClause { description.append(" USING \(columnConvertibleList.joined())") return self } } extension JoinClause: TableOrSubqueryConvertible { public func asTableOrSubquery() -> Subquery { return Subquery(with: self) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/LiteralValue.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct LiteralValue: Describable { public private(set) var description: String public init(_ value: Int32) { description = String(value) } public init(_ value: Int64) { description = String(value) } public init(_ value: Bool) { description = String(value ? 1 : 0) } public init(_ value: Double) { description = String(value) } public init(_ value: String) { description = "'\(value.replacingOccurrences(of: "'", with: "''"))'" } public init(_ value: Data) { let string = String(data: value, encoding: .utf8) ?? "" description = "'\(string.replacingOccurrences(of: "'", with: "''"))'" } public init(_ value: Void?) { description = "NULL" } public init(_ value: FundamentalValue) { switch value.type { case .integer32: self.init(value.int32Value) case .integer64: self.init(value.int64Value) case .float: self.init(value.doubleValue) case .text: self.init(value.stringValue) case .BLOB: self.init(value.dataValue) case .null: self.init(nil) } } public init(_ encodedValue: T) { self.init(encodedValue.archivedValue()) } } extension LiteralValue: ExpressibleByNilLiteral { public init(nilLiteral: ()) { self.init(nilLiteral) } } extension LiteralValue: ExpressibleByIntegerLiteral { public init(integerLiteral value: Int) { self.init(value) } } extension LiteralValue: ExpressibleByBooleanLiteral { public init(booleanLiteral value: Bool) { self.init(value) } } extension LiteralValue: ExpressibleByFloatLiteral { public init(floatLiteral value: Double) { self.init(value) } } extension LiteralValue: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self.init(value) } } extension LiteralValue: LiteralValueConvertible { public func asLiteralValue() -> LiteralValue { return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/ModuleArgument.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct ModuleArgument: Describable { public private(set) var description: String public init(with tableConstraint: TableConstraint) { description = tableConstraint.description } public init(with columnDef: ColumnDef) { description = columnDef.description } public init(left: String, right: String) { description = "\(left)=\(right)" } public init(with tokenize: Tokenize) { self.init(left: "tokenize", right: tokenize.module.name) } public init(withTokenize tokenize: String) { self.init(left: "tokenize", right: tokenize) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Operable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation extension ExpressionConvertible { static func operate(prefix: String, operand: ExpressionConvertible) -> Expression { return Expression(withRaw: "(\(prefix)\(operand.asExpression().description))") } static func operate(title: String, infix: String?, operands: [ExpressionConvertible]) -> Expression { return Expression(withRaw: "\(title)(\(infix != nil ? infix!+" " : "")\(operands.joined()))") } static func operate(operand: ExpressionConvertible, postfix: String) -> Expression { return Expression(withRaw: "(\(operand.asExpression().description) \(postfix))") } static func operate(left: ExpressionConvertible, `operator`: String, right: ExpressionConvertible) -> Expression { let leftDescription = left.asExpression().description let rightDescription = right.asExpression().description return Expression(withRaw: "(\(leftDescription) \(`operator`) \(rightDescription))") } static func operate(one: ExpressionConvertible, operator1: String, two: ExpressionConvertible, operator2: String, three: ExpressionConvertible) -> Expression { let oneDescription = one.asExpression().description let twoDescription = two.asExpression().description let threeDescription = three.asExpression().description let raws = [oneDescription, operator1, twoDescription, operator2, threeDescription] return Expression(withRaw: "(\(raws.joined(separateBy: " ")))") } } public protocol ExpressionCanBeOperated: ExpressionConvertible { static func || ( left: Self, right: ExpressionOperableType) -> Expression static func && ( left: Self, right: ExpressionOperableType) -> Expression static func * ( left: Self, right: ExpressionOperableType) -> Expression static func / ( left: Self, right: ExpressionOperableType) -> Expression static func % ( left: Self, right: ExpressionOperableType) -> Expression static func + ( left: Self, right: ExpressionOperableType) -> Expression static func - ( left: Self, right: ExpressionOperableType) -> Expression static func << ( left: Self, right: ExpressionOperableType) -> Expression static func >> ( left: Self, right: ExpressionOperableType) -> Expression static func & ( left: Self, right: ExpressionOperableType) -> Expression static func | ( left: Self, right: ExpressionOperableType) -> Expression static func < ( left: Self, right: ExpressionOperableType) -> Expression static func <= ( left: Self, right: ExpressionOperableType) -> Expression static func > ( left: Self, right: ExpressionOperableType) -> Expression static func >= ( left: Self, right: ExpressionOperableType) -> Expression static func == ( left: Self, right: ExpressionOperableType) -> Expression static func != ( left: Self, right: ExpressionOperableType) -> Expression } extension ExpressionCanBeOperated { public static func || ( left: Self, right: ExpressionOperableType) -> Expression { return Expression.operate(left: left, operator: "OR", right: right) } public static func && ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "AND", right: right) } public static func * ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "*", right: right) } public static func / ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "/", right: right) } public static func % ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "%", right: right) } public static func + ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "+", right: right) } public static func - ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "-", right: right) } public static func << ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "<<", right: right) } public static func >> ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: ">>", right: right) } public static func & ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "&", right: right) } public static func | ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "|", right: right) } public static func < ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "<", right: right) } public static func <= ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "<=", right: right) } public static func > ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: ">", right: right) } public static func >= ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: ">=", right: right) } public static func == ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "=", right: right) } public static func != ( left: Self, right: ExpressionOperableType) -> Expression { return operate(left: left, operator: "!=", right: right) } } public protocol ExpressionOperable: ExpressionCanBeOperated { //Unary prefix static func ! (operand: Self) -> Expression prefix static func + (operand: Self) -> Expression prefix static func - (operand: Self) -> Expression prefix static func ~ (operand: Self) -> Expression //Binary static func || ( left: Self, right: ExpressionConvertibleType) -> Expression static func && ( left: Self, right: ExpressionConvertibleType) -> Expression static func * (left: Self, right: ExpressionConvertibleType) -> Expression static func / ( left: Self, right: ExpressionConvertibleType) -> Expression static func % ( left: Self, right: ExpressionConvertibleType) -> Expression static func + ( left: Self, right: ExpressionConvertibleType) -> Expression static func - ( left: Self, right: ExpressionConvertibleType) -> Expression static func << ( left: Self, right: ExpressionConvertibleType) -> Expression static func >> ( left: Self, right: ExpressionConvertibleType) -> Expression static func & ( left: Self, right: ExpressionConvertibleType) -> Expression static func | ( left: Self, right: ExpressionConvertibleType) -> Expression static func < ( left: Self, right: ExpressionConvertibleType) -> Expression static func <= ( left: Self, right: ExpressionConvertibleType) -> Expression static func > ( left: Self, right: ExpressionConvertibleType) -> Expression static func >= ( left: Self, right: ExpressionConvertibleType) -> Expression static func == ( left: Self, right: ExpressionConvertibleType) -> Expression static func != ( left: Self, right: ExpressionConvertibleType) -> Expression func concat(_ operand: ExpressionConvertible) -> Expression func substr(start: ExpressionConvertible, length: ExpressionConvertible) -> Expression func like(_ operand: ExpressionConvertible) -> Expression func glob(_ operand: ExpressionConvertible) -> Expression func match(_ operand: ExpressionConvertible) -> Expression func regexp(_ operand: ExpressionConvertible) -> Expression func notLike(_ operand: ExpressionConvertible) -> Expression func notGlob(_ operand: ExpressionConvertible) -> Expression func notMatch(_ operand: ExpressionConvertible) -> Expression func notRegexp(_ operand: ExpressionConvertible) -> Expression func like(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression func glob(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression func match(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression func regexp(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression func notLike(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression func notGlob(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression func notMatch(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression func notRegexp(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression func isNull() -> Expression func isNotNull() -> Expression func `is`(_ operand: ExpressionConvertible) -> Expression func isNot(_ operand: ExpressionConvertible) -> Expression func between(_ begin: ExpressionConvertible, _ end: ExpressionConvertible) -> Expression func notBetween(_ begin: ExpressionConvertible, _ end: ExpressionConvertible) -> Expression func `in`(_ statementSelect: StatementSelect) -> Expression func notIn(_ statementSelect: StatementSelect) -> Expression func `in`(_ expressionConvertibleList: ExpressionConvertible...) -> Expression func notIn(_ expressionConvertibleList: ExpressionConvertible...) -> Expression func `in`(_ expressionConvertibleList: [ExpressionConvertible]) -> Expression func notIn(_ expressionConvertibleList: [ExpressionConvertible]) -> Expression //aggregate functions func avg(isDistinct: Bool) -> Expression func count(isDistinct: Bool) -> Expression func groupConcat(isDistinct: Bool) -> Expression func groupConcat(isDistinct: Bool, separateBy seperator: String) -> Expression func max(isDistinct: Bool) -> Expression func min(isDistinct: Bool) -> Expression func sum(isDistinct: Bool) -> Expression func total(isDistinct: Bool) -> Expression //core functions func abs(isDistinct: Bool) -> Expression func hex(isDistinct: Bool) -> Expression func length(isDistinct: Bool) -> Expression func lower(isDistinct: Bool) -> Expression func upper(isDistinct: Bool) -> Expression func round(isDistinct: Bool) -> Expression //FTS3 func matchinfo() -> Expression func offsets() -> Expression func snippet() -> Expression } extension ExpressionOperable { //Unary public prefix static func ! (operand: Self) -> Expression { return operate(prefix: "NOT ", operand: operand) } public prefix static func + (operand: Self) -> Expression { return operate(prefix: "", operand: operand) } public prefix static func - (operand: Self) -> Expression { return operate(prefix: "-", operand: operand) } public prefix static func ~ (operand: Self) -> Expression { return operate(prefix: "~", operand: operand) } //Binary public static func || ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "OR", right: right) } public static func && ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "AND", right: right) } public static func * ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "*", right: right) } public static func / ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "/", right: right) } public static func % ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "%", right: right) } public static func + ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "+", right: right) } public static func - ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "-", right: right) } public static func << ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "<<", right: right) } public static func >> ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: ">>", right: right) } public static func & ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "&", right: right) } public static func | ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "|", right: right) } public static func < ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "<", right: right) } public static func <= ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "<=", right: right) } public static func > ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: ">", right: right) } public static func >= ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: ">=", right: right) } public static func == ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "=", right: right) } public static func != ( left: Self, right: ExpressionConvertibleType) -> Expression { return operate(left: left, operator: "!=", right: right) } public func concat(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "||", right: operand) } public func substr(start: ExpressionConvertible, length: ExpressionConvertible) -> Expression { let description = asExpression().description let startDescription = start.asExpression().description let lengthDescription = length.asExpression().description return Expression(withRaw: "SUBSTR(\(description), \(startDescription), \(lengthDescription))") } public func like(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "LIKE", right: operand) } public func glob(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "GLOB", right: operand) } public func match(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "MATCH", right: operand) } public func regexp(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "REGEXP", right: operand) } public func notLike(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "NOT LIKE", right: operand) } public func notGlob(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "NOT GLOB", right: operand) } public func notMatch(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "NOT MATCH", right: operand) } public func notRegexp(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "NOT REGEXP", right: operand) } public func like(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression { return Self.operate(one: self, operator1: "LIKE", two: operand, operator2: "ESCAPE", three: escape) } public func glob(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression { return Self.operate(one: self, operator1: "GLOB", two: operand, operator2: "ESCAPE", three: escape) } public func match(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression { return Self.operate(one: self, operator1: "MATCH", two: operand, operator2: "ESCAPE", three: escape) } public func regexp(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression { return Self.operate(one: self, operator1: "REGEXP", two: operand, operator2: "ESCAPE", three: escape) } public func notLike(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression { return Self.operate(one: self, operator1: "NOT LIKE", two: operand, operator2: "ESCAPE", three: escape) } public func notGlob(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression { return Self.operate(one: self, operator1: "NOT GLOB", two: operand, operator2: "ESCAPE", three: escape) } public func notMatch(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression { return Self.operate(one: self, operator1: "NOT MATCH", two: operand, operator2: "ESCAPE", three: escape) } public func notRegexp(_ operand: ExpressionConvertible, escape: ExpressionConvertible) -> Expression { return Self.operate(one: self, operator1: "NOT REGEXP", two: operand, operator2: "ESCAPE", three: escape) } public func isNull() -> Expression { return Self.operate(operand: self, postfix: "ISNULL") } public func isNotNull() -> Expression { return Self.operate(operand: self, postfix: "NOTNULL") } public func `is`(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "IS", right: operand) } public func isNot(_ operand: ExpressionConvertible) -> Expression { return Self.operate(left: self, operator: "IS NOT", right: operand) } public func between(_ begin: ExpressionConvertible, _ end: ExpressionConvertible) -> Expression { return Self.operate(one: self, operator1: "BETWEEN", two: begin, operator2: "AND", three: end) } public func notBetween(_ begin: ExpressionConvertible, _ end: ExpressionConvertible) -> Expression { return Self.operate(one: self, operator1: "NOT BETWEEN", two: begin, operator2: "AND", three: end) } public func `in`(_ statementSelect: StatementSelect) -> Expression { return Self.operate(prefix: "IN ", operand: statementSelect) } public func notIn(_ statementSelect: StatementSelect) -> Expression { return Self.operate(prefix: "NOT IN ", operand: statementSelect) } public func `in`(_ expressionConvertibleList: ExpressionConvertible...) -> Expression { return self.`in`(expressionConvertibleList) } public func notIn(_ expressionConvertibleList: ExpressionConvertible...) -> Expression { return self.notIn(expressionConvertibleList) } public func `in`(_ expressionConvertibleList: [ExpressionConvertible]) -> Expression { return Self.operate(operand: self, postfix: "IN(\(expressionConvertibleList.joined()))") } public func notIn(_ expressionConvertibleList: [ExpressionConvertible]) -> Expression { return Self.operate(operand: self, postfix: "NOT IN(\(expressionConvertibleList.joined()))") } //aggregate functions public func avg(isDistinct: Bool = false) -> Expression { return Expression.function(named: "AVG", self, isDistinct: isDistinct) } public func count(isDistinct: Bool = false) -> Expression { return Expression.function(named: "COUNT", self, isDistinct: isDistinct) } public func groupConcat(isDistinct: Bool = false) -> Expression { return Expression.function(named: "GROUP_CONCAT", self, isDistinct: isDistinct) } public func groupConcat(isDistinct: Bool = false, separateBy seperator: String) -> Expression { return Expression.function(named: "GROUP_CONCAT", self, seperator, isDistinct: isDistinct) } public func max(isDistinct: Bool = false) -> Expression { return Expression.function(named: "MAX", self, isDistinct: isDistinct) } public func min(isDistinct: Bool = false) -> Expression { return Expression.function(named: "MIN", self, isDistinct: isDistinct) } public func sum(isDistinct: Bool = false) -> Expression { return Expression.function(named: "SUM", self, isDistinct: isDistinct) } public func total(isDistinct: Bool = false) -> Expression { return Expression.function(named: "TOTAL", self, isDistinct: isDistinct) } //core functions public func abs(isDistinct: Bool = false) -> Expression { return Expression.function(named: "ABS", self, isDistinct: isDistinct) } public func hex(isDistinct: Bool = false) -> Expression { return Expression.function(named: "HEX", self, isDistinct: isDistinct) } public func length(isDistinct: Bool = false) -> Expression { return Expression.function(named: "LENGTH", self, isDistinct: isDistinct) } public func lower(isDistinct: Bool = false) -> Expression { return Expression.function(named: "LOWER", self, isDistinct: isDistinct) } public func upper(isDistinct: Bool = false) -> Expression { return Expression.function(named: "UPPER", self, isDistinct: isDistinct) } public func round(isDistinct: Bool = false) -> Expression { return Expression.function(named: "ROUND", self, isDistinct: isDistinct) } //FTS3 public func matchinfo() -> Expression { return Expression.function(named: "MATCHINFO", self) } public func offsets() -> Expression { return Expression.function(named: "OFFSETS", self) } public func snippet() -> Expression { return Expression.function(named: "SNIPPET", self) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Order.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct Order: Describable { public private(set) var description: String public init(with expressionConvertible: ExpressionConvertible, by term: OrderTerm? = nil) { description = "\(expressionConvertible.asExpression().description)\(term != nil ? " "+term!.description : "")" } } extension Order: OrderConvertible { public func asOrder() -> Order { return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/OrderTerm.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public enum OrderTerm: Describable { case ascending case descending public var description: String { switch self { case .ascending: return "ASC" case .descending: return "DESC" } } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Pragma.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct Pragma: Describable { public private(set) var description: String fileprivate init(named name: String) { description = name } public static let applicationId = Pragma(named: "application_id") public static let autoVacuum = Pragma(named: "auto_vacuum") public static let automaticIndex = Pragma(named: "automatic_index") public static let busyTimeout = Pragma(named: "busy_timeout") public static let cacheSize = Pragma(named: "cache_size") public static let cacheSpill = Pragma(named: "cache_spill") public static let caseSensitiveLike = Pragma(named: "case_sensitive_like") public static let cellSizeCheck = Pragma(named: "cell_size_check") public static let checkpointFullfsync = Pragma(named: "checkpoint_fullfsync") public static let cipher = Pragma(named: "cipher") public static let cipherAddRandom = Pragma(named: "cipher_add_random") public static let cipherDefaultKdfIter = Pragma(named: "cipher_default_kdf_iter") public static let cipherDefaultPageSize = Pragma(named: "cipher_default_page_size") public static let cipherDefaultUseHmac = Pragma(named: "cipher_default_use_hmac") public static let cipherMigrate = Pragma(named: "cipher_migrate") public static let cipherProfile = Pragma(named: "cipher_profile") public static let cipherProvider = Pragma(named: "cipher_provider") public static let cipherProviderVersion = Pragma(named: "cipher_provider_version") public static let cipherUseHmac = Pragma(named: "cipher_use_hmac") public static let cipherVersion = Pragma(named: "cipher_version") public static let cipherPageSize = Pragma(named: "cipher_page_size") public static let collationList = Pragma(named: "collation_list") public static let compileOptions = Pragma(named: "compile_options") public static let countChanges = Pragma(named: "count_changes") public static let dataStoreDirectory = Pragma(named: "data_store_directory") public static let dataVersion = Pragma(named: "data_version") public static let databaseList = Pragma(named: "database_list") public static let defaultCacheSize = Pragma(named: "default_cache_size") public static let deferForeignKeys = Pragma(named: "defer_foreign_keys") public static let emptyResultCallbacks = Pragma(named: "empty_result_callbacks") public static let encoding = Pragma(named: "encoding") public static let foreignKeyCheck = Pragma(named: "foreign_key_check") public static let foreignKeyList = Pragma(named: "foreign_key_list") public static let foreignKeys = Pragma(named: "foreign_keys") public static let freelistCount = Pragma(named: "freelist_count") public static let fullColumnNames = Pragma(named: "full_column_names") public static let fullfsync = Pragma(named: "fullfsync") public static let ignoreCheckConstraints = Pragma(named: "ignore_check_constraints") public static let incrementalVacuum = Pragma(named: "incremental_vacuum") public static let indexInfo = Pragma(named: "index_info") public static let indexList = Pragma(named: "index_list") public static let indexXinfo = Pragma(named: "index_xinfo") public static let integrityCheck = Pragma(named: "integrity_check") public static let journalMode = Pragma(named: "journal_mode") public static let journalSizeLimit = Pragma(named: "journal_size_limit") public static let key = Pragma(named: "key") public static let kdfIter = Pragma(named: "kdf_iter") public static let legacyFileFormat = Pragma(named: "legacy_file_format") public static let lockingMode = Pragma(named: "locking_mode") public static let maxPageCount = Pragma(named: "max_page_count") public static let mmapSize = Pragma(named: "mmap_size") public static let pageCount = Pragma(named: "page_count") public static let pageSize = Pragma(named: "page_size") public static let parserTrace = Pragma(named: "parser_trace") public static let queryOnly = Pragma(named: "query_only") public static let quickCheck = Pragma(named: "quick_check") public static let readUncommitted = Pragma(named: "read_uncommitted") public static let recursiveTriggers = Pragma(named: "recursive_triggers") public static let rekey = Pragma(named: "rekey") public static let reverseUnorderedSelects = Pragma(named: "reverse_unordered_selects") public static let schemaVersion = Pragma(named: "schema_version") public static let secureDelete = Pragma(named: "secure_delete") public static let shortColumnNames = Pragma(named: "short_column_names") public static let shrinkMemory = Pragma(named: "shrink_memory") public static let softHeapLimit = Pragma(named: "soft_heap_limit") public static let stats = Pragma(named: "stats") public static let synchronous = Pragma(named: "synchronous") public static let tableInfo = Pragma(named: "table_info") public static let tempStore = Pragma(named: "temp_store") public static let tempStoreDirectory = Pragma(named: "temp_store_directory") public static let threads = Pragma(named: "threads") public static let userVersion = Pragma(named: "user_version") public static let vdbeAddoptrace = Pragma(named: "vdbe_addoptrace") public static let vdbeDebug = Pragma(named: "vdbe_debug") public static let vdbeListing = Pragma(named: "vdbe_listing") public static let vdbeTrace = Pragma(named: "vdbe_trace") public static let walAutocheckpoint = Pragma(named: "wal_autocheckpoint") public static let walCheckpoint = Pragma(named: "wal_checkpoint") public static let writableSchema = Pragma(named: "writable_schema") } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Statement.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public enum StatementType { case alterTable case createIndex case createTable case delete case dropIndex case dropTable case insert case pragma case select case transaction case update case createVirtualTable case attach case detach case explain case savepoint case release case rollback case vacuum case reindex } public protocol Statement: Describable { var statementType: StatementType {get} } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementAlterTable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementAlterTable: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .alterTable } public init() {} @discardableResult public func alter(table: String) -> StatementAlterTable { description.append("ALTER TABLE \(table)") return self } @discardableResult public func rename(to newTable: String) -> StatementAlterTable { description.append(" RENAME TO \(newTable)") return self } @discardableResult public func addColumn(with columnDef: ColumnDef) -> StatementAlterTable { description.append(" ADD COLUMN \(columnDef.description)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementAttach.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementAttach: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .attach } public init() {} @discardableResult public func attach(_ expressionConvertible: ExpressionConvertible, asSchema schema: String) -> StatementAttach { description.append("ATTACH \(expressionConvertible.asExpression().description) AS \(schema)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementCreateIndex.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementCreateIndex: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .createIndex } public init() {} @discardableResult public func create(index: String, isUnique: Bool = false, ifNotExists: Bool = true) -> StatementCreateIndex { description.append("CREATE ") if isUnique { description.append("UNIQUE ") } description.append("INDEX ") if ifNotExists { description.append("IF NOT EXISTS ") } description.append(index) return self } @discardableResult public func on(table: String, indexesBy columnIndexConvertibleList: ColumnIndexConvertible...) -> StatementCreateIndex { return on(table: table, indexesBy: columnIndexConvertibleList) } @discardableResult public func on(table: String, indexesBy columnIndexConvertibleList: [ColumnIndexConvertible]) -> StatementCreateIndex { description.append(" ON \(table)(\(columnIndexConvertibleList.joined()))") return self } @discardableResult public func `where`(_ expressionConvertible: ExpressionConvertible) -> StatementCreateIndex { let expression = expressionConvertible.asExpression() if !expression.description.isEmpty { description.append(" WHERE \(expression.description)") } return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementCreateTable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementCreateTable: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .createTable } public init() {} @discardableResult public func create(table: String, ifNotExists: Bool = true, as statementSelect: StatementSelect) -> StatementCreateTable { description.append("CREATE TABLE ") if ifNotExists { description.append("IF NOT EXISTS ") } description.append("\(table) AS \(statementSelect.description)") return self } @discardableResult public func create(table: String, ifNotExists: Bool = true, with columnDefs: ColumnDef..., and tableConstraints: [TableConstraint]? = nil) -> StatementCreateTable { return self.create(table: table, ifNotExists: ifNotExists, with: columnDefs, and: tableConstraints) } @discardableResult public func create(table: String, ifNotExists: Bool = true, with columnDefs: [ColumnDef], and optionalTableConstraints: [TableConstraint]? = nil) -> StatementCreateTable { description.append("CREATE TABLE ") if ifNotExists { description.append("IF NOT EXISTS ") } description.append("\(table)(\(columnDefs.joined())") if let tableConstraints = optionalTableConstraints { description.append(", \(tableConstraints.joined())") } description.append(")") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementCreateVirtualTable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementCreateVirtualTable: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .createVirtualTable } public init() {} @discardableResult public func create(virtualTable table: String, ifNotExists: Bool = true) -> StatementCreateVirtualTable { description.append("CREATE VIRTUAL TABLE ") if ifNotExists { description.append("IF NOT EXISTS ") } description.append(table) return self } @discardableResult public func using(module: String, arguments: [ModuleArgument]? = nil) -> StatementCreateVirtualTable { description.append(" USING \(module)") if arguments != nil && !arguments!.isEmpty { description.append("(\(arguments!.joined()))") } return self } @discardableResult public func using(module: String, arguments: ModuleArgument...) -> StatementCreateVirtualTable { return using(module: module, arguments: arguments) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementDelete.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementDelete: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .delete } public init() {} @discardableResult public func delete(fromTable table: String) -> StatementDelete { description.append("DELETE FROM \(table)") return self } @discardableResult public func `where`(_ expressionConvertible: ExpressionConvertible) -> StatementDelete { let expression = expressionConvertible.asExpression() if !expression.description.isEmpty { description.append(" WHERE \(expression.description)") } return self } @discardableResult public func order(by orderConvertibleList: OrderConvertible...) -> StatementDelete { return order(by: orderConvertibleList) } @discardableResult public func order(by orderConvertibleList: [OrderConvertible]) -> StatementDelete { if !orderConvertibleList.isEmpty { description.append(" ORDER BY \(orderConvertibleList.joined())") } return self } @discardableResult public func limit(from expressionConvertibleFrom: ExpressionConvertible, to expressionConvertibleTo: ExpressionConvertible) -> StatementDelete { let from = expressionConvertibleFrom.asExpression() if !from.description.isEmpty { description.append(" LIMIT \(from.description)") let to = expressionConvertibleTo.asExpression() if !to.description.isEmpty { description.append(", \(to.description)") } } return self } @discardableResult public func limit(_ expressionConvertibleLimit: ExpressionConvertible) -> StatementDelete { let limit = expressionConvertibleLimit.asExpression() if !limit.description.isEmpty { description.append(" LIMIT \(limit.description)") } return self } @discardableResult public func limit(_ expressionConvertibleLimit: ExpressionConvertible, offset expressionConvertibleOffset: ExpressionConvertible) -> StatementDelete { let limit = expressionConvertibleLimit.asExpression() if !limit.description.isEmpty { description.append(" LIMIT \(limit.description)") let offset = expressionConvertibleOffset.asExpression() if !offset.description.isEmpty { description.append(" OFFSET \(offset.description)") } } return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementDetach.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementDetach: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .detach } public init() {} @discardableResult public func detach(schema: String) -> StatementDetach { description.append("DETACH \(schema)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementDropIndex.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementDropIndex: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .dropIndex } public init() {} @discardableResult public func drop(index: String, ifExists: Bool = true) -> StatementDropIndex { description.append("DROP INDEX ") if ifExists { description.append("IF EXISTS ") } description.append(index) return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementDropTable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementDropTable: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .dropTable } public init() {} public func drop(table: String, ifExists: Bool = true) -> StatementDropTable { description.append("DROP TABLE ") if ifExists { description.append("IF EXISTS ") } description.append(table) return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementExplain.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementExplain: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .explain } public init() {} @discardableResult public func explain(_ statement: Statement) -> StatementExplain { description.append("EXPLAIN \(statement.description)") return self } @discardableResult public func explainQueryPlan(_ statement: Statement) -> StatementExplain { description.append("EXPLAIN QUERY PLAN \(statement.description)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementInsert.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementInsert: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .insert } public init() {} @discardableResult public func insert(intoTable table: String, with columnConvertibleList: ColumnConvertible..., onConflict conflict: Conflict? = nil) -> StatementInsert { return insert(intoTable: table, with: columnConvertibleList, onConflict: conflict) } @discardableResult public func insert(intoTable table: String, with columnConvertibleList: [ColumnConvertible]? = nil, onConflict conflict: Conflict? = nil) -> StatementInsert { description.append("INSERT") if conflict != nil { description.append(" OR \(conflict!.description)") } description.append(" INTO \(table)") if columnConvertibleList != nil { description.append("(\(columnConvertibleList!.joined()))") } return self } @discardableResult public func values(_ expressionConvertibleList: ExpressionConvertible...) -> StatementInsert { return values(expressionConvertibleList) } @discardableResult public func values(_ expressionConvertibleList: [ExpressionConvertible]) -> StatementInsert { if !expressionConvertibleList.isEmpty { description.append(" VALUES(\(expressionConvertibleList.joined()))") } return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementPragma.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementPragma: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .pragma } public init() {} @discardableResult public func pragma(_ pragma: Pragma) -> StatementPragma { description.append("PRAGMA \(pragma.description)") return self } @discardableResult public func pragma(_ pragma: Pragma, to literalValueConvertible: LiteralValueConvertible) -> StatementPragma { description.append("PRAGMA \(pragma.description)=\(literalValueConvertible.asLiteralValue().description)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementReindex.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementReindex: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .reindex } public init() {} @discardableResult public func reindex(_ name: String) -> StatementReindex { description.append("REINDEX \(name)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementRelease.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementRelease: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .release } public init() {} @discardableResult public func release(savepoint name: String) -> StatementRelease { description.append("RELEASE \(name)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementRollback.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementRollback: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .rollback } public init() {} public func rollback(toSavepoint optionalName: String? = nil) -> StatementRollback { description.append("ROLLBACK") if let name = optionalName { description.append(" TO \(name)") } return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementSavepoint.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementSavepoint: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .savepoint } public init() {} @discardableResult public func savepoint(_ name: String) -> StatementSavepoint { description.append("SAVEPOINT \(name)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementSelect.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementSelect: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .select } public init() {} @discardableResult public func select(distinct: Bool = false, _ columnResultConvertibleList: ColumnResultConvertible...) -> StatementSelect { return select(distinct: distinct, columnResultConvertibleList) } @discardableResult public func select(distinct: Bool = false, _ columnResultConvertibleList: [ColumnResultConvertible]) -> StatementSelect { description.append("SELECT ") if distinct { description.append("DISTINCT ") } description.append(columnResultConvertibleList.joined()) return self } @discardableResult public func from(_ tableOrSubqueryConvertibleList: TableOrSubqueryConvertible...) -> StatementSelect { return from(tableOrSubqueryConvertibleList) } @discardableResult public func from(_ tableOrSubqueryConvertibleList: [TableOrSubqueryConvertible]) -> StatementSelect { description.append(" FROM \(tableOrSubqueryConvertibleList.joined())") return self } @discardableResult public func `where`(_ expressionConvertible: ExpressionConvertible) -> StatementSelect { let expression = expressionConvertible.asExpression() if !expression.description.isEmpty { description.append(" WHERE \(expression.description)") } return self } @discardableResult public func order(by orderConvertibleList: OrderConvertible...) -> StatementSelect { return order(by: orderConvertibleList) } @discardableResult public func order(by orderConvertibleList: [OrderConvertible]) -> StatementSelect { if !orderConvertibleList.isEmpty { description.append(" ORDER BY \(orderConvertibleList.joined())") } return self } @discardableResult public func limit(from expressionConvertibleFrom: ExpressionConvertible, to expressionConvertibleTo: ExpressionConvertible) -> StatementSelect { let from = expressionConvertibleFrom.asExpression() if !from.description.isEmpty { description.append(" LIMIT \(from.description)") let to = expressionConvertibleTo.asExpression() if !to.description.isEmpty { description.append(", \(to.description)") } } return self } @discardableResult public func limit(_ expressionConvertibleLimit: ExpressionConvertible) -> StatementSelect { let limit = expressionConvertibleLimit.asExpression() if !limit.description.isEmpty { description.append(" LIMIT \(limit.description)") } return self } @discardableResult public func limit(_ expressionConvertibleLimit: ExpressionConvertible, offset expressionConvertibleOffset: ExpressionConvertible) -> StatementSelect { let limit = expressionConvertibleLimit.asExpression() if !limit.description.isEmpty { description.append(" LIMIT \(limit.description)") let offset = expressionConvertibleOffset.asExpression() if !offset.description.isEmpty { description.append(" OFFSET \(offset.description)") } } return self } @discardableResult public func group(by expressionConvertibleGroupList: ExpressionConvertible...) -> StatementSelect { return group(by: expressionConvertibleGroupList) } @discardableResult public func group(by expressionConvertibleGroupList: [ExpressionConvertible]) -> StatementSelect { if !expressionConvertibleGroupList.isEmpty { description.append(" GROUP BY \(expressionConvertibleGroupList.joined())") } return self } @discardableResult public func having(_ expressionConvertibleHaving: ExpressionConvertible) -> StatementSelect { let having = expressionConvertibleHaving.asExpression() if !having.description.isEmpty { description.append(" HAVING \(having.description)") } return self } } extension StatementSelect: ExpressionConvertible, TableOrSubqueryConvertible { public func asExpression() -> Expression { return Expression(with: self) } public func asTableOrSubquery() -> Subquery { return Subquery(with: self) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementTransaction.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementTransaction: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .transaction } public init() {} public enum TransactionType: Describable { case begin case commit case rollback public var description: String { switch self { case .begin: return "BEGIN" case .commit: return "COMMIT" case .rollback: return "ROLLBACK" } } } public private(set) var transactionType: TransactionType? public enum Mode: Describable { case deferred case immediate case exclusive public var description: String { switch self { case .deferred: return "DEFERRED" case .immediate: return "IMMEDIATE" case .exclusive: return "EXCLUSIVE" } } } @discardableResult public func begin(_ mode: Mode? = nil) -> StatementTransaction { transactionType = .begin description.append(transactionType!.description) if mode != nil { description.append(" \(mode!.description)") } return self } @discardableResult public func commit() -> StatementTransaction { transactionType = .commit description.append(transactionType!.description) return self } @discardableResult public func rollback() -> StatementTransaction { transactionType = .rollback description.append(transactionType!.description) return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementUpdate.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementUpdate: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .update } public init() {} @discardableResult public func update(table: String, onConflict conflict: Conflict? = nil) -> StatementUpdate { description.append("UPDATE ") if conflict != nil { description.append("OR \(conflict!.description) ") } description.append(table) return self } public typealias ValueType = (_: ColumnConvertible, _: ExpressionConvertible) @discardableResult public func set(_ values: [ValueType]) -> StatementUpdate { let valueString = values.joined({ $0.0.asColumn().description + "=" + $0.1.asExpression().description }) description.append(" SET \(valueString)") return self } @discardableResult public func set(_ values: ValueType...) -> StatementUpdate { return set(values) } @discardableResult public func `where`(_ expressionConvertible: ExpressionConvertible) -> StatementUpdate { let expression = expressionConvertible.asExpression() if !expression.description.isEmpty { description.append(" WHERE \(expression.description)") } return self } @discardableResult public func order(by orderConvertibleList: OrderConvertible...) -> StatementUpdate { return order(by: orderConvertibleList) } @discardableResult public func order(by orderConvertibleList: [OrderConvertible]) -> StatementUpdate { if !orderConvertibleList.isEmpty { description.append(" ORDER BY \(orderConvertibleList.joined())") } return self } @discardableResult public func limit(from expressionConvertibleFrom: ExpressionConvertible, to expressionConvertibleTo: ExpressionConvertible) -> StatementUpdate { let from = expressionConvertibleFrom.asExpression() if !from.description.isEmpty { description.append(" LIMIT \(from.description)") let to = expressionConvertibleTo.asExpression() if !to.description.isEmpty { description.append(", \(to.description)") } } return self } @discardableResult public func limit(_ expressionConvertibleLimit: ExpressionConvertible) -> StatementUpdate { let limit = expressionConvertibleLimit.asExpression() if !limit.description.isEmpty { description.append(" LIMIT \(limit.description)") } return self } @discardableResult public func limit(_ expressionConvertibleLimit: ExpressionConvertible, offset expressionConvertibleOffset: ExpressionConvertible) -> StatementUpdate { let limit = expressionConvertibleLimit.asExpression() if !limit.description.isEmpty { description.append(" LIMIT \(limit.description)") let offset = expressionConvertibleOffset.asExpression() if !offset.description.isEmpty { description.append(" OFFSET \(offset.description)") } } return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/StatementVacuum.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class StatementVacuum: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .vacuum } public init() {} @discardableResult public func vacuum(schema: String) -> StatementVacuum { description.append("VACUUM \(schema)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Subquery.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class Subquery: Describable { public private(set) var description: String public init(with joinClause: JoinClause) { description = "(\(joinClause.description))" } public init(with statementSelect: StatementSelect) { description = "(\(statementSelect.description))" } public init(withTable table: String) { description = table } @discardableResult public func `as`(alias: String) -> Subquery { description.append(" AS \(alias)") return self } } extension Subquery: TableOrSubqueryConvertible { public func asTableOrSubquery() -> Subquery { return self } } extension String: TableOrSubqueryConvertible { public func asTableOrSubquery() -> Subquery { return Subquery(withTable: self) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/TableConstraint.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class TableConstraint: Describable { public private(set) var description: String public init(named name: String) { description = "CONSTRAINT \(name)" } @discardableResult public func makePrimary(indexesBy columnIndexConvertibleList: ColumnIndexConvertible...) -> TableConstraint { return makePrimary(indexesBy: columnIndexConvertibleList) } @discardableResult public func makePrimary(indexesBy columnIndexConvertibleList: [ColumnIndexConvertible]) -> TableConstraint { description.append(" PRIMARY KEY(\(columnIndexConvertibleList.joined()))") return self } @discardableResult public func makeUnique(indexesBy columnIndexConvertibleList: ColumnIndexConvertible...) -> TableConstraint { return makeUnique(indexesBy: columnIndexConvertibleList) } @discardableResult public func makeUnique(indexesBy columnIndexConvertibleList: [ColumnIndexConvertible]) -> TableConstraint { description.append(" UNIQUE(\(columnIndexConvertibleList.joined()))") return self } @discardableResult public func onConflict(_ conflict: Conflict) -> TableConstraint { description.append(" ON CONFLICT \(conflict.description)") return self } @discardableResult public func check(_ expressionConvertible: ExpressionConvertible) -> TableConstraint { description.append(" CHECK\(expressionConvertible.asExpression().description)") return self } @discardableResult public func makeForeignKey(_ columnConvertibleList: ColumnConvertible..., foreignKey: ForeignKey) -> TableConstraint { return makeForeignKey(columnConvertibleList, foreignKey: foreignKey) } @discardableResult public func makeForeignKey(_ columnConvertibleList: [ColumnConvertible], foreignKey: ForeignKey) -> TableConstraint { description.append(" FOREIGN KEY(\(columnConvertibleList.joined())) \(foreignKey.description)") return self } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Tokenize.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public protocol TokenizerInfoBase: class { init(withArgc argc: Int32, andArgv argv: UnsafePointer?>?) } public protocol CursorInfoBase: class { init(withInput pInput: UnsafePointer?, count: Int32, tokenizerInfo: TokenizerInfoBase) func step(pToken: inout UnsafePointer?, count: inout Int32, startOffset: inout Int32, endOffset: inout Int32, position: inout Int32) -> Int32 } public protocol ModuleBase: class { static var name: String {get} static var module: sqlite3_tokenizer_module {get} static var address: Data {get} static func create(argc: Int32, argv: UnsafePointer?>?, ppTokenizer: UnsafeMutablePointer?>?) -> Int32 static func destroy(pTokenizer optionalTokenizerPointer: UnsafeMutablePointer?) -> Int32 static func open(pTokenizer: UnsafeMutablePointer?, pInput: UnsafePointer?, nBytes: Int32, ppCursor: UnsafeMutablePointer?>?) -> Int32 static func close(pCursor optionalCursorPointer: UnsafeMutablePointer?) -> Int32 static func next(pCursor optionalCursorPointer: UnsafeMutablePointer?, ppToken: UnsafeMutablePointer?>?, pnBytes: UnsafeMutablePointer?, piStartOffset: UnsafeMutablePointer?, piEndOffset: UnsafeMutablePointer?, piPosition: UnsafeMutablePointer?) -> Int32 } public protocol Module: ModuleBase { associatedtype TokenizerInfo: TokenizerInfoBase associatedtype CursorInfo: CursorInfoBase } extension Module { public static func create(argc: Int32, argv: UnsafePointer?>?, ppTokenizer: UnsafeMutablePointer?>?) -> Int32 { let tokenizerSize = MemoryLayout.size let optionalTokenizerRawPointer = sqlite3_malloc(Int32(tokenizerSize)) guard let tokenizerRawPointer = optionalTokenizerRawPointer else { return SQLITE_NOMEM } memset(tokenizerRawPointer, 0, tokenizerSize) let info = TokenizerInfo(withArgc: argc, andArgv: argv) let tokenizerPointer = tokenizerRawPointer.assumingMemoryBound(to: Tokenizer.self) let opaqueInfo = Unmanaged.passRetained(info).toOpaque() tokenizerPointer.pointee.info = opaqueInfo ppTokenizer!.pointee = tokenizerRawPointer.assumingMemoryBound(to: sqlite3_tokenizer.self) return SQLITE_OK } public static func destroy(pTokenizer optionalTokenizerPointer: UnsafeMutablePointer?) -> Int32 { if let tokenizerRawPointer = UnsafeMutableRawPointer(optionalTokenizerPointer) { let tokenizerPointer = tokenizerRawPointer.assumingMemoryBound(to: Tokenizer.self) Unmanaged.fromOpaque(tokenizerPointer.pointee.info).release() sqlite3_free(tokenizerRawPointer) } return SQLITE_OK } public static func open(pTokenizer: UnsafeMutablePointer?, pInput: UnsafePointer?, nBytes: Int32, ppCursor: UnsafeMutablePointer?>?) -> Int32 { var bytes: Int32 = nBytes if pInput == nil { bytes = 0 }else if bytes < 0 { bytes = Int32(strlen(pInput!)) } let cursorSize = MemoryLayout.size let optionalCursorRawPointer = sqlite3_malloc(Int32(cursorSize)) guard let cursorRawPointer = optionalCursorRawPointer else { return SQLITE_NOMEM } memset(cursorRawPointer, 0, cursorSize) let tokenizerPointer = UnsafeMutableRawPointer(pTokenizer!).assumingMemoryBound(to: Tokenizer.self) let cursorPointer = cursorRawPointer.assumingMemoryBound(to: Cursor.self) let tokenizerInfo = Unmanaged.fromOpaque(tokenizerPointer.pointee.info).takeUnretainedValue() let cursorInfo = CursorInfo(withInput: pInput, count: bytes, tokenizerInfo: tokenizerInfo) cursorPointer.pointee.info = Unmanaged.passRetained(cursorInfo).toOpaque() ppCursor!.pointee = cursorRawPointer.assumingMemoryBound(to: sqlite3_tokenizer_cursor.self) return SQLITE_OK } public static func close(pCursor optionalCursorPointer: UnsafeMutablePointer?) -> Int32 { if let cursorRawPointer = UnsafeMutableRawPointer(optionalCursorPointer) { let cursorPointer = cursorRawPointer.assumingMemoryBound(to: Cursor.self) Unmanaged.fromOpaque(cursorPointer.pointee.info).release() sqlite3_free(cursorRawPointer) } return SQLITE_OK } public static func next(pCursor optionalCursorPointer: UnsafeMutablePointer?, ppToken: UnsafeMutablePointer?>?, pnBytes: UnsafeMutablePointer?, piStartOffset: UnsafeMutablePointer?, piEndOffset: UnsafeMutablePointer?, piPosition: UnsafeMutablePointer?) -> Int32 { if let cursorRawPointer = UnsafeMutableRawPointer(optionalCursorPointer) { let cursorPointer = cursorRawPointer.assumingMemoryBound(to: Cursor.self) let cursorInfoPointer = Unmanaged.fromOpaque(cursorPointer.pointee.info) let rc = cursorInfoPointer.takeUnretainedValue().step(pToken: &ppToken!.pointee, count: &pnBytes!.pointee, startOffset: &piStartOffset!.pointee, endOffset: &piEndOffset!.pointee, position: &piPosition!.pointee) return rc } return SQLITE_NOMEM } } public struct Tokenize { let module: ModuleBase.Type } public struct FTSModule { public let name: String } extension FTSModule { public static let fts3 = FTSModule(name: "fts3") } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/abstract/Tracer.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class Tracer { typealias SQLTracer = (String) -> Void // SQL private var sqlTracer: SQLTracer? = nil { didSet { setup() } } typealias PerformanceTracer = ([String: Int], Int64, Any?) -> Void // (SQL, count), cost, userInfo private var performanceTracer: PerformanceTracer? = nil { didSet { setup() } } var shouldAggregation = false private var footprint: [String: Int] = [:] private var cost: Int64 = 0 private let handle: SQLite3 var userInfo: Any? init(with handle: SQLite3) { self.handle = handle } deinit { reportPerformance() } struct TraceType: OptionSet { let rawValue: UInt32 static let stmt = TraceType(rawValue: UInt32(SQLITE_TRACE_STMT)) static let profile = TraceType(rawValue: UInt32(SQLITE_TRACE_PROFILE)) } private var type: TraceType { var type = TraceType() if sqlTracer != nil { type.insert(.stmt) } if performanceTracer != nil { type.insert(.profile) } return type } private func setup() { let traceType = self.type guard !traceType.isEmpty else { sqlite3_trace_v2(handle, 0, nil, nil) return } sqlite3_trace_v2(handle, traceType.rawValue, { (flag, context, stmtPointer, costPointer) -> Int32 in let traceType = TraceType(rawValue: flag) let pointer = Unmanaged.fromOpaque(context!) let tracer = pointer.takeUnretainedValue() let stmt = OpaquePointer(stmtPointer) guard let csql = sqlite3_sql(stmt) else { return SQLITE_MISUSE } let sql = String(cString: csql) switch traceType { case .stmt: tracer.report(sql: sql) case .profile: let cost: Int64 = UnsafePointer(OpaquePointer(costPointer)!).pointee //report last track if !tracer.shouldAggregation { tracer.reportPerformance() } tracer.recordPerformance(sql: sql, cost: cost) default: break } return SQLITE_OK }, Unmanaged.passUnretained(self).toOpaque()) } private func report(sql: String) { guard let sqlTracer = self.sqlTracer else { return } sqlTracer(sql) } private func reportPerformance() { guard !footprint.isEmpty else { return } guard let performanceTracer = self.performanceTracer else { return } performanceTracer(footprint, cost, userInfo) footprint.removeAll() cost = 0 } private func recordPerformance(sql: String, cost: Int64) { self.footprint[sql] = self.footprint[sql] ?? 1 self.cost = cost } func trace(sql sqlTracer: @escaping SQLTracer) { self.sqlTracer = sqlTracer } func track(performance performanceTracer: @escaping PerformanceTracer) { self.performanceTracer = performanceTracer } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/builtin/CodableType.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /* * Builtin codable implementation * * .integer32: * Bool, Int, Int8, Int16, Int32, UInt, UInt8, UInt16, UInt32 * .integer64: * Int64, UInt64 * .float: * Float, Double, Date * .text: * String, URL * .BLOB: * Data, Array, Dictionary, Set * */ //Bultin Column Codable extension Int8: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer32 } public init?(with value: FundamentalValue) { self = value.int32Value.toInt8() } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.toInt32()) } } extension Int16: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer32 } public init?(with value: FundamentalValue) { self = value.int32Value.toInt16() } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.toInt32()) } } extension Int32: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer32 } public init?(with value: FundamentalValue) { self = value.int32Value } public func archivedValue() -> FundamentalValue { return FundamentalValue(self) } } extension Int64: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer64 } public init?(with value: FundamentalValue) { self = value.int64Value } public func archivedValue() -> FundamentalValue { return FundamentalValue(self) } } extension Int: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer64 } public init?(with value: FundamentalValue) { self = value.int64Value.toInt() } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.toInt64()) } } extension UInt8: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer32 } public init?(with value: FundamentalValue) { self = value.int32Value.toUInt8() } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.toInt32()) } } extension UInt16: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer32 } public init?(with value: FundamentalValue) { self = value.int32Value.toUInt16() } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.toInt32()) } } extension UInt32: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer32 } public init?(with value: FundamentalValue) { self = value.int32Value.toUInt32() } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.toInt32()) } } extension UInt64: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer64 } public init?(with value: FundamentalValue) { self = value.int64Value.toUInt64() } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.toInt64()) } } extension UInt: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer64 } public init?(with value: FundamentalValue) { self = value.int64Value.toUInt() } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.toInt64()) } } extension Bool: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .integer32 } public init?(with value: FundamentalValue) { self = value.int32Value.toBool() } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.toInt32()) } } extension Float: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .float } public init?(with value: FundamentalValue) { self = value.doubleValue.toFloat() } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.toDouble()) } } extension Double: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .float } public init?(with value: FundamentalValue) { self = value.doubleValue } public func archivedValue() -> FundamentalValue { return FundamentalValue(self) } } extension String: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .text } public init?(with value: FundamentalValue) { self = value.stringValue } public func archivedValue() -> FundamentalValue { return FundamentalValue(self) } } extension Data: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .BLOB } public init?(with value: FundamentalValue) { self = value.dataValue } public func archivedValue() -> FundamentalValue { return FundamentalValue(self) } } extension Date: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .float } public init?(with value: FundamentalValue) { self.init(timeIntervalSince1970: value.doubleValue) } public func archivedValue() -> FundamentalValue { return FundamentalValue(timeIntervalSince1970) } } extension URL: ColumnCodable, LiteralValueConvertible, ExpressionCanBeOperated { public static var columnType: ColumnType { return .text } public init?(with value: FundamentalValue) { self.init(string: value.stringValue) } public func archivedValue() -> FundamentalValue { return FundamentalValue(self.absoluteString) } } extension Array: ColumnCodable where Element: Codable { public static var columnType: ColumnType { return .BLOB } public init?(with value: FundamentalValue) { guard let decodable = try? JSONDecoder().decode(Array.self, from: value.dataValue) else { return nil } self = decodable } public func archivedValue() -> FundamentalValue { guard let encoded = try? JSONEncoder().encode(self) else { return FundamentalValue(nil) } return FundamentalValue(encoded) } } extension Dictionary: ColumnCodable where Key: Codable, Value: Codable { public static var columnType: ColumnType { return .BLOB } public init?(with value: FundamentalValue) { guard let decodable = try? JSONDecoder().decode(Dictionary.self, from: value.dataValue) else { return nil } self = decodable } public func archivedValue() -> FundamentalValue { guard let encoded = try? JSONEncoder().encode(self) else { return FundamentalValue(nil) } return FundamentalValue(encoded) } } extension Set: ColumnCodable where Element: Codable { public static var columnType: ColumnType { return .BLOB } public init?(with value: FundamentalValue) { guard let decodable = try? JSONDecoder().decode(Set.self, from: value.dataValue) else { return nil } self = decodable } public func archivedValue() -> FundamentalValue { guard let encoded = try? JSONEncoder().encode(self) else { return FundamentalValue(nil) } return FundamentalValue(encoded) } } //JSONCodable public protocol ColumnJSONEncodable: ColumnEncodable {} extension ColumnJSONEncodable { public func archivedValue() -> FundamentalValue { guard let encoded = try? JSONEncoder().encode(self) else { return FundamentalValue(nil) } return FundamentalValue(encoded) } } public protocol ColumnJSONDecodable: ColumnDecodable {} extension ColumnJSONDecodable { public init?(with value: FundamentalValue) { guard let decodable = try? JSONDecoder().decode(Self.self, from: value.dataValue) else { return nil } self = decodable } } public protocol ColumnJSONCodable: ColumnJSONEncodable, ColumnJSONDecodable {} extension ColumnJSONCodable { public static var columnType: ColumnType { return .BLOB } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/builtin/CommonStatement.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class CommonStatement { public static let enableFullfsync = StatementPragma().pragma(.fullfsync, to: true) public static let getJournalMode = StatementPragma().pragma(.journalMode) public static let enableJournalModeWAL = StatementPragma().pragma(.journalMode, to: "WAL") public static let enableSynchronousNormal = StatementPragma().pragma(.synchronous, to: "NORMAL") public static let enableSynchronousFull = StatementPragma().pragma(.synchronous, to: "FULL") public static let getLockingMode = StatementPragma().pragma(.lockingMode) public static let enableLockingModeNormal = StatementPragma().pragma(.lockingMode, to: "NORMAL") public static let checkpoint = StatementPragma().pragma(.walCheckpoint) public static let fts3Tokenizer = StatementSelect() .select(Expression.function(named: "fts3_tokenizer", Array(repeating: Expression.bindParameter, count: 2)) ) public static let beginTransactionImmediate = StatementTransaction().begin(.immediate) public static let commitTransaction = StatementTransaction().commit() public static let rollbackTransaction = StatementTransaction().rollback() } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/builtin/Master.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct Master: TableCodable { public static let builtinTableName: String = "sqlite_master" public var type: String? public var name: String? public var tableName: String? public var rootpage: Int? public var sql: String? public enum CodingKeys: String, CodingTableKey { public typealias Root = Master case type case name case tableName = "tbl_name" case rootpage case sql public static let objectRelationalMapping = TableBinding(Master.CodingKeys.self) } public init() {} } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/base/Config.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation //TODO: refactor for configs public struct Configs { public typealias Callback = Config.Callback public typealias Order = Config.Order public struct Config: Equatable { public typealias Callback = (Handle) throws -> Void typealias TaggedCallback = Tagged public typealias Order = Int let name: String let callback: TaggedCallback? let order: Order init(named name: String, with callback: @escaping Callback, orderBy order: Order) { self.name = name self.callback = Tagged(callback) self.order = order } init(emptyConfigNamed name: String, orderBy order: Order) { self.name = name self.callback = nil self.order = order } public static func == (lhs: Config, rhs: Config) -> Bool { return lhs.name == rhs.name && lhs.callback == rhs.callback && lhs.order == rhs.order } } private var configs = ContiguousArray() init(_ configs: ContiguousArray) { self.configs = configs } mutating func setConfig(named name: String, with callback: @escaping Callback, orderBy order: Order) { var inserted: Bool = false var newConfigs: ContiguousArray = configs.reduce(into: ContiguousArray()) { (result, config) in if !inserted && order < config.order { result.append(Config(named: name, with: callback, orderBy: order)) inserted = true } else if name != config.name { result.append(config) } } if !inserted { newConfigs.append(Config(named: name, with: callback, orderBy: order)) } configs = newConfigs } mutating func setConfig(named name: String, with callback: @escaping Callback) { var inserted: Bool = false var newConfigs: ContiguousArray = configs.reduce(into: ContiguousArray()) { (result, config) in if name != config.name { result.append(config) } else { result.append(Config(named: name, with: callback, orderBy: config.order)) inserted = true } } if !inserted { newConfigs.append(Config(named: name, with: callback, orderBy: Order.max)) } configs = newConfigs } func invoke(handle: Handle) throws { let configs = self.configs for config in configs { try config.callback?.value(handle) } } func config(by name: String) -> Callback? { let configs = self.configs return configs.first { $0.name == name }?.callback?.value } } extension Configs: Equatable { public static func == (lhs: Configs, rhs: Configs) -> Bool { guard lhs.configs.count == rhs.configs.count else { return false } for i in 0.. RecyclableHandleStatement { assert(statement.statementType != .transaction, "Using [begin], [commit], [rollback] or [Exec] method to do a transaction") let handleStatement = try recyclableHandle.raw.handle.prepare(statement) return RecyclableHandleStatement(recyclableHandle: recyclableHandle, handleStatement: handleStatement) } final func exec(_ statement: Statement, in recyclableHandle: RecyclableHandle) throws { assert(statement.statementType != .transaction, "Using [begin], [commit], [rollback] method to do a transaction") return try recyclableHandle.raw.handle.exec(statement) } func prepare(_ statement: Statement) throws -> RecyclableHandleStatement { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func exec(_ statement: Statement) throws { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } /// Prepare a specific sql. /// Note that you can use this interface to prepare a SQL that is not contained in the WCDB interface layer /// /// - Parameter statement: WINQ statement /// - Returns: CoreStatement /// - Throws: `Error` public func prepare(_ statement: Statement) throws -> CoreStatement { return CoreStatement(with: self, and: try prepare(statement)) } public func isTableExists(_ table: String) throws -> Bool { let statementSelect = StatementSelect().select(1).from(table).limit(0) Error.threadedSlient.value = true; defer { Error.threadedSlient.value = false } do { let handleStatement: RecyclableHandleStatement = try prepare(statementSelect) try handleStatement.raw.step() return true } catch let error as Error { guard error.code.value == Int(SQLITE_ERROR) else { throw error } } return false } func begin(_ mode: StatementTransaction.Mode = .immediate) throws { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func commit() throws { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func rollback() throws { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } public typealias TransactionClosure = () throws -> Void public typealias ControlableTransactionClosure = () throws -> Bool /// Run a transaction in closure /// /// try database.run(transaction: { () throws -> Void in /// try database.insert(objects: objects, intoTable: table) /// }) /// /// - Parameter transaction: Operation inside transaction /// - Throws: `Error` public func run(transaction: TransactionClosure) throws { try begin(.immediate) do { try transaction() try commit() } catch let error { try? rollback() throw error } } /// Run a controllable transaction in closure /// /// try database.run(controllableTransaction: { () throws -> Bool in /// try database.insert(objects: objects, intoTable: table) /// return true // return true to commit transaction and return false to rollback transaction. /// }) /// /// - Parameter controllableTransaction: Operation inside transaction /// - Throws: `Error` public func run(controllableTransaction: ControlableTransactionClosure) throws { try begin(.immediate) var shouldRollback = true do { if try controllableTransaction() { try commit() } else { shouldRollback = false try rollback() } } catch let error { if shouldRollback { try? rollback() } throw error } } func run(embeddedTransaction: TransactionClosure) throws { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } } protocol CoreRepresentable: class { var tag: Tag? {get} var path: String {get} } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/base/CoreStatement.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /// CoreStatement for preparing or executing WINQ sql. public final class CoreStatement { private let core: Core private let recyclableHandleStatement: RecyclableHandleStatement init(with core: Core, and recyclableHandleStatement: RecyclableHandleStatement) { self.core = core self.recyclableHandleStatement = recyclableHandleStatement } private var handleStatement: HandleStatement { return recyclableHandleStatement.raw } /// The wrapper of `sqlite3_bind_*` for binding property of object to index. /// /// - Parameters: /// - propertyConvertible: `Property` or `CodingTableKey` /// - object: Table encodable object /// - index: Begin with 1 /// - Throws: `Error` public func bind( _ propertyConvertible: PropertyConvertible, of object: TableEncodableType, toIndex index: Int = 1) throws { try bind([(propertyConvertible, toIndex: index)], of: object) } /// The wrapper of `sqlite3_bind_*` for binding properties of object to indexes. /// /// - Parameters: /// - indexedPropertyConvertibleList: Indexed `Property` or `CodingTableKey` list /// - object: Table encodable object /// - Throws: Begin with 1 public func bind( _ indexedPropertyConvertibleList: [(_: PropertyConvertible, toIndex: Int)], of object: TableEncodableType) throws { var hashedKeys: TableEncoder.HashedKey = [:] for args in indexedPropertyConvertibleList { hashedKeys[args.0.codingTableKey.stringValue.hashValue] = args.toIndex } let encoder = TableEncoder(hashedKeys, on: recyclableHandleStatement) try object.encode(to: encoder) } /// The wrapper of `sqlite3_bind_*` for binding properties of object. /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - object: Table encodable object /// - Throws: Begin with 1 public func bind( _ propertyConvertibleList: [PropertyConvertible], of object: TableEncodableType) throws { var hashedKeys: TableEncoder.HashedKey = [:] for (index, propertyConvertible) in propertyConvertibleList.enumerated() { hashedKeys[propertyConvertible.codingTableKey.stringValue.hashValue] = index } let encoder = TableEncoder(hashedKeys, on: recyclableHandleStatement) try object.encode(to: encoder) } /// The wrapper of `sqlite3_bind_*` for binding column encodable object. /// /// - Parameters: /// - value: Column encodable object /// - index: Begin with 1 public func bind(_ value: ColumnEncodable?, toIndex index: Int) { guard let bindingValue = value?.archivedValue() else { handleStatement.bind(nil, toIndex: index) return } handleStatement.bind(bindingValue, toIndex: index) } /// The wrapper of `sqlite3_column_*` for getting column decodable value. /// /// - Parameters: /// - index: Begin with 0 /// - type: Type of column codable object /// - Returns: Same as type public func value(atIndex index: Int, of type: ColumnDecodable.Type) -> ColumnDecodable? { guard handleStatement.columnType(atIndex: index) != .null else { return nil } return type.init(with: handleStatement.columnValue(atIndex: index)) } /// The wrapper of `sqlite3_column_*` for getting column decodable value. /// /// - Parameters: /// - index: Begin with 0 /// - type: Type of column codable object /// - Returns: Same as type public func value( atIndex index: Int, of type: ColumnDecodableType.Type = ColumnDecodableType.self) -> ColumnDecodableType? { guard handleStatement.columnType(atIndex: index) != .null else { return nil } return type.init(with: handleStatement.columnValue(atIndex: index)) } /// The wrapper of `sqlite3_column_*` for getting fundamentable value. /// /// - Parameter index: Begin with 0 /// - Returns: `Int32`, `Int64`, `Double`, `String`, `Data` or `nil` value. public func value(atIndex index: Int) -> FundamentalValue { switch handleStatement.columnType(atIndex: index) { case .integer32: return FundamentalValue(handleStatement.columnValue(atIndex: index, of: Int32.self)) case .integer64: return FundamentalValue(handleStatement.columnValue(atIndex: index, of: Int64.self)) case .float: return FundamentalValue(handleStatement.columnValue(atIndex: index, of: Double.self)) case .text: return FundamentalValue(handleStatement.columnValue(atIndex: index, of: String.self)) case .BLOB: return FundamentalValue(handleStatement.columnValue(atIndex: index, of: Data.self)) case .null: return FundamentalValue(nil) } } /// The wrapper of `sqlite3_column_*` for getting column decodable value. /// /// - Parameters: /// - name: Name of the column /// - type: Type of column codable object /// - Returns: Same as type. Nil will be returned if no such a column. public func value( byName name: String, of type: ColumnDecodableType.Type = ColumnDecodableType.self) -> ColumnDecodableType? { guard let index = index(byName: name) else { return nil } return value(atIndex: index) } /// Get index by column name. /// /// - Parameter name: Name of the column /// - Returns: Index of given column name. Nil will be returned if no such a column. public func index(byName name: String) -> Int? { for index in 0.. Bool { return try handleStatement.step() } /// The wrapper of `sqlite3_reset` /// /// - Throws: `Error` public func reset() throws { try handleStatement.reset() } /// The wrapper of `sqlite3_column_type` /// /// - Parameter index: Begin with 0 /// - Returns: Type of the column public func columnType(atIndex index: Int) -> ColumnType { return handleStatement.columnType(atIndex: index) } /// The wrapper of `sqlite3_column_type` /// /// - Parameter name: Name of the column /// - Returns: Column type. For a non-exists column, `.null` will be returned. public func columnType(byName name: String) -> ColumnType { guard let index = index(byName: name) else { return .null } return columnType(atIndex: index) } /// The wrapper of `sqlite3_column_count`. /// /// - Returns: Count of column result public func columnCount() -> Int { return handleStatement.columnCount() } /// The wrapper of `sqlite3_column_name`. /// /// - Parameter index: Begin with 0 /// - Returns: Column name public func columnName(atIndex index: Int) -> String { return handleStatement.columnName(atIndex: index) } /// The wrapper of `sqlite3_column_table_name`. /// /// - Parameter index: Begin with 0 /// - Returns: The related table of column at index public func columnTableName(atIndex index: Int) -> String { return handleStatement.columnTableName(atIndex: index) } /// The wrapper of `sqlite3_finalize` /// /// - Throws: `Error` public func finalize() throws { try handleStatement.finalize() } /// The number of changed rows in the most recent call. /// It should be called after executing successfully public var changes: Int { return handleStatement.changes } /// The row id of most recent insertion. public var lastInsertedRowID: Int64 { return handleStatement.lastInsertedRowID } } extension CoreStatement { /// The tag of the related database. public var tag: Tag? { return core.tag } /// The path of the related database. public var path: String { return core.path } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/base/Database.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation #if WCDB_IOS import UIKit #endif //WCDB_IOS /// Thread-safe Database object public final class Database: Core { /// Init a database from path. /// Note that all database objects with same path share the same core. /// So you can create multiple database objects. WCDB will manage them automatically. /// Note that WCDB will not generate a sqlite handle until the first operation, /// which is also called as lazy initialization. /// /// - Parameter path: Path to your database public convenience init(withPath path: String) { self.init(withFileURL: URL(fileURLWithPath: path)) } /// Init a database from file url. /// Note that all database objects with same path share the same core. /// So you can create multiple database objects. WCDB will manage them automatically. /// Note that WCDB will not generate a sqlite handle until the first operation, /// which is also called as lazy initialization. /// /// - Parameter url: File url to your database public init(withFileURL url: URL) { #if swift(>=4.2) #else Error.fatalError("Swift 4.2 is required.") #endif #if WCDB_IOS DispatchQueue.once(name: "com.Tencent.WCDB.swift.purge", { let purgeFreeHandleQueue: DispatchQueue = DispatchQueue(label: "com.Tencent.WCDB.swift.purge") _ = NotificationCenter.default.addObserver( forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: nil, using: { (_) in purgeFreeHandleQueue.async { Database.purge() } }) }) #endif //WCDB_IOS super.init(with: HandlePool.getPool(withPath: url.standardizedFileURL.path, defaultConfigs: Database.defaultConfigs)) } /// Init a database from existing tag. /// Note that all database objects with same path share the same core. /// So you can create multiple database objects. WCDB will manage them automatically. /// Note that WCDB will not generate a sqlite handle until the first operation, /// which is also called as lazy initialization. /// /// - Parameter tag: An existing tag. /// - Throws: `Error` while tag is not exists public init(withExistingTag tag: Tag) throws { super.init(with: try HandlePool.getExistingPool(with: tag)) } init(withExistingPath path: String) throws { super.init(with: try HandlePool.getExistingPool(withPath: path)) } /// The tag of the database. Default to nil. /// You should set it on a database and can get it from all kind of Core objects, /// including `Database`, `Table`, `Transaction`, `Select`, `RowSelect`, `MultiSelect`, `Insert`, `Delete`, /// `Update` and so on. /// Note that core objects with same path share this tag, even they are not the same object. /// /// let database1 = Database(withPath: path) /// let database2 = Database(withPath: path) /// database1.tag = 1 /// print("Tag: \(database2.tag!)") // print 1 /// public override var tag: Tag? { get { return handlePool.tag } set { handlePool.tag = newValue } } private static var threadedHandles = ThreadLocal<[String: RecyclableHandle]>(defaultTo: [:]) private func flowOut() throws -> RecyclableHandle { let threadedHandles = Database.threadedHandles.value if let handle = threadedHandles[path] { return handle } return try handlePool.flowOut() } /// Since WCDB is using lazy initialization, /// `init(withPath:)`, `init(withFileURL:)` never failed even the database can't open. /// So you can call this to check whether the database can be opened. /// Return false if an error occurs during sqlite handle initialization. public var canOpen: Bool { return !handlePool.isDrained || ((try? handlePool.fillOne()) != nil) } /// Check database is already opened. public var isOpened: Bool { return !handlePool.isDrained } /// Check whether database is blockaded. public var isBlockaded: Bool { return handlePool.isBlockaded } public typealias OnClosed = HandlePool.OnDrained /// Close the database. /// Since Multi-threaded operation is supported in WCDB, /// other operations in different thread can open the closed database. /// So this method can make sure database is closed in the `onClosed` block. /// All other operations will be blocked until this method returns. /// /// A close operation consists of 4 steps: /// 1. `blockade`, which blocks all other operations. /// 2. `close`, which waits until all sqlite handles return and closes them. /// 3. `onClosed`, which trigger the callback. /// 4. `unblokade`, which unblocks all other opreations. /// /// You can simply call `close:` to do all steps above or call these separately. /// Since this method will wait until all sqlite handles return, it may lead to deadlock in some bad practice. /// The key to avoid deadlock is to make sure all WCDB objects in current thread is dealloced. In detail: /// 1. You should not keep WCDB objects, including `Insert`, `Delete`, `Update`, `Select`, `RowSelect`, /// `MultiSelect`, `CoreStatement`, `Transaction`. These objects should not be kept. /// You should get them, use them, then release them right away. /// 2. WCDB objects may not be out of its' scope. /// The best practice is to call `close:` in sub-thread and display a loading animation in main thread. /// /// //close directly /// database.close(onClosed: { () throws -> Void in /// //do something on this closed database /// }) /// /// //close separately /// database.blockade() /// database.close() /// //do something on this closed database /// database.unblockade() /// /// - Parameter onClosed: Trigger on database closed. /// - Throws: Rethrows your error. public func close(onClosed: OnClosed) rethrows { try handlePool.drain(onDrained: onClosed) } /// Close the database. public func close() { handlePool.drain() } /// Blockade the database. public func blockade() { handlePool.blockade() } /// Unblockade the database. public func unblockade() { handlePool.unblockade() } /// Purge all unused memory of this database. /// WCDB will cache and reuse some sqlite handles to improve performance. /// The max count of free sqlite handles is same /// as the number of concurrent threads supported by the hardware implementation. /// You can call it to save some memory. public func purge() { handlePool.purgeFreeHandles() } /// Purge all unused memory of all databases. /// Note that WCDB will call this interface automatically while it receives memory warning on iOS. public static func purge() { HandlePool.purgeFreeHandlesInAllPools() } override func prepare(_ statement: Statement) throws -> RecyclableHandleStatement { let recyclableHandle = try flowOut() return try prepare(statement, in: recyclableHandle) } /// Exec a specific sql. /// Note that you can use this interface to execute a SQL that is not contained in the WCDB interface layer. /// /// - Parameter statement: WINQ statement /// - Throws: `Error` public override func exec(_ statement: Statement) throws { try exec(statement, in: flowOut()) } /// Separate interface of `run(transaction:)` /// You should call `begin`, `commit`, `rollback` and all other operations in same thread. /// To do a cross-thread transaction, use `getTransaction`. /// - Throws: `Error` public override func begin(_ mode: StatementTransaction.Mode = .immediate) throws { let statement = mode == .immediate ? CommonStatement.beginTransactionImmediate : StatementTransaction().begin(mode) let recyableHandlePool = try flowOut() try recyableHandlePool.raw.handle.exec(statement) Database.threadedHandles.value[path] = recyableHandlePool } /// Separate interface of `run(transaction:)` /// You should call `begin`, `commit`, `rollback` and all other operations in same thread. /// To do a cross-thread transaction, use `getTransaction`. /// - Throws: `Error` public override func commit() throws { let recyableHandlePool = try flowOut() try recyableHandlePool.raw.handle.exec(CommonStatement.commitTransaction) Database.threadedHandles.value.removeValue(forKey: path) } /// Separate interface of run(transaction:) /// You should call `begin`, `commit`, `rollback` and all other operations in same thread. /// To do a cross-thread transaction, use `getTransaction`. /// - Throws: `Error` public override func rollback() throws { Database.threadedHandles.value.removeValue(forKey: path) let recyableHandlePool = try flowOut() try recyableHandlePool.raw.handle.exec(CommonStatement.rollbackTransaction) } /// Run a embedded transaction in closure /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// try database.run(embeddedTransaction: { () throws -> Void in /// try database.insert(objects: objects, intoTable: table) /// }) /// /// - Parameter embeddedTransaction: Operation inside transaction /// - Throws: `Error` public override func run(embeddedTransaction: TransactionClosure) throws { if Database.threadedHandles.value[path] != nil { return try embeddedTransaction() } return try run(transaction: embeddedTransaction) } } //Config extension Database { /// Set cipher key for a database. /// For an encrypted database, you must call it before all other operation. /// The cipher page size defaults to 4096 in WCDB, but it defaults to 1024 in other databases. /// So for an existing database created by other database framework, you should set it to 1024. /// Otherwise, you'd better to use cipher page size with 4096 /// or simply call setCipherKey: interface to get better performance. /// /// - Parameters: /// - optionalKey: Cipher key. Nil for no cipher. /// - pageSize: Cipher page size. public func setCipher(key optionalKey: Data?, pageSize: Int = 4096) { if let key = optionalKey { handlePool.setConfig(named: DefaultConfigOrder.cipher.description, with: { (handle: Handle) throws in let statementPragmaPageSize = StatementPragma().pragma(.cipherPageSize, to: pageSize) try handle.setCipher(key: key) try handle.exec(statementPragmaPageSize) }) } else { handlePool.setConfig(named: DefaultConfigOrder.cipher.description, with: { (_) in }) } } public typealias PerformanceTracer = Handle.PerformanceTracer public typealias SQLTracer = Handle.SQLTracer static private var performanceTracer = Atomic() static private var sqlTracer = Atomic() /// You can register a tracer to monitor the performance of all SQLs. /// It returns /// 1. The collection of SQLs and the executions count of each SQL. /// 2. Time consuming in nanoseconds. /// 3. Tag of database. /// /// Note that: /// 1. You should register trace before all db operations. /// 2. Global tracer will be recovered by db tracer. /// /// Database.globalTrace(ofPerformance: { (tag, sqls, cost) in /// if let wrappedTag = tag { /// print("Tag: \(wrappedTag) ") /// }else { /// print("Nil tag") /// } /// sqls.forEach({ (arg) in /// print("SQL: \(arg.key) Count: \(arg.value)") /// }) /// print("Total cost \(cost) nanoseconds") /// }) /// /// Tracer may cause wcdb performance degradation, according to your needs to choose whether to open. /// /// - Parameter trace: trace. Nil to disable global preformance trace. public static func globalTrace(ofPerformance trace: @escaping PerformanceTracer) { performanceTracer.assign(trace) } public static func globalTrace(ofPerformance: Void?) { performanceTracer.assign(nil) } /// You can register a tracer to monitor the execution of all SQLs. /// It returns a prepared or executed SQL. /// Note that you should register trace before all db operations. /// /// Database.globalTrace(ofSQL: { (sql) in /// print("SQL: \(sql)") /// }) /// /// Tracer may cause wcdb performance degradation, according to your needs to choose whether to open. /// /// - Parameter trace: trace. Nil to disable global sql trace. public static func globalTrace(ofSQL trace: @escaping SQLTracer) { sqlTracer.assign(trace) } public static func globalTrace(ofSQL: Void?) { sqlTracer.assign(nil) } /// You can register a reporter to monitor all errors. /// /// Database.globalTrace(ofError: { (error) in /// switch error.type { /// case .sqliteGlobal: /// debugPrint("[WCDB][DEBUG] \(error.description)") /// case .warning: /// print("[WCDB][WARNING] \(error.description)") /// default: /// print("[WCDB][ERROR] \(error.description)") /// } /// }) /// /// - Parameter errorReporter: report public static func globalTrace(ofError errorReporter: @escaping Error.Reporter) { Error.setReporter(errorReporter) } public static func globalTrace(ofError: Void?) { Error.setReporter(nil) } /// Reset global error trace to default trace. public static func resetGlobalTraceOfError() { Error.resetReporter() } private static let subthreadCheckpointDelay: TimeInterval = 2 private static let subthreadCheckpointPages: Int = 1000 private static let timedQueue = TimedQueue(withDelay: subthreadCheckpointDelay) private static let defaultConfigs: Configs = Configs(ContiguousArray(arrayLiteral: Configs.Config(named: DefaultConfigOrder.fileProtection.description, with: { (handle: Handle) throws in #if WCDB_IOS try Handle.subfixs.forEach { try File.addFirstUserAuthenticationFileProtection(atPath: handle.path+$0) } #endif //WCDB_IOS }, orderBy: DefaultConfigOrder.fileProtection.rawValue), Configs.Config(named: DefaultConfigOrder.trace.description, with: { (handle: Handle) throws in if let sqlTracer = Database.sqlTracer.raw { handle.trace(sql: sqlTracer) } if let performanceTracer = Database.performanceTracer.raw { handle.trace(performance: performanceTracer) } }, orderBy: DefaultConfigOrder.trace.rawValue), Configs.Config(emptyConfigNamed: DefaultConfigOrder.cipher.description, orderBy: DefaultConfigOrder.cipher.rawValue), Configs.Config(named: DefaultConfigOrder.basic.description, with: { (handle: Handle) throws in guard !handle.isReadonly else { let handleStatement = try handle.prepare(CommonStatement.getJournalMode) try handleStatement.step() let journalMode: String = handleStatement.columnValue(atIndex: 0) try handleStatement.finalize() assert(journalMode.caseInsensitiveCompare("WAL") == ComparisonResult.orderedSame, "It is not possible to open read-only WAL databases.") return } //Locking Mode do { //Get Locking Mode let handleStatement = try handle.prepare(CommonStatement.getLockingMode) try handleStatement.step() let lockingMode: String = handleStatement.columnValue(atIndex: 0) try handleStatement.finalize() //Set Locking Mode if lockingMode.caseInsensitiveCompare("NORMAL") != ComparisonResult.orderedSame { try handle.exec(CommonStatement.enableLockingModeNormal) } } //Synchronous do { try handle.exec(CommonStatement.enableSynchronousNormal) } //Journal Mode do { //Get Journal Mode let handleStatement = try handle.prepare(CommonStatement.getJournalMode) try handleStatement.step() let journalMode: String = handleStatement.columnValue(atIndex: 0) try handleStatement.finalize() //Set Journal Mode if journalMode.caseInsensitiveCompare("WAL") != ComparisonResult.orderedSame { try handle.exec(CommonStatement.enableJournalModeWAL) } } //Fullfsync do { try handle.exec(CommonStatement.enableFullfsync) } }, orderBy: DefaultConfigOrder.basic.rawValue), Configs.Config(emptyConfigNamed: DefaultConfigOrder.synchronous.description, orderBy: DefaultConfigOrder.synchronous.rawValue), Configs.Config(named: DefaultConfigOrder.checkpoint.description, with: { (handle: Handle) throws in handle.register(onCommitted: { (handle, pages, _) in guard pages > subthreadCheckpointPages else { return } DispatchQueue.once(name: "com.Tencent.WCDB.swift.checkpoint", { DispatchQueue(label: "com.Tencent.WCDB.swift.checkpoint").async { while true { Database.timedQueue.wait(untilExpired: { try? Database(withExistingPath: $0).exec(CommonStatement.checkpoint) }) } } }) Database.timedQueue.reQueue(with: handle.path) }) }, orderBy: DefaultConfigOrder.checkpoint.rawValue), Configs.Config(emptyConfigNamed: DefaultConfigOrder.tokenize.description, orderBy: DefaultConfigOrder.tokenize.rawValue) )) /// Default config order public enum DefaultConfigOrder: Int, CustomStringConvertible { case fileProtection = 0 case trace = 1 case cipher = 2 case basic = 3 case synchronous = 4 case checkpoint = 5 case tokenize = 6 public var description: String { switch self { case .fileProtection: return "fileProtection" case .trace: return "trace" case .cipher: return "cipher" case .basic: return "basic" case .synchronous: return "synchronous" case .checkpoint: return "checkpoint" case .tokenize: return "tokenize" } } } public typealias Config = HandlePool.Config public typealias ConfigOrder = HandlePool.ConfigOrder /// Set config for this database. /// /// Since WCDB is a multi-handle database, an executing handle will not apply this config immediately. /// Instead, all handles will run this config before its next operation. /// /// database.setConfig(named: "demo", with: { (handle: Handle) throws in /// try handle.exec(StatementPragma().pragma(.secureDelete, to: true)) /// }, orderBy: 1) /// /// - Parameters: /// - name: The Identifier for this config /// - callback: config /// - order: The smaller number is called first public func setConfig(named name: String, with callback: @escaping Config, orderBy order: ConfigOrder) { handlePool.setConfig(named: name, with: callback, orderBy: order) } /// This interface is equivalent to `database.setConfig(named: name, with: callback, orederBy: Int.max)`. /// /// - Parameters: /// - name: The Identifier for this config /// - callback: config public func setConfig(named name: String, with callback: @escaping Config) { handlePool.setConfig(named: name, with: callback) } /// Set Synchronous for this database. It will disable checkpoint opti to avoid performance degradation. /// Synchronous can improve the stability of the database and reduce database damage, /// but there will be performance degradation. /// /// - Parameter isFull: enable or disable full synchronous public func setSynchronous(isFull: Bool) { if isFull { handlePool.setConfig(named: DefaultConfigOrder.synchronous.description, with: { (handle: Handle) throws in try handle.exec(CommonStatement.enableSynchronousFull) }) handlePool.setConfig(named: DefaultConfigOrder.checkpoint.description, with: { (handle: Handle) throws in }) } else { handlePool.setConfig(named: DefaultConfigOrder.synchronous.description, with: { (handle: Handle) throws in }) let checkpointConfig = Database.defaultConfigs.config( by: DefaultConfigOrder.checkpoint.description) assert(checkpointConfig != nil, "It should not be failed. If you think it's a bug, please report an issue to us.") handlePool.setConfig(named: DefaultConfigOrder.checkpoint.description, with: checkpointConfig!) } } /// This interface is equivalent to `database.setTokenizes(tokenizes)` /// /// - Parameter tokenizes: registed tokenizeName. You can use builtin tokenizer named `.WCDB` or `.Apple` public func setTokenizes(_ tokenizes: Tokenize...) { setTokenizes(tokenizes) } /// Setup multiple tokenizers with names for current database. /// /// - Parameter tokenizes: registed tokenizeName. You can use builtin tokenizer named .WCDB or .Apple public func setTokenizes(_ tokenizes: [Tokenize]) { handlePool.setConfig(named: DefaultConfigOrder.tokenize.description) { (handle: Handle) throws in try tokenizes.forEach({ (tokenize) in let module = tokenize.module let handleStatement = try handle.prepare(CommonStatement.fts3Tokenizer) handleStatement.bind(module.name, toIndex: 1) handleStatement.bind(module.address, toIndex: 2) try handleStatement.step() try handleStatement.finalize() }) } } } //Transaction extension Database { /// Generation a `Transaction` object to do a transaction. /// /// - Returns: Transaction /// - Throws: `Error` public func getTransaction() throws -> Transaction { return Transaction(with: recyclableHandlePool, and: try flowOut()) } } //Table extension Database { /// Get a wrapper from an existing table. /// /// - Parameters: /// - name: The name of the table. /// - type: A class conform to TableCodable protocol. /// - Returns: Nil for a non-existent table. /// - Throws: `Error` public func getTable( named name: String, of type: Root.Type = Root.self) throws -> Table? { guard try isTableExists(name) else { return nil } return Table(withDatabase: self, named: name) } } //File extension Database { /// Subfix of paths to all database-related files. public static var subfixs: [String] { return Handle.subfixs } /// URLs to all database-related files. public var urls: [URL] { return paths.map({ (path) -> URL in return URL(fileURLWithPath: path) }) } /// Paths to all database-related files. public var paths: [String] { return Database.subfixs.map({ (subfix) -> String in return path+subfix }) } /// Remove all database-related files. /// You should call it on a closed database. Otherwise you will get a warning. /// /// - Throws: `Error` public func removeFiles() throws { if !isBlockaded || isOpened { Error.warning("Removing files on an opened database may cause unknown results") } try File.remove(files: paths) } /// This interface is equivalent `moveFiles(toDirectory:withExtraFiles:)` /// /// - Parameters: /// - directory: destination /// - extraFiles: extraFiles /// - Throws: `Error` public func moveFiles(toDirectory directory: String, withExtraFiles extraFiles: String...) throws { try moveFiles(toDirectory: directory, withExtraFiles: extraFiles) } /// Move all database-related files and some extra files to directory safely. /// You should call it on a closed database. Otherwise you will get a warning and you may get a corrupted database. /// /// - Parameters: /// - directory: destination /// - extraFiles: extraFiles /// - Throws: `Error` public func moveFiles(toDirectory directory: String, withExtraFiles extraFiles: [String]) throws { try File.createDirectoryWithIntermediateDirectories(atPath: directory) var recovers: [String] = [] let paths = self.paths + extraFiles do { try paths.forEach({ (path) in guard File.isExists(atPath: path) else { return } let file = path.lastPathComponent let newPaths = directory.stringByAppending(pathComponent: file) if File.isExists(atPath: newPaths) { try File.remove(files: [newPaths]) } try File.hardlink(atPath: path, toPath: newPaths) recovers.append(newPaths) }) } catch let error { try? File.remove(files: recovers) throw error } try? File.remove(files: paths) } /// Get the space used by the database files. /// You should call it on a closed database. Otherwise you will get a warning. /// /// - Returns: The sum of files size in bytes. /// - Throws: `Error` public func getFilesSize() throws -> UInt64 { if !isBlockaded || isOpened { Error.warning("Getting files size on an opened database may get incorrect results") } return try File.getSize(ofFiles: paths) } } //Repair extension Database { /// Backup metadata to recover. /// Since metadata will be changed while a table or an index is created or dropped, /// you should call this periodically. /// /// - Parameter key: The cipher key for backup. Nil for non-encrypted. /// - Throws: `Error` public func backup(withKey key: Data? = nil) throws { let handle = try flowOut() try handle.raw.handle.backup(withKey: key) } /// Recover data from a corruped db. You'd better to recover a closed database. /// /// - Parameters: /// - source: The path to the corrupted database /// - pageSize: Page size of the corrupted database. It's default to 4096 on iOS. /// Page size never change unless you can call "PRAGMA page_size=NewPageSize" to set it. /// Also, you can call "PRAGMA page_size" to check the current value while database is not corrupted. /// - databaseKey: The cipher key for corrupeted database /// - backupKey: The cipher key for backup /// - Throws: `Error` public func recover(fromPath source: String, withPageSize pageSize: Int32 = 4096, databaseKey: Data? = nil, backupKey: Data? = nil) throws { let handle = try flowOut() try handle.raw.handle.recover(fromPath: source, withPageSize: pageSize, databaseKey: databaseKey, backupKey: backupKey) } } extension Database: InsertChainCallInterface {} extension Database: UpdateChainCallInterface {} extension Database: DeleteChainCallInterface {} extension Database: RowSelectChainCallInterface {} extension Database: SelectChainCallInterface {} extension Database: MultiSelectChainCallInterface {} extension Database: InsertInterface {} extension Database: UpdateInterface {} extension Database: DeleteInterface {} extension Database: RowSelectInterface {} extension Database: SelectInterface {} extension Database: TableInterface {} ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/base/HandlePool.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class HandlePool { private class Wrap { let handlePool: HandlePool var reference: Int = 0 init(_ handlePool: HandlePool) { self.handlePool = handlePool } } private static let spin = Spin() private static var pools: [String: Wrap] = [:] static func getPool(withPath path: String, defaultConfigs: Configs) -> RecyclableHandlePool { spin.lock(); defer { spin.unlock() } var index = pools.index(forKey: path) if index == nil { let handlePool = HandlePool(withPath: path, defaultConfigs: defaultConfigs) pools[path] = Wrap(handlePool) index = pools.index(forKey: path) } return getExistingPool(atIndex: index!) } static func getExistingPool(with tag: Tag) throws -> RecyclableHandlePool { spin.lock(); defer { spin.unlock() } guard let index = pools.index(where: { (arg) -> Bool in return arg.value.handlePool.tag == tag }) else { throw Error.reportCore(tag: tag, path: "", operation: .getPool, code: .misuse, message: "Database with tag: \(tag) is not exists.") } return getExistingPool(atIndex: index) } static func getExistingPool(withPath path: String) throws -> RecyclableHandlePool { spin.lock(); defer { spin.unlock() } guard let index = pools.index(forKey: path) else { throw Error.reportCore(tag: nil, path: path, operation: .getPool, code: .misuse, message: "Database at path: \(path) is not exists.") } return getExistingPool(atIndex: index) } private static func getExistingPool(atIndex index: Dictionary.Index) -> RecyclableHandlePool { let node = pools[index] let path = node.key var wrap = node.value wrap.reference += 1 return Recyclable(wrap.handlePool, onRecycled: { spin.lock(); defer { spin.unlock() } let wrap = pools[path]! wrap.reference -= 1 if wrap.reference == 0 { pools.removeValue(forKey: path) } }) } typealias HandleWrap = (handle: Handle, configs: Configs) private let handles = ConcurrentList(withCapacityCap: maxHardwareConcurrency) var tag: Tag? let path: String private let rwlock = RWLock() private let aliveHandleCount = Atomic(0) private var configs: Configs private init(withPath path: String, defaultConfigs: Configs) { self.path = path self.configs = defaultConfigs } var isDrained: Bool { return aliveHandleCount == 0 } func fillOne() throws { rwlock.lockRead(); defer { rwlock.unlockRead() } let handle = try generate() if handles.pushBack(handle) { aliveHandleCount += 1 } } private func invoke(handleWrap: inout HandleWrap) throws { let newConfigs = self.configs if newConfigs != handleWrap.configs { try newConfigs.invoke(handle: handleWrap.handle) handleWrap.configs = newConfigs } } static private let maxConcurrency = max(maxHardwareConcurrency, 64) static private let maxHardwareConcurrency = ProcessInfo.processInfo.processorCount func flowOut() throws -> RecyclableHandle { var unlock = true rwlock.lockRead(); defer { if unlock { rwlock.unlockRead() } } var handleWrap = handles.popBack() if handleWrap == nil { guard aliveHandleCount < HandlePool.maxConcurrency else { throw Error.reportCore(tag: tag, path: path, operation: .flowOut, code: .exceed, message: "The concurrency of database exceeds the max concurrency") } handleWrap = try generate() aliveHandleCount += 1 if aliveHandleCount > HandlePool.maxHardwareConcurrency { var warning = "The concurrency of database: \(tag ?? 0) with \(aliveHandleCount)" warning.append(" exceeds the concurrency of hardware: \(HandlePool.maxHardwareConcurrency)") Error.warning(warning) } } handleWrap!.handle.tag = self.tag try invoke(handleWrap: &handleWrap!) unlock = false return RecyclableHandle(handleWrap!, onRecycled: { self.flowBack(handleWrap!) }) } private func flowBack(_ handleWrap: HandleWrap) { let inserted = handles.pushBack(handleWrap) rwlock.unlockRead() if !inserted { aliveHandleCount -= 1 } } private func generate() throws -> HandleWrap { let handle = Handle(withPath: path) handle.tag = tag let configs = self.configs try handle.open() try configs.invoke(handle: handle) return HandleWrap(handle: handle, configs: configs) } func blockade() { rwlock.lockWrite() } func unblockade() { rwlock.unlockWrite() } var isBlockaded: Bool { return rwlock.isWriting } public typealias OnDrained = () throws -> Void func drain(onDrained: OnDrained) rethrows { blockade(); defer { unblockade() } let size = handles.clear() aliveHandleCount -= size try onDrained() } func drain() { blockade(); defer { unblockade() } let size = handles.clear() aliveHandleCount -= size } func purgeFreeHandles() { rwlock.lockRead(); defer { rwlock.unlockRead() } let size = handles.clear() aliveHandleCount -= size } public typealias Config = Configs.Callback public typealias ConfigOrder = Configs.Order func setConfig(named name: String, with callback: @escaping Config, orderBy order: ConfigOrder) { configs.setConfig(named: name, with: callback, orderBy: order) } func setConfig(named name: String, with callback: @escaping Config) { configs.setConfig(named: name, with: callback) } static func purgeFreeHandlesInAllPools() { let handlePools: [HandlePool]! do { spin.lock(); defer { spin.unlock() } handlePools = pools.values.reduce(into: []) { $0.append($1.handlePool) } } handlePools.forEach { $0.purgeFreeHandles() } } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/base/RecyclableCore.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation typealias RecyclableHandlePool = Recyclable final class RecyclableHandleStatement: Recyclable { private let recyclableHandle: RecyclableHandle init(recyclableHandle: RecyclableHandle, handleStatement: HandleStatement) { self.recyclableHandle = recyclableHandle super.init(handleStatement) } } typealias RecyclableHandle = Recyclable ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/base/Transaction.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /// Thread-safe Transaction object public final class Transaction: Core { private let recyclableHandle: RecyclableHandle /// Check whether is already in transaction. public private(set) var isInTransaction: Bool = false private var mutex: RecursiveMutex = RecursiveMutex() init(with recyclableHandlePool: RecyclableHandlePool, and recyclableHandle: RecyclableHandle) { self.recyclableHandle = recyclableHandle super.init(with: recyclableHandlePool) } deinit { if isInTransaction { try? rollback() } } private var handle: Handle { return recyclableHandle.raw.handle } override func prepare(_ statement: Statement) throws -> RecyclableHandleStatement { mutex.lock(); defer { mutex.unlock() } return try prepare(statement, in: recyclableHandle) } /// Exec a specific sql. /// Note that you can use this interface to execute a SQL that is not contained in the WCDB interface layer. /// /// - Parameter statement: WINQ statement /// - Throws: `Error` public override func exec(_ statement: Statement) throws { mutex.lock(); defer { mutex.unlock() } try exec(statement, in: recyclableHandle) } /// Prepare a specific sql. /// Note that you can use this interface to prepare a SQL that is not contained in the WCDB interface layer /// /// - Parameter statement: WINQ statement /// - Returns: CoreStatement /// - Throws: `Error` public override func prepare(_ statement: Statement) throws -> CoreStatement { mutex.lock(); defer { mutex.unlock() } return try super.prepare(statement) } /// Check whether table exists /// /// - Parameter table: The name of the table to be checked. /// - Returns: True if table exists. False if table does not exist. /// - Throws: `Error` public override func isTableExists(_ table: String) throws -> Bool { mutex.lock(); defer { mutex.unlock() } return try super.isTableExists(table) } /// Run a transaction in closure /// /// try transaction.run(transaction: { () throws -> Void in /// try transaction.insert(objects: objects, intoTable: table) /// }) /// /// - Parameter transaction: Operation inside transaction /// - Throws: `Error` public override func run(transaction: TransactionClosure) throws { mutex.lock(); defer { mutex.unlock() } try super.run(transaction: transaction) } /// Run a controllable transaction in closure /// /// try transaction.run(controllableTransaction: { () throws -> Bool in /// try transaction.insert(objects: objects, intoTable: table) /// return true // return true to commit transaction and return false to rollback transaction. /// }) /// /// - Parameter controllableTransaction: Operation inside transaction /// - Throws: `Error` public override func run(controllableTransaction: ControlableTransactionClosure) throws { mutex.lock(); defer { mutex.unlock() } try super.run(controllableTransaction: controllableTransaction) } /// Separate interface of `run(transaction:)` /// You should call `begin`, `commit`, `rollback` and all other operations in same thread. /// - Throws: `Error` public override func begin(_ mode: StatementTransaction.Mode = .immediate) throws { mutex.lock(); defer { mutex.unlock() } let statement = mode == .immediate ? CommonStatement.beginTransactionImmediate : StatementTransaction().begin(mode) try handle.exec(statement) isInTransaction = true } /// Separate interface of `run(transaction:)` /// You should call `begin`, `commit`, `rollback` and all other operations in same thread. /// - Throws: `Error` public override func commit() throws { mutex.lock(); defer { mutex.unlock() } try handle.exec(CommonStatement.commitTransaction) isInTransaction = false } /// Separate interface of run(transaction:) /// You should call `begin`, `commit`, `rollback` and all other operations in same thread. /// - Throws: `Error` public override func rollback() throws { mutex.lock(); defer { mutex.unlock() } isInTransaction = false try handle.exec(CommonStatement.rollbackTransaction) } /// Run a embedded transaction in closure /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// try transaction.run(embeddedTransaction: { () throws -> Void in /// try transaction.insert(objects: objects, intoTable: table) /// }) /// /// - Parameter embeddedTransaction: Operation inside transaction /// - Throws: `Error` public override func run(embeddedTransaction: TransactionClosure) throws { mutex.lock(); defer { mutex.unlock() } if isInTransaction { return try embeddedTransaction() } return try run(transaction: embeddedTransaction) } /// The number of changed rows in the most recent call. /// It should be called after executing successfully public var changes: Int { mutex.lock(); defer { mutex.unlock() } return handle.changes } } extension Transaction: InsertChainCallInterface {} extension Transaction: UpdateChainCallInterface {} extension Transaction: DeleteChainCallInterface {} extension Transaction: RowSelectChainCallInterface {} extension Transaction: SelectChainCallInterface {} extension Transaction: MultiSelectChainCallInterface {} extension Transaction: InsertInterface {} extension Transaction: UpdateInterface {} extension Transaction: DeleteInterface {} extension Transaction: RowSelectInterface {} extension Transaction: SelectInterface {} extension Transaction: TableInterface {} ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/binding/ColumnConstraintBinding.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct ColumnConstraintBinding { let isPrimary: Bool let isAutoIncrement: Bool let defaultValue: ColumnDef.DefaultType? let conflict: Conflict? let isNotNull: Bool let isUnique: Bool let term: OrderTerm? public init(isPrimary: Bool = false, orderBy term: OrderTerm? = nil, isAutoIncrement: Bool = false, onConflict conflict: Conflict? = nil, isNotNull: Bool = false, isUnique: Bool = false, defaultTo defaultValue: ColumnDef.DefaultType? = nil) { self.isPrimary = isPrimary self.isAutoIncrement = isAutoIncrement self.isNotNull = isNotNull self.isUnique = isUnique self.defaultValue = defaultValue self.term = term self.conflict = conflict } public init( isPrimary: Bool = false, orderBy term: OrderTerm? = nil, isAutoIncrement: Bool = false, onConflict conflict: Conflict? = nil, isNotNull: Bool = false, isUnique: Bool = false, defaultTo defaultEncodedValue: T) { var defaultValue: ColumnDef.DefaultType! let value = defaultEncodedValue.archivedValue() switch T.columnType { case .integer32: defaultValue = .int32(value.int32Value) case .integer64: defaultValue = .int64(value.int64Value) case .text: defaultValue = .text(value.stringValue) case .float: defaultValue = .float(value.doubleValue) case .BLOB: defaultValue = .BLOB(value.dataValue) case .null: defaultValue = .null } self.init(isPrimary: isPrimary, orderBy: term, isAutoIncrement: isAutoIncrement, onConflict: conflict, isNotNull: isNotNull, isUnique: isUnique, defaultTo: defaultValue) } func generateColumnDef(with rawColumnDef: ColumnDef) -> ColumnDef { let columnDef = rawColumnDef if isPrimary { columnDef.makePrimary(orderBy: term, isAutoIncrement: isAutoIncrement, onConflict: conflict) } if isNotNull { columnDef.makeNotNull() } if isUnique { columnDef.makeUnique() } if defaultValue != nil { columnDef.makeDefault(to: defaultValue!) } return columnDef } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/binding/IndexBinding.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct IndexBinding { public typealias Subfix = String let columnIndexes: [ColumnIndex] let isUnique: Bool public init(isUnique: Bool = false, indexesBy columnIndexConvertibleList: [ColumnIndexConvertible]) { self.columnIndexes = columnIndexConvertibleList.asIndexes() self.isUnique = isUnique } public init(isUnique: Bool = false, indexesBy columnIndexConvertibleList: ColumnIndexConvertible...) { self.init(isUnique: isUnique, indexesBy: columnIndexConvertibleList) } func generateCreateIndexStatement(onTable tableName: String, withIndexSubfix indexSubfix: String) -> StatementCreateIndex { return StatementCreateIndex() .create(index: tableName+indexSubfix, isUnique: isUnique, ifNotExists: true) .on(table: tableName, indexesBy: columnIndexes) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/binding/Property.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public protocol PropertyConvertible: ColumnConvertible, PropertyRedirectable { var codingTableKey: CodingTableKeyBase {get} func asProperty() -> Property func `in`(table: String) -> Property } public typealias PropertyOperable = PropertyConvertible & ExpressionOperable public final class Property: Describable { public private(set) var description: String public private(set) var codingTableKey: CodingTableKeyBase public init(named name: String, with codingTableKey: CodingTableKeyBase) { self.codingTableKey = codingTableKey self.description = name } public init(with codingTableKey: CodingTableKeyBase) { self.codingTableKey = codingTableKey self.description = codingTableKey.stringValue } public var name: String { return description } } extension Property: PropertyOperable { public func asProperty() -> Property { return self } public func `in`(table: String) -> Property { let column: Column = self.in(table: table) return Property(named: column.description, with: codingTableKey) } public func asColumn() -> Column { return Column(named: name) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/binding/Redirectable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public protocol PropertyRedirectable { func `as`(_ propertyConvertible: PropertyConvertible) -> Property } extension PropertyRedirectable where Self: Describable { public func `as`(_ propertyConvertible: PropertyConvertible) -> Property { return Property(named: description, with: propertyConvertible.codingTableKey) } } extension Column: PropertyRedirectable {} extension ColumnResult: PropertyRedirectable {} extension Expression: PropertyRedirectable {} ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/binding/TableBinding.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class TableBinding { private let properties: [CodingTableKeyType: Property] let allProperties: [Property] let allKeys: [CodingTableKeyType] private lazy var columnTypes: [String: ColumnType] = { // CodingTableKeyType.Root must conform to TableEncodable protocol. let tableDecodableType = CodingTableKeyType.Root.self as! TableDecodableBase.Type return ColumnTypeDecoder.types(of: tableDecodableType) }() private lazy var allColumnDef: [ColumnDef] = allKeys.map { (key) -> ColumnDef in return generateColumnDef(with: key) } private typealias ColumnConstraintBindingMap = [CodingTableKeyType: ColumnConstraintBinding] private lazy var columnConstraintBindings: ColumnConstraintBindingMap? = CodingTableKeyType.columnConstraintBindings private typealias IndexBindingMap = [IndexBinding.Subfix: IndexBinding] private lazy var indexBindings: IndexBindingMap? = CodingTableKeyType.indexBindings private typealias TableConstraintBindingMap = [TableConstraintBinding.Name: TableConstraintBinding] private lazy var tableConstraintBindings: TableConstraintBindingMap? = CodingTableKeyType.tableConstraintBindings private lazy var virtualTableBinding: VirtualTableBinding? = CodingTableKeyType.virtualTableBinding private lazy var primaryKey: CodingTableKeyType? = { guard let filtered = columnConstraintBindings?.filter({ (args) -> Bool in return args.value.isPrimary }) else { return nil } guard filtered.count == 1 else { assert(filtered.count == 0, "Only one primary key is supported. Use MultiPrimaryBinding instead") return nil } return filtered.first!.key }() public init(_ type: CodingTableKeyType.Type) { var allProperties: [Property] = [] var properties: [CodingTableKeyType: Property] = [:] var allKeys: [CodingTableKeyType] = [] var i = 0 while true { guard let key = (withUnsafePointer(to: &i) { return $0.withMemoryRebound(to: CodingTableKeyType?.self, capacity: 1, { return $0.pointee }) }) else { break } allKeys.append(key) i += 1 } for key in allKeys { let property = Property(with: key) properties[key] = property allProperties.append(property) } self.allKeys = allKeys self.properties = properties self.allProperties = allProperties #if DEBUG if let tableDecodableType = CodingTableKeyType.Root.self as? TableDecodableBase.Type { let types = ColumnTypeDecoder.types(of: tableDecodableType) let keys = allKeys.filter({ (key) -> Bool in return types.index(forKey: key.stringValue) == nil }) assert(keys.count == 0, """ The following keys: \(keys) can't be decoded. \ 1. Try to change their definition from `let` to `var` or report an issue to us. \ 2. Try to rename the `static` variable with same name. """) } #endif } typealias TypedCodingTableKeyType = CodingTableKeyType func property(from codingTableKey: CodingTableKeyType) -> Property { let typedCodingTableKey = codingTableKey as? TypedCodingTableKeyType assert(typedCodingTableKey != nil, "[\(codingTableKey)] must conform to CodingTableKey protocol.") let typedProperty = properties[typedCodingTableKey!] assert(typedProperty != nil, "It should not be failed. If you think it's a bug, please report an issue to us.") return typedProperty! } func generateColumnDef(with key: CodingTableKeyBase) -> ColumnDef { let codingTableKey = key as? CodingTableKeyType assert(codingTableKey != nil, "[\(key)] must conform to CodingTableKey protocol.") let columnType = columnTypes[codingTableKey!.stringValue] assert(columnType != nil, "It should not be failed. If you think it's a bug, please report an issue to us.") var columnDef = ColumnDef(with: codingTableKey!, and: columnType!) if let index = columnConstraintBindings?.index(forKey: codingTableKey!) { columnDef = columnConstraintBindings![index].value.generateColumnDef(with: columnDef) } return columnDef } public func generateCreateVirtualTableStatement(named table: String) -> StatementCreateVirtualTable { assert(virtualTableBinding != nil, "Virtual table binding is not defined") let columnModuleArguments = allColumnDef.map { ModuleArgument(with: $0) } let tableCostraintArguments = tableConstraintBindings?.map { (tableConstraintBinding) -> ModuleArgument in let key = tableConstraintBinding.key return ModuleArgument(with: tableConstraintBinding.value.generateConstraint(withName: key)) } ?? [] let arguments = columnModuleArguments + tableCostraintArguments + virtualTableBinding!.arguments return StatementCreateVirtualTable() .create(virtualTable: table) .using(module: virtualTableBinding!.module, arguments: arguments) } public func generateCreateTableStatement(named table: String) -> StatementCreateTable { let tableConstraints = tableConstraintBindings?.map { $0.value.generateConstraint(withName: $0.key) } return StatementCreateTable().create(table: table, with: allColumnDef, and: tableConstraints) } public func generateCreateIndexStatements(onTable table: String) -> [StatementCreateIndex]? { guard let indexBindings = self.indexBindings else { return nil } return indexBindings.map { $0.value.generateCreateIndexStatement(onTable: table, withIndexSubfix: $0.key)} } func getPrimaryKey() -> CodingTableKeyBase? { return primaryKey } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/binding/TableConstraintBinding.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public protocol TableConstraintBinding { typealias Name = String func generateConstraint(withName name: String) -> TableConstraint } public struct MultiPrimaryBinding: TableConstraintBinding { let conflict: Conflict? let columnIndexConvertibleList: [ColumnIndexConvertible] public init(indexesBy columnIndexConvertibleList: ColumnIndexConvertible..., onConflict conflict: Conflict? = nil) { self.init(indexesBy: columnIndexConvertibleList, onConflict: conflict) } public init(indexesBy columnIndexConvertibleList: [ColumnIndexConvertible], onConflict conflict: Conflict? = nil) { self.columnIndexConvertibleList = columnIndexConvertibleList self.conflict = conflict } public func generateConstraint(withName name: String) -> TableConstraint { let tableConstraint = TableConstraint(named: name).makePrimary(indexesBy: columnIndexConvertibleList) if let wrappedConflict = conflict { tableConstraint.onConflict(wrappedConflict) } return tableConstraint } } public struct MultiUniqueBinding: TableConstraintBinding { let conflict: Conflict? let columnIndexConvertibleList: [ColumnIndexConvertible] public init(indexesBy columnIndexConvertibleList: ColumnIndexConvertible..., onConflict conflict: Conflict? = nil) { self.init(indexesBy: columnIndexConvertibleList, onConflict: conflict) } public init(indexesBy columnIndexConvertibleList: [ColumnIndexConvertible], onConflict conflict: Conflict? = nil) { self.columnIndexConvertibleList = columnIndexConvertibleList self.conflict = conflict } public func generateConstraint(withName name: String) -> TableConstraint { let tableConstraint = TableConstraint(named: name).makeUnique(indexesBy: columnIndexConvertibleList) if let wrappedConflict = conflict { tableConstraint.onConflict(wrappedConflict) } return tableConstraint } } public struct CheckBinding: TableConstraintBinding { let condition: Expression public init(check condition: Expression) { self.condition = condition } public func generateConstraint(withName name: String) -> TableConstraint { return TableConstraint(named: name).check(condition) } } public struct ForeignKeyBinding: TableConstraintBinding { let columnConvertibleList: [ColumnConvertible] let foreignKey: ForeignKey public init(_ columnConvertibleList: ColumnConvertible..., foreignKey: ForeignKey) { self.init(columnConvertibleList, foreignKey: foreignKey) } public init(_ columnConvertibleList: [ColumnConvertible], foreignKey: ForeignKey) { self.columnConvertibleList = columnConvertibleList self.foreignKey = foreignKey } public func generateConstraint(withName name: String) -> TableConstraint { return TableConstraint(named: name).makeForeignKey(columnConvertibleList, foreignKey: foreignKey) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/binding/VirtualTableBinding.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct VirtualTableBinding { let arguments: [ModuleArgument] let module: String public init(withModule module: String, and arguments: [ModuleArgument]) { self.module = module self.arguments = arguments } public init(withModule module: String, and arguments: ModuleArgument...) { self.init(withModule: module, and: arguments) } public init(with module: FTSModule, and arguments: [ModuleArgument]) { self.init(withModule: module.name, and: arguments) } public init(with module: FTSModule, and arguments: ModuleArgument...) { self.init(with: module, and: arguments) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/codable/CodingTableKey.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public protocol CodingTableKeyBase: CodingKey { var rootType: TableCodableBase.Type {get} } public protocol CodingTableKey: CodingTableKeyBase, Hashable, PropertyOperable, RawRepresentable where RawValue == String { associatedtype Root: TableCodableBase static var all: [Property] {get} static var any: Column {get} static var objectRelationalMapping: TableBinding {get} static var columnConstraintBindings: [Self: ColumnConstraintBinding]? {get} static var indexBindings: [IndexBinding.Subfix: IndexBinding]? {get} static var tableConstraintBindings: [TableConstraintBinding.Name: TableConstraintBinding]? {get} static var virtualTableBinding: VirtualTableBinding? {get} } extension CodingTableKey { public var rootType: TableCodableBase.Type { return Root.self } } extension CodingTableKey { public static var all: [Property] { return objectRelationalMapping.allProperties } public static var any: Column { return Column.all } } extension CodingTableKey { public static var columnConstraintBindings: [Self: ColumnConstraintBinding]? { return nil } public static var indexBindings: [IndexBinding.Subfix: IndexBinding]? { return nil } public static var tableConstraintBindings: [TableConstraintBinding.Name: TableConstraintBinding]? { return nil } public static var virtualTableBinding: VirtualTableBinding? { return nil } } extension CodingTableKey { public var codingTableKey: CodingTableKeyBase { return self } public func `as`(_ propertyConvertible: PropertyConvertible) -> Property { return Property(named: stringValue, with: propertyConvertible.codingTableKey) } public func asProperty() -> Property { return Self.objectRelationalMapping.property(from: self) } public func `in`(table: String) -> Property { return asProperty().`in`(table: table) } public func asExpression() -> Expression { return asColumn().asExpression() } public func asColumn() -> Column { return Column(named: stringValue) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/codable/ColumnCodable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation // Column public protocol ColumnCodableBase { static var columnType: ColumnType {get} } public protocol ColumnEncodable: Encodable, ColumnCodableBase { func archivedValue() -> FundamentalValue } public extension ColumnEncodable where Self: LiteralValueConvertible { func asLiteralValue() -> LiteralValue { return LiteralValue(self) } } public protocol ColumnDecodable: Decodable, ColumnCodableBase { init?(with value: FundamentalValue) } public typealias ColumnCodable = ColumnCodableBase & ColumnEncodable & ColumnDecodable ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/codable/ColumnTypeDecoder.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class ColumnTypeDecoder: Decoder { private var results: [String: ColumnType] = [:] static func types(of type: TableDecodableBase.Type) -> [String: ColumnType] { let decoder = ColumnTypeDecoder() _ = try? type.init(from: decoder) return decoder.results } func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { return KeyedDecodingContainer(ColumnTypeDecodingContainer(with: self)) } private final class ColumnTypeDecodingContainer: KeyedDecodingContainerProtocol { typealias Key = CodingKeys private let decoder: ColumnTypeDecoder private struct SizedPointer { private let pointer: UnsafeMutableRawPointer private let size: Int init(of type: T.Type = T.self) { size = MemoryLayout.size pointer = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: 1) memset(pointer, 0, size) } func deallocate() { pointer.deallocate() } func getPointee(of type: T.Type = T.self) -> T { return pointer.assumingMemoryBound(to: type).pointee } } private var sizedPointers: ContiguousArray init(with decoder: ColumnTypeDecoder) { self.decoder = decoder self.sizedPointers = ContiguousArray() } deinit { for sizedPointer in sizedPointers { sizedPointer.deallocate() } } func contains(_ key: Key) -> Bool { return true } func decodeNil(forKey key: Key) throws -> Bool { decoder.results[key.stringValue] = Bool.columnType return false } func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { decoder.results[key.stringValue] = type.columnType return false } func decode(_ type: Int.Type, forKey key: Key) throws -> Int { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { decoder.results[key.stringValue] = type.columnType return 0 } func decode(_ type: String.Type, forKey key: Key) throws -> String { decoder.results[key.stringValue] = type.columnType return "" } func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable { // `type` must conform to ColumnDecodableBase protocol let columnDecodableType = type as! ColumnDecodable.Type decoder.results[key.stringValue] = columnDecodableType.columnType let sizedPointer = SizedPointer(of: T.self) sizedPointers.append(sizedPointer) return sizedPointer.getPointee() } var codingPath: [CodingKey] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } var allKeys: [Key] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func superDecoder() throws -> Decoder { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func superDecoder(forKey key: Key) throws -> Decoder { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } } var codingPath: [CodingKey] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } var userInfo: [CodingUserInfoKey: Any] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func unkeyedContainer() throws -> UnkeyedDecodingContainer { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func singleValueContainer() throws -> SingleValueDecodingContainer { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/codable/TableCodable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public protocol TableCodableBase {} public protocol TableEncodableBase: Encodable, TableCodableBase {} public protocol TableEncodable: TableEncodableBase where CodingKeys.Root == Self { associatedtype CodingKeys: CodingTableKey typealias Properties = CodingKeys var isAutoIncrement: Bool {get} var lastInsertedRowID: Int64 {get set} } extension TableEncodable { public var isAutoIncrement: Bool { return false } public var lastInsertedRowID: Int64 { get { return Int64.min } set { } } } public protocol TableDecodableBase: Decodable, TableCodableBase {} public protocol TableDecodable: TableDecodableBase where CodingKeys.Root == Self { associatedtype CodingKeys: CodingTableKey typealias Properties = CodingKeys } public typealias TableCodable = TableEncodable & TableDecodable ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/codable/TableDecoder.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class TableDecoder: Decoder { private let recyclableHandleStatement: RecyclableHandleStatement typealias HashedKey = [Int: Int] // hash value -> index private let hashedKeys: HashedKey private var container: Any? init(_ codingTableKeys: [CodingTableKeyBase], on recyclableHandleStatement: RecyclableHandleStatement) { var hashedKeys: HashedKey = [:] for (index, key) in codingTableKeys.enumerated() { hashedKeys[key.stringValue.hashValue] = index } self.hashedKeys = hashedKeys self.recyclableHandleStatement = recyclableHandleStatement } init(_ hashedKeys: HashedKey, on recyclableHandleStatement: RecyclableHandleStatement) { self.hashedKeys = hashedKeys self.recyclableHandleStatement = recyclableHandleStatement } func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { if container == nil { container = KeyedDecodingContainer(KeyedDecodingTableContainer(with: hashedKeys, on: recyclableHandleStatement.raw)) } // It should not be failed. If you think it's a bug, please report an issue to us. return container as! KeyedDecodingContainer } private final class KeyedDecodingTableContainer : KeyedDecodingContainerProtocol { typealias Key = CodingKeys private let handleStatement: HandleStatement private let hashedKeys: HashedKey init(with hashedKeys: HashedKey, on handleStatement: HandleStatement) { self.handleStatement = handleStatement self.hashedKeys = hashedKeys } private func columnIndex(by key: Key) -> Int { let index = hashedKeys[key.stringValue.hashValue] assert(index != nil, "If [\(key)] would not be decoded, please make it optional.") return index! } private func columnIndexIfPresent(by key: Key) -> Int? { return hashedKeys[key.stringValue.hashValue] } func contains(_ key: Key) -> Bool { return true } //Decode func decodeNil(forKey key: Key) throws -> Bool { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Int32.self).toBool() } func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Int32.self).toBool() } func decode(_ type: Int.Type, forKey key: Key) throws -> Int { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Int64.self).toInt() } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Int32.self).toInt8() } func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Int32.self).toInt16() } func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index) } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index) } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Int64.self).toUInt() } func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Int32.self).toUInt8() } func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Int32.self).toUInt16() } func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Int32.self).toUInt32() } func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Int64.self).toUInt64() } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index, of: Double.self).toFloat() } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index) } func decode(_ type: String.Type, forKey key: Key) throws -> String { let index: Int = columnIndex(by: key) return handleStatement.columnValue(atIndex: index) } func decode(_ type: Object.Type, forKey key: Key) throws -> Object where Object: Decodable { let index: Int = columnIndex(by: key) //`key` must conform to ColumnDecodable protocol. let decodableType = Object.self as! ColumnDecodable.Type guard let wrappedDecoded = decodableType.init(with: handleStatement.columnValue(atIndex: index)) else { throw Error.reportCore(tag: handleStatement.tag, path: handleStatement.path, operation: .encode, code: .misuse, message: "If [\(key)] would be decoded as nil, please make it optional.") } //It should not be failed. If you think it's a bug, please report an issue to us. return wrappedDecoded as! Object } //Decode if present func decodeIfPresent(_ type: Bool.Type, forKey key: Key) throws -> Bool? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index, of: Int32.self).toBool() } func decodeIfPresent(_ type: Int.Type, forKey key: Key) throws -> Int? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index, of: Int64.self).toInt() } func decodeIfPresent(_ type: Int8.Type, forKey key: Key) throws -> Int8? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index, of: Int32.self).toInt8() } func decodeIfPresent(_ type: Int16.Type, forKey key: Key) throws -> Int16? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index, of: Int32.self).toInt16() } func decodeIfPresent(_ type: Int32.Type, forKey key: Key) throws -> Int32? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index) } func decodeIfPresent(_ type: Int64.Type, forKey key: Key) throws -> Int64? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index) } func decodeIfPresent(_ type: UInt.Type, forKey key: Key) throws -> UInt? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index, of: Int64.self).toUInt() } func decodeIfPresent(_ type: UInt8.Type, forKey key: Key) throws -> UInt8? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index, of: Int32.self).toUInt8() } func decodeIfPresent(_ type: UInt16.Type, forKey key: Key) throws -> UInt16? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index, of: Int32.self).toUInt16() } func decodeIfPresent(_ type: UInt32.Type, forKey key: Key) throws -> UInt32? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index, of: Int32.self).toUInt32() } func decodeIfPresent(_ type: UInt64.Type, forKey key: Key) throws -> UInt64? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index, of: Int64.self).toUInt64() } func decodeIfPresent(_ type: Float.Type, forKey key: Key) throws -> Float? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index, of: Double.self).toFloat() } func decodeIfPresent(_ type: Double.Type, forKey key: Key) throws -> Double? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index) } func decodeIfPresent(_ type: String.Type, forKey key: Key) throws -> String? { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } return handleStatement.columnValue(atIndex: index) } func decodeIfPresent(_ type: Object.Type, forKey key: Key) throws -> Object? where Object: Decodable { guard let index = columnIndexIfPresent(by: key), handleStatement.columnType(atIndex: index) != .null else { return nil } //`key` must conform to ColumnDecodable protocol. let decodableType = Object.self as! ColumnDecodable.Type guard let wrappedDecoded = decodableType.init(with: handleStatement.columnValue(atIndex: index)) else { return nil } //It should not be failed. If you think it's a bug, please report an issue to us. return (wrappedDecoded as! Object) } var codingPath: [CodingKey] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } var allKeys: [Key] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func superDecoder() throws -> Swift.Decoder { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func superDecoder(forKey key: Key) throws -> Swift.Decoder { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } } var codingPath: [CodingKey] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } var userInfo: [CodingUserInfoKey: Any] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func unkeyedContainer() throws -> UnkeyedDecodingContainer { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func singleValueContainer() throws -> SingleValueDecodingContainer { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/codable/TableEncoder.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class TableEncoder: Encoder { private var container: Any? private var keyedPrimaryKeyEncodableTableContainer: KeyedPrimaryKeyEncodableTableContainer? private let recyclableHandleStatement: RecyclableHandleStatement typealias HashedKey = [Int: Int] // hash value -> index private let hashedKeys: HashedKey var primaryKeyHash: Int? var isPrimaryKeyEncoded = true init(_ codingTableKeys: [CodingTableKeyBase], on recyclableHandleStatement: RecyclableHandleStatement) { var hashedKeys: HashedKey = [:] for (index, key) in codingTableKeys.enumerated() { hashedKeys[key.stringValue.hashValue] = index + 1 } self.hashedKeys = hashedKeys self.recyclableHandleStatement = recyclableHandleStatement } init(_ hashedKeys: HashedKey, on recyclableHandleStatement: RecyclableHandleStatement) { self.hashedKeys = hashedKeys self.recyclableHandleStatement = recyclableHandleStatement } func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { if container == nil { let keyedContainer = KeyedEncodingTableContainer(with: hashedKeys, on: recyclableHandleStatement.raw) keyedPrimaryKeyEncodableTableContainer = keyedContainer container = KeyedEncodingContainer(keyedContainer) } if isPrimaryKeyEncoded { keyedPrimaryKeyEncodableTableContainer?.primaryKeyHash = nil } else { keyedPrimaryKeyEncodableTableContainer?.primaryKeyHash = primaryKeyHash } return container as! KeyedEncodingContainer } private class KeyedPrimaryKeyEncodableTableContainer { final var primaryKeyHash: Int? } private final class KeyedEncodingTableContainer : KeyedPrimaryKeyEncodableTableContainer, KeyedEncodingContainerProtocol { typealias Key = CodingKeys private let handleStatement: HandleStatement private let hashedKeys: HashedKey init(with hashedKeys: HashedKey, on handleStatement: HandleStatement) { self.hashedKeys = hashedKeys self.handleStatement = handleStatement super.init() } private func bindIndex(by hashValue: Int) -> Int? { return hashedKeys[hashValue] } func bindPrimaryKeyOrReturnIndex(forKey key: Key) -> Int? { let hashValue = key.stringValue.hashValue guard let index = bindIndex(by: hashValue) else { return nil } guard hashValue == primaryKeyHash else { return index } handleStatement.bind(nil, toIndex: index) return nil } func encodeNil(forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(nil, toIndex: index) } func encode(_ value: Int, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value.toInt64(), toIndex: index) } func encode(_ value: Bool, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value.toInt32(), toIndex: index) } func encode(_ value: Float, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value.toDouble(), toIndex: index) } func encode(_ value: Double, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value, toIndex: index) } func encode(_ value: String, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value, toIndex: index) } func encode(_ value: Object, forKey key: Key) throws where Object: Encodable { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } //`key` must conform to ColumnEncodable protocol. let encodableColumnValue = value as! ColumnEncodable handleStatement.bind(encodableColumnValue.archivedValue(), toIndex: index) } func encode(_ value: Int8, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value.toInt32(), toIndex: index) } func encode(_ value: Int16, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value.toInt32(), toIndex: index) } func encode(_ value: Int32, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value, toIndex: index) } func encode(_ value: Int64, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value, toIndex: index) } func encode(_ value: UInt, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value.toInt64(), toIndex: index) } func encode(_ value: UInt8, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value.toInt32(), toIndex: index) } func encode(_ value: UInt16, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value.toInt32(), toIndex: index) } func encode(_ value: UInt32, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value.toInt32(), toIndex: index) } func encode(_ value: UInt64, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } handleStatement.bind(value.toInt64(), toIndex: index) } func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue.toInt32(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: Int?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue.toInt64(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: Int8?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue.toInt32(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: Int16?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue.toInt32(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: Int32?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue, toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: Int64?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue, toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: UInt?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue.toInt64(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: UInt8?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue.toInt32(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: UInt16?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue.toInt32(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: UInt32?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue.toInt32(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: UInt64?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue.toInt64(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: Float?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue.toDouble(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: Double?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue, toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: String?, forKey key: Key) throws { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if let wrappedValue = value { handleStatement.bind(wrappedValue, toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } func encodeIfPresent(_ value: Object?, forKey key: Key) throws where Object: Encodable { guard let index = bindPrimaryKeyOrReturnIndex(forKey: key) else { return } if value != nil { //`key` must conform to ColumnEncodable protocol. let encodableColumnValue = value! as! ColumnEncodable handleStatement.bind(encodableColumnValue.archivedValue(), toIndex: index) } else { handleStatement.bind(nil, toIndex: index) } } var codingPath: [CodingKey] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func superEncoder() -> Swift.Encoder { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func superEncoder(forKey key: Key) -> Swift.Encoder { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey: CodingKey { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } } var codingPath: [CodingKey] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } var userInfo: [CodingUserInfoKey: Any] { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func singleValueContainer() -> SingleValueEncodingContainer { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } func unkeyedContainer() -> UnkeyedEncodingContainer { fatalError("It should not be called. If you think it's a bug, please report an issue to us.") } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/fts/WCDBTokenize.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class WCDBTokenizerInfo: TokenizerInfoBase { public required init(withArgc argc: Int32, andArgv argv: UnsafePointer?>?) {} } public final class WCDBCursorInfo: CursorInfoBase { private enum TokenType: UInt { case basicMultilingualPlaneLetter = 0x00000001 case basicMultilingualPlaneDigit = 0x00000002 case basicMultilingualPlaneSymbol = 0x00000003 case basicMultilingualPlaneOther = 0x0000FFFF case auxiliaryPlaneOther = 0xFFFFFFFF } private let input: UnsafePointer! private let inputLength: Int32 private var position: Int32 = 0 private var startOffset: Int32 = 0 private var endOffset: Int32 = 0 private var cursor: Int32 = 0 private var cursorTokenType: TokenType? private var cursorTokenLength: Int32 = 0 private lazy var symbolCharacterSet: CFCharacterSet? = { var characterSet = CFCharacterSetCreateMutable(kCFAllocatorDefault) CFCharacterSetUnion(characterSet, CFCharacterSetGetPredefined(CFCharacterSetPredefinedSet.control)) CFCharacterSetUnion(characterSet, CFCharacterSetGetPredefined(CFCharacterSetPredefinedSet.whitespaceAndNewline)) CFCharacterSetUnion(characterSet, CFCharacterSetGetPredefined(CFCharacterSetPredefinedSet.nonBase)) CFCharacterSetUnion(characterSet, CFCharacterSetGetPredefined(CFCharacterSetPredefinedSet.punctuation)) CFCharacterSetUnion(characterSet, CFCharacterSetGetPredefined(CFCharacterSetPredefinedSet.symbol)) CFCharacterSetUnion(characterSet, CFCharacterSetGetPredefined(CFCharacterSetPredefinedSet.illegal)) return characterSet }() private var lemmaBuffer: [UInt8] = [] private var lemmaBufferLength: Int32 = 0 //>0 lemma is not empty private var subTokensLengthArray: [UInt8] = [] private var subTokensCursor: Int32 = 0 private var subTokensDoubleChar: Bool = false private var buffer: [UInt8] = [] private var bufferLength: Int32 = 0 public required init(withInput pInput: UnsafePointer?, count: Int32, tokenizerInfo: TokenizerInfoBase) { input = pInput inputLength = count } func cursorStep() -> Int32 { guard cursor + cursorTokenLength < inputLength else { cursor = inputLength cursorTokenType = nil cursorTokenLength = 0 return SQLITE_OK } cursor += cursorTokenLength return cursorSetup() } func cursorSetup() -> Int32 { var rc = SQLITE_OK let first = UInt8(bitPattern: input.advanced(by: Int(cursor)).pointee) switch first { case ..<0xC0: cursorTokenLength = 1 switch first { case 0x30...0x39: cursorTokenType = .basicMultilingualPlaneDigit case 0x41...0x5a, 0x61...0x7a: cursorTokenType = .basicMultilingualPlaneLetter default: var result = false rc = isSymbol(unicodeChar: UInt16(first), result: &result) guard rc == SQLITE_OK else { return rc } cursorTokenType = result ? .basicMultilingualPlaneSymbol : .basicMultilingualPlaneOther } case ..<0xF0: var unicode: UInt16 = 0 switch first { case ..<0xE0: cursorTokenLength = 2 unicode = UInt16(first & 0x1F) default: cursorTokenLength = 3 unicode = UInt16(first & 0x0F) } for i in cursor+1.. Int32 { guard let symbolCharacterSet = self.symbolCharacterSet else { return SQLITE_NOMEM } result = CFCharacterSetIsCharacterMember(symbolCharacterSet, unicodeChar) return SQLITE_OK } static let orthography = NSOrthography(dominantScript: "Latin", languageMap: [ "Latn": ["en"]]) func lemmatization(input: UnsafePointer, inputLength: Int32) -> Int32 { if inputLength > buffer.count { buffer.expand(toNewSize: Int(inputLength)) } for i in 0.. String? in return String(bytes: bytes.baseAddress!.assumingMemoryBound(to: Int8.self), count: Int(inputLength), encoding: .ascii) } guard let string = optionalString else { return SQLITE_OK } var optionalLemma: String? = nil string.enumerateLinguisticTags(in: string.startIndex.. 0, lemma.caseInsensitiveCompare(string) != ComparisonResult.orderedSame else { return SQLITE_OK } lemmaBufferLength = Int32(lemma.count) if lemmaBufferLength > lemmaBuffer.capacity { lemmaBuffer.expand(toNewSize: Int(lemmaBufferLength)) } memcpy(&lemmaBuffer, lemma.cString, Int(lemmaBufferLength)) return SQLITE_OK } func subTokensStep() { self.startOffset = self.subTokensCursor self.bufferLength = Int32(self.subTokensLengthArray[0]) if self.subTokensDoubleChar { if self.subTokensLengthArray.count > 1 { self.bufferLength += Int32(self.subTokensLengthArray[1]) self.subTokensDoubleChar = false } else { self.subTokensLengthArray.removeAll() } } else { self.subTokensCursor += Int32(subTokensLengthArray[0]) self.subTokensLengthArray.removeFirst() self.subTokensDoubleChar = true } self.endOffset = self.startOffset + self.bufferLength } public func step(pToken: inout UnsafePointer?, count: inout Int32, startOffset: inout Int32, endOffset: inout Int32, position: inout Int32) -> Int32 { var rc = SQLITE_OK if self.position == 0 { rc = cursorSetup() guard rc == SQLITE_OK else { return rc } } if self.lemmaBufferLength == 0 { if self.subTokensLengthArray.isEmpty { guard self.cursorTokenType != nil else { return SQLITE_DONE } //Skip symbol while self.cursorTokenType == .basicMultilingualPlaneSymbol { rc = cursorStep() guard rc == SQLITE_OK else { return rc } } guard let tokenType = self.cursorTokenType else { return SQLITE_DONE } switch tokenType { case .basicMultilingualPlaneLetter: fallthrough case .basicMultilingualPlaneDigit: self.startOffset = self.cursor repeat { rc = cursorStep() }while(rc == SQLITE_OK && self.cursorTokenType == tokenType) guard rc == SQLITE_OK else { return rc } self.endOffset = self.cursor self.bufferLength = self.endOffset - self.startOffset case .basicMultilingualPlaneOther: fallthrough case .auxiliaryPlaneOther: self.subTokensLengthArray.append(UInt8(self.cursorTokenLength)) self.subTokensCursor = self.cursor self.subTokensDoubleChar = true rc = cursorStep() while rc == SQLITE_OK && self.cursorTokenType == tokenType { self.subTokensLengthArray.append(UInt8(cursorTokenLength)) rc = cursorStep() } guard rc == SQLITE_OK else { return rc } subTokensStep() default: break } if tokenType == .basicMultilingualPlaneLetter { rc = lemmatization(input: self.input.advanced(by: Int(self.startOffset)), inputLength: self.bufferLength) guard rc == SQLITE_OK else { return rc } } else { if self.bufferLength > self.buffer.count { self.buffer.expand(toNewSize: Int(self.bufferLength)) } memcpy(&self.buffer, input.advanced(by: Int(self.startOffset)), Int(self.bufferLength)) } } else { subTokensStep() if self.bufferLength > self.buffer.capacity { self.buffer.expand(toNewSize: Int(self.bufferLength)) } memcpy(&self.buffer, input.advanced(by: Int(self.startOffset)), Int(self.bufferLength)) } pToken = self.buffer.withUnsafeBytes { (bytes) -> UnsafePointer in return bytes.baseAddress!.assumingMemoryBound(to: Int8.self) } count = self.bufferLength } else { pToken = self.lemmaBuffer.withUnsafeBytes { (bytes) -> UnsafePointer in return bytes.baseAddress!.assumingMemoryBound(to: Int8.self) } count = self.lemmaBufferLength self.lemmaBufferLength = 0 } startOffset = self.startOffset endOffset = self.endOffset self.position += 1 position = self.position return SQLITE_OK } } public final class WCDBModule: Module { public typealias TokenizerInfo = WCDBTokenizerInfo public typealias CursorInfo = WCDBCursorInfo public static let name = "WCDB" public private(set) static var module = sqlite3_tokenizer_module( iVersion: 0, xCreate: { (argc, argv, ppTokenizer) -> Int32 in return WCDBModule.create(argc: argc, argv: argv, ppTokenizer: ppTokenizer) }, xDestroy: { (pTokenizer) -> Int32 in return WCDBModule.destroy(pTokenizer: pTokenizer) }, xOpen: { (pTokenizer, pInput, nBytes, ppCursor) -> Int32 in return WCDBModule.open(pTokenizer: pTokenizer, pInput: pInput, nBytes: nBytes, ppCursor: ppCursor) }, xClose: { (pCursor) -> Int32 in return WCDBModule.close(pCursor: pCursor) }, xNext: { (pCursor, ppToken, pnBytes, piStartOffset, piEndOffset, piPosition) -> Int32 in return WCDBModule.next(pCursor: pCursor, ppToken: ppToken, pnBytes: pnBytes, piStartOffset: piStartOffset, piEndOffset: piEndOffset, piPosition: piPosition) }, xLanguageid: nil) public static let address = { () -> Data in var pointer = UnsafeMutableRawPointer(&module) return Data(bytes: &pointer, count: MemoryLayout.size(ofValue: pointer)) }() } extension Tokenize { public static let WCDB = Tokenize(module: WCDBModule.self) } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/ChainCall.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /// ChainCall interface for inserting public protocol InsertChainCallInterface: class { /// Prepare chain call for inserting of `TableEncodable` object /// /// - Parameters: /// - cls: Type of table codable object /// - table: Table name /// - Returns: `Insert` /// - Throws: `Error` func prepareInsert(of cls: Root.Type, intoTable table: String) throws -> Insert /// Prepare chain call for inserting or replacing of `TableEncodable` object /// /// - Parameters: /// - cls: Type of table codable object /// - table: Table name /// - Returns: `Insert` /// - Throws: `Error` func prepareInsertOrReplace(of cls: Root.Type, intoTable table: String) throws -> Insert /// Prepare chain call for inserting on specific properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - Returns: `Insert` /// - Throws: `Error` func prepareInsert(on propertyConvertibleList: PropertyConvertible..., intoTable table: String) throws -> Insert /// Prepare chain call for inserting or replacing on specific properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - Returns: `Insert` /// - Throws: `Error` func prepareInsertOrReplace(on propertyConvertibleList: PropertyConvertible..., intoTable table: String) throws -> Insert /// Prepare chain call for inserting on specific properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - Returns: `Insert` /// - Throws: `Error` func prepareInsert(on propertyConvertibleList: [PropertyConvertible], intoTable table: String) throws -> Insert /// Prepare chain call for inserting or replacing on specific properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - Returns: `Insert` /// - Throws: `Error` func prepareInsertOrReplace(on propertyConvertibleList: [PropertyConvertible], intoTable table: String) throws -> Insert } extension InsertChainCallInterface where Self: Core { public func prepareInsert(of cls: Root.Type, intoTable table: String) throws -> Insert { return try Insert(with: self, named: table, on: cls.Properties.all, isReplace: false) } public func prepareInsertOrReplace( of cls: Root.Type, intoTable table: String) throws -> Insert { return try Insert(with: self, named: table, on: cls.Properties.all, isReplace: true) } public func prepareInsert(on propertyConvertibleList: PropertyConvertible..., intoTable table: String) throws -> Insert { return try prepareInsert(on: propertyConvertibleList, intoTable: table) } public func prepareInsertOrReplace(on propertyConvertibleList: PropertyConvertible..., intoTable table: String) throws -> Insert { return try prepareInsertOrReplace(on: propertyConvertibleList, intoTable: table) } public func prepareInsert(on propertyConvertibleList: [PropertyConvertible], intoTable table: String) throws -> Insert { return try Insert(with: self, named: table, on: propertyConvertibleList, isReplace: false) } public func prepareInsertOrReplace(on propertyConvertibleList: [PropertyConvertible], intoTable table: String) throws -> Insert { return try Insert(with: self, named: table, on: propertyConvertibleList, isReplace: true) } } /// ChainCall interface for deleting public protocol DeleteChainCallInterface: class { /// Prepare chain call for deleting on specific properties /// /// - Parameter table: Table name /// - Returns: `Delete` /// - Throws: `Error` func prepareDelete(fromTable table: String) throws -> Delete } extension DeleteChainCallInterface where Self: Core { public func prepareDelete(fromTable table: String) throws -> Delete { return try Delete(with: self, andTableName: table) } } /// ChainCall interface for updating public protocol UpdateChainCallInterface: class { /// Prepare chain call for updating on specific properties /// /// - Parameters: /// - table: Table name /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - Returns: `Update` /// - Throws: `Error` func prepareUpdate(table: String, on propertyConvertibleList: PropertyConvertible...) throws -> Update /// Prepare chain call for updating on specific properties /// /// - Parameters: /// - table: Table name /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - Returns: `Update` /// - Throws: `Error` func prepareUpdate(table: String, on propertyConvertibleList: [PropertyConvertible]) throws -> Update } extension UpdateChainCallInterface where Self: Core { public func prepareUpdate(table: String, on propertyConvertibleList: PropertyConvertible...) throws -> Update { return try prepareUpdate(table: table, on: propertyConvertibleList) } public func prepareUpdate(table: String, on propertyConvertibleList: [PropertyConvertible]) throws -> Update { return try Update(with: self, on: propertyConvertibleList, andTable: table) } } /// ChainCall interface for row-selecting public protocol RowSelectChainCallInterface: class { /// Prepare chain call for row-selecting on specific column results /// /// - Parameters: /// - columnResultConvertibleList: `ColumnResult` list /// - tables: Table name list /// - isDistinct: Is distinct or not /// - Returns: `RowSelect` /// - Throws: `Error` func prepareRowSelect(on columnResultConvertibleList: ColumnResultConvertible..., fromTables tables: [String], isDistinct: Bool) throws -> RowSelect /// Prepare chain call for row-selecting on specific column results /// /// - Parameters: /// - columnResultConvertibleList: `ColumnResult` list /// - tables: Table name list /// - isDistinct: Is distinct or not /// - Returns: `RowSelect` /// - Throws: `Error` func prepareRowSelect(on columnResultConvertibleList: [ColumnResultConvertible], fromTables tables: [String], isDistinct: Bool) throws -> RowSelect /// Prepare chain call for row-selecting on specific column results /// /// - Parameters: /// - columnResultConvertibleList: `ColumnResult` list /// - tables: Table name /// - isDistinct: Is distinct or not /// - Returns: `RowSelect` /// - Throws: `Error` func prepareRowSelect(on columnResultConvertibleList: ColumnResultConvertible..., fromTable table: String, isDistinct: Bool) throws -> RowSelect /// Prepare chain call for row-selecting on specific column results /// /// - Parameters: /// - columnResultConvertibleList: `ColumnResult` list /// - tables: Table name /// - isDistinct: Is distinct or not /// - Returns: `RowSelect` /// - Throws: `Error` func prepareRowSelect(on columnResultConvertibleList: [ColumnResultConvertible], fromTable table: String, isDistinct: Bool) throws -> RowSelect } extension RowSelectChainCallInterface where Self: Core { public func prepareRowSelect(on columnResultConvertibleList: ColumnResultConvertible..., fromTables tables: [String], isDistinct: Bool = false) throws -> RowSelect { return try prepareRowSelect(on: columnResultConvertibleList.isEmpty ? [Column.all] : columnResultConvertibleList, fromTables: tables, isDistinct: isDistinct) } public func prepareRowSelect(on columnResultConvertibleList: [ColumnResultConvertible], fromTables tables: [String], isDistinct: Bool = false) throws -> RowSelect { return try RowSelect(with: self, results: columnResultConvertibleList, tables: tables, isDistinct: isDistinct) } public func prepareRowSelect(on columnResultConvertibleList: ColumnResultConvertible..., fromTable table: String, isDistinct: Bool = false) throws -> RowSelect { return try prepareRowSelect(on: columnResultConvertibleList.isEmpty ? [Column.all] : columnResultConvertibleList, fromTable: table, isDistinct: isDistinct) } public func prepareRowSelect(on columnResultConvertibleList: [ColumnResultConvertible], fromTable table: String, isDistinct: Bool = false) throws -> RowSelect { return try RowSelect(with: self, results: columnResultConvertibleList, tables: [table], isDistinct: isDistinct) } } /// ChainCall interface for selecting public protocol SelectChainCallInterface: class { /// Prepare chain call for selecting of `TableDecodable` object /// /// - Parameters: /// - cls: Type of table decodable object /// - table: Table name /// - isDistinct: Is distinct or not /// - Returns: `Select` /// - Throws: `Error` func prepareSelect(of cls: Root.Type, fromTable table: String, isDistinct: Bool) throws -> Select /// Prepare chain call for selecting on specific properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - isDistinct: Is distinct or not /// - Returns: `Select` /// - Throws: `Error` func prepareSelect(on propertyConvertibleList: PropertyConvertible..., fromTable table: String, isDistinct: Bool) throws -> Select /// Prepare chain call for selecting on specific properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - isDistinct: Is distinct or not /// - Returns: `Select` /// - Throws: `Error` func prepareSelect(on propertyConvertibleList: [PropertyConvertible], fromTable table: String, isDistinct: Bool) throws -> Select } extension SelectChainCallInterface where Self: Core { public func prepareSelect(of cls: Root.Type, fromTable table: String, isDistinct: Bool = false) throws -> Select { return try Select(with: self, on: cls.Properties.all, table: table, isDistinct: isDistinct) } public func prepareSelect(on propertyConvertibleList: PropertyConvertible..., fromTable table: String, isDistinct: Bool = false) throws -> Select { return try prepareSelect(on: propertyConvertibleList, fromTable: table, isDistinct: isDistinct) } public func prepareSelect(on propertyConvertibleList: [PropertyConvertible], fromTable table: String, isDistinct: Bool = false) throws -> Select { return try Select(with: self, on: propertyConvertibleList, table: table, isDistinct: isDistinct) } } /// ChainCall interface for multi-selecting public protocol MultiSelectChainCallInterface: class { /// Prepare chain call for multi-selecting on specific properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - tables: Table name list /// - Returns: `MultiSelect` /// - Throws: `Error` func prepareMultiSelect(on propertyConvertibleList: PropertyConvertible..., fromTables tables: [String]) throws -> MultiSelect /// Prepare chain call for multi-selecting on specific properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - tables: Table name list /// - Returns: `MultiSelect` /// - Throws: `Error` func prepareMultiSelect(on propertyConvertibleList: [PropertyConvertible], fromTables tables: [String]) throws -> MultiSelect } extension MultiSelectChainCallInterface where Self: Core { public func prepareMultiSelect(on propertyConvertibleList: PropertyConvertible..., fromTables tables: [String]) throws -> MultiSelect { return try prepareMultiSelect(on: propertyConvertibleList, fromTables: tables) } public func prepareMultiSelect(on propertyConvertibleList: [PropertyConvertible], fromTables tables: [String]) throws -> MultiSelect { return try MultiSelect(with: self, on: propertyConvertibleList, tables: tables) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/Declare.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public typealias Condition = ExpressionConvertible public typealias Limit = ExpressionConvertible public typealias Offset = ExpressionConvertible public typealias GroupBy = ExpressionConvertible public typealias Having = ExpressionConvertible public typealias OrderBy = OrderConvertible ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/Delete.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /// Chain call for deleting public final class Delete { private var core: Core private let statement = StatementDelete() /// The number of changed rows in the most recent call. /// It should be called after executing successfully public var changes: Int? init(with core: Core, andTableName tableName: String) throws { guard tableName.count > 0 else { throw Error.reportInterface(tag: core.tag, path: core.path, operation: .delete, code: .misuse, message: "Nil table name") } statement.delete(fromTable: tableName) self.core = core } /// WINQ interface for SQL /// /// - Parameter condition: Expression convertible /// - Returns: `self` @discardableResult public func `where`(_ condition: Condition) -> Delete { statement.where(condition) return self } /// WINQ interface for SQL /// /// - Parameter orderList: Order convertible list /// - Returns: `self` @discardableResult public func order(by orderList: OrderBy...) -> Delete { return order(by: orderList) } /// WINQ interface for SQL /// /// - Parameter orderList: Order convertible list /// - Returns: `self` @discardableResult public func order(by orderList: [OrderBy]) -> Delete { statement.order(by: orderList) return self } /// WINQ interface for SQL /// /// - Parameters: /// - begin: Expression convertible /// - end: Expression convertible /// - Returns: `self` @discardableResult public func limit(from begin: Limit, to end: Limit) -> Delete { statement.limit(from: begin, to: end) return self } /// WINQ interface for SQL /// /// - Parameter limit: Expression convertible /// - Returns: `self` @discardableResult public func limit(_ limit: Limit) -> Delete { statement.limit(limit) return self } /// WINQ interface for SQL /// /// - Parameters: /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: `self` @discardableResult public func limit(_ limit: Limit, offset: Offset) -> Delete { statement.limit(limit, offset: offset) return self } /// Execute the delete chain call. /// /// - Throws: Error public func execute() throws { let recyclableHandleStatement: RecyclableHandleStatement = try core.prepare(statement) let handleStatement = recyclableHandleStatement.raw try handleStatement.step() changes = handleStatement.changes } } extension Delete: CoreRepresentable { /// The tag of the related database. public var tag: Tag? { return core.tag } /// The path of the related database. public var path: String { return core.path } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/Insert.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /// Chain call for inserting public final class Insert { private let core: Core private var properties: [PropertyConvertible]? private let name: String private let isReplace: Bool init(with core: Core, named name: String, on propertyConvertibleList: [PropertyConvertible]?, isReplace: Bool = false) throws { guard name.count > 0 else { throw Error.reportInterface(tag: core.tag, path: core.path, operation: .insert, code: .misuse, message: "Empty table name") } self.name = name self.properties = propertyConvertibleList self.isReplace = isReplace self.core = core } private var conflict: Conflict? { return isReplace ? Conflict.replace : nil } private lazy var statement: StatementInsert = StatementInsert() .insert(intoTable: name, with: properties!, onConflict: self.conflict) .values(Array(repeating: Expression.bindParameter, count: properties!.count)) /// Execute the insert chain call with objects. /// /// Note that it will run embedded transaction while objects.count>1. /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameter objects: Object to be inserted /// - Throws: Error public func execute(with objects: Object...) throws { try execute(with: objects) } /// Execute the insert chain call with objects. /// /// Note that it will run embedded transaction while objects.count>1. /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameter objects: Object to be inserted /// - Throws: Error public func execute(with objects: [Object]) throws { guard objects.count > 0 else { Error.warning("Inserting with an empty/nil object") return } let orm = Object.CodingKeys.objectRelationalMapping func doInsertObject() throws { properties = properties ?? Object.Properties.all let recyclableHandleStatement: RecyclableHandleStatement = try core.prepare(statement) let handleStatement = recyclableHandleStatement.raw let encoder = TableEncoder(properties!.asCodingTableKeys(), on: recyclableHandleStatement) if !isReplace { encoder.primaryKeyHash = orm.getPrimaryKey()?.stringValue.hashValue } for var object in objects { let isAutoIncrement = object.isAutoIncrement encoder.isPrimaryKeyEncoded = !isAutoIncrement try object.encode(to: encoder) try handleStatement.step() if !isReplace && isAutoIncrement { object.lastInsertedRowID = handleStatement.lastInsertedRowID } try handleStatement.reset() } } return objects.count == 1 ? try doInsertObject() : try core.run(embeddedTransaction: doInsertObject ) } } extension Insert: CoreRepresentable { /// The tag of the related database. public var tag: Tag? { return core.tag } /// The path of the related database. public var path: String { return core.path } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/Interface.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /// Convenient interface for inserting public protocol InsertInterface: class { /// Execute inserting with `TableEncodable` object on specific(or all) properties /// /// Note that it will run embedded transaction while objects.count>1. /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameters: /// - objects: Table encodable object /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - Throws: `Error` func insert( objects: Object..., on propertyConvertibleList: [PropertyConvertible]?, intoTable table: String) throws /// Execute inserting with `TableEncodable` object on specific(or all) properties /// /// Note that it will run embedded transaction while objects.count>1. /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameters: /// - objects: Table encodable object /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - Throws: `Error` func insert( objects: [Object], on propertyConvertibleList: [PropertyConvertible]?, intoTable table: String) throws /// Execute inserting or replacing with `TableEncodable` object on specific(or all) properties. /// It will replace the original row while they have same primary key or row id. /// /// Note that it will run embedded transaction while objects.count>1. /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameters: /// - objects: Table encodable object /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - Throws: `Error` func insertOrReplace( objects: Object..., on propertyConvertibleList: [PropertyConvertible]?, intoTable table: String) throws /// Execute inserting or replacing with `TableEncodable` object on specific(or all) properties. /// It will replace the original row while they have same primary key or row id. /// /// Note that it will run embedded transaction while objects.count>1. /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameters: /// - objects: Table encodable object /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - Throws: `Error` func insertOrReplace( objects: [Object], on propertyConvertibleList: [PropertyConvertible]?, intoTable table: String) throws } extension InsertInterface where Self: Core { public func insert( objects: [Object], on propertyConvertibleList: [PropertyConvertible]? = nil, intoTable table: String) throws { let insert = try Insert(with: self, named: table, on: propertyConvertibleList, isReplace: false) return try insert.execute(with: objects) } public func insertOrReplace( objects: [Object], on propertyConvertibleList: [PropertyConvertible]? = nil, intoTable table: String) throws { let insert = try Insert(with: self, named: table, on: propertyConvertibleList, isReplace: true) return try insert.execute(with: objects) } public func insert( objects: Object..., on propertyConvertibleList: [PropertyConvertible]? = nil, intoTable table: String) throws { return try insert(objects: objects, on: propertyConvertibleList, intoTable: table) } public func insertOrReplace( objects: Object..., on propertyConvertibleList: [PropertyConvertible]? = nil, intoTable table: String) throws { return try insertOrReplace(objects: objects, on: propertyConvertibleList, intoTable: table) } } /// Convenient interface for updating public protocol UpdateInterface: class { /// Execute updating with `TableEncodable` object on specific(or all) properties. /// /// - Parameters: /// - table: Table name /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - object: Table encodable object /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Throws: `Error` func update( table: String, on propertyConvertibleList: PropertyConvertible..., with object: Object, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws /// Execute updating with `TableEncodable` object on specific(or all) properties. /// /// - Parameters: /// - table: Table name /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - object: Table encodable object /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Throws: `Error` func update( table: String, on propertyConvertibleList: [PropertyConvertible], with object: Object, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws /// Execute updating with row on specific(or all) properties. /// /// - Parameters: /// - table: Table name /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - object: Table encodable object /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Throws: `Error` func update(table: String, on propertyConvertibleList: PropertyConvertible..., with row: [ColumnEncodable], where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws /// Execute updating with row on specific(or all) properties. /// /// - Parameters: /// - table: Table name /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - object: Table encodable object /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Throws: `Error` func update(table: String, on propertyConvertibleList: [PropertyConvertible], with row: [ColumnEncodable], where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws } extension UpdateInterface where Self: Core { public func update( table: String, on propertyConvertibleList: [PropertyConvertible], with object: Object, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws { let update = try Update(with: self, on: propertyConvertibleList, andTable: table) if condition != nil { update.where(condition!) } if orderList != nil { update.order(by: orderList!) } if limit != nil { if offset != nil { update.limit(limit!, offset: offset!) } else { update.limit(limit!) } } return try update.execute(with: object) } public func update( table: String, on propertyConvertibleList: PropertyConvertible..., with object: Object, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws { return try update(table: table, on: propertyConvertibleList, with: object, where: condition, orderBy: orderList, limit: limit, offset: offset) } public func update(table: String, on propertyConvertibleList: PropertyConvertible..., with row: [ColumnEncodable], where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws { return try update(table: table, on: propertyConvertibleList, with: row, where: condition, orderBy: orderList, limit: limit, offset: offset) } public func update(table: String, on propertyConvertibleList: [PropertyConvertible], with row: [ColumnEncodable], where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws { let update = try Update(with: self, on: propertyConvertibleList, andTable: table) if condition != nil { update.where(condition!) } if orderList != nil { update.order(by: orderList!) } if limit != nil { if offset != nil { update.limit(limit!, offset: offset!) } else { update.limit(limit!) } } return try update.execute(with: row) } } /// Convenient interface for deleting public protocol DeleteInterface: class { /// Execute deleting /// /// - Parameters: /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Throws: `Error` func delete(fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws } extension DeleteInterface where Self: Core { public func delete(fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws { let delete = try Delete(with: self, andTableName: table) if condition != nil { delete.where(condition!) } if orderList != nil { delete.order(by: orderList!) } if limit != nil { if offset != nil { delete.limit(limit!, offset: offset!) } else { delete.limit(limit!) } } return try delete.execute() } } /// Convenient interface for row selecting public protocol RowSelectInterface: class { /// Get rows by specific selecting /// /// - Parameters: /// - columnResultConvertibleList: WINQ column result list /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: `FundamentalRowXColumn` /// - Throws: `Error` func getRows(on columnResultConvertibleList: [ColumnResultConvertible], fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalRowXColumn /// Get rows by specific selecting /// /// - Parameters: /// - columnResultConvertibleList: WINQ column result list /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: `FundamentalRowXColumn` /// - Throws: `Error` func getRows(on columnResultConvertibleList: ColumnResultConvertible..., fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalRowXColumn /// Get row by specific selecting /// /// - Parameters: /// - columnResultConvertibleList: WINQ column result list /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: `FundamentalRow` /// - Throws: `Error` func getRow(on columnResultConvertibleList: ColumnResultConvertible..., fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, offset: Offset?) throws -> FundamentalRow /// Get row by specific selecting /// /// - Parameters: /// - columnResultConvertibleList: WINQ column result list /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: `FundamentalRow` /// - Throws: `Error` func getRow(on columnResultConvertibleList: [ColumnResultConvertible], fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, offset: Offset?) throws -> FundamentalRow /// Get column by specific selecting /// /// - Parameters: /// - columnResultConvertible: WINQ column result /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: `FundamentalColumn` /// - Throws: `Error` func getColumn(on columnResultConvertible: ColumnResultConvertible, fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalColumn /// Get distinct column by specific selecting /// /// - Parameters: /// - columnResultConvertible: WINQ column result /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: `FundamentalColumn` /// - Throws: `Error` func getDistinctColumn(on columnResultConvertible: ColumnResultConvertible, fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalColumn /// Get value by specific selecting /// /// - Parameters: /// - columnResultConvertible: WINQ column result /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: `FundamentalValue` /// - Throws: `Error` func getValue(on columnResultConvertible: ColumnResultConvertible, fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalValue /// Get distinct value by specific selecting /// /// - Parameters: /// - columnResultConvertible: WINQ column result /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: `FundamentalValue` /// - Throws: `Error` func getDistinctValue(on result: ColumnResultConvertible, fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalValue } extension RowSelectInterface where Self: Core { public func getRows(on columnResultConvertibleList: [ColumnResultConvertible], fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalRowXColumn { let rowSelect = try RowSelect(with: self, results: columnResultConvertibleList, tables: [table], isDistinct: false) if condition != nil { rowSelect.where(condition!) } if orderList != nil { rowSelect.order(by: orderList!) } if limit != nil { if offset != nil { rowSelect.limit(limit!, offset: offset!) } else { rowSelect.limit(limit!) } } return try rowSelect.allRows() } public func getRows(on columnResultConvertibleList: ColumnResultConvertible..., fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalRowXColumn { return try getRows( on: columnResultConvertibleList.isEmpty ? [Column.all] : columnResultConvertibleList, fromTable: table, where: condition, orderBy: orderList, limit: limit, offset: offset) } public func getRow(on columnResultConvertibleList: ColumnResultConvertible..., fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, offset: Offset? = nil) throws -> FundamentalRow { return try getRow( on: columnResultConvertibleList.isEmpty ? [Column.all] : columnResultConvertibleList, fromTable: table, where: condition, orderBy: orderList, offset: offset) } public func getRow(on columnResultConvertibleList: [ColumnResultConvertible], fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, offset: Offset? = nil) throws -> FundamentalRow { return try getRows(on: columnResultConvertibleList, fromTable: table, where: condition, orderBy: orderList, limit: 1, offset: offset).first ?? [] } public func getColumn(on result: ColumnResultConvertible, fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalColumn { let rowSelect = try RowSelect(with: self, results: [result], tables: [table], isDistinct: false) if condition != nil { rowSelect.where(condition!) } if orderList != nil { rowSelect.order(by: orderList!) } if limit != nil { if offset != nil { rowSelect.limit(limit!, offset: offset!) } else { rowSelect.limit(limit!) } } return try rowSelect.allValues() } public func getDistinctColumn(on result: ColumnResultConvertible, fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalColumn { let rowSelect = try RowSelect(with: self, results: [result], tables: [table], isDistinct: true) if condition != nil { rowSelect.where(condition!) } if orderList != nil { rowSelect.order(by: orderList!) } if limit != nil { if offset != nil { rowSelect.limit(limit!, offset: offset!) } else { rowSelect.limit(limit!) } } return try rowSelect.allValues() } public func getValue(on result: ColumnResultConvertible, fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalValue { return (try getRows(on: result, fromTable: table, where: condition, orderBy: orderList, limit: 1, offset: offset).first?.first) ?? FundamentalValue(nil) } public func getDistinctValue(on result: ColumnResultConvertible, fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalValue { return (try getDistinctColumn(on: result, fromTable: table).first) ?? FundamentalValue(nil) } } /// Convenient interface for selecting public protocol SelectInterface: class { //TODO: Add generic property convertible to fit the type /// Get objects on specific(or all) properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: Table decodable objects /// - Throws: `Error` func getObjects( on propertyConvertibleList: [PropertyConvertible], fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> [Object] /// Get objects on specific(or all) properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: Table decodable objects /// - Throws: `Error` func getObjects( on propertyConvertibleList: PropertyConvertible..., fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> [Object] /// Get object on specific(or all) properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: Table decodable objects /// - Throws: `Error` func getObject( on propertyConvertibleList: [PropertyConvertible], fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, offset: Offset?) throws -> Object? /// Get object on specific(or all) properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - table: Table name /// - condition: Expression convertible /// - orderList: Order convertible list /// - condition: Expression convertible /// - Returns: Table decodable objects /// - Throws: `Error` func getObject( on propertyConvertibleList: PropertyConvertible..., fromTable table: String, where condition: Condition?, orderBy orderList: [OrderBy]?, offset: Offset?) throws -> Object? } extension SelectInterface where Self: Core { public func getObjects( on propertyConvertibleList: [PropertyConvertible], fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> [Object] { let select = try Select(with: self, on: propertyConvertibleList, table: table, isDistinct: false) if condition != nil { select.where(condition!) } if orderList != nil { select.order(by: orderList!) } if limit != nil { if offset != nil { select.limit(limit!, offset: offset!) } else { select.limit(limit!) } } return try select.allObjects() } public func getObjects( on propertyConvertibleList: PropertyConvertible..., fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> [Object] { return try getObjects(on: propertyConvertibleList.isEmpty ? Object.Properties.all : propertyConvertibleList, fromTable: table, where: condition, orderBy: orderList, limit: limit, offset: offset) } public func getObject( on propertyConvertibleList: [PropertyConvertible], fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, offset: Offset? = nil) throws -> Object? { return try getObjects(on: propertyConvertibleList, fromTable: table, where: condition, orderBy: orderList, limit: 1, offset: offset).first } public func getObject( on propertyConvertibleList: PropertyConvertible..., fromTable table: String, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, offset: Offset? = nil) throws -> Object? { return try getObject(on: propertyConvertibleList.isEmpty ? Object.Properties.all : propertyConvertibleList, fromTable: table, where: condition, orderBy: orderList, offset: offset) } } /// Convenient interface for table related operation public protocol TableInterface: class { /// Create table, related indexes and constraints with specific type /// /// Note that it will create defined indexes automatically. /// The name of index is `"\(tableName)\(indexSubfixName)"` while `indexSubfixName` is defined by `IndexBinding`. /// BUT, it will not drop the undefined indexes. You should drop it manually. /// /// Note that it will add the newly defined column automatically. /// AND, it will skip the undefined column. It can be very developer-friendly while upgrading your database column. /// /// Note that it will run embedded transaction /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameters: /// - name: Table name. /// - rootType: Type of table encodable object /// - Throws: `Error` func create(table name: String, of rootType: Root.Type) throws /// Create virtual table and constraints with specific type /// /// Note that it will run embedded transaction /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameters: /// - name: Table name. /// - rootType: Type of table encodable object /// - Throws: `Error` func create(virtualTable name: String, of rootType: Root.Type) throws /// Create table manually /// /// - Parameters: /// - name: Table name /// - columnDefList: WINQ column definition list /// - constraintList: WINQ constraint list. /// - Throws: `Error` func create(table name: String, with columnDefList: [ColumnDef], and constraintList: [TableConstraint]?) throws /// Create table manually /// /// - Parameters: /// - name: Table name /// - columnDefList: WINQ column definition list /// - constraintList: WINQ constraint list. /// - Throws: `Error` func create(table name: String, with columnDefList: ColumnDef..., and constraintList: [TableConstraint]?) throws /// Create new column /// /// - Parameters: /// - columnDef: WINQ column definition /// - table: Table name /// - Throws: `Error` func addColumn(with columnDef: ColumnDef, forTable table: String) throws /// Drop table /// /// - Parameter name: Table name /// - Throws: `Erro` func drop(table name: String) throws /// Create index manually /// /// - Parameters: /// - name: Index name /// - columnIndexConvertibleList: WINQ column index list /// - table: Table name /// - Throws: `Error` func create(index name: String, with columnIndexConvertibleList: [ColumnIndexConvertible], forTable table: String) throws /// Create index manually /// /// - Parameters: /// - name: Index name /// - columnIndexConvertibleList: WINQ column index list /// - table: Table name /// - Throws: `Error` func create(index name: String, with columnIndexConvertibleList: ColumnIndexConvertible..., forTable table: String) throws /// Drop index /// /// - Parameter name: Index name /// - Throws: `Error` func drop(index name: String) throws } extension TableInterface where Self: Core { public func create( table name: String, of rootType: Root.Type) throws { try run(embeddedTransaction: { let orm = Root.CodingKeys.objectRelationalMapping if try isTableExists(name) { var columnNames: [String] = [] do { let statementPragma = StatementPragma().pragma(.tableInfo, to: name) let recyclableHandleStatement: RecyclableHandleStatement = try prepare(statementPragma) let handleStatement = recyclableHandleStatement.raw while try handleStatement.step() { let columnName: String = handleStatement.columnValue(atIndex: 1) columnNames.append(columnName) } } var keys = orm.allKeys for columnName in columnNames { if let index = keys.index(where: { (key) -> Bool in return key.stringValue.caseInsensitiveCompare(columnName) == ComparisonResult.orderedSame }) { keys.remove(at: index) } else { Error.warning("Skip column named [\(columnName)] for table [\(name)]") } } try keys.forEach { try exec(StatementAlterTable().alter(table: name).addColumn(with: orm.generateColumnDef(with: $0))) } } else { try exec(orm.generateCreateTableStatement(named: name)) } try orm.generateCreateIndexStatements(onTable: name)?.forEach { try exec($0) } }) } public func create(virtualTable name: String, of rootType: Root.Type) throws { try run(transaction: { try exec(rootType.CodingKeys.objectRelationalMapping.generateCreateVirtualTableStatement(named: name)) }) } public func create(table name: String, with columnDefList: ColumnDef..., and constraintList: [TableConstraint]? = nil) throws { try create(table: name, with: columnDefList, and: constraintList) } public func create(table name: String, with columnDefList: [ColumnDef], and constraintList: [TableConstraint]? = nil) throws { try exec(StatementCreateTable().create(table: name, with: columnDefList, and: constraintList)) } public func addColumn(with columnDef: ColumnDef, forTable table: String) throws { try exec(StatementAlterTable().alter(table: table).addColumn(with: columnDef)) } public func drop(table name: String) throws { try exec(StatementDropTable().drop(table: name)) } public func create(index name: String, with columnIndexConvertibleList: ColumnIndexConvertible..., forTable table: String) throws { try create(index: name, with: columnIndexConvertibleList, forTable: table) } public func create(index name: String, with columnIndexConvertibleList: [ColumnIndexConvertible], forTable table: String) throws { try exec(StatementCreateIndex().create(index: name).on(table: table, indexesBy: columnIndexConvertibleList)) } public func drop(index name: String) throws { try exec(StatementDropIndex().drop(index: name)) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/MultiSelect.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /// Chain call for multi-selecting public final class MultiSelect: Selectable { private let keys: [CodingTableKeyBase] init(with core: Core, on propertyConvertibleList: [PropertyConvertible], tables: [String], isDistinct: Bool = false) throws { guard propertyConvertibleList.count > 0 else { throw Error.reportInterface(tag: core.tag, path: core.path, operation: .select, code: .misuse, message: "Selecting nothing from \(tables) is invalid") } guard tables.count > 0 else { throw Error.reportInterface(tag: core.tag, path: core.path, operation: .select, code: .misuse, message: "Empty table") } self.keys = propertyConvertibleList.map({ (propertyConvertible) -> CodingTableKeyBase in let codingTableKey = propertyConvertible.codingTableKey assert(codingTableKey.rootType is TableDecodableBase.Type, "\(codingTableKey.rootType) must conform to TableDecodable protocol.") return codingTableKey }) let statement = StatementSelect().select(distinct: isDistinct, propertyConvertibleList).from(tables) super.init(with: core, statement: statement) } private typealias Generator = () throws -> TableDecodableBase struct TypedIndexedKeys { let type: TableDecodableBase.Type var indexedKeys: TableDecoder.HashedKey init(_ type: TableDecodableBase.Type, key: String, index: Int) { self.type = type self.indexedKeys = [key.hashValue: index] } } private lazy var generators: [String: Generator] = { var mappedKeys: [String: TypedIndexedKeys] = [:] let handleStatement = try? lazyHandleStatement() assert(handleStatement != nil, "It should not be failed. If you think it's a bug, please report an issue to us.") for (index, key) in keys.enumerated() { let tableName = handleStatement!.columnTableName(atIndex: index) var typedIndexedKeys: TypedIndexedKeys! = mappedKeys[tableName] if typedIndexedKeys == nil { let tableDecodableType = key.rootType as? TableDecodableBase.Type assert(tableDecodableType != nil, "\(key.rootType) must conform to TableDecodable protocol.") typedIndexedKeys = TypedIndexedKeys(tableDecodableType!, key: key.stringValue, index: index) } else { typedIndexedKeys.indexedKeys[key.stringValue.hashValue] = index } mappedKeys[tableName] = typedIndexedKeys } var generators: [String: Generator] = [:] for (tableName, typedIndexedKey) in mappedKeys { let decoder = TableDecoder(typedIndexedKey.indexedKeys, on: optionalRecyclableHandleStatement!) let type = typedIndexedKey.type let generator = { () throws -> TableDecodableBase in return try type.init(from: decoder) } generators[tableName] = generator } return generators }() private func extractMultiObject() throws -> [String: TableDecodableBase] { var multiObject: [String: TableDecodableBase] = [:] for (tableName, generator) in generators { multiObject[tableName] = try generator() } return multiObject } /// Get next selected object. You can do an iteration using it. /// /// while let multiObject = try self.nextMultiObject() { /// let object1 = multiObject[tableName1] /// let object2 = multiObject[tableName2] /// //... /// } /// /// - Returns: Mapping from table name to object. Nil means the end of iteration. /// - Throws: `Error` public func nextMultiObject() throws -> [String: TableDecodableBase]? { guard try next() else { return nil } return try extractMultiObject() } /// Get all selected objects. /// /// - Returns: Array contained mapping from table name to object. /// - Throws: `Error` public func allMultiObjects() throws -> [[String: TableDecodableBase]] { var multiObjects: [[String: TableDecodableBase]] = [] while try next() { multiObjects.append(try extractMultiObject()) } return multiObjects } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/RowSelect.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /// Chain call for row-selecting public final class RowSelect: Selectable { init(with core: Core, results columnResultConvertibleList: [ColumnResultConvertible], tables: [String], isDistinct: Bool) throws { guard tables.count > 0 else { throw Error.reportInterface(tag: core.tag, path: core.path, operation: .select, code: .misuse, message: "Empty table") } let statement = StatementSelect().select(distinct: isDistinct, columnResultConvertibleList).from(tables) super.init(with: core, statement: statement) } private func extract(atIndex index: Int) throws -> FundamentalValue { let handleStatement = try self.lazyHandleStatement() switch handleStatement.columnType(atIndex: index) { case .integer32: return FundamentalValue(handleStatement.columnValue(atIndex: index, of: Int32.self)) case .integer64: return FundamentalValue(handleStatement.columnValue(atIndex: index, of: Int64.self)) case .float: return FundamentalValue(handleStatement.columnValue(atIndex: index, of: Double.self)) case .text: return FundamentalValue(handleStatement.columnValue(atIndex: index, of: String.self)) case .BLOB: return FundamentalValue(handleStatement.columnValue(atIndex: index, of: Data.self)) case .null: return FundamentalValue(nil) } } private func extract() throws -> FundamentalRow { var row: FundamentalRow = [] let handleStatement = try self.lazyHandleStatement() for index in 0.. FundamentalRow? { guard try next() else { return nil } return try extract() } /// Get all selected row. /// /// - Returns: Array with `Array` /// - Throws: `Error` public func allRows() throws -> FundamentalRowXColumn { var rows: [[FundamentalValue]] = [] while try next() { rows.append(try extract()) } return rows } /// Get next selected value. You can do an iteration using it. /// /// while let value = try nextValue() { /// print(value.int32Value) /// } /// /// - Returns: `FundamentalValue`. Nil means the end of iteration. /// - Throws: `Error` public func nextValue() throws -> FundamentalValue? { guard try next() else { return nil } return try extract(atIndex: 0) } /// Get all selected values. /// /// - Returns: Array with `FundamentalValue`. /// - Throws: `Error` public func allValues() throws -> FundamentalColumn { var values: [FundamentalValue] = [] while try next() { values.append(try extract(atIndex: 0)) } return values } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/Select.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public final class Select: Selectable { private let keys: [CodingTableKeyBase] private lazy var decoder = TableDecoder(keys, on: optionalRecyclableHandleStatement!) init(with core: Core, on propertyConvertibleList: [PropertyConvertible], table: String, isDistinct: Bool) throws { //TODO: Use generic to check all coding table keys conform to same root type keys = propertyConvertibleList.asCodingTableKeys() let statement = StatementSelect().select(distinct: isDistinct, propertyConvertibleList).from(table) super.init(with: core, statement: statement) } /// Get next selected object according to the `CodingTableKey`. You can do an iteration using it. /// /// - Returns: Table decodable object according to the `CodingTableKey`. Nil means the end of iteration. /// - Throws: `Error` public func nextObject() throws -> Any? { let rootType = keys[0].rootType as? TableDecodableBase.Type assert(rootType != nil, "\(keys[0].rootType) must conform to TableDecodable protocol.") guard try next() else { return nil } return try rootType!.init(from: decoder) } /// Get all selected objects according to the `CodingTableKey`. /// /// - Returns: Table decodable objects according to the `CodingTableKey` /// - Throws: `Error` public func allObjects() throws -> [Any] { let rootType = keys[0].rootType as? TableDecodableBase.Type assert(rootType != nil, "\(keys[0].rootType) must conform to TableDecodable protocol.") var objects: [Any] = [] while try next() { objects.append(try rootType!.init(from: decoder)) } return objects } /// Get next selected object with type. You can do an iteration using it. /// /// - Parameter type: Type of table decodable object /// - Returns: Table decodable object. Nil means the end of iteration. /// - Throws: `Error` public func nextObject(of type: Object.Type = Object.self) throws -> Object? { assert(keys is [Object.CodingKeys], "Properties must belong to \(Object.self).CodingKeys.") guard try next() else { return nil } return try Object.init(from: decoder) } /// Get all selected objects. /// /// - Parameter type: Type of table decodable object /// - Returns: Table decodable objects. /// - Throws: `Error` public func allObjects(of type: Object.Type = Object.self) throws -> [Object] { assert(keys is [Object.CodingKeys], "Properties must belong to \(Object.self).CodingKeys.") var objects: [Object] = [] while try next() { objects.append(try Object.init(from: decoder)) } return objects } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/Selectable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public class Selectable { private final var core: Core final var optionalRecyclableHandleStatement: RecyclableHandleStatement? final var statement: StatementSelect init(with core: Core, statement: StatementSelect) { self.statement = statement self.core = core } deinit { try? finalize() } final func finalize() throws { if let recyclableHandleStatement = optionalRecyclableHandleStatement { try recyclableHandleStatement.raw.finalize() optionalRecyclableHandleStatement = nil } } final func lazyHandleStatement() throws -> HandleStatement { if optionalRecyclableHandleStatement == nil { optionalRecyclableHandleStatement = try core.prepare(statement) } return optionalRecyclableHandleStatement!.raw } //Since `next()` may throw errors, it can't conform to `Sequence` protocol to fit a `for in` loop. @discardableResult public final func next() throws -> Bool { do { return try lazyHandleStatement().step() } catch let error { try? finalize() throw error } } /// WINQ interface /// /// - Parameter condition: Expression convertible /// - Returns: `self` @discardableResult public final func `where`(_ condition: Condition) -> Self { statement.where(condition) return self } /// WINQ interface for SQL /// /// - Parameter orderList: Order convertible list /// - Returns: `self` @discardableResult public final func order(by orderConvertibleList: OrderBy...) -> Self { return order(by: orderConvertibleList) } /// WINQ interface for SQL /// /// - Parameter orderList: Order convertible list /// - Returns: `self` @discardableResult public final func order(by orderConvertibleList: [OrderBy]) -> Self { statement.order(by: orderConvertibleList) return self } /// WINQ interface for SQL /// /// - Parameters: /// - begin: Expression convertible /// - end: Expression convertible /// - Returns: `self` @discardableResult public final func limit(from begin: Limit, to end: Limit) -> Self { statement.limit(from: begin, to: end) return self } @discardableResult public final func limit(_ expressionConvertibleLimit: Limit) -> Self { statement.limit(expressionConvertibleLimit) return self } @discardableResult public final func limit(_ expressionConvertibleLimit: Limit, offset expressionConvertibleOffset: Offset) -> Self { statement.limit(expressionConvertibleLimit, offset: expressionConvertibleOffset) return self } @discardableResult public final func group(by expressionConvertibleGroupList: GroupBy...) -> Self { return group(by: expressionConvertibleGroupList) } @discardableResult public final func group(by expressionConvertibleGroupList: [GroupBy]) -> Self { statement.group(by: expressionConvertibleGroupList) return self } @discardableResult public final func having(_ expressionConvertibleHaving: Having) -> Self { statement.having(expressionConvertibleHaving) return self } } extension Selectable: CoreRepresentable { /// The tag of the related database. public final var tag: Tag? { return core.tag } /// The path of the related database. public final var path: String { return core.path } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/Table.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /// Convenient table interface public final class Table { private let database: Database /// Table name public let name: String /// Table related type public var rootType: Root.Type { return Root.self } init(withDatabase database: Database, named name: String, of type: Root.Type = Root.self) { self.database = database self.name = name } } extension Table: CoreRepresentable { public typealias Object = Root /// The tag of the related database. public var tag: Tag? { return database.tag } /// The path of the related database. public var path: String { return database.path } } extension Table: InsertTableInterface { /// Execute inserting with `TableCodable` object on specific(or all) properties /// /// Note that it will run embedded transaction while objects.count>1. /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameters: /// - objects: Table encodable object /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - Throws: `Error` public func insert(objects: [Object], on propertyConvertibleList: [PropertyConvertible]? = nil) throws { let insert = try Insert(with: self.database, named: self.name, on: propertyConvertibleList, isReplace: false) return try insert.execute(with: objects) } /// Execute inserting or replacing with `TableCodable` object on specific(or all) properties. /// It will replace the original row while they have same primary key or row id. /// /// Note that it will run embedded transaction while objects.count>1. /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameters: /// - objects: Table encodable object /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - Throws: `Error` public func insertOrReplace(objects: [Object], on propertyConvertibleList: [PropertyConvertible]? = nil) throws { let insert = try Insert(with: self.database, named: self.name, on: propertyConvertibleList, isReplace: true) return try insert.execute(with: objects) } /// Execute inserting with `TableCodable` object on specific(or all) properties /// /// Note that it will run embedded transaction while objects.count>1. /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameters: /// - objects: Table encodable object /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - Throws: `Error` public func insert(objects: Object..., on propertyConvertibleList: [PropertyConvertible]? = nil) throws { return try insert(objects: objects, on: propertyConvertibleList) } /// Execute inserting or replacing with `TableCodable` object on specific(or all) properties. /// It will replace the original row while they have same primary key or row id. /// /// Note that it will run embedded transaction while objects.count>1. /// The embedded transaction means that it will run a transaction if it's not in other transaction, /// otherwise it will be executed within the existing transaction. /// /// - Parameters: /// - objects: Table encodable object /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - Throws: `Error` public func insertOrReplace(objects: Object..., on propertyConvertibleList: [PropertyConvertible]? = nil) throws { return try insertOrReplace(objects: objects, on: propertyConvertibleList) } } extension Table: UpdateTableInterface { /// Execute updating with `TableCodable` object on specific(or all) properties. /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - object: Table encodable object /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Throws: `Error` public func update(on propertyConvertibleList: [PropertyConvertible], with object: Object, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws { let update = try Update(with: self.database, on: propertyConvertibleList, andTable: self.name) if condition != nil { update.where(condition!) } if orderList != nil { update.order(by: orderList!) } if limit != nil { if offset != nil { update.limit(limit!, offset: offset!) } else { update.limit(limit!) } } return try update.execute(with: object) } /// Execute updating with `TableCodable` object on specific(or all) properties. /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - object: Table encodable object /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Throws: `Error` public func update(on propertyConvertibleList: PropertyConvertible..., with object: Object, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws { return try update(on: propertyConvertibleList, with: object, where: condition, orderBy: orderList, limit: limit, offset: offset) } /// Execute updating with row on specific(or all) properties. /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - object: Table encodable object /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Throws: `Error` public func update(on propertyConvertibleList: PropertyConvertible..., with row: [ColumnEncodable], where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws { return try update(on: propertyConvertibleList, with: row, where: condition, orderBy: orderList, limit: limit, offset: offset) } /// Execute updating with row on specific(or all) properties. /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - object: Table encodable object /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Throws: `Error` public func update(on propertyConvertibleList: [PropertyConvertible], with row: [ColumnEncodable], where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws { let update = try Update(with: self.database, on: propertyConvertibleList, andTable: self.name) if condition != nil { update.where(condition!) } if orderList != nil { update.order(by: orderList!) } if limit != nil { if offset != nil { update.limit(limit!, offset: offset!) } else { update.limit(limit!) } } return try update.execute(with: row) } } extension Table: DeleteTableInterface { /// Execute deleting /// /// - Parameters: /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Throws: `Error` public func delete(where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws { let delete = try Delete(with: self.database, andTableName: self.name) if condition != nil { delete.where(condition!) } if orderList != nil { delete.order(by: orderList!) } if limit != nil { if offset != nil { delete.limit(limit!, offset: offset!) } else { delete.limit(limit!) } } return try delete.execute() } } extension Table: SelectTableInterface { /// Get objects on specific(or all) properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: Table decodable objects /// - Throws: `Error` public func getObjects(on propertyConvertibleList: [PropertyConvertible], where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> [Object] { let select = try Select(with: self.database, on: propertyConvertibleList, table: self.name, isDistinct: false) if condition != nil { select.where(condition!) } if orderList != nil { select.order(by: orderList!) } if limit != nil { if offset != nil { select.limit(limit!, offset: offset!) } else { select.limit(limit!) } } return try select.allObjects() } /// Get objects on specific(or all) properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: Table decodable objects /// - Throws: `Error` public func getObjects(on propertyConvertibleList: PropertyConvertible..., where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> [Object] { return try getObjects(on: propertyConvertibleList.isEmpty ? Object.Properties.all : propertyConvertibleList, where: condition, orderBy: orderList, limit: limit, offset: offset) } /// Get object on specific(or all) properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: Table decodable objects /// - Throws: `Error` public func getObject(on propertyConvertibleList: [PropertyConvertible], where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, offset: Offset? = nil) throws -> Object? { return try getObjects(on: propertyConvertibleList, where: condition, orderBy: orderList, limit: 1, offset: offset).first } /// Get object on specific(or all) properties /// /// - Parameters: /// - propertyConvertibleList: `Property` or `CodingTableKey` list /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: Table decodable objects /// - Throws: `Error` public func getObject(on propertyConvertibleList: PropertyConvertible..., where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, offset: Offset? = nil) throws -> Object? { return try getObjects(on: propertyConvertibleList.isEmpty ? Object.Properties.all : propertyConvertibleList, where: condition, orderBy: orderList, limit: 1, offset: offset).first } } extension Table: RowSelectTableInterface { /// Get rows by specific selecting /// /// - Parameters: /// - columnResultConvertibleList: WINQ column result list /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: `FundamentalRowXColumn` /// - Throws: `Error` public func getRows(on columnResultConvertibleList: [ColumnResultConvertible], where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalRowXColumn { let rowSelect = try RowSelect(with: self.database, results: columnResultConvertibleList, tables: [self.name], isDistinct: false) if condition != nil { rowSelect.where(condition!) } if orderList != nil { rowSelect.order(by: orderList!) } if limit != nil { if offset != nil { rowSelect.limit(limit!, offset: offset!) } else { rowSelect.limit(limit!) } } return try rowSelect.allRows() } /// Get rows by specific selecting /// /// - Parameters: /// - columnResultConvertibleList: WINQ column result list /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: `FundamentalRowXColumn` /// - Throws: `Error` public func getRows(on columnResultConvertibleList: ColumnResultConvertible..., where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalRowXColumn { return try getRows(on: columnResultConvertibleList.isEmpty ? [Column.all] : columnResultConvertibleList, where: condition, orderBy: orderList, limit: limit, offset: offset) } /// Get row by specific selecting /// /// - Parameters: /// - columnResultConvertibleList: WINQ column result list /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: `FundamentalRowXColumn` /// - Throws: `Error` public func getRow(on columnResultConvertibleList: ColumnResultConvertible..., where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, offset: Offset? = nil) throws -> FundamentalRow { return try getRows(on: columnResultConvertibleList.isEmpty ? [Column.all] : columnResultConvertibleList, where: condition, orderBy: orderList, limit: 1, offset: offset).first ?? [] } /// Get row by specific selecting /// /// - Parameters: /// - columnResultConvertibleList: WINQ column result list /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: `FundamentalRowXColumn` /// - Throws: `Error` public func getRow(on columnResultConvertibleList: [ColumnResultConvertible], where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, offset: Offset? = nil) throws -> FundamentalRow { return try getRows(on: columnResultConvertibleList, where: condition, orderBy: orderList, limit: 1, offset: offset).first ?? [] } /// Get column by specific selecting /// /// - Parameters: /// - columnResultConvertible: WINQ column result /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: `FundamentalColumn` /// - Throws: `Error` public func getColumn(on result: ColumnResultConvertible, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalColumn { let rowSelect = try RowSelect(with: self.database, results: [result], tables: [self.name], isDistinct: false) if condition != nil { rowSelect.where(condition!) } if orderList != nil { rowSelect.order(by: orderList!) } if limit != nil { if offset != nil { rowSelect.limit(limit!, offset: offset!) } else { rowSelect.limit(limit!) } } return try rowSelect.allValues() } /// Get distinct column by specific selecting /// /// - Parameters: /// - columnResultConvertible: WINQ column result /// - condition: Expression convertible /// - orderList: Order convertible list /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: `FundamentalColumn` /// - Throws: `Error` public func getDistinctColumn(on result: ColumnResultConvertible, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalColumn { let rowSelect = try RowSelect(with: self.database, results: [result], tables: [self.name], isDistinct: true) if condition != nil { rowSelect.where(condition!) } if orderList != nil { rowSelect.order(by: orderList!) } if limit != nil { if offset != nil { rowSelect.limit(limit!, offset: offset!) } else { rowSelect.limit(limit!) } } return try rowSelect.allValues() } /// Get value by specific selecting /// /// - Parameters: /// - columnResultConvertible: WINQ column result /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: `FundamentalValue` /// - Throws: `Error` public func getValue(on result: ColumnResultConvertible, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalValue { return (try getRows(on: result, where: condition, orderBy: orderList, limit: 1, offset: offset).first?.first) ?? FundamentalValue(nil) } /// Get distinct value by specific selecting /// /// - Parameters: /// - columnResultConvertible: WINQ column result /// - condition: Expression convertible /// - orderList: Order convertible list /// - offset: Expression convertible /// - Returns: `FundamentalValue` /// - Throws: `Error` public func getDistinctValue(on result: ColumnResultConvertible, where condition: Condition? = nil, orderBy orderList: [OrderBy]? = nil, limit: Limit? = nil, offset: Offset? = nil) throws -> FundamentalValue { return (try getDistinctColumn(on: result).first) ?? FundamentalValue(nil) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/TableInterface.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public protocol InsertTableInterface: class { associatedtype Object: TableEncodable func insert(objects: Object..., on propertyConvertibleList: [PropertyConvertible]?) throws func insert(objects: [Object], on propertyConvertibleList: [PropertyConvertible]?) throws func insertOrReplace(objects: Object..., on propertyConvertibleList: [PropertyConvertible]?) throws func insertOrReplace(objects: [Object], on propertyConvertibleList: [PropertyConvertible]?) throws } public protocol UpdateTableInterface: class { associatedtype Object: TableEncodable func update(on propertyConvertibleList: PropertyConvertible..., with object: Object, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws func update(on propertyConvertibleList: [PropertyConvertible], with object: Object, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws func update(on propertyConvertibleList: PropertyConvertible..., with row: [ColumnEncodable], where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws func update(on propertyConvertibleList: [PropertyConvertible], with row: [ColumnEncodable], where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws } public protocol DeleteTableInterface: class { func delete(where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws } public protocol RowSelectTableInterface: class { func getRows(on columnResultConvertibleList: [ColumnResultConvertible], where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalRowXColumn func getRows(on columnResultConvertibleList: ColumnResultConvertible..., where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalRowXColumn func getRow(on columnResultConvertibleList: ColumnResultConvertible..., where condition: Condition?, orderBy orderList: [OrderBy]?, offset: Offset?) throws -> FundamentalRow func getRow(on columnResultConvertibleList: [ColumnResultConvertible], where condition: Condition?, orderBy orderList: [OrderBy]?, offset: Offset?) throws -> FundamentalRow func getColumn(on result: ColumnResultConvertible, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalColumn func getDistinctColumn(on result: ColumnResultConvertible, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalColumn func getValue(on result: ColumnResultConvertible, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalValue func getDistinctValue(on result: ColumnResultConvertible, where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> FundamentalValue } public protocol SelectTableInterface: class { associatedtype Object: TableDecodable //TODO: Add generic property convertible to fit the type func getObjects(on propertyConvertibleList: [PropertyConvertible], where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> [Object] func getObjects(on propertyConvertibleList: PropertyConvertible..., where condition: Condition?, orderBy orderList: [OrderBy]?, limit: Limit?, offset: Offset?) throws -> [Object] func getObject(on propertyConvertibleList: [PropertyConvertible], where condition: Condition?, orderBy orderList: [OrderBy]?, offset: Offset?) throws -> Object? func getObject(on propertyConvertibleList: PropertyConvertible..., where condition: Condition?, orderBy orderList: [OrderBy]?, offset: Offset?) throws -> Object? } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/core/interface/Update.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation /// The chain call for updating public final class Update { private var core: Core private let statement = StatementUpdate() private let keys: [CodingTableKeyBase] /// The number of changed rows in the most recent call. /// It should be called after executing successfully public var changes: Int? init(with core: Core, on propertyConvertibleList: [PropertyConvertible], andTable table: String) throws { guard propertyConvertibleList.count > 0 else { throw Error.reportInterface(tag: core.tag, path: core.path, operation: .update, code: .misuse, message: "Updating \(table) with empty property") } guard table.count > 0 else { throw Error.reportInterface(tag: core.tag, path: core.path, operation: .update, code: .misuse, message: "Nil table name") } self.keys = propertyConvertibleList.asCodingTableKeys() self.core = core self.statement .update(table: table) .set(propertyConvertibleList.map { (propertyConvertible) -> StatementUpdate.ValueType in return (propertyConvertible, Expression.bindParameter) }) } /// WINQ interface for SQL /// /// - Parameter condition: Expression convertible /// - Returns: `self` @discardableResult public func `where`(_ condition: Condition) -> Update { statement.where(condition) return self } /// WINQ interface for SQL /// /// - Parameter orderList: Order convertible list /// - Returns: `self` @discardableResult public func order(by orderList: OrderBy...) -> Update { statement.order(by: orderList) return self } /// WINQ interface for SQL /// /// - Parameter orderList: Order convertible list /// - Returns: `self` @discardableResult public func order(by orderList: [OrderBy]) -> Update { statement.order(by: orderList) return self } /// WINQ interface for SQL /// /// - Parameters: /// - begin: Expression convertible /// - end: Expression convertible /// - Returns: `self` @discardableResult public func limit(from begin: Limit, to end: Limit) -> Update { statement.limit(from: begin, to: end) return self } /// WINQ interface for SQL /// /// - Parameter limit: Expression convertible /// - Returns: `self` @discardableResult public func limit(_ limit: Limit) -> Update { statement.limit(limit) return self } /// WINQ interface for SQL /// /// - Parameters: /// - limit: Expression convertible /// - offset: Expression convertible /// - Returns: `self` @discardableResult public func limit(_ limit: Limit, offset: Offset) -> Update { statement.limit(limit, offset: offset) return self } /// Execute the update chain call with object. /// /// - Parameter object: Table encodable object /// - Throws: `Error` public func execute(with object: Object) throws { let recyclableHandleStatement: RecyclableHandleStatement = try core.prepare(statement) let handleStatement = recyclableHandleStatement.raw let encoder = TableEncoder(keys, on: recyclableHandleStatement) try object.encode(to: encoder) try handleStatement.step() changes = handleStatement.changes } /// Execute the update chain call with row. /// /// - Parameter row: Column encodable row /// - Throws: `Error` public func execute(with row: [ColumnEncodable?]) throws { let recyclableHandleStatement: RecyclableHandleStatement = try core.prepare(statement) let handleStatement = recyclableHandleStatement.raw for (index, value) in row.enumerated() { let bindingIndex = index + 1 if let archivedValue = value?.archivedValue() { handleStatement.bind(archivedValue, toIndex: bindingIndex) } else { handleStatement.bind(nil, toIndex: bindingIndex) } } try handleStatement.step() changes = handleStatement.changes } } extension Update: CoreRepresentable { /// The tag of the related database. public var tag: Tag? { return core.tag } /// The path of the related database. public var path: String { return core.path } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/Atomic.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class Atomic { var raw: Value let spin = Spin() init(_ raw: Value) { self.raw = raw } var value: Value { spin.lock(); defer { spin.unlock() } return raw } func withValue(_ closure: (Value) -> Value) { spin.lock(); defer { spin.unlock() } raw = closure(raw) } func assign(_ newValue: Value) { spin.lock(); defer { spin.unlock() } self.raw = newValue } } extension Atomic where Value==Int { static func += (left: Atomic, right: Value) { left.withValue { (value) -> Value in return value + right } } static func -= (left: Atomic, right: Value) { left.withValue { (value) -> Value in return value - right } } static prefix func ++ (atomic: Atomic) -> Value { var newValue: Value = 0 atomic.withValue { (value) -> Value in newValue = value + 1 return newValue } return newValue } } extension Atomic where Value: Equatable { static func == (left: Atomic, right: Value) -> Bool { return left.value == right } } extension Atomic where Value: Comparable { static func < (left: Atomic, right: Value) -> Bool { return left.value < right } static func > (left: Atomic, right: Value) -> Bool { return left.value > right } } extension Atomic where Value: OptionalRepresentable { convenience init() { self.init(Value.`nil`) } } extension Atomic: CustomStringConvertible { var description: String { return "\(value)" } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/ConcurrentList.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class ConcurrentList { var values: [Value] = [] let capacityCap: Int let spin = Spin() init(withCapacityCap capacityCap: Int) { self.capacityCap = capacityCap } func pushBack(_ value: Value) -> Bool { spin.lock(); defer { spin.unlock() } if values.count < capacityCap { values.append(value) return true } return false } func popBack() -> Value? { spin.lock(); defer { spin.unlock() } if !values.isEmpty { return values.removeLast() } return nil } func clear() -> Int { spin.lock(); defer { spin.unlock() } let count = values.count values.removeAll() return count } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/Convenience.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation extension Array { func joined(_ map: (Element) -> String, separateBy separator: String = ", ") -> String { var flag = false return reduce(into: "") { (output, element) in if flag { output.append(separator) } else { flag = true } output.append(map(element)) } } } extension Array where Element: StringProtocol { func joined(separateBy separator: String = ", ") -> String { var flag = false return reduce(into: "") { (output, element) in if flag { output.append(separator) } else { flag = true } output.append(String(element)) } } } extension Array where Element: Describable { func joined(separateBy separator: String = ", ") -> String { return joined({ $0.description }, separateBy: separator) } } extension Array where Element==ColumnResultConvertible { func joined(separateBy separator: String = ", ") -> String { return joined({ $0.asColumnResult().description }, separateBy: separator) } } extension Array where Element==ExpressionConvertible { func joined(separateBy separator: String = ", ") -> String { return joined({ $0.asExpression().description }, separateBy: separator) } } extension Array where Element==ColumnConvertible { func joined(separateBy separator: String = ", ") -> String { return joined({ $0.asColumn().description }, separateBy: separator) } } extension Array where Element==TableOrSubqueryConvertible { func joined(separateBy separator: String = ", ") -> String { return joined({ $0.asTableOrSubquery().description }, separateBy: separator) } } extension Array where Element==OrderConvertible { func joined(separateBy separator: String = ", ") -> String { return joined({ $0.asOrder().description }, separateBy: separator) } } extension Array where Element==ColumnIndexConvertible { func joined(separateBy separator: String = ", ") -> String { return joined({ $0.asIndex().description }, separateBy: separator) } func asIndexes() -> [ColumnIndex] { return map { $0.asIndex() } } } extension Array where Element==PropertyConvertible { func asCodingTableKeys() -> [CodingTableKeyBase] { return reduce(into: [CodingTableKeyBase]()) { (result, element) in result.append(element.codingTableKey) } } } extension Array { mutating func expand(toNewSize newSize: Int, fillWith value: Iterator.Element) { if count < newSize { append(contentsOf: repeatElement(value, count: count.distance(to: newSize))) } } } extension Array where Iterator.Element: FixedWidthInteger { mutating func expand(toNewSize newSize: Int) { expand(toNewSize: newSize, fillWith: 0) } } extension Dictionary { func joined(_ map: (Key, Value) -> String, separateBy separator: String = "," ) -> String { var flag = false return reduce(into: "", { (output, arg) in if flag { output.append(separator) } else { flag = true } output.append(map(arg.key, arg.value)) }) } } extension String { var lastPathComponent: String { return URL(fileURLWithPath: self).lastPathComponent } func stringByAppending(pathComponent: String) -> String { return URL(fileURLWithPath: self).appendingPathComponent(pathComponent).path } var cString: UnsafePointer? { return UnsafePointer((self as NSString).utf8String) } init?(bytes: UnsafeRawPointer, count: Int, encoding: String.Encoding) { self.init(data: Data(bytes: bytes, count: count), encoding: encoding) } func range(from begin: Int, to end: Int) -> Range { return index(startIndex, offsetBy: begin).. Range { return range(from: location, to: location + length) } } extension Bool { @inline(__always) func toInt32() -> Int32 { return self ? 1 : 0 } } extension Int { @inline(__always) func toInt64() -> Int64 { return Int64(self) } } extension Int8 { @inline(__always) func toInt32() -> Int32 { return Int32(self) } } extension Int16 { @inline(__always) func toInt32() -> Int32 { return Int32(self) } } extension Int32 { @inline(__always) func toBool() -> Bool { return self != 0 } @inline(__always) func toInt8() -> Int8 { return Int8(truncatingIfNeeded: self) } @inline(__always) func toInt16() -> Int16 { return Int16(truncatingIfNeeded: self) } @inline(__always) func toUInt8() -> UInt8 { return UInt8(bitPattern: Int8(truncatingIfNeeded: self)) } @inline(__always) func toUInt16() -> UInt16 { return UInt16(bitPattern: Int16(truncatingIfNeeded: self)) } @inline(__always) func toUInt32() -> UInt32 { return UInt32(bitPattern: self) } } extension Int64 { @inline(__always) func toInt() -> Int { return Int(truncatingIfNeeded: self) } @inline(__always) func toUInt() -> UInt { return UInt(bitPattern: Int(truncatingIfNeeded: self)) } @inline(__always) func toUInt64() -> UInt64 { return UInt64(bitPattern: self) } } extension UInt { @inline(__always) func toInt64() -> Int64 { return Int64(bitPattern: UInt64(self)) } } extension UInt8 { @inline(__always) func toInt32() -> Int32 { return Int32(bitPattern: UInt32(self)) } } extension UInt16 { @inline(__always) func toInt32() -> Int32 { return Int32(bitPattern: UInt32(self)) } } extension UInt32 { @inline(__always) func toInt32() -> Int32 { return Int32(bitPattern: self) } } extension UInt64 { @inline(__always) func toInt64() -> Int64 { return Int64(bitPattern: self) } } extension Float { @inline(__always) func toDouble() -> Double { return Double(self) } } extension Double { @inline(__always) func toFloat() -> Float { return Float(self) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/Error.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public struct ErrorValue { public enum ErrorValueType { case int case string } private let value: Any public let type: ErrorValueType init(_ value: String) { self.value = value self.type = .string } init(_ value: Int) { self.value = value self.type = .int } public var intValue: Int { switch type { case .int: return value as! Int case .string: return Int(value as! String) ?? 0 } } public var stringValue: String { switch type { case .int: return String(value as! Int) case .string: return value as! String } } } public final class Error: Swift.Error, CustomStringConvertible { public enum Key: Int, CustomStringConvertible { case tag = 1 case operation = 2 case extendedCode = 3 case message = 4 case SQL = 5 case path = 6 public var description: String { switch self { case .tag: return "Tag" case .operation: return "Operation" case .extendedCode: return "ExtendedCode" case .message: return "Message" case .SQL: return "SQL" case .path: return "Path" } } } public enum ErrorType: Int, CustomStringConvertible { case sqlite = 1 case systemCall = 2 case core = 3 case interface = 4 case warning = 6 case sqliteGlobal = 7 case repair = 8 public var description: String { switch self { case .sqlite: return "SQLite" case .systemCall: return "SystemCall" case .core: return "Core" case .interface: return "Interface" case .warning: return "Warning" case .sqliteGlobal: return "SQLiteGlobal" case .repair: return "Repair" } } } public enum Operation { public enum HandleOperation: Int { case open = 1 case close = 2 case prepare = 3 case exec = 4 case step = 5 case finalize = 6 case setCipherKey = 7 case isTableExists = 8 } public enum InterfaceOperation: Int { case handleStatement = 1 case insert = 2 case update = 3 case select = 4 case table = 5 case chainCall = 6 case delete = 7 } public enum CoreOperation: Int { case prepare = 1 case exec = 2 case begin = 3 case commit = 4 case rollback = 5 case getThreadedHandle = 6 case flowOut = 7 case tokenize = 8 case encode = 9 case decode = 10 case getPool = 11 } public enum SystemCallOperation: Int { case remove = 257 // 1 + 1<<8 Swift not supported case link = 258 case createDirectory = 259 case getFileSize = 260 case getAttributes = 261 case setAttributes = 262 } public enum RepairOperation: Int { case saveMaster case loadMaster case repair } case handle(HandleOperation) case interface(InterfaceOperation) case core(CoreOperation) case systemCall(SystemCallOperation) case repair(RepairOperation) public var value: Int { switch self { case .handle(let value): return value.rawValue case .interface(let value): return value.rawValue case .core(let value): return value.rawValue case .systemCall(let value): return value.rawValue case .repair(let value): return value.rawValue } } } public enum Code { public enum CoreCode: Int { case misuse = 1 case exceed = 2 } public enum InterfaceCode: Int { case ORM = 1 case inconsistent = 2 case misuse = 4 } public enum GlobalCode: Int { case warning = 1 case abort = 2 } case repair(Int) case sqliteGlobal(Int) case systemCall(Int) case sqlite(Int) case core(CoreCode) case interface(InterfaceCode) case global(GlobalCode) public var value: Int { switch self { case .repair(let value): return value case .sqliteGlobal(let value): return value case .systemCall(let value): return value case .sqlite(let value): return value case .core(let value): return value.rawValue case .interface(let value): return value.rawValue case .global(let value): return value.rawValue } } } public typealias Infos = [Error.Key: ErrorValue] public let infos: Infos public let type: ErrorType public let code: Error.Code private init(type: Error.ErrorType, code: Error.Code, infos: Infos) { self.infos = infos self.type = type self.code = code } public var tag: Tag? { return infos[.tag]?.intValue } public var operationValue: Int? { return infos[.operation]?.intValue } public var extendedCode: Int? { return infos[.extendedCode]?.intValue } public var message: String? { return infos[.message]?.stringValue } public var sql: String? { return infos[.SQL]?.stringValue } public var path: String? { return infos[.path]?.stringValue } public var description: String { return "Code:\(code), Type:\(type.description), \(infos.joined({ "\($0.description):\($1.stringValue)" }))" } static let threadedSlient = ThreadLocal(defaultTo: false) public typealias Reporter = (Error) -> Void static private let defaultReporter: Reporter = { switch $0.type { case .sqliteGlobal: debugPrint("[WCDB][DEBUG] \($0.description)") case .warning: print("[WCDB][WARNING] \($0.description)") default: print("[WCDB][ERROR] \($0.description)") } } static private let reporter: Atomic = Atomic(defaultReporter) static public func setReporter(_ reporter: @escaping Reporter) { Error.reporter.assign(reporter) } static public func setReporter(_: Void?) { Error.reporter.assign(nil) } static public func resetReporter() { Error.reporter.assign(defaultReporter) } private func report() { guard !Error.threadedSlient.value else { return } Error.reporter.raw?(self) } @discardableResult static func report(type: ErrorType, code: Error.Code, infos: Error.Infos) -> Error { let error = Error(type: type, code: code, infos: infos) error.report() return error } @discardableResult static func reportSQLite(tag: Tag?, path: String, operation: Error.Operation.HandleOperation, extendedError: Int32? = nil, sql: String? = nil, code: Int32, message: String) -> Error { var infos = [ Error.Key.operation: ErrorValue(operation.rawValue), Error.Key.message: ErrorValue(message), Error.Key.path: ErrorValue(path)] if extendedError != nil { infos[Error.Key.extendedCode] = ErrorValue(Int(extendedError!)) } if sql != nil { infos[Error.Key.SQL] = ErrorValue(sql!) } if tag != nil { infos[Error.Key.tag] = ErrorValue(tag!) } return Error.report(type: .sqlite, code: .sqlite(Int(code)), infos: infos) } @discardableResult static func reportCore(tag: Tag?, path: String, operation: Error.Operation.CoreOperation, code: Error.Code.CoreCode, message: String) -> Error { var infos = [ Error.Key.operation: ErrorValue(operation.rawValue), Error.Key.message: ErrorValue(message), Error.Key.path: ErrorValue(path)] if tag != nil { infos[Error.Key.tag] = ErrorValue(tag!) } return Error.report(type: .core, code: .core(code), infos: infos) } @discardableResult static func reportInterface(tag: Tag?, path: String, operation: Error.Operation.InterfaceOperation, code: Error.Code.InterfaceCode, message: String) -> Error { var infos = [ Error.Key.operation: ErrorValue(operation.rawValue), Error.Key.message: ErrorValue(message), Error.Key.path: ErrorValue(path)] if tag != nil { infos[Error.Key.tag] = ErrorValue(tag!) } return Error.report(type: .interface, code: .interface(code), infos: infos) } @discardableResult static func reportSystemCall(operation: Error.Operation.SystemCallOperation, path: String, errno: Int, message: String) -> Error { return Error.report(type: .systemCall, code: .systemCall(errno), infos: [ Error.Key.operation: ErrorValue(operation.rawValue), Error.Key.message: ErrorValue(message), Error.Key.path: ErrorValue(path) ]) } @discardableResult static func reportSQLiteGlobal(code: Int, message: String) -> Error { return Error.report(type: .sqliteGlobal, code: .sqliteGlobal(code), infos: [Error.Key.message: ErrorValue(message)] ) } @discardableResult static func reportRepair(path: String, operation: Error.Operation.RepairOperation, code: Int) -> Error { return Error.report(type: .repair, code: .repair(code), infos: [ Error.Key.path: ErrorValue(path), Error.Key.operation: ErrorValue(operation.rawValue)]) } static func warning(_ message: String) { Error.report(type: .warning, code: .global(.warning), infos: [Error.Key.message: ErrorValue(message)]) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/File.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class File { static func remove(files: [String]) throws { let fileManager = FileManager.default try files.forEach { (file) in do { if fileManager.fileExists(atPath: file) { try FileManager.default.removeItem(atPath: file) } } catch let error as NSError { throw Error.reportSystemCall(operation: .remove, path: file, errno: error.code, message: error.localizedDescription) } } } static func getSize(ofFiles files: [String]) throws -> UInt64 { let fileManager = FileManager.default return try files.reduce(into: 0, { (filesSize, file) in do { filesSize += (try fileManager.attributesOfItem(atPath: file)[.size] as? UInt64) ?? 0 } catch let error as NSError { throw Error.reportSystemCall(operation: .getFileSize, path: file, errno: error.code, message: error.localizedDescription) } }) } static func isExists(atPath path: String) -> Bool { return FileManager.default.fileExists(atPath: path) } static func hardlink(atPath source: String, toPath destination: String) throws { do { try FileManager.default.linkItem(atPath: source, toPath: destination) } catch let error as NSError { throw Error.reportSystemCall(operation: .link, path: destination, errno: error.code, message: error.localizedDescription) } } static func createDirectoryWithIntermediateDirectories(atPath path: String) throws { do { try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) } catch let error as NSError { throw Error.reportSystemCall(operation: .createDirectory, path: path, errno: error.code, message: error.localizedDescription) } } #if WCDB_IOS static func addFirstUserAuthenticationFileProtection(atPath path: String) throws { let fileManager = FileManager.default guard fileManager.fileExists(atPath: path) else { return } var attributes: [FileAttributeKey: Any]! do { attributes = try fileManager.attributesOfItem(atPath: path) } catch let error as NSError { throw Error.reportSystemCall(operation: .getAttributes, path: path, errno: error.code, message: error.localizedDescription) } guard let fileProtection = attributes[.protectionKey] as? String, (fileProtection == FileProtectionType.completeUntilFirstUserAuthentication.rawValue || fileProtection == FileProtectionType.none.rawValue) else { return } attributes[.protectionKey] = FileProtectionType.completeUntilFirstUserAuthentication.rawValue do { try fileManager.setAttributes(attributes, ofItemAtPath: path) } catch let error as NSError { throw Error.reportSystemCall(operation: .setAttributes, path: path, errno: error.code, message: error.localizedDescription) } } #endif //WCDB_IOS } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/Lock.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation protocol Lockable: class { func lock() func unlock() } @available(iOS 10.0, OSX 10.12, watchOS 3.0, tvOS 10.0, *) final class UnfairLock: Lockable { private var unfairLock = os_unfair_lock_s() func lock() { os_unfair_lock_lock(&unfairLock) } func unlock() { os_unfair_lock_unlock(&unfairLock) } } final class Mutex: Lockable { private var mutex = pthread_mutex_t() init() { pthread_mutex_init(&mutex, nil) } deinit { pthread_mutex_destroy(&mutex) } func lock() { pthread_mutex_lock(&mutex) } func unlock() { pthread_mutex_unlock(&mutex) } } final class RecursiveMutex: Lockable { private var mutex = pthread_mutex_t() init() { var attr = pthread_mutexattr_t() pthread_mutexattr_init(&attr) pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) pthread_mutex_init(&mutex, &attr) } deinit { pthread_mutex_destroy(&mutex) } func lock() { pthread_mutex_lock(&mutex) } func unlock() { pthread_mutex_unlock(&mutex) } } final class Spin: Lockable { private let locker: Lockable init() { if #available(iOS 10.0, macOS 10.12, watchOS 3.0, tvOS 10.0, *) { locker = UnfairLock() } else { locker = Mutex() } } func lock() { locker.lock() } func unlock() { locker.unlock() } } final class ConditionLock: Lockable { private var mutex = pthread_mutex_t() private var cond = pthread_cond_t() init() { pthread_mutex_init(&mutex, nil) pthread_cond_init(&cond, nil) } deinit { pthread_cond_destroy(&cond) pthread_mutex_destroy(&mutex) } func lock() { pthread_mutex_lock(&mutex) } func unlock() { pthread_mutex_unlock(&mutex) } func wait() { pthread_cond_wait(&cond, &mutex) } func wait(timeout: TimeInterval) { let integerPart = Int(timeout.nextDown) let fractionalPart = timeout - Double(integerPart) var ts = timespec(tv_sec: integerPart, tv_nsec: Int(fractionalPart * 1000000000)) pthread_cond_timedwait_relative_np(&cond, &mutex, &ts) } func signal() { pthread_cond_signal(&cond) } } extension DispatchQueue { static private let spin = Spin() static private var tracker: Set = [] static func once(name: String, _ block: () -> Void) { spin.lock(); defer { spin.unlock() } guard !tracker.contains(name) else { return } block() tracker.insert(name) } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/Optional.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation public protocol OptionalRepresentable { associatedtype WrappedType static var `nil`: Self {get} } extension Optional: OptionalRepresentable { public static var `nil`: Wrapped? { return nil } public typealias WrappedType = Wrapped } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/RWLock.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class RWLock { var mutex = pthread_mutex_t() var cond = pthread_cond_t() var reader = 0 var writer = 0 var pending = 0 init() { pthread_mutex_init(&mutex, nil) pthread_cond_init(&cond, nil) } deinit { pthread_cond_destroy(&cond) pthread_mutex_destroy(&mutex) } func lockRead() { pthread_mutex_lock(&mutex); defer { pthread_mutex_unlock(&mutex) } while writer>0 || pending>0 { pthread_cond_wait(&cond, &mutex) } reader += 1 } func unlockRead() { pthread_mutex_lock(&mutex); defer { pthread_mutex_unlock(&mutex) } reader -= 1 if reader == 0 { pthread_cond_broadcast(&cond) } } func lockWrite() { pthread_mutex_lock(&mutex); defer { pthread_mutex_unlock(&mutex) } pending += 1 while writer>0||reader>0 { pthread_cond_wait(&cond, &mutex) } pending -= 1 writer += 1 } func unlockWrite() { pthread_mutex_lock(&mutex); defer { pthread_mutex_unlock(&mutex) } writer -= 1 pthread_cond_broadcast(&cond) } var isWriting: Bool { pthread_mutex_lock(&mutex); defer { pthread_mutex_unlock(&mutex) } return writer>0 } // var isReading: Bool { // pthread_mutex_lock(&mutex); defer { pthread_mutex_unlock(&mutex) } // return reader>0 // } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/Recyclable.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation //TODO: Refactor class Recyclable { typealias OnRecycled = () -> Void let onRecycled: OnRecycled? final let raw: Value init(_ raw: Value, onRecycled: @escaping OnRecycled) { self.raw = raw self.onRecycled = onRecycled } init(_ raw: Value) { self.raw = raw self.onRecycled = nil } deinit { if onRecycled != nil { onRecycled!() } } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/SQLite-Bridging.c ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include int sqlite3_bind_text_transient(sqlite3_stmt *a, int b, const char *c, int d) { return sqlite3_bind_text(a, b, c, d, SQLITE_TRANSIENT); } int sqlite3_bind_blob_transient(sqlite3_stmt *a, int b, const void *c, int n) { return sqlite3_bind_blob(a, b, c, n, SQLITE_TRANSIENT); } int sqlite3_config_multithread(void) { return sqlite3_config(SQLITE_CONFIG_MULTITHREAD); } int sqlite3_config_memstatus(int a) { return sqlite3_config(SQLITE_CONFIG_MEMSTATUS, a); } int sqlite3_config_log(sqlite3_global_log a, void *b) { return sqlite3_config(SQLITE_CONFIG_LOG, a, b); } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/SQLite-Bridging.h ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef SQLite_Bridging_h #define SQLite_Bridging_h #import #import struct Tokenizer { sqlite3_tokenizer base; void *info; }; typedef struct Tokenizer Tokenizer; struct Cursor { sqlite3_tokenizer_cursor base; void *info; }; typedef struct Cursor Cursor; typedef struct sqlite3_stmt sqlite3_stmt; int sqlite3_bind_text_transient(sqlite3_stmt *, int, const char *, int); int sqlite3_bind_blob_transient(sqlite3_stmt *, int, const void *, int n); int sqlite3_config_multithread(void); int sqlite3_config_memstatus(int); typedef void (*sqlite3_global_log)(void *, int, const char *); int sqlite3_config_log(sqlite3_global_log, void *); #endif /* SQLite_Bridging_h */ ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/SQLite-Bridging.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation typealias SQLite3 = OpaquePointer typealias SQLite3Statement = OpaquePointer typealias SQLite3Value = OpaquePointer ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/SteadyClock.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation struct SteadyClock { private var time: TimeInterval //monotonic init() { time = ProcessInfo.processInfo.systemUptime } private init(with time: TimeInterval) { self.time = time } static func >= (lhs: SteadyClock, rhs: SteadyClock) -> Bool { return lhs.time >= rhs.time } static func - (lhs: SteadyClock, rhs: SteadyClock) -> TimeInterval { return lhs.time - rhs.time } static func + (steadyClock: SteadyClock, timeInterval: TimeInterval) -> SteadyClock { return SteadyClock(with: steadyClock.time + timeInterval) } static func now() -> SteadyClock { return SteadyClock() } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/Tagged.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class Counter { static let counter = Atomic(0) static func step() -> Int { return ++counter } } final class Tagged: Equatable { let identifier: Int let value: Value init(_ value: Value) { self.value = value self.identifier = Counter.step() } static func == (lhs: Tagged, rhs: Tagged) -> Bool { return lhs.identifier == rhs.identifier } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/ThreadLocal.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class ThreadLocal { private final class Wrapper: RawRepresentable { typealias RawValue = Value var rawValue: RawValue init(rawValue: RawValue) { self.rawValue = rawValue } } private var key = pthread_key_t() private let defaultValue: Value init(defaultTo defaultValue: Value) { self.defaultValue = defaultValue pthread_key_create(&key, { Unmanaged.fromOpaque($0).release() }) } deinit { pthread_key_delete(key) } var value: Value { get { guard let pointer = pthread_getspecific(key) else { return defaultValue } return Unmanaged.fromOpaque(pointer).takeUnretainedValue().rawValue } set { if let pointer = pthread_getspecific(key) { Unmanaged.fromOpaque(pointer).release() } pthread_setspecific(key, Unmanaged.passRetained(Wrapper(rawValue: newValue)).toOpaque()) } } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/TimedQueue.swift ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation final class TimedQueue { typealias Element = (key: Key, clock: SteadyClock) typealias List = ContiguousArray typealias Map = [Key: List.Index] let delay: TimeInterval let conditionLock = ConditionLock() var list: List = [] var map: Map = [:] init(withDelay delay: TimeInterval) { self.delay = delay } func remove(with key: Key) { conditionLock.lock(); defer { conditionLock.unlock() } guard let index = map.index(forKey: key) else { return } list.remove(at: map[index].value) map.remove(at: index) } func reQueue(with key: Key) { conditionLock.lock(); defer { conditionLock.unlock() } let signal = list.isEmpty if let index = map.index(forKey: key) { list.remove(at: map[index].value) map.remove(at: index) } list.append((key, SteadyClock.now()+delay)) map[key] = list.startIndex if signal { conditionLock.signal() } } func wait(untilExpired onExpired: (Key) -> Void) { while true { var key: Key! do { conditionLock.lock(); defer { conditionLock.unlock() } guard let element = list.first else { conditionLock.wait() continue } let now = SteadyClock.now() guard now >= element.clock else { conditionLock.wait(timeout: element.clock - now) continue } key = element.key list.removeFirst() map.removeValue(forKey: key) } onExpired(key) break } } } ================================================ FILE: JetChat/Pods/WCDB.swift/swift/source/util/WCDB-Bridging.h ================================================ /* * Tencent is pleased to support the open source community by making * WCDB available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import ================================================ FILE: JetChat/Pods/WCDB.swift.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 013C49983F0EECA1D8A34DA533D54FDA /* SQLite-Bridging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E08A31B9944C20AF702BFF31487B26 /* SQLite-Bridging.swift */; }; 027FBB30386906CB7D666487E2FF4A66 /* SteadyClock.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF09C4684DAF5C53F651A12665E712EE /* SteadyClock.swift */; }; 03E2C5F1955ECD1EF15D82E5EC9B8FD9 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B686C32813C936FDE11A3DB35EE1223C /* Lock.swift */; }; 057F01C39FB58FF22E03AFFBB607460B /* StatementDelete.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB30B2758A6A3EB1B0440F07A1AAFE53 /* StatementDelete.swift */; }; 09B5305126B6501FA25712E8B70A9B21 /* LiteralValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9957C59BB8278AA543EED9EE7B3F717 /* LiteralValue.swift */; }; 0A44240895C20253A1BEDB25406F0D24 /* ConcurrentList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4100FB1572B4FFF9BC0823334E46D0A /* ConcurrentList.swift */; }; 0A735124CB220FF49D59A2839E279005 /* WCDB-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = 4CE2457E3A785CCC1F1BBAF4D3BFC797 /* WCDB-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0CE6D870E82E92DAC1D268BD09384182 /* StatementUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEA07CFC06457A8A467D0342B82B515 /* StatementUpdate.swift */; }; 108CEB813AC77900FA27318731EAB7E7 /* StatementCreateVirtualTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA94E73A6AA7AFADBF222AD631C2EA3 /* StatementCreateVirtualTable.swift */; }; 1158B3A7F7B13FE1B18B20E798F9C037 /* StatementDetach.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9858E8604AF48747AACB12A457C85A9 /* StatementDetach.swift */; }; 164FC431C2C050C58EE8D3FE13AD3B7C /* CommonStatement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BFDC83A079F013EC393EDEA5311AC1F /* CommonStatement.swift */; }; 1A5AE75CB325FDE0AC2ED71CF101DCD3 /* Delete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E8F2029D1DA6D5161B42F08326743A4 /* Delete.swift */; }; 1A9F71623EE8C0788EACB8EC3BCC0B51 /* ForeignKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D3E48F9A5461DD463EE0C3EEF332E81 /* ForeignKey.swift */; }; 1F99EF46B98800CDB68AF68EA1E262EB /* Tagged.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87EF7DE89F073C5CF0E2E2EF1EDF8403 /* Tagged.swift */; }; 25F87752AFB1F3692DC3352F345F061A /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2022CF10542329DC8936CACA73E04998 /* Property.swift */; }; 27285EA438ACA4C7C3FA010768CC874A /* Conflict.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8885D350E8A42497761E274A992B9B73 /* Conflict.swift */; }; 2742E413003BFBFFE684E5C8AFF9A033 /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA215A4BDE4A8A073E0A73804A039E6D /* Transaction.swift */; }; 27542F22338E7E2D0A2F52B7F98267BF /* HandleStatement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F25A282B00D6F61100BA139521AD44 /* HandleStatement.swift */; }; 2D733EC011ADC52E59FC1B434469A9C4 /* StatementPragma.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAABB3B3ABC33ECEAF478AB029E1D1AA /* StatementPragma.swift */; }; 2FB28D118E2AA9CCD57C014FABFDC43B /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97C5BA46911778FF62662D3A5426C702 /* File.swift */; }; 3081896F89A0CEAF2A6DFA5D27A71D30 /* StatementReindex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FDDEB9BF881C859C978B9701954B7B /* StatementReindex.swift */; }; 3507771F850452E52FD00F0240FB87CC /* TableBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0389C7B7CFECA4C3920EF57E7776FA /* TableBinding.swift */; }; 35AE817FAAD12574525181BD78289CD4 /* WCDBTokenize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B616A9C7C145026160A1AFE77E1A3EC /* WCDBTokenize.swift */; }; 36C85945DDAA28C3540DD5203E864757 /* Column.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B5711A41F0174277AA69BFCDEFE72C2 /* Column.swift */; }; 41057DCF05FEA8754E5BBEC9D03D2C9D /* SQLite-Bridging.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6ED89BF4002C56CBA2CE19D641F86C /* SQLite-Bridging.c */; }; 411540B926330F2B4C0CD1A0B73C7970 /* StatementAlterTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F18E2A59564F802783ED4470309818 /* StatementAlterTable.swift */; }; 4170027AF8D5B066446C5E63991E8CE4 /* WCDB.swift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 852712D12B3C1B1496D1441A3DA7E0D1 /* WCDB.swift-dummy.m */; }; 420C83E705B190C9651D39CC25183A98 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = A072A85577232D3602D67420D247B29A /* Database.swift */; }; 4226345D7B0904E0CDCBCBF8BDBAA85B /* StatementCreateTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F3A6B036B3DC3E9662CB7451A13D9AB /* StatementCreateTable.swift */; }; 44A50DDDF23FF7261487BFBE84FBCC6F /* CodableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F0B9AB8B06849F407E79FAE05BA4B1 /* CodableType.swift */; }; 45E6F826C2A1EB894323A3AB835DDF74 /* Tracer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFA7959A7E09FF6A1C90096FCE5917D7 /* Tracer.swift */; }; 460B13220E63BB29C50B9A81C920E55C /* TableInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2724EBAEEDA40E053BC7D2972D82C75 /* TableInterface.swift */; }; 4BC70847EDB20C944B7C2A351C4C835B /* TimedQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F512DEA5FEBAED06568F9441A7A750 /* TimedQueue.swift */; }; 4C04341C03D6210505D3AAF473E60C2C /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69293118695D0B2F47F645F7DDC212B /* Atomic.swift */; }; 4D8872457636DC7DE18B1EDD8FF1F68D /* StatementTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E79C7E6D850BF720AA67DB4A260627 /* StatementTransaction.swift */; }; 4E35F048A60D684A1AF3A39C65E8627D /* ColumnIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 556DEB63AD1B01D869D33C066F258EDF /* ColumnIndex.swift */; }; 4E45EB2AA7EE071B3AF29273757A5A20 /* Table.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADB932391AEBE2A3A9E779CDC6BC8105 /* Table.swift */; }; 4EE9AD3E9910A15E43E19B4953EB086E /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = A30DD9370A1DCF87A4E790AE0049A6D2 /* Expression.swift */; }; 51D5C837D9DE34211B0C02FC784A16EC /* Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8150A573BDC1D3AC81059AC513F9191 /* Interface.swift */; }; 5253A5A13AD9217F4A6D962982016F24 /* Recyclable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41556C5905EFD8DE76843D36C798E080 /* Recyclable.swift */; }; 541ABFAD60FE4CCF0E04C294742060A0 /* Redirectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6069FE906D31FAAA5A5A51C9268B322E /* Redirectable.swift */; }; 58AFD8D688AF2FAB15C5BC32E1D931B2 /* WCDB.swift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D4CFA836F2E528CC0D78A38E5F733BE /* WCDB.swift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 590F28AADEDE7D779CE69EFF2FE4B5FA /* Operable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0A19A0B8631B858C9A51608BD63E32 /* Operable.swift */; }; 597FA03029BCE471C1A35587575D5739 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A97867C652EC26F33DC49DD65FB7EEF /* Error.swift */; }; 5C9B13F548604463AA2A1CBB33B81AF6 /* Pragma.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C26E33616678A12E1A7C495FBED9A /* Pragma.swift */; }; 5D31B58AA8971222E6E3F4ADA955A3C2 /* ColumnResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40CA9C2304C25B3020B2BF7019736E2A /* ColumnResult.swift */; }; 60E18F9137E555849F436FB0E538EAAC /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BFD9ABD3192FB6F0CFC111E4BEE8691 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; 61982D488EA71F7E4D918CD9FA8DDA6C /* OrderTerm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D22AC97B70A4F6FB69CC0A286A0C8AF /* OrderTerm.swift */; }; 62123155EAC9628D827282EE79E3ADA8 /* ColumnTypeDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AF3E73D5B06E0DC15057AEAA3572EB8 /* ColumnTypeDecoder.swift */; }; 636D6CB65315F5F70587FC6CA12A1859 /* Describable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C69153D11363A2714DF4C7CAB842058 /* Describable.swift */; }; 64F8386A2A23D68CFDA0779FA1187AFB /* Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B1D4127B880F134D921AD7117AA9A4 /* Convenience.swift */; }; 66C797FDA5E6AEF8F5D9166A0D52DDAD /* Order.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FF5DAF62E9CF36950C3CA905AFEC47 /* Order.swift */; }; 73EE699088AAFF42A92738A03ECE1800 /* ThreadLocal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C187450BC6F810CB23539C1FA17CB4C5 /* ThreadLocal.swift */; }; 75948F0B5BECFD01391CE7784BEAADD7 /* TableConstraintBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB0C43C9618877FC8828D1CAD186D17 /* TableConstraintBinding.swift */; }; 7B06E90A86A20EC975331C2185E7163C /* CodingTableKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8B4A394B4DAE41E5094878EB41D450 /* CodingTableKey.swift */; }; 7BFBAC1D28016BB288D7814B65E53FBF /* ColumnConstraintBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AED83AF31225358668C4DEAADE0EF75 /* ColumnConstraintBinding.swift */; }; 7BFEC7A885716EF738132187F3D6E8B4 /* RecyclableCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF55B1E1B831714705F639D14C78EEB /* RecyclableCore.swift */; }; 7D0CAF33CD81F1786DBD53A1780F046F /* VirtualTableBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = C543EC9361C34A1EE9316FE21535A3AA /* VirtualTableBinding.swift */; }; 84863242E71D9D5EBD62788E0E2463F1 /* StatementCreateIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23A8F76EA2BE173F73C4C243450DB7C /* StatementCreateIndex.swift */; }; 8502E94D8CA6CE7A531262889EF319BF /* ColumnDef.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8636DEAD76BFB246F040D3DC6B8E163 /* ColumnDef.swift */; }; 8A53B7A24C3E4361FB14031B0F8A1458 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6DAE773118685440F8C119A1D0F5E73 /* Config.swift */; }; 8ADD29697D81A1508C5A436306F5404A /* StatementDropTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5FA1277FA83A71E00D492B1C940A833 /* StatementDropTable.swift */; }; 8B674C42B8B88408B148FB437BA872A3 /* Handle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850CFA069C42499D5C735AE5C455A6C2 /* Handle.swift */; }; 8C8E09AD9884DC975A09D7ADFF1EDFC5 /* Insert.swift in Sources */ = {isa = PBXBuildFile; fileRef = E61D7A41AF5364A0019326AC4C36B881 /* Insert.swift */; }; 8DD2A2F22AC74D04A0F3310C0B4477CA /* RWLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 250C4BEF407076E8FFF7BE2AA61C4E5A /* RWLock.swift */; }; 935B19477DBCFD792B7DA59BAB5E5987 /* StatementExplain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2136B4CB345E0F5522D6E38BE472E8E /* StatementExplain.swift */; }; 96F2516496AB35A6D00D6469FE37A7E8 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8237AE1F54F9AD9FF023E544033D4B5D /* Optional.swift */; }; 9894E474B1A3C23EDBF953CCF7C09721 /* StatementRollback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D798F17EF5348E08DF758B97F35FC1 /* StatementRollback.swift */; }; 9A1BEFAE555EFECAD64F6E650FB39065 /* JoinClause.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCF72CEBC9D21F967DB7060D7659E0E /* JoinClause.swift */; }; 9A326BB36F84A0970CDD0B04EED7FD90 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8E3F113A1A6BBDC6BDA3640C627C7CE /* Update.swift */; }; A0A39F09D5F414426870C6D45919F9CE /* CoreStatement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C91CD175DA1BB88A2027B09CF48974C /* CoreStatement.swift */; }; A18E768F7DCBA92E7300EBA7786BE7B5 /* Master.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B1549E3CE4C7AF651944E5C6CB02D98 /* Master.swift */; }; AE37BAF478BDC3ECE38CA8E7CBEBBC80 /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73B5C1E80F9096A63A4754C85DA2F25 /* Selectable.swift */; }; B1F017764DE4DE2493F9EFBB133B1F61 /* TableCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D14197B2368AB82106A039E2A064446 /* TableCodable.swift */; }; B641AC664B036BDFA8B4D32FC40C3038 /* StatementInsert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACB56B5CEC68AD9F5E791E9BDCE8DBA /* StatementInsert.swift */; }; B75738C42FF3E2B1ADD4C093DEA4FA36 /* FundamentalValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 818E7628E7138CED02695F2EDE6C209E /* FundamentalValue.swift */; }; B7DAADF119C813387EE275612F979CC9 /* RowSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28E20AA21F95F75B43DE81757EADCB33 /* RowSelect.swift */; }; B975DD5356F7614B19AEBA695F49B60F /* ChainCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBF8021882798D76E0B02F4A5481B4E5 /* ChainCall.swift */; }; B992BD920F062A98DB76CAA57CC65016 /* Select.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6FAF1EE7E0958C5A0D4CF010C31215A /* Select.swift */; }; BB45D150EA5D8F723BE7B917E33CDA40 /* StatementAttach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876E5371414417DDF4BEA602518CFA3A /* StatementAttach.swift */; }; C0B88E4E8E5C37FDCD5FD19FF1D310E2 /* TableDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2ED18B51A4C7DD87CCE7295981261F /* TableDecoder.swift */; }; C0E494FF213635338AEC64E3A2F2902B /* StatementRelease.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F57086D5E003E32ABBFA302D1DD71A /* StatementRelease.swift */; }; C2E0636249C902EAA944FD025C99F6D6 /* TableConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091CDD59C8ED5A90B7B4852772AD98B0 /* TableConstraint.swift */; }; C569E19DB5C136C106A9980145994EC6 /* StatementSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD960FB3ADAA50C308AABB9D8CDECD3 /* StatementSelect.swift */; }; C5D519944ACDC263510487399B391475 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F7325B2767832D11A0011B90C35553B /* Foundation.framework */; }; CABCF14E49BA41994954081BF8ABE15C /* StatementVacuum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0FEBB911E31FCB4314040A892CB75 /* StatementVacuum.swift */; }; CD6E19C7279B89860B5B9C5087DB94A8 /* HandlePool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35632D7059C7C3D63BFDAE3C2950F99B /* HandlePool.swift */; }; D4373ABC40D7F0C67A742963EE8FF6EB /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76099F2136F86EB5DD070AEB13864A2 /* Core.swift */; }; D631F583C2EA5472A16E5F7AB962CF91 /* Subquery.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0973A02C166F02D2B2BB547B0783968 /* Subquery.swift */; }; D9A7050E1F3203213F4E34AEAAAE44E4 /* IndexBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F37419A6C8B2273C4C381E263B15D9A /* IndexBinding.swift */; }; DA582DC413045D8D18A139F7A390F858 /* TableEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65594E69A47D5B4D128588D57936C5E5 /* TableEncoder.swift */; }; E77E7B2339FF313F9E403D38E13AC970 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE14A0D584C0DFDF40A5F5FA4D9097D2 /* Statement.swift */; }; E8147E0DEDC9530280573CCD81E3683A /* MultiSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1515A54E06ADA4740E6FE41C28AF8E0A /* MultiSelect.swift */; }; EB8C24F2A6056F99304A7F19E4F9A6D3 /* Convertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289B4713A044588BE0671E1371F51F4C /* Convertible.swift */; }; EBE779F57B5DB9845B4231091950A55B /* Tokenize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780D868999536DD6588F13D551D87EE5 /* Tokenize.swift */; }; EC3A07095422AFD616641F8B09AB759D /* StatementDropIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B03A3018E6B6D4746F18BF0CCC1CCF2 /* StatementDropIndex.swift */; }; EDF37404F21B1CA75000D2FD87315797 /* StatementSavepoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B2677580CE29ECC98DFE7AE0128AF9D /* StatementSavepoint.swift */; }; FAEC088031C3391ED364D1AC2D5BE42C /* ColumnCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE229B49576F2A4BC0D0D023C1A6CC22 /* ColumnCodable.swift */; }; FCA55725F335187FD21571F95AD07D24 /* ModuleArgument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25CDB6F01644F8DCC4AACD6BA8B3D033 /* ModuleArgument.swift */; }; FDE71B5195873F497F5A43A841A88485 /* Declare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5707A207867E6D67EBA1B96EA4485C /* Declare.swift */; }; FFF528C305E19A5ECE6C9A1BFFED7C09 /* ColumnType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3299F12080A3B1EFA7669D7E3FFE6D5 /* ColumnType.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ B53AE04AAEE7B23EFE228ED0AFD8249C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A3BC5694ADA3E5F70C591744B5EFCC9C /* WCDBOptimizedSQLCipher.xcodeproj */; proxyType = 1; remoteGlobalIDString = 8820E4661B26990965C45655F51ED18B; remoteInfo = WCDBOptimizedSQLCipher; }; F27637A61560590496E5791936F3AD3F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 27D1A72264D8220BAED8CE47CC9BA36F /* SQLiteRepairKit.xcodeproj */; proxyType = 1; remoteGlobalIDString = 1A6A317D19224BA6C654767A5DA5460D; remoteInfo = SQLiteRepairKit; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 08E08A31B9944C20AF702BFF31487B26 /* SQLite-Bridging.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SQLite-Bridging.swift"; path = "swift/source/util/SQLite-Bridging.swift"; sourceTree = ""; }; 091CDD59C8ED5A90B7B4852772AD98B0 /* TableConstraint.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TableConstraint.swift; path = swift/source/abstract/TableConstraint.swift; sourceTree = ""; }; 0A5707A207867E6D67EBA1B96EA4485C /* Declare.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Declare.swift; path = swift/source/core/interface/Declare.swift; sourceTree = ""; }; 0A97867C652EC26F33DC49DD65FB7EEF /* Error.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Error.swift; path = swift/source/util/Error.swift; sourceTree = ""; }; 0ACB56B5CEC68AD9F5E791E9BDCE8DBA /* StatementInsert.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementInsert.swift; path = swift/source/abstract/StatementInsert.swift; sourceTree = ""; }; 0B5711A41F0174277AA69BFCDEFE72C2 /* Column.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Column.swift; path = swift/source/abstract/Column.swift; sourceTree = ""; }; 0BFDC83A079F013EC393EDEA5311AC1F /* CommonStatement.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CommonStatement.swift; path = swift/source/builtin/CommonStatement.swift; sourceTree = ""; }; 0C0389C7B7CFECA4C3920EF57E7776FA /* TableBinding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TableBinding.swift; path = swift/source/core/binding/TableBinding.swift; sourceTree = ""; }; 0D4CFA836F2E528CC0D78A38E5F733BE /* WCDB.swift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "WCDB.swift-umbrella.h"; sourceTree = ""; }; 11F25A282B00D6F61100BA139521AD44 /* HandleStatement.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HandleStatement.swift; path = swift/source/abstract/HandleStatement.swift; sourceTree = ""; }; 1515A54E06ADA4740E6FE41C28AF8E0A /* MultiSelect.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultiSelect.swift; path = swift/source/core/interface/MultiSelect.swift; sourceTree = ""; }; 1F0A19A0B8631B858C9A51608BD63E32 /* Operable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Operable.swift; path = swift/source/abstract/Operable.swift; sourceTree = ""; }; 2022CF10542329DC8936CACA73E04998 /* Property.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Property.swift; path = swift/source/core/binding/Property.swift; sourceTree = ""; }; 250C4BEF407076E8FFF7BE2AA61C4E5A /* RWLock.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RWLock.swift; path = swift/source/util/RWLock.swift; sourceTree = ""; }; 25AB8830D14FEBB1F527B331B408FD4D /* WCDB.swift */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = WCDB.swift; path = WCDBSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 25CDB6F01644F8DCC4AACD6BA8B3D033 /* ModuleArgument.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ModuleArgument.swift; path = swift/source/abstract/ModuleArgument.swift; sourceTree = ""; }; 27D1A72264D8220BAED8CE47CC9BA36F /* SQLiteRepairKit */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SQLiteRepairKit; path = SQLiteRepairKit.xcodeproj; sourceTree = ""; }; 289B4713A044588BE0671E1371F51F4C /* Convertible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Convertible.swift; path = swift/source/abstract/Convertible.swift; sourceTree = ""; }; 28E20AA21F95F75B43DE81757EADCB33 /* RowSelect.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RowSelect.swift; path = swift/source/core/interface/RowSelect.swift; sourceTree = ""; }; 28F18E2A59564F802783ED4470309818 /* StatementAlterTable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementAlterTable.swift; path = swift/source/abstract/StatementAlterTable.swift; sourceTree = ""; }; 2B1549E3CE4C7AF651944E5C6CB02D98 /* Master.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Master.swift; path = swift/source/builtin/Master.swift; sourceTree = ""; }; 2C91CD175DA1BB88A2027B09CF48974C /* CoreStatement.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CoreStatement.swift; path = swift/source/core/base/CoreStatement.swift; sourceTree = ""; }; 2D14197B2368AB82106A039E2A064446 /* TableCodable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TableCodable.swift; path = swift/source/core/codable/TableCodable.swift; sourceTree = ""; }; 2FCF72CEBC9D21F967DB7060D7659E0E /* JoinClause.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JoinClause.swift; path = swift/source/abstract/JoinClause.swift; sourceTree = ""; }; 31FF5DAF62E9CF36950C3CA905AFEC47 /* Order.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Order.swift; path = swift/source/abstract/Order.swift; sourceTree = ""; }; 35632D7059C7C3D63BFDAE3C2950F99B /* HandlePool.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HandlePool.swift; path = swift/source/core/base/HandlePool.swift; sourceTree = ""; }; 35F0B9AB8B06849F407E79FAE05BA4B1 /* CodableType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CodableType.swift; path = swift/source/builtin/CodableType.swift; sourceTree = ""; }; 3E8F2029D1DA6D5161B42F08326743A4 /* Delete.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Delete.swift; path = swift/source/core/interface/Delete.swift; sourceTree = ""; }; 3F37419A6C8B2273C4C381E263B15D9A /* IndexBinding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IndexBinding.swift; path = swift/source/core/binding/IndexBinding.swift; sourceTree = ""; }; 40CA9C2304C25B3020B2BF7019736E2A /* ColumnResult.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColumnResult.swift; path = swift/source/abstract/ColumnResult.swift; sourceTree = ""; }; 41556C5905EFD8DE76843D36C798E080 /* Recyclable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Recyclable.swift; path = swift/source/util/Recyclable.swift; sourceTree = ""; }; 4B6C26E33616678A12E1A7C495FBED9A /* Pragma.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Pragma.swift; path = swift/source/abstract/Pragma.swift; sourceTree = ""; }; 4BA0FEBB911E31FCB4314040A892CB75 /* StatementVacuum.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementVacuum.swift; path = swift/source/abstract/StatementVacuum.swift; sourceTree = ""; }; 4BFD9ABD3192FB6F0CFC111E4BEE8691 /* SQLite-Bridging.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "SQLite-Bridging.h"; path = "swift/source/util/SQLite-Bridging.h"; sourceTree = ""; }; 4CE2457E3A785CCC1F1BBAF4D3BFC797 /* WCDB-Bridging.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "WCDB-Bridging.h"; path = "swift/source/util/WCDB-Bridging.h"; sourceTree = ""; }; 4F8B4A394B4DAE41E5094878EB41D450 /* CodingTableKey.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CodingTableKey.swift; path = swift/source/core/codable/CodingTableKey.swift; sourceTree = ""; }; 556DEB63AD1B01D869D33C066F258EDF /* ColumnIndex.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColumnIndex.swift; path = swift/source/abstract/ColumnIndex.swift; sourceTree = ""; }; 56B1D4127B880F134D921AD7117AA9A4 /* Convenience.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Convenience.swift; path = swift/source/util/Convenience.swift; sourceTree = ""; }; 5F3A6B036B3DC3E9662CB7451A13D9AB /* StatementCreateTable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementCreateTable.swift; path = swift/source/abstract/StatementCreateTable.swift; sourceTree = ""; }; 6069FE906D31FAAA5A5A51C9268B322E /* Redirectable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Redirectable.swift; path = swift/source/core/binding/Redirectable.swift; sourceTree = ""; }; 61F512DEA5FEBAED06568F9441A7A750 /* TimedQueue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimedQueue.swift; path = swift/source/util/TimedQueue.swift; sourceTree = ""; }; 62D798F17EF5348E08DF758B97F35FC1 /* StatementRollback.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementRollback.swift; path = swift/source/abstract/StatementRollback.swift; sourceTree = ""; }; 65594E69A47D5B4D128588D57936C5E5 /* TableEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TableEncoder.swift; path = swift/source/core/codable/TableEncoder.swift; sourceTree = ""; }; 6AF3E73D5B06E0DC15057AEAA3572EB8 /* ColumnTypeDecoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColumnTypeDecoder.swift; path = swift/source/core/codable/ColumnTypeDecoder.swift; sourceTree = ""; }; 6BB0C43C9618877FC8828D1CAD186D17 /* TableConstraintBinding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TableConstraintBinding.swift; path = swift/source/core/binding/TableConstraintBinding.swift; sourceTree = ""; }; 6C69153D11363A2714DF4C7CAB842058 /* Describable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Describable.swift; path = swift/source/abstract/Describable.swift; sourceTree = ""; }; 6D3E48F9A5461DD463EE0C3EEF332E81 /* ForeignKey.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ForeignKey.swift; path = swift/source/abstract/ForeignKey.swift; sourceTree = ""; }; 6D794FD8E0619A9A29319C0E5C081F2D /* WCDB.swift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "WCDB.swift-prefix.pch"; sourceTree = ""; }; 72FDDEB9BF881C859C978B9701954B7B /* StatementReindex.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementReindex.swift; path = swift/source/abstract/StatementReindex.swift; sourceTree = ""; }; 74F57086D5E003E32ABBFA302D1DD71A /* StatementRelease.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementRelease.swift; path = swift/source/abstract/StatementRelease.swift; sourceTree = ""; }; 780D868999536DD6588F13D551D87EE5 /* Tokenize.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Tokenize.swift; path = swift/source/abstract/Tokenize.swift; sourceTree = ""; }; 7AED83AF31225358668C4DEAADE0EF75 /* ColumnConstraintBinding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColumnConstraintBinding.swift; path = swift/source/core/binding/ColumnConstraintBinding.swift; sourceTree = ""; }; 7B03A3018E6B6D4746F18BF0CCC1CCF2 /* StatementDropIndex.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementDropIndex.swift; path = swift/source/abstract/StatementDropIndex.swift; sourceTree = ""; }; 7F7325B2767832D11A0011B90C35553B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 7F76470DDEFC667342A99BB50D821A2C /* WCDB.swift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = WCDB.swift.debug.xcconfig; sourceTree = ""; }; 818E7628E7138CED02695F2EDE6C209E /* FundamentalValue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FundamentalValue.swift; path = swift/source/abstract/FundamentalValue.swift; sourceTree = ""; }; 8237AE1F54F9AD9FF023E544033D4B5D /* Optional.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Optional.swift; path = swift/source/util/Optional.swift; sourceTree = ""; }; 850CFA069C42499D5C735AE5C455A6C2 /* Handle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Handle.swift; path = swift/source/abstract/Handle.swift; sourceTree = ""; }; 852712D12B3C1B1496D1441A3DA7E0D1 /* WCDB.swift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "WCDB.swift-dummy.m"; sourceTree = ""; }; 876E5371414417DDF4BEA602518CFA3A /* StatementAttach.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementAttach.swift; path = swift/source/abstract/StatementAttach.swift; sourceTree = ""; }; 87EF7DE89F073C5CF0E2E2EF1EDF8403 /* Tagged.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Tagged.swift; path = swift/source/util/Tagged.swift; sourceTree = ""; }; 8885D350E8A42497761E274A992B9B73 /* Conflict.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Conflict.swift; path = swift/source/abstract/Conflict.swift; sourceTree = ""; }; 8B2677580CE29ECC98DFE7AE0128AF9D /* StatementSavepoint.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementSavepoint.swift; path = swift/source/abstract/StatementSavepoint.swift; sourceTree = ""; }; 8B616A9C7C145026160A1AFE77E1A3EC /* WCDBTokenize.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = WCDBTokenize.swift; path = swift/source/core/fts/WCDBTokenize.swift; sourceTree = ""; }; 8BEA07CFC06457A8A467D0342B82B515 /* StatementUpdate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementUpdate.swift; path = swift/source/abstract/StatementUpdate.swift; sourceTree = ""; }; 8D22AC97B70A4F6FB69CC0A286A0C8AF /* OrderTerm.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = OrderTerm.swift; path = swift/source/abstract/OrderTerm.swift; sourceTree = ""; }; 927502B7DA546FD969269DE71CE79192 /* WCDB.swift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = WCDB.swift.release.xcconfig; sourceTree = ""; }; 9308F505A163D227DD4D7E7BA7577B2F /* WCDB.swift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "WCDB.swift-Info.plist"; sourceTree = ""; }; 97C5BA46911778FF62662D3A5426C702 /* File.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = File.swift; path = swift/source/util/File.swift; sourceTree = ""; }; 98E79C7E6D850BF720AA67DB4A260627 /* StatementTransaction.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementTransaction.swift; path = swift/source/abstract/StatementTransaction.swift; sourceTree = ""; }; 9BA94E73A6AA7AFADBF222AD631C2EA3 /* StatementCreateVirtualTable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementCreateVirtualTable.swift; path = swift/source/abstract/StatementCreateVirtualTable.swift; sourceTree = ""; }; 9BD960FB3ADAA50C308AABB9D8CDECD3 /* StatementSelect.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementSelect.swift; path = swift/source/abstract/StatementSelect.swift; sourceTree = ""; }; 9C2ED18B51A4C7DD87CCE7295981261F /* TableDecoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TableDecoder.swift; path = swift/source/core/codable/TableDecoder.swift; sourceTree = ""; }; A072A85577232D3602D67420D247B29A /* Database.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Database.swift; path = swift/source/core/base/Database.swift; sourceTree = ""; }; A30DD9370A1DCF87A4E790AE0049A6D2 /* Expression.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Expression.swift; path = swift/source/abstract/Expression.swift; sourceTree = ""; }; A3BC5694ADA3E5F70C591744B5EFCC9C /* WCDBOptimizedSQLCipher */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = WCDBOptimizedSQLCipher; path = WCDBOptimizedSQLCipher.xcodeproj; sourceTree = ""; }; AA215A4BDE4A8A073E0A73804A039E6D /* Transaction.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Transaction.swift; path = swift/source/core/base/Transaction.swift; sourceTree = ""; }; ADB932391AEBE2A3A9E779CDC6BC8105 /* Table.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Table.swift; path = swift/source/core/interface/Table.swift; sourceTree = ""; }; B0973A02C166F02D2B2BB547B0783968 /* Subquery.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Subquery.swift; path = swift/source/abstract/Subquery.swift; sourceTree = ""; }; B2136B4CB345E0F5522D6E38BE472E8E /* StatementExplain.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementExplain.swift; path = swift/source/abstract/StatementExplain.swift; sourceTree = ""; }; B686C32813C936FDE11A3DB35EE1223C /* Lock.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Lock.swift; path = swift/source/util/Lock.swift; sourceTree = ""; }; B8E3F113A1A6BBDC6BDA3640C627C7CE /* Update.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Update.swift; path = swift/source/core/interface/Update.swift; sourceTree = ""; }; B9858E8604AF48747AACB12A457C85A9 /* StatementDetach.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementDetach.swift; path = swift/source/abstract/StatementDetach.swift; sourceTree = ""; }; BF6ED89BF4002C56CBA2CE19D641F86C /* SQLite-Bridging.c */ = {isa = PBXFileReference; includeInIndex = 1; name = "SQLite-Bridging.c"; path = "swift/source/util/SQLite-Bridging.c"; sourceTree = ""; }; BFF55B1E1B831714705F639D14C78EEB /* RecyclableCore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RecyclableCore.swift; path = swift/source/core/base/RecyclableCore.swift; sourceTree = ""; }; C187450BC6F810CB23539C1FA17CB4C5 /* ThreadLocal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ThreadLocal.swift; path = swift/source/util/ThreadLocal.swift; sourceTree = ""; }; C23A8F76EA2BE173F73C4C243450DB7C /* StatementCreateIndex.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementCreateIndex.swift; path = swift/source/abstract/StatementCreateIndex.swift; sourceTree = ""; }; C3299F12080A3B1EFA7669D7E3FFE6D5 /* ColumnType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColumnType.swift; path = swift/source/abstract/ColumnType.swift; sourceTree = ""; }; C543EC9361C34A1EE9316FE21535A3AA /* VirtualTableBinding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = VirtualTableBinding.swift; path = swift/source/core/binding/VirtualTableBinding.swift; sourceTree = ""; }; C69293118695D0B2F47F645F7DDC212B /* Atomic.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Atomic.swift; path = swift/source/util/Atomic.swift; sourceTree = ""; }; C9957C59BB8278AA543EED9EE7B3F717 /* LiteralValue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LiteralValue.swift; path = swift/source/abstract/LiteralValue.swift; sourceTree = ""; }; D8636DEAD76BFB246F040D3DC6B8E163 /* ColumnDef.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColumnDef.swift; path = swift/source/abstract/ColumnDef.swift; sourceTree = ""; }; E4100FB1572B4FFF9BC0823334E46D0A /* ConcurrentList.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConcurrentList.swift; path = swift/source/util/ConcurrentList.swift; sourceTree = ""; }; E61D7A41AF5364A0019326AC4C36B881 /* Insert.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Insert.swift; path = swift/source/core/interface/Insert.swift; sourceTree = ""; }; E8150A573BDC1D3AC81059AC513F9191 /* Interface.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Interface.swift; path = swift/source/core/interface/Interface.swift; sourceTree = ""; }; EAABB3B3ABC33ECEAF478AB029E1D1AA /* StatementPragma.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementPragma.swift; path = swift/source/abstract/StatementPragma.swift; sourceTree = ""; }; EB02F4F74A4565C0F92FB153D2E560EA /* WCDB.swift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = WCDB.swift.modulemap; sourceTree = ""; }; EBF8021882798D76E0B02F4A5481B4E5 /* ChainCall.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ChainCall.swift; path = swift/source/core/interface/ChainCall.swift; sourceTree = ""; }; EF09C4684DAF5C53F651A12665E712EE /* SteadyClock.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SteadyClock.swift; path = swift/source/util/SteadyClock.swift; sourceTree = ""; }; EFA7959A7E09FF6A1C90096FCE5917D7 /* Tracer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Tracer.swift; path = swift/source/abstract/Tracer.swift; sourceTree = ""; }; F2724EBAEEDA40E053BC7D2972D82C75 /* TableInterface.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TableInterface.swift; path = swift/source/core/interface/TableInterface.swift; sourceTree = ""; }; F5FA1277FA83A71E00D492B1C940A833 /* StatementDropTable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementDropTable.swift; path = swift/source/abstract/StatementDropTable.swift; sourceTree = ""; }; F6DAE773118685440F8C119A1D0F5E73 /* Config.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Config.swift; path = swift/source/core/base/Config.swift; sourceTree = ""; }; F6FAF1EE7E0958C5A0D4CF010C31215A /* Select.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Select.swift; path = swift/source/core/interface/Select.swift; sourceTree = ""; }; F73B5C1E80F9096A63A4754C85DA2F25 /* Selectable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Selectable.swift; path = swift/source/core/interface/Selectable.swift; sourceTree = ""; }; F76099F2136F86EB5DD070AEB13864A2 /* Core.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Core.swift; path = swift/source/core/base/Core.swift; sourceTree = ""; }; FB30B2758A6A3EB1B0440F07A1AAFE53 /* StatementDelete.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StatementDelete.swift; path = swift/source/abstract/StatementDelete.swift; sourceTree = ""; }; FE14A0D584C0DFDF40A5F5FA4D9097D2 /* Statement.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Statement.swift; path = swift/source/abstract/Statement.swift; sourceTree = ""; }; FE229B49576F2A4BC0D0D023C1A6CC22 /* ColumnCodable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColumnCodable.swift; path = swift/source/core/codable/ColumnCodable.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 6FF27EFBD6A895986DFB940400F9F7EF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C5D519944ACDC263510487399B391475 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 1BAD409412479B7BF8677027E209C837 /* Dependencies */ = { isa = PBXGroup; children = ( 27D1A72264D8220BAED8CE47CC9BA36F /* SQLiteRepairKit */, A3BC5694ADA3E5F70C591744B5EFCC9C /* WCDBOptimizedSQLCipher */, ); name = Dependencies; sourceTree = ""; }; 350D81E6D2082728231D701E9FC01667 = { isa = PBXGroup; children = ( 1BAD409412479B7BF8677027E209C837 /* Dependencies */, 4DFED91E7A67EAC77DDCFE364BE72B3C /* Frameworks */, 82A12F9016C64B4B732571E309CA7DB2 /* Products */, D7978CBB9612827C3929E5734BD85C7D /* WCDB.swift */, ); sourceTree = ""; }; 4DFED91E7A67EAC77DDCFE364BE72B3C /* Frameworks */ = { isa = PBXGroup; children = ( 69246AF91BAF43213DBF8AE72F9DD7F2 /* iOS */, ); name = Frameworks; sourceTree = ""; }; 66D35A177A3E35081E3B2CDFF9178B60 /* Support Files */ = { isa = PBXGroup; children = ( EB02F4F74A4565C0F92FB153D2E560EA /* WCDB.swift.modulemap */, 852712D12B3C1B1496D1441A3DA7E0D1 /* WCDB.swift-dummy.m */, 9308F505A163D227DD4D7E7BA7577B2F /* WCDB.swift-Info.plist */, 6D794FD8E0619A9A29319C0E5C081F2D /* WCDB.swift-prefix.pch */, 0D4CFA836F2E528CC0D78A38E5F733BE /* WCDB.swift-umbrella.h */, 7F76470DDEFC667342A99BB50D821A2C /* WCDB.swift.debug.xcconfig */, 927502B7DA546FD969269DE71CE79192 /* WCDB.swift.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/WCDB.swift"; sourceTree = ""; }; 69246AF91BAF43213DBF8AE72F9DD7F2 /* iOS */ = { isa = PBXGroup; children = ( 7F7325B2767832D11A0011B90C35553B /* Foundation.framework */, ); name = iOS; sourceTree = ""; }; 82A12F9016C64B4B732571E309CA7DB2 /* Products */ = { isa = PBXGroup; children = ( 25AB8830D14FEBB1F527B331B408FD4D /* WCDB.swift */, ); name = Products; sourceTree = ""; }; D7978CBB9612827C3929E5734BD85C7D /* WCDB.swift */ = { isa = PBXGroup; children = ( C69293118695D0B2F47F645F7DDC212B /* Atomic.swift */, EBF8021882798D76E0B02F4A5481B4E5 /* ChainCall.swift */, 35F0B9AB8B06849F407E79FAE05BA4B1 /* CodableType.swift */, 4F8B4A394B4DAE41E5094878EB41D450 /* CodingTableKey.swift */, 0B5711A41F0174277AA69BFCDEFE72C2 /* Column.swift */, FE229B49576F2A4BC0D0D023C1A6CC22 /* ColumnCodable.swift */, 7AED83AF31225358668C4DEAADE0EF75 /* ColumnConstraintBinding.swift */, D8636DEAD76BFB246F040D3DC6B8E163 /* ColumnDef.swift */, 556DEB63AD1B01D869D33C066F258EDF /* ColumnIndex.swift */, 40CA9C2304C25B3020B2BF7019736E2A /* ColumnResult.swift */, C3299F12080A3B1EFA7669D7E3FFE6D5 /* ColumnType.swift */, 6AF3E73D5B06E0DC15057AEAA3572EB8 /* ColumnTypeDecoder.swift */, 0BFDC83A079F013EC393EDEA5311AC1F /* CommonStatement.swift */, E4100FB1572B4FFF9BC0823334E46D0A /* ConcurrentList.swift */, F6DAE773118685440F8C119A1D0F5E73 /* Config.swift */, 8885D350E8A42497761E274A992B9B73 /* Conflict.swift */, 56B1D4127B880F134D921AD7117AA9A4 /* Convenience.swift */, 289B4713A044588BE0671E1371F51F4C /* Convertible.swift */, F76099F2136F86EB5DD070AEB13864A2 /* Core.swift */, 2C91CD175DA1BB88A2027B09CF48974C /* CoreStatement.swift */, A072A85577232D3602D67420D247B29A /* Database.swift */, 0A5707A207867E6D67EBA1B96EA4485C /* Declare.swift */, 3E8F2029D1DA6D5161B42F08326743A4 /* Delete.swift */, 6C69153D11363A2714DF4C7CAB842058 /* Describable.swift */, 0A97867C652EC26F33DC49DD65FB7EEF /* Error.swift */, A30DD9370A1DCF87A4E790AE0049A6D2 /* Expression.swift */, 97C5BA46911778FF62662D3A5426C702 /* File.swift */, 6D3E48F9A5461DD463EE0C3EEF332E81 /* ForeignKey.swift */, 818E7628E7138CED02695F2EDE6C209E /* FundamentalValue.swift */, 850CFA069C42499D5C735AE5C455A6C2 /* Handle.swift */, 35632D7059C7C3D63BFDAE3C2950F99B /* HandlePool.swift */, 11F25A282B00D6F61100BA139521AD44 /* HandleStatement.swift */, 3F37419A6C8B2273C4C381E263B15D9A /* IndexBinding.swift */, E61D7A41AF5364A0019326AC4C36B881 /* Insert.swift */, E8150A573BDC1D3AC81059AC513F9191 /* Interface.swift */, 2FCF72CEBC9D21F967DB7060D7659E0E /* JoinClause.swift */, C9957C59BB8278AA543EED9EE7B3F717 /* LiteralValue.swift */, B686C32813C936FDE11A3DB35EE1223C /* Lock.swift */, 2B1549E3CE4C7AF651944E5C6CB02D98 /* Master.swift */, 25CDB6F01644F8DCC4AACD6BA8B3D033 /* ModuleArgument.swift */, 1515A54E06ADA4740E6FE41C28AF8E0A /* MultiSelect.swift */, 1F0A19A0B8631B858C9A51608BD63E32 /* Operable.swift */, 8237AE1F54F9AD9FF023E544033D4B5D /* Optional.swift */, 31FF5DAF62E9CF36950C3CA905AFEC47 /* Order.swift */, 8D22AC97B70A4F6FB69CC0A286A0C8AF /* OrderTerm.swift */, 4B6C26E33616678A12E1A7C495FBED9A /* Pragma.swift */, 2022CF10542329DC8936CACA73E04998 /* Property.swift */, 41556C5905EFD8DE76843D36C798E080 /* Recyclable.swift */, BFF55B1E1B831714705F639D14C78EEB /* RecyclableCore.swift */, 6069FE906D31FAAA5A5A51C9268B322E /* Redirectable.swift */, 28E20AA21F95F75B43DE81757EADCB33 /* RowSelect.swift */, 250C4BEF407076E8FFF7BE2AA61C4E5A /* RWLock.swift */, F6FAF1EE7E0958C5A0D4CF010C31215A /* Select.swift */, F73B5C1E80F9096A63A4754C85DA2F25 /* Selectable.swift */, BF6ED89BF4002C56CBA2CE19D641F86C /* SQLite-Bridging.c */, 4BFD9ABD3192FB6F0CFC111E4BEE8691 /* SQLite-Bridging.h */, 08E08A31B9944C20AF702BFF31487B26 /* SQLite-Bridging.swift */, FE14A0D584C0DFDF40A5F5FA4D9097D2 /* Statement.swift */, 28F18E2A59564F802783ED4470309818 /* StatementAlterTable.swift */, 876E5371414417DDF4BEA602518CFA3A /* StatementAttach.swift */, C23A8F76EA2BE173F73C4C243450DB7C /* StatementCreateIndex.swift */, 5F3A6B036B3DC3E9662CB7451A13D9AB /* StatementCreateTable.swift */, 9BA94E73A6AA7AFADBF222AD631C2EA3 /* StatementCreateVirtualTable.swift */, FB30B2758A6A3EB1B0440F07A1AAFE53 /* StatementDelete.swift */, B9858E8604AF48747AACB12A457C85A9 /* StatementDetach.swift */, 7B03A3018E6B6D4746F18BF0CCC1CCF2 /* StatementDropIndex.swift */, F5FA1277FA83A71E00D492B1C940A833 /* StatementDropTable.swift */, B2136B4CB345E0F5522D6E38BE472E8E /* StatementExplain.swift */, 0ACB56B5CEC68AD9F5E791E9BDCE8DBA /* StatementInsert.swift */, EAABB3B3ABC33ECEAF478AB029E1D1AA /* StatementPragma.swift */, 72FDDEB9BF881C859C978B9701954B7B /* StatementReindex.swift */, 74F57086D5E003E32ABBFA302D1DD71A /* StatementRelease.swift */, 62D798F17EF5348E08DF758B97F35FC1 /* StatementRollback.swift */, 8B2677580CE29ECC98DFE7AE0128AF9D /* StatementSavepoint.swift */, 9BD960FB3ADAA50C308AABB9D8CDECD3 /* StatementSelect.swift */, 98E79C7E6D850BF720AA67DB4A260627 /* StatementTransaction.swift */, 8BEA07CFC06457A8A467D0342B82B515 /* StatementUpdate.swift */, 4BA0FEBB911E31FCB4314040A892CB75 /* StatementVacuum.swift */, EF09C4684DAF5C53F651A12665E712EE /* SteadyClock.swift */, B0973A02C166F02D2B2BB547B0783968 /* Subquery.swift */, ADB932391AEBE2A3A9E779CDC6BC8105 /* Table.swift */, 0C0389C7B7CFECA4C3920EF57E7776FA /* TableBinding.swift */, 2D14197B2368AB82106A039E2A064446 /* TableCodable.swift */, 091CDD59C8ED5A90B7B4852772AD98B0 /* TableConstraint.swift */, 6BB0C43C9618877FC8828D1CAD186D17 /* TableConstraintBinding.swift */, 9C2ED18B51A4C7DD87CCE7295981261F /* TableDecoder.swift */, 65594E69A47D5B4D128588D57936C5E5 /* TableEncoder.swift */, F2724EBAEEDA40E053BC7D2972D82C75 /* TableInterface.swift */, 87EF7DE89F073C5CF0E2E2EF1EDF8403 /* Tagged.swift */, C187450BC6F810CB23539C1FA17CB4C5 /* ThreadLocal.swift */, 61F512DEA5FEBAED06568F9441A7A750 /* TimedQueue.swift */, 780D868999536DD6588F13D551D87EE5 /* Tokenize.swift */, EFA7959A7E09FF6A1C90096FCE5917D7 /* Tracer.swift */, AA215A4BDE4A8A073E0A73804A039E6D /* Transaction.swift */, B8E3F113A1A6BBDC6BDA3640C627C7CE /* Update.swift */, C543EC9361C34A1EE9316FE21535A3AA /* VirtualTableBinding.swift */, 4CE2457E3A785CCC1F1BBAF4D3BFC797 /* WCDB-Bridging.h */, 8B616A9C7C145026160A1AFE77E1A3EC /* WCDBTokenize.swift */, 66D35A177A3E35081E3B2CDFF9178B60 /* Support Files */, ); name = WCDB.swift; path = WCDB.swift; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 3E778AAF43692530078774B231566A67 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 60E18F9137E555849F436FB0E538EAAC /* SQLite-Bridging.h in Headers */, 0A735124CB220FF49D59A2839E279005 /* WCDB-Bridging.h in Headers */, 58AFD8D688AF2FAB15C5BC32E1D931B2 /* WCDB.swift-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 4EAAAD4A9736C05D0B4707614E368B84 /* WCDB.swift */ = { isa = PBXNativeTarget; buildConfigurationList = 9B4A5017CADBE63A13DE5E2EE1227B13 /* Build configuration list for PBXNativeTarget "WCDB.swift" */; buildPhases = ( 3E778AAF43692530078774B231566A67 /* Headers */, 570AFC623D0FC42F102168AF4E718AA2 /* Sources */, 6FF27EFBD6A895986DFB940400F9F7EF /* Frameworks */, C30CB7672BFD79662B0E8ECAC97D4795 /* Resources */, ); buildRules = ( ); dependencies = ( F884DD8FAC8AF6846C56A11D92EFBCAD /* PBXTargetDependency */, 85159E469D47CE874338B22DAF9B1816 /* PBXTargetDependency */, ); name = WCDB.swift; productName = WCDBSwift; productReference = 25AB8830D14FEBB1F527B331B408FD4D /* WCDB.swift */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F986FBAAA2DD46A809EA6A4C46AFACEA /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; }; buildConfigurationList = F2635CC716BC28B2A821061573912CA4 /* Build configuration list for PBXProject "WCDB.swift" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( Base, en, ); mainGroup = 350D81E6D2082728231D701E9FC01667; productRefGroup = 82A12F9016C64B4B732571E309CA7DB2 /* Products */; projectDirPath = ""; projectReferences = ( { ProjectRef = A3BC5694ADA3E5F70C591744B5EFCC9C /* WCDBOptimizedSQLCipher */; }, { ProjectRef = 27D1A72264D8220BAED8CE47CC9BA36F /* SQLiteRepairKit */; }, ); projectRoot = ""; targets = ( 4EAAAD4A9736C05D0B4707614E368B84 /* WCDB.swift */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ C30CB7672BFD79662B0E8ECAC97D4795 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 570AFC623D0FC42F102168AF4E718AA2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 4C04341C03D6210505D3AAF473E60C2C /* Atomic.swift in Sources */, B975DD5356F7614B19AEBA695F49B60F /* ChainCall.swift in Sources */, 44A50DDDF23FF7261487BFBE84FBCC6F /* CodableType.swift in Sources */, 7B06E90A86A20EC975331C2185E7163C /* CodingTableKey.swift in Sources */, 36C85945DDAA28C3540DD5203E864757 /* Column.swift in Sources */, FAEC088031C3391ED364D1AC2D5BE42C /* ColumnCodable.swift in Sources */, 7BFBAC1D28016BB288D7814B65E53FBF /* ColumnConstraintBinding.swift in Sources */, 8502E94D8CA6CE7A531262889EF319BF /* ColumnDef.swift in Sources */, 4E35F048A60D684A1AF3A39C65E8627D /* ColumnIndex.swift in Sources */, 5D31B58AA8971222E6E3F4ADA955A3C2 /* ColumnResult.swift in Sources */, FFF528C305E19A5ECE6C9A1BFFED7C09 /* ColumnType.swift in Sources */, 62123155EAC9628D827282EE79E3ADA8 /* ColumnTypeDecoder.swift in Sources */, 164FC431C2C050C58EE8D3FE13AD3B7C /* CommonStatement.swift in Sources */, 0A44240895C20253A1BEDB25406F0D24 /* ConcurrentList.swift in Sources */, 8A53B7A24C3E4361FB14031B0F8A1458 /* Config.swift in Sources */, 27285EA438ACA4C7C3FA010768CC874A /* Conflict.swift in Sources */, 64F8386A2A23D68CFDA0779FA1187AFB /* Convenience.swift in Sources */, EB8C24F2A6056F99304A7F19E4F9A6D3 /* Convertible.swift in Sources */, D4373ABC40D7F0C67A742963EE8FF6EB /* Core.swift in Sources */, A0A39F09D5F414426870C6D45919F9CE /* CoreStatement.swift in Sources */, 420C83E705B190C9651D39CC25183A98 /* Database.swift in Sources */, FDE71B5195873F497F5A43A841A88485 /* Declare.swift in Sources */, 1A5AE75CB325FDE0AC2ED71CF101DCD3 /* Delete.swift in Sources */, 636D6CB65315F5F70587FC6CA12A1859 /* Describable.swift in Sources */, 597FA03029BCE471C1A35587575D5739 /* Error.swift in Sources */, 4EE9AD3E9910A15E43E19B4953EB086E /* Expression.swift in Sources */, 2FB28D118E2AA9CCD57C014FABFDC43B /* File.swift in Sources */, 1A9F71623EE8C0788EACB8EC3BCC0B51 /* ForeignKey.swift in Sources */, B75738C42FF3E2B1ADD4C093DEA4FA36 /* FundamentalValue.swift in Sources */, 8B674C42B8B88408B148FB437BA872A3 /* Handle.swift in Sources */, CD6E19C7279B89860B5B9C5087DB94A8 /* HandlePool.swift in Sources */, 27542F22338E7E2D0A2F52B7F98267BF /* HandleStatement.swift in Sources */, D9A7050E1F3203213F4E34AEAAAE44E4 /* IndexBinding.swift in Sources */, 8C8E09AD9884DC975A09D7ADFF1EDFC5 /* Insert.swift in Sources */, 51D5C837D9DE34211B0C02FC784A16EC /* Interface.swift in Sources */, 9A1BEFAE555EFECAD64F6E650FB39065 /* JoinClause.swift in Sources */, 09B5305126B6501FA25712E8B70A9B21 /* LiteralValue.swift in Sources */, 03E2C5F1955ECD1EF15D82E5EC9B8FD9 /* Lock.swift in Sources */, A18E768F7DCBA92E7300EBA7786BE7B5 /* Master.swift in Sources */, FCA55725F335187FD21571F95AD07D24 /* ModuleArgument.swift in Sources */, E8147E0DEDC9530280573CCD81E3683A /* MultiSelect.swift in Sources */, 590F28AADEDE7D779CE69EFF2FE4B5FA /* Operable.swift in Sources */, 96F2516496AB35A6D00D6469FE37A7E8 /* Optional.swift in Sources */, 66C797FDA5E6AEF8F5D9166A0D52DDAD /* Order.swift in Sources */, 61982D488EA71F7E4D918CD9FA8DDA6C /* OrderTerm.swift in Sources */, 5C9B13F548604463AA2A1CBB33B81AF6 /* Pragma.swift in Sources */, 25F87752AFB1F3692DC3352F345F061A /* Property.swift in Sources */, 5253A5A13AD9217F4A6D962982016F24 /* Recyclable.swift in Sources */, 7BFEC7A885716EF738132187F3D6E8B4 /* RecyclableCore.swift in Sources */, 541ABFAD60FE4CCF0E04C294742060A0 /* Redirectable.swift in Sources */, B7DAADF119C813387EE275612F979CC9 /* RowSelect.swift in Sources */, 8DD2A2F22AC74D04A0F3310C0B4477CA /* RWLock.swift in Sources */, B992BD920F062A98DB76CAA57CC65016 /* Select.swift in Sources */, AE37BAF478BDC3ECE38CA8E7CBEBBC80 /* Selectable.swift in Sources */, 41057DCF05FEA8754E5BBEC9D03D2C9D /* SQLite-Bridging.c in Sources */, 013C49983F0EECA1D8A34DA533D54FDA /* SQLite-Bridging.swift in Sources */, E77E7B2339FF313F9E403D38E13AC970 /* Statement.swift in Sources */, 411540B926330F2B4C0CD1A0B73C7970 /* StatementAlterTable.swift in Sources */, BB45D150EA5D8F723BE7B917E33CDA40 /* StatementAttach.swift in Sources */, 84863242E71D9D5EBD62788E0E2463F1 /* StatementCreateIndex.swift in Sources */, 4226345D7B0904E0CDCBCBF8BDBAA85B /* StatementCreateTable.swift in Sources */, 108CEB813AC77900FA27318731EAB7E7 /* StatementCreateVirtualTable.swift in Sources */, 057F01C39FB58FF22E03AFFBB607460B /* StatementDelete.swift in Sources */, 1158B3A7F7B13FE1B18B20E798F9C037 /* StatementDetach.swift in Sources */, EC3A07095422AFD616641F8B09AB759D /* StatementDropIndex.swift in Sources */, 8ADD29697D81A1508C5A436306F5404A /* StatementDropTable.swift in Sources */, 935B19477DBCFD792B7DA59BAB5E5987 /* StatementExplain.swift in Sources */, B641AC664B036BDFA8B4D32FC40C3038 /* StatementInsert.swift in Sources */, 2D733EC011ADC52E59FC1B434469A9C4 /* StatementPragma.swift in Sources */, 3081896F89A0CEAF2A6DFA5D27A71D30 /* StatementReindex.swift in Sources */, C0E494FF213635338AEC64E3A2F2902B /* StatementRelease.swift in Sources */, 9894E474B1A3C23EDBF953CCF7C09721 /* StatementRollback.swift in Sources */, EDF37404F21B1CA75000D2FD87315797 /* StatementSavepoint.swift in Sources */, C569E19DB5C136C106A9980145994EC6 /* StatementSelect.swift in Sources */, 4D8872457636DC7DE18B1EDD8FF1F68D /* StatementTransaction.swift in Sources */, 0CE6D870E82E92DAC1D268BD09384182 /* StatementUpdate.swift in Sources */, CABCF14E49BA41994954081BF8ABE15C /* StatementVacuum.swift in Sources */, 027FBB30386906CB7D666487E2FF4A66 /* SteadyClock.swift in Sources */, D631F583C2EA5472A16E5F7AB962CF91 /* Subquery.swift in Sources */, 4E45EB2AA7EE071B3AF29273757A5A20 /* Table.swift in Sources */, 3507771F850452E52FD00F0240FB87CC /* TableBinding.swift in Sources */, B1F017764DE4DE2493F9EFBB133B1F61 /* TableCodable.swift in Sources */, C2E0636249C902EAA944FD025C99F6D6 /* TableConstraint.swift in Sources */, 75948F0B5BECFD01391CE7784BEAADD7 /* TableConstraintBinding.swift in Sources */, C0B88E4E8E5C37FDCD5FD19FF1D310E2 /* TableDecoder.swift in Sources */, DA582DC413045D8D18A139F7A390F858 /* TableEncoder.swift in Sources */, 460B13220E63BB29C50B9A81C920E55C /* TableInterface.swift in Sources */, 1F99EF46B98800CDB68AF68EA1E262EB /* Tagged.swift in Sources */, 73EE699088AAFF42A92738A03ECE1800 /* ThreadLocal.swift in Sources */, 4BC70847EDB20C944B7C2A351C4C835B /* TimedQueue.swift in Sources */, EBE779F57B5DB9845B4231091950A55B /* Tokenize.swift in Sources */, 45E6F826C2A1EB894323A3AB835DDF74 /* Tracer.swift in Sources */, 2742E413003BFBFFE684E5C8AFF9A033 /* Transaction.swift in Sources */, 9A326BB36F84A0970CDD0B04EED7FD90 /* Update.swift in Sources */, 7D0CAF33CD81F1786DBD53A1780F046F /* VirtualTableBinding.swift in Sources */, 4170027AF8D5B066446C5E63991E8CE4 /* WCDB.swift-dummy.m in Sources */, 35AE817FAAD12574525181BD78289CD4 /* WCDBTokenize.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 85159E469D47CE874338B22DAF9B1816 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = WCDBOptimizedSQLCipher; targetProxy = B53AE04AAEE7B23EFE228ED0AFD8249C /* PBXContainerItemProxy */; }; F884DD8FAC8AF6846C56A11D92EFBCAD /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SQLiteRepairKit; targetProxy = F27637A61560590496E5791936F3AD3F /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 2F40409524C5D9AD1790324AE7802613 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 927502B7DA546FD969269DE71CE79192 /* WCDB.swift.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/WCDB.swift/WCDB.swift-prefix.pch"; INFOPLIST_FILE = "Target Support Files/WCDB.swift/WCDB.swift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/WCDB.swift/WCDB.swift.modulemap"; PRODUCT_MODULE_NAME = WCDBSwift; PRODUCT_NAME = WCDBSwift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 489C4045EB8D7AAB143886206FAC11BF /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7F76470DDEFC667342A99BB50D821A2C /* WCDB.swift.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/WCDB.swift/WCDB.swift-prefix.pch"; INFOPLIST_FILE = "Target Support Files/WCDB.swift/WCDB.swift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MODULEMAP_FILE = "Target Support Files/WCDB.swift/WCDB.swift.modulemap"; PRODUCT_MODULE_NAME = WCDBSwift; PRODUCT_NAME = WCDBSwift; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; D093CE025FB8AA23185865C03B74A811 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_DEBUG=1", "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; F3B432464B71A29BDA3865FA10236A74 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 9B4A5017CADBE63A13DE5E2EE1227B13 /* Build configuration list for PBXNativeTarget "WCDB.swift" */ = { isa = XCConfigurationList; buildConfigurations = ( 489C4045EB8D7AAB143886206FAC11BF /* Debug */, 2F40409524C5D9AD1790324AE7802613 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F2635CC716BC28B2A821061573912CA4 /* Build configuration list for PBXProject "WCDB.swift" */ = { isa = XCConfigurationList; buildConfigurations = ( D093CE025FB8AA23185865C03B74A811 /* Debug */, F3B432464B71A29BDA3865FA10236A74 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F986FBAAA2DD46A809EA6A4C46AFACEA /* Project object */; } ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/LICENSE ================================================ Copyright (c) 2008, ZETETIC LLC All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the ZETETIC LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/README.md ================================================ ## SQLCipher SQLCipher extends the [SQLite](https://www.sqlite.org) database library to add security enhancements that make it more suitable for encrypted local data storage such as on-the-fly encryption, tamper evidence, and key derivation. Based on SQLite, SQLCipher closely tracks SQLite and periodically integrates stable SQLite release features. SQLCipher is maintained by Zetetic, LLC, the official site can be found [here](https://www.zetetic.net/sqlcipher/). ## Features - Fast performance with as little as 5-15% overhead for encryption on many operations - 100% of data in the database file is encrypted - Good security practices (CBC mode, HMAC, key derivation) - Zero-configuration and application level cryptography - Algorithms provided by the peer reviewed OpenSSL crypto library. - Configurable crypto providers ## Contributions We welcome contributions, to contribute to SQLCipher, a [contributor agreement](https://www.zetetic.net/contributions/) needs to be submitted. All submissions should be based on the `prerelease` branch. ## Compiling Building SQLCipher is almost the same as compiling a regular version of SQLite with two small exceptions: 1. You *must* define `SQLITE_HAS_CODEC` and `SQLITE_TEMP_STORE=2` when building sqlcipher. 2. If compiling against the default OpenSSL crypto provider, you will need to link libcrypto Example Static linking (replace /opt/local/lib with the path to libcrypto.a). Note in this example, `--enable-tempstore=yes` is setting `SQLITE_TEMP_STORE=2` for the build. $ ./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" \ LDFLAGS="/opt/local/lib/libcrypto.a" $ make Example Dynamic linking $ ./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" \ LDFLAGS="-lcrypto" $ make ## Encrypting a database To specify an encryption passphrase for the database via the SQL interface you use a pragma. The passphrase you enter is passed through PBKDF2 key derivation to obtain the encryption key for the database PRAGMA key = 'passphrase'; Alternately, you can specify an exact byte sequence using a blob literal. If you use this method it is your responsibility to ensure that the data you provide a 64 character hex string, which will be converted directly to 32 bytes (256 bits) of key data without key derivation. PRAGMA key = "x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'"; To encrypt a database programatically you can use the `sqlite3_key` function. The data provided in `pKey` is converted to an encryption key according to the same rules as `PRAGMA key`. int sqlite3_key(sqlite3 *db, const void *pKey, int nKey); `PRAGMA key` or `sqlite3_key` should be called as the first operation when a database is open. ## Changing a database key To change the encryption passphrase for an existing database you may use the rekey pragma after you've supplied the correct database password; PRAGMA key = 'passphrase'; -- start with the existing database passphrase PRAGMA rekey = 'new-passphrase'; -- rekey will reencrypt with the new passphrase The hex rekey pragma may be used to rekey to a specific binary value PRAGMA rekey = "x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'"; This can be accomplished programtically by using sqlite3_rekey; sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) ## Support The primary avenue for support and discussions is the SQLCipher discuss site: https://discuss.zetetic.net/c/sqlcipher Issues or support questions on using SQLCipher should be entered into the GitHub Issue tracker: https://github.com/sqlcipher/sqlcipher/issues Please DO NOT post issues, support questions, or other problems to blog posts about SQLCipher as we do not monitor them frequently. If you are using SQLCipher in your own software please let us know at support@zetetic.net! ## License Copyright (c) 2016, ZETETIC LLC All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the ZETETIC LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3.c ================================================ /* ** 2006 Oct 10 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This is an SQLite module implementing full-text search. */ /* ** The code in this file is only compiled if: ** ** * The FTS3 module is being built as an extension ** (in which case SQLITE_CORE is not defined), or ** ** * The FTS3 module is being built into the core of ** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). */ /* The full-text index is stored in a series of b+tree (-like) ** structures called segments which map terms to doclists. The ** structures are like b+trees in layout, but are constructed from the ** bottom up in optimal fashion and are not updatable. Since trees ** are built from the bottom up, things will be described from the ** bottom up. ** ** **** Varints **** ** The basic unit of encoding is a variable-length integer called a ** varint. We encode variable-length integers in little-endian order ** using seven bits * per byte as follows: ** ** KEY: ** A = 0xxxxxxx 7 bits of data and one flag bit ** B = 1xxxxxxx 7 bits of data and one flag bit ** ** 7 bits - A ** 14 bits - BA ** 21 bits - BBA ** and so on. ** ** This is similar in concept to how sqlite encodes "varints" but ** the encoding is not the same. SQLite varints are big-endian ** are are limited to 9 bytes in length whereas FTS3 varints are ** little-endian and can be up to 10 bytes in length (in theory). ** ** Example encodings: ** ** 1: 0x01 ** 127: 0x7f ** 128: 0x81 0x00 ** ** **** Document lists **** ** A doclist (document list) holds a docid-sorted list of hits for a ** given term. Doclists hold docids and associated token positions. ** A docid is the unique integer identifier for a single document. ** A position is the index of a word within the document. The first ** word of the document has a position of 0. ** ** FTS3 used to optionally store character offsets using a compile-time ** option. But that functionality is no longer supported. ** ** A doclist is stored like this: ** ** array { ** varint docid; (delta from previous doclist) ** array { (position list for column 0) ** varint position; (2 more than the delta from previous position) ** } ** array { ** varint POS_COLUMN; (marks start of position list for new column) ** varint column; (index of new column) ** array { ** varint position; (2 more than the delta from previous position) ** } ** } ** varint POS_END; (marks end of positions for this document. ** } ** ** Here, array { X } means zero or more occurrences of X, adjacent in ** memory. A "position" is an index of a token in the token stream ** generated by the tokenizer. Note that POS_END and POS_COLUMN occur ** in the same logical place as the position element, and act as sentinals ** ending a position list array. POS_END is 0. POS_COLUMN is 1. ** The positions numbers are not stored literally but rather as two more ** than the difference from the prior position, or the just the position plus ** 2 for the first position. Example: ** ** label: A B C D E F G H I J K ** value: 123 5 9 1 1 14 35 0 234 72 0 ** ** The 123 value is the first docid. For column zero in this document ** there are two matches at positions 3 and 10 (5-2 and 9-2+3). The 1 ** at D signals the start of a new column; the 1 at E indicates that the ** new column is column number 1. There are two positions at 12 and 45 ** (14-2 and 35-2+12). The 0 at H indicate the end-of-document. The ** 234 at I is the delta to next docid (357). It has one position 70 ** (72-2) and then terminates with the 0 at K. ** ** A "position-list" is the list of positions for multiple columns for ** a single docid. A "column-list" is the set of positions for a single ** column. Hence, a position-list consists of one or more column-lists, ** a document record consists of a docid followed by a position-list and ** a doclist consists of one or more document records. ** ** A bare doclist omits the position information, becoming an ** array of varint-encoded docids. ** **** Segment leaf nodes **** ** Segment leaf nodes store terms and doclists, ordered by term. Leaf ** nodes are written using LeafWriter, and read using LeafReader (to ** iterate through a single leaf node's data) and LeavesReader (to ** iterate through a segment's entire leaf layer). Leaf nodes have ** the format: ** ** varint iHeight; (height from leaf level, always 0) ** varint nTerm; (length of first term) ** char pTerm[nTerm]; (content of first term) ** varint nDoclist; (length of term's associated doclist) ** char pDoclist[nDoclist]; (content of doclist) ** array { ** (further terms are delta-encoded) ** varint nPrefix; (length of prefix shared with previous term) ** varint nSuffix; (length of unshared suffix) ** char pTermSuffix[nSuffix];(unshared suffix of next term) ** varint nDoclist; (length of term's associated doclist) ** char pDoclist[nDoclist]; (content of doclist) ** } ** ** Here, array { X } means zero or more occurrences of X, adjacent in ** memory. ** ** Leaf nodes are broken into blocks which are stored contiguously in ** the %_segments table in sorted order. This means that when the end ** of a node is reached, the next term is in the node with the next ** greater node id. ** ** New data is spilled to a new leaf node when the current node ** exceeds LEAF_MAX bytes (default 2048). New data which itself is ** larger than STANDALONE_MIN (default 1024) is placed in a standalone ** node (a leaf node with a single term and doclist). The goal of ** these settings is to pack together groups of small doclists while ** making it efficient to directly access large doclists. The ** assumption is that large doclists represent terms which are more ** likely to be query targets. ** ** TODO(shess) It may be useful for blocking decisions to be more ** dynamic. For instance, it may make more sense to have a 2.5k leaf ** node rather than splitting into 2k and .5k nodes. My intuition is ** that this might extend through 2x or 4x the pagesize. ** ** **** Segment interior nodes **** ** Segment interior nodes store blockids for subtree nodes and terms ** to describe what data is stored by the each subtree. Interior ** nodes are written using InteriorWriter, and read using ** InteriorReader. InteriorWriters are created as needed when ** SegmentWriter creates new leaf nodes, or when an interior node ** itself grows too big and must be split. The format of interior ** nodes: ** ** varint iHeight; (height from leaf level, always >0) ** varint iBlockid; (block id of node's leftmost subtree) ** optional { ** varint nTerm; (length of first term) ** char pTerm[nTerm]; (content of first term) ** array { ** (further terms are delta-encoded) ** varint nPrefix; (length of shared prefix with previous term) ** varint nSuffix; (length of unshared suffix) ** char pTermSuffix[nSuffix]; (unshared suffix of next term) ** } ** } ** ** Here, optional { X } means an optional element, while array { X } ** means zero or more occurrences of X, adjacent in memory. ** ** An interior node encodes n terms separating n+1 subtrees. The ** subtree blocks are contiguous, so only the first subtree's blockid ** is encoded. The subtree at iBlockid will contain all terms less ** than the first term encoded (or all terms if no term is encoded). ** Otherwise, for terms greater than or equal to pTerm[i] but less ** than pTerm[i+1], the subtree for that term will be rooted at ** iBlockid+i. Interior nodes only store enough term data to ** distinguish adjacent children (if the rightmost term of the left ** child is "something", and the leftmost term of the right child is ** "wicked", only "w" is stored). ** ** New data is spilled to a new interior node at the same height when ** the current node exceeds INTERIOR_MAX bytes (default 2048). ** INTERIOR_MIN_TERMS (default 7) keeps large terms from monopolizing ** interior nodes and making the tree too skinny. The interior nodes ** at a given height are naturally tracked by interior nodes at ** height+1, and so on. ** ** **** Segment directory **** ** The segment directory in table %_segdir stores meta-information for ** merging and deleting segments, and also the root node of the ** segment's tree. ** ** The root node is the top node of the segment's tree after encoding ** the entire segment, restricted to ROOT_MAX bytes (default 1024). ** This could be either a leaf node or an interior node. If the top ** node requires more than ROOT_MAX bytes, it is flushed to %_segments ** and a new root interior node is generated (which should always fit ** within ROOT_MAX because it only needs space for 2 varints, the ** height and the blockid of the previous root). ** ** The meta-information in the segment directory is: ** level - segment level (see below) ** idx - index within level ** - (level,idx uniquely identify a segment) ** start_block - first leaf node ** leaves_end_block - last leaf node ** end_block - last block (including interior nodes) ** root - contents of root node ** ** If the root node is a leaf node, then start_block, ** leaves_end_block, and end_block are all 0. ** ** **** Segment merging **** ** To amortize update costs, segments are grouped into levels and ** merged in batches. Each increase in level represents exponentially ** more documents. ** ** New documents (actually, document updates) are tokenized and ** written individually (using LeafWriter) to a level 0 segment, with ** incrementing idx. When idx reaches MERGE_COUNT (default 16), all ** level 0 segments are merged into a single level 1 segment. Level 1 ** is populated like level 0, and eventually MERGE_COUNT level 1 ** segments are merged to a single level 2 segment (representing ** MERGE_COUNT^2 updates), and so on. ** ** A segment merge traverses all segments at a given level in ** parallel, performing a straightforward sorted merge. Since segment ** leaf nodes are written in to the %_segments table in order, this ** merge traverses the underlying sqlite disk structures efficiently. ** After the merge, all segment blocks from the merged level are ** deleted. ** ** MERGE_COUNT controls how often we merge segments. 16 seems to be ** somewhat of a sweet spot for insertion performance. 32 and 64 show ** very similar performance numbers to 16 on insertion, though they're ** a tiny bit slower (perhaps due to more overhead in merge-time ** sorting). 8 is about 20% slower than 16, 4 about 50% slower than ** 16, 2 about 66% slower than 16. ** ** At query time, high MERGE_COUNT increases the number of segments ** which need to be scanned and merged. For instance, with 100k docs ** inserted: ** ** MERGE_COUNT segments ** 16 25 ** 8 12 ** 4 10 ** 2 6 ** ** This appears to have only a moderate impact on queries for very ** frequent terms (which are somewhat dominated by segment merge ** costs), and infrequent and non-existent terms still seem to be fast ** even with many segments. ** ** TODO(shess) That said, it would be nice to have a better query-side ** argument for MERGE_COUNT of 16. Also, it is possible/likely that ** optimizations to things like doclist merging will swing the sweet ** spot around. ** ** ** **** Handling of deletions and updates **** ** Since we're using a segmented structure, with no docid-oriented ** index into the term index, we clearly cannot simply update the term ** index when a document is deleted or updated. For deletions, we ** write an empty doclist (varint(docid) varint(POS_END)), for updates ** we simply write the new doclist. Segment merges overwrite older ** data for a particular docid with newer data, so deletes or updates ** will eventually overtake the earlier data and knock it out. The ** query logic likewise merges doclists so that newer data knocks out ** older data. */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE) # define SQLITE_CORE 1 #endif #include #include #include #include #include #include #include "fts3.h" #ifndef SQLITE_CORE # include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 #endif static int fts3EvalNext(Fts3Cursor *pCsr); static int fts3EvalStart(Fts3Cursor *pCsr); static int fts3TermSegReaderCursor( Fts3Cursor *, const char *, int, int, Fts3MultiSegReader **); #ifndef SQLITE_AMALGAMATION # if defined(SQLITE_DEBUG) int sqlite3Fts3Always(int b) { assert( b ); return b; } int sqlite3Fts3Never(int b) { assert( !b ); return b; } # endif #endif /* ** Write a 64-bit variable-length integer to memory starting at p[0]. ** The length of data written will be between 1 and FTS3_VARINT_MAX bytes. ** The number of bytes written is returned. */ int sqlite3Fts3PutVarint(char *p, sqlite_int64 v){ unsigned char *q = (unsigned char *) p; sqlite_uint64 vu = v; do{ *q++ = (unsigned char) ((vu & 0x7f) | 0x80); vu >>= 7; }while( vu!=0 ); q[-1] &= 0x7f; /* turn off high bit in final byte */ assert( q - (unsigned char *)p <= FTS3_VARINT_MAX ); return (int) (q - (unsigned char *)p); } #define GETVARINT_STEP(v, ptr, shift, mask1, mask2, var, ret) \ v = (v & mask1) | ( (*ptr++) << shift ); \ if( (v & mask2)==0 ){ var = v; return ret; } #define GETVARINT_INIT(v, ptr, shift, mask1, mask2, var, ret) \ v = (*ptr++); \ if( (v & mask2)==0 ){ var = v; return ret; } /* ** Read a 64-bit variable-length integer from memory starting at p[0]. ** Return the number of bytes read, or 0 on error. ** The value is stored in *v. */ int sqlite3Fts3GetVarint(const char *p, sqlite_int64 *v){ const char *pStart = p; u32 a; u64 b; int shift; GETVARINT_INIT(a, p, 0, 0x00, 0x80, *v, 1); GETVARINT_STEP(a, p, 7, 0x7F, 0x4000, *v, 2); GETVARINT_STEP(a, p, 14, 0x3FFF, 0x200000, *v, 3); GETVARINT_STEP(a, p, 21, 0x1FFFFF, 0x10000000, *v, 4); b = (a & 0x0FFFFFFF ); for(shift=28; shift<=63; shift+=7){ u64 c = *p++; b += (c&0x7F) << shift; if( (c & 0x80)==0 ) break; } *v = b; return (int)(p - pStart); } /* ** Similar to sqlite3Fts3GetVarint(), except that the output is truncated to a ** 32-bit integer before it is returned. */ int sqlite3Fts3GetVarint32(const char *p, int *pi){ u32 a; #ifndef fts3GetVarint32 GETVARINT_INIT(a, p, 0, 0x00, 0x80, *pi, 1); #else a = (*p++); assert( a & 0x80 ); #endif GETVARINT_STEP(a, p, 7, 0x7F, 0x4000, *pi, 2); GETVARINT_STEP(a, p, 14, 0x3FFF, 0x200000, *pi, 3); GETVARINT_STEP(a, p, 21, 0x1FFFFF, 0x10000000, *pi, 4); a = (a & 0x0FFFFFFF ); *pi = (int)(a | ((u32)(*p & 0x0F) << 28)); return 5; } /* ** Return the number of bytes required to encode v as a varint */ int sqlite3Fts3VarintLen(sqlite3_uint64 v){ int i = 0; do{ i++; v >>= 7; }while( v!=0 ); return i; } /* ** Convert an SQL-style quoted string into a normal string by removing ** the quote characters. The conversion is done in-place. If the ** input does not begin with a quote character, then this routine ** is a no-op. ** ** Examples: ** ** "abc" becomes abc ** 'xyz' becomes xyz ** [pqr] becomes pqr ** `mno` becomes mno ** */ void sqlite3Fts3Dequote(char *z){ char quote; /* Quote character (if any ) */ quote = z[0]; if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){ int iIn = 1; /* Index of next byte to read from input */ int iOut = 0; /* Index of next byte to write to output */ /* If the first byte was a '[', then the close-quote character is a ']' */ if( quote=='[' ) quote = ']'; while( z[iIn] ){ if( z[iIn]==quote ){ if( z[iIn+1]!=quote ) break; z[iOut++] = quote; iIn += 2; }else{ z[iOut++] = z[iIn++]; } } z[iOut] = '\0'; } } /* ** Read a single varint from the doclist at *pp and advance *pp to point ** to the first byte past the end of the varint. Add the value of the varint ** to *pVal. */ static void fts3GetDeltaVarint(char **pp, sqlite3_int64 *pVal){ sqlite3_int64 iVal; *pp += sqlite3Fts3GetVarint(*pp, &iVal); *pVal += iVal; } /* ** When this function is called, *pp points to the first byte following a ** varint that is part of a doclist (or position-list, or any other list ** of varints). This function moves *pp to point to the start of that varint, ** and sets *pVal by the varint value. ** ** Argument pStart points to the first byte of the doclist that the ** varint is part of. */ static void fts3GetReverseVarint( char **pp, char *pStart, sqlite3_int64 *pVal ){ sqlite3_int64 iVal; char *p; /* Pointer p now points at the first byte past the varint we are ** interested in. So, unless the doclist is corrupt, the 0x80 bit is ** clear on character p[-1]. */ for(p = (*pp)-2; p>=pStart && *p&0x80; p--); p++; *pp = p; sqlite3Fts3GetVarint(p, &iVal); *pVal = iVal; } /* ** The xDisconnect() virtual table method. */ static int fts3DisconnectMethod(sqlite3_vtab *pVtab){ Fts3Table *p = (Fts3Table *)pVtab; int i; assert( p->nPendingData==0 ); assert( p->pSegments==0 ); /* Free any prepared statements held */ for(i=0; iaStmt); i++){ sqlite3_finalize(p->aStmt[i]); } sqlite3_free(p->zSegmentsTbl); sqlite3_free(p->zReadExprlist); sqlite3_free(p->zWriteExprlist); sqlite3_free(p->zContentTbl); sqlite3_free(p->zLanguageid); /* Invoke the tokenizer destructor to free the tokenizer. */ p->pTokenizer->pModule->xDestroy(p->pTokenizer); sqlite3_free(p); return SQLITE_OK; } /* ** Write an error message into *pzErr */ void sqlite3Fts3ErrMsg(char **pzErr, const char *zFormat, ...){ va_list ap; sqlite3_free(*pzErr); va_start(ap, zFormat); *pzErr = sqlite3_vmprintf(zFormat, ap); va_end(ap); } /* ** Construct one or more SQL statements from the format string given ** and then evaluate those statements. The success code is written ** into *pRc. ** ** If *pRc is initially non-zero then this routine is a no-op. */ static void fts3DbExec( int *pRc, /* Success code */ sqlite3 *db, /* Database in which to run SQL */ const char *zFormat, /* Format string for SQL */ ... /* Arguments to the format string */ ){ va_list ap; char *zSql; if( *pRc ) return; va_start(ap, zFormat); zSql = sqlite3_vmprintf(zFormat, ap); va_end(ap); if( zSql==0 ){ *pRc = SQLITE_NOMEM; }else{ *pRc = sqlite3_exec(db, zSql, 0, 0, 0); sqlite3_free(zSql); } } /* ** The xDestroy() virtual table method. */ static int fts3DestroyMethod(sqlite3_vtab *pVtab){ Fts3Table *p = (Fts3Table *)pVtab; int rc = SQLITE_OK; /* Return code */ const char *zDb = p->zDb; /* Name of database (e.g. "main", "temp") */ sqlite3 *db = p->db; /* Database handle */ /* Drop the shadow tables */ if( p->zContentTbl==0 ){ fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_content'", zDb, p->zName); } fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments'", zDb,p->zName); fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segdir'", zDb, p->zName); fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_docsize'", zDb, p->zName); fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_stat'", zDb, p->zName); /* If everything has worked, invoke fts3DisconnectMethod() to free the ** memory associated with the Fts3Table structure and return SQLITE_OK. ** Otherwise, return an SQLite error code. */ return (rc==SQLITE_OK ? fts3DisconnectMethod(pVtab) : rc); } /* ** Invoke sqlite3_declare_vtab() to declare the schema for the FTS3 table ** passed as the first argument. This is done as part of the xConnect() ** and xCreate() methods. ** ** If *pRc is non-zero when this function is called, it is a no-op. ** Otherwise, if an error occurs, an SQLite error code is stored in *pRc ** before returning. */ static void fts3DeclareVtab(int *pRc, Fts3Table *p){ if( *pRc==SQLITE_OK ){ int i; /* Iterator variable */ int rc; /* Return code */ char *zSql; /* SQL statement passed to declare_vtab() */ char *zCols; /* List of user defined columns */ const char *zLanguageid; zLanguageid = (p->zLanguageid ? p->zLanguageid : "__langid"); sqlite3_vtab_config(p->db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); /* Create a list of user columns for the virtual table */ zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]); for(i=1; zCols && inColumn; i++){ zCols = sqlite3_mprintf("%z%Q, ", zCols, p->azColumn[i]); } /* Create the whole "CREATE TABLE" statement to pass to SQLite */ zSql = sqlite3_mprintf( "CREATE TABLE x(%s %Q HIDDEN, docid HIDDEN, %Q HIDDEN)", zCols, p->zName, zLanguageid ); if( !zCols || !zSql ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_declare_vtab(p->db, zSql); } sqlite3_free(zSql); sqlite3_free(zCols); *pRc = rc; } } /* ** Create the %_stat table if it does not already exist. */ void sqlite3Fts3CreateStatTable(int *pRc, Fts3Table *p){ fts3DbExec(pRc, p->db, "CREATE TABLE IF NOT EXISTS %Q.'%q_stat'" "(id INTEGER PRIMARY KEY, value BLOB);", p->zDb, p->zName ); if( (*pRc)==SQLITE_OK ) p->bHasStat = 1; } /* ** Create the backing store tables (%_content, %_segments and %_segdir) ** required by the FTS3 table passed as the only argument. This is done ** as part of the vtab xCreate() method. ** ** If the p->bHasDocsize boolean is true (indicating that this is an ** FTS4 table, not an FTS3 table) then also create the %_docsize and ** %_stat tables required by FTS4. */ static int fts3CreateTables(Fts3Table *p){ int rc = SQLITE_OK; /* Return code */ int i; /* Iterator variable */ sqlite3 *db = p->db; /* The database connection */ if( p->zContentTbl==0 ){ const char *zLanguageid = p->zLanguageid; char *zContentCols; /* Columns of %_content table */ /* Create a list of user columns for the content table */ zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY"); for(i=0; zContentCols && inColumn; i++){ char *z = p->azColumn[i]; zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z); } if( zLanguageid && zContentCols ){ zContentCols = sqlite3_mprintf("%z, langid", zContentCols, zLanguageid); } if( zContentCols==0 ) rc = SQLITE_NOMEM; /* Create the content table */ fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_content'(%s)", p->zDb, p->zName, zContentCols ); sqlite3_free(zContentCols); } /* Create other tables */ fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_segments'(blockid INTEGER PRIMARY KEY, block BLOB);", p->zDb, p->zName ); fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_segdir'(" "level INTEGER," "idx INTEGER," "start_block INTEGER," "leaves_end_block INTEGER," "end_block INTEGER," "root BLOB," "PRIMARY KEY(level, idx)" ");", p->zDb, p->zName ); if( p->bHasDocsize ){ fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_docsize'(docid INTEGER PRIMARY KEY, size BLOB);", p->zDb, p->zName ); } assert( p->bHasStat==p->bFts4 ); if( p->bHasStat ){ sqlite3Fts3CreateStatTable(&rc, p); } return rc; } /* ** Store the current database page-size in bytes in p->nPgsz. ** ** If *pRc is non-zero when this function is called, it is a no-op. ** Otherwise, if an error occurs, an SQLite error code is stored in *pRc ** before returning. */ static void fts3DatabasePageSize(int *pRc, Fts3Table *p){ if( *pRc==SQLITE_OK ){ int rc; /* Return code */ char *zSql; /* SQL text "PRAGMA %Q.page_size" */ sqlite3_stmt *pStmt; /* Compiled "PRAGMA %Q.page_size" statement */ zSql = sqlite3_mprintf("PRAGMA %Q.page_size", p->zDb); if( !zSql ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0); if( rc==SQLITE_OK ){ sqlite3_step(pStmt); p->nPgsz = sqlite3_column_int(pStmt, 0); rc = sqlite3_finalize(pStmt); }else if( rc==SQLITE_AUTH ){ p->nPgsz = 1024; rc = SQLITE_OK; } } assert( p->nPgsz>0 || rc!=SQLITE_OK ); sqlite3_free(zSql); *pRc = rc; } } /* ** "Special" FTS4 arguments are column specifications of the following form: ** ** = ** ** There may not be whitespace surrounding the "=" character. The ** term may be quoted, but the may not. */ static int fts3IsSpecialColumn( const char *z, int *pnKey, char **pzValue ){ char *zValue; const char *zCsr = z; while( *zCsr!='=' ){ if( *zCsr=='\0' ) return 0; zCsr++; } *pnKey = (int)(zCsr-z); zValue = sqlite3_mprintf("%s", &zCsr[1]); if( zValue ){ sqlite3Fts3Dequote(zValue); } *pzValue = zValue; return 1; } /* ** Append the output of a printf() style formatting to an existing string. */ static void fts3Appendf( int *pRc, /* IN/OUT: Error code */ char **pz, /* IN/OUT: Pointer to string buffer */ const char *zFormat, /* Printf format string to append */ ... /* Arguments for printf format string */ ){ if( *pRc==SQLITE_OK ){ va_list ap; char *z; va_start(ap, zFormat); z = sqlite3_vmprintf(zFormat, ap); va_end(ap); if( z && *pz ){ char *z2 = sqlite3_mprintf("%s%s", *pz, z); sqlite3_free(z); z = z2; } if( z==0 ) *pRc = SQLITE_NOMEM; sqlite3_free(*pz); *pz = z; } } /* ** Return a copy of input string zInput enclosed in double-quotes (") and ** with all double quote characters escaped. For example: ** ** fts3QuoteId("un \"zip\"") -> "un \"\"zip\"\"" ** ** The pointer returned points to memory obtained from sqlite3_malloc(). It ** is the callers responsibility to call sqlite3_free() to release this ** memory. */ static char *fts3QuoteId(char const *zInput){ int nRet; char *zRet; nRet = 2 + (int)strlen(zInput)*2 + 1; zRet = sqlite3_malloc(nRet); if( zRet ){ int i; char *z = zRet; *(z++) = '"'; for(i=0; zInput[i]; i++){ if( zInput[i]=='"' ) *(z++) = '"'; *(z++) = zInput[i]; } *(z++) = '"'; *(z++) = '\0'; } return zRet; } /* ** Return a list of comma separated SQL expressions and a FROM clause that ** could be used in a SELECT statement such as the following: ** ** SELECT FROM %_content AS x ... ** ** to return the docid, followed by each column of text data in order ** from left to write. If parameter zFunc is not NULL, then instead of ** being returned directly each column of text data is passed to an SQL ** function named zFunc first. For example, if zFunc is "unzip" and the ** table has the three user-defined columns "a", "b", and "c", the following ** string is returned: ** ** "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c') FROM %_content AS x" ** ** The pointer returned points to a buffer allocated by sqlite3_malloc(). It ** is the responsibility of the caller to eventually free it. ** ** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and ** a NULL pointer is returned). Otherwise, if an OOM error is encountered ** by this function, NULL is returned and *pRc is set to SQLITE_NOMEM. If ** no error occurs, *pRc is left unmodified. */ static char *fts3ReadExprList(Fts3Table *p, const char *zFunc, int *pRc){ char *zRet = 0; char *zFree = 0; char *zFunction; int i; if( p->zContentTbl==0 ){ if( !zFunc ){ zFunction = ""; }else{ zFree = zFunction = fts3QuoteId(zFunc); } fts3Appendf(pRc, &zRet, "docid"); for(i=0; inColumn; i++){ fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]); } if( p->zLanguageid ){ fts3Appendf(pRc, &zRet, ", x.%Q", "langid"); } sqlite3_free(zFree); }else{ fts3Appendf(pRc, &zRet, "rowid"); for(i=0; inColumn; i++){ fts3Appendf(pRc, &zRet, ", x.'%q'", p->azColumn[i]); } if( p->zLanguageid ){ fts3Appendf(pRc, &zRet, ", x.%Q", p->zLanguageid); } } fts3Appendf(pRc, &zRet, " FROM '%q'.'%q%s' AS x", p->zDb, (p->zContentTbl ? p->zContentTbl : p->zName), (p->zContentTbl ? "" : "_content") ); return zRet; } /* ** Return a list of N comma separated question marks, where N is the number ** of columns in the %_content table (one for the docid plus one for each ** user-defined text column). ** ** If argument zFunc is not NULL, then all but the first question mark ** is preceded by zFunc and an open bracket, and followed by a closed ** bracket. For example, if zFunc is "zip" and the FTS3 table has three ** user-defined text columns, the following string is returned: ** ** "?, zip(?), zip(?), zip(?)" ** ** The pointer returned points to a buffer allocated by sqlite3_malloc(). It ** is the responsibility of the caller to eventually free it. ** ** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and ** a NULL pointer is returned). Otherwise, if an OOM error is encountered ** by this function, NULL is returned and *pRc is set to SQLITE_NOMEM. If ** no error occurs, *pRc is left unmodified. */ static char *fts3WriteExprList(Fts3Table *p, const char *zFunc, int *pRc){ char *zRet = 0; char *zFree = 0; char *zFunction; int i; if( !zFunc ){ zFunction = ""; }else{ zFree = zFunction = fts3QuoteId(zFunc); } fts3Appendf(pRc, &zRet, "?"); for(i=0; inColumn; i++){ fts3Appendf(pRc, &zRet, ",%s(?)", zFunction); } if( p->zLanguageid ){ fts3Appendf(pRc, &zRet, ", ?"); } sqlite3_free(zFree); return zRet; } /* ** This function interprets the string at (*pp) as a non-negative integer ** value. It reads the integer and sets *pnOut to the value read, then ** sets *pp to point to the byte immediately following the last byte of ** the integer value. ** ** Only decimal digits ('0'..'9') may be part of an integer value. ** ** If *pp does not being with a decimal digit SQLITE_ERROR is returned and ** the output value undefined. Otherwise SQLITE_OK is returned. ** ** This function is used when parsing the "prefix=" FTS4 parameter. */ static int fts3GobbleInt(const char **pp, int *pnOut){ const int MAX_NPREFIX = 10000000; const char *p; /* Iterator pointer */ int nInt = 0; /* Output value */ for(p=*pp; p[0]>='0' && p[0]<='9'; p++){ nInt = nInt * 10 + (p[0] - '0'); if( nInt>MAX_NPREFIX ){ nInt = 0; break; } } if( p==*pp ) return SQLITE_ERROR; *pnOut = nInt; *pp = p; return SQLITE_OK; } /* ** This function is called to allocate an array of Fts3Index structures ** representing the indexes maintained by the current FTS table. FTS tables ** always maintain the main "terms" index, but may also maintain one or ** more "prefix" indexes, depending on the value of the "prefix=" parameter ** (if any) specified as part of the CREATE VIRTUAL TABLE statement. ** ** Argument zParam is passed the value of the "prefix=" option if one was ** specified, or NULL otherwise. ** ** If no error occurs, SQLITE_OK is returned and *apIndex set to point to ** the allocated array. *pnIndex is set to the number of elements in the ** array. If an error does occur, an SQLite error code is returned. ** ** Regardless of whether or not an error is returned, it is the responsibility ** of the caller to call sqlite3_free() on the output array to free it. */ static int fts3PrefixParameter( const char *zParam, /* ABC in prefix=ABC parameter to parse */ int *pnIndex, /* OUT: size of *apIndex[] array */ struct Fts3Index **apIndex /* OUT: Array of indexes for this table */ ){ struct Fts3Index *aIndex; /* Allocated array */ int nIndex = 1; /* Number of entries in array */ if( zParam && zParam[0] ){ const char *p; nIndex++; for(p=zParam; *p; p++){ if( *p==',' ) nIndex++; } } aIndex = sqlite3_malloc(sizeof(struct Fts3Index) * nIndex); *apIndex = aIndex; if( !aIndex ){ return SQLITE_NOMEM; } memset(aIndex, 0, sizeof(struct Fts3Index) * nIndex); if( zParam ){ const char *p = zParam; int i; for(i=1; i=0 ); if( nPrefix==0 ){ nIndex--; i--; }else{ aIndex[i].nPrefix = nPrefix; } p++; } } *pnIndex = nIndex; return SQLITE_OK; } /* ** This function is called when initializing an FTS4 table that uses the ** content=xxx option. It determines the number of and names of the columns ** of the new FTS4 table. ** ** The third argument passed to this function is the value passed to the ** config=xxx option (i.e. "xxx"). This function queries the database for ** a table of that name. If found, the output variables are populated ** as follows: ** ** *pnCol: Set to the number of columns table xxx has, ** ** *pnStr: Set to the total amount of space required to store a copy ** of each columns name, including the nul-terminator. ** ** *pazCol: Set to point to an array of *pnCol strings. Each string is ** the name of the corresponding column in table xxx. The array ** and its contents are allocated using a single allocation. It ** is the responsibility of the caller to free this allocation ** by eventually passing the *pazCol value to sqlite3_free(). ** ** If the table cannot be found, an error code is returned and the output ** variables are undefined. Or, if an OOM is encountered, SQLITE_NOMEM is ** returned (and the output variables are undefined). */ static int fts3ContentColumns( sqlite3 *db, /* Database handle */ const char *zDb, /* Name of db (i.e. "main", "temp" etc.) */ const char *zTbl, /* Name of content table */ const char ***pazCol, /* OUT: Malloc'd array of column names */ int *pnCol, /* OUT: Size of array *pazCol */ int *pnStr, /* OUT: Bytes of string content */ char **pzErr /* OUT: error message */ ){ int rc = SQLITE_OK; /* Return code */ char *zSql; /* "SELECT *" statement on zTbl */ sqlite3_stmt *pStmt = 0; /* Compiled version of zSql */ zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", zDb, zTbl); if( !zSql ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); if( rc!=SQLITE_OK ){ sqlite3Fts3ErrMsg(pzErr, "%s", sqlite3_errmsg(db)); } } sqlite3_free(zSql); if( rc==SQLITE_OK ){ const char **azCol; /* Output array */ int nStr = 0; /* Size of all column names (incl. 0x00) */ int nCol; /* Number of table columns */ int i; /* Used to iterate through columns */ /* Loop through the returned columns. Set nStr to the number of bytes of ** space required to store a copy of each column name, including the ** nul-terminator byte. */ nCol = sqlite3_column_count(pStmt); for(i=0; i module name ("fts3" or "fts4") ** argv[1] -> database name ** argv[2] -> table name ** argv[...] -> "column name" and other module argument fields. */ static int fts3InitVtab( int isCreate, /* True for xCreate, false for xConnect */ sqlite3 *db, /* The SQLite database connection */ void *pAux, /* Hash table containing tokenizers */ int argc, /* Number of elements in argv array */ const char * const *argv, /* xCreate/xConnect argument array */ sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */ char **pzErr /* Write any error message here */ ){ Fts3Hash *pHash = (Fts3Hash *)pAux; Fts3Table *p = 0; /* Pointer to allocated vtab */ int rc = SQLITE_OK; /* Return code */ int i; /* Iterator variable */ int nByte; /* Size of allocation used for *p */ int iCol; /* Column index */ int nString = 0; /* Bytes required to hold all column names */ int nCol = 0; /* Number of columns in the FTS table */ char *zCsr; /* Space for holding column names */ int nDb; /* Bytes required to hold database name */ int nName; /* Bytes required to hold table name */ int isFts4 = (argv[0][3]=='4'); /* True for FTS4, false for FTS3 */ const char **aCol; /* Array of column names */ sqlite3_tokenizer *pTokenizer = 0; /* Tokenizer for this table */ int nIndex = 0; /* Size of aIndex[] array */ struct Fts3Index *aIndex = 0; /* Array of indexes for this table */ /* The results of parsing supported FTS4 key=value options: */ int bNoDocsize = 0; /* True to omit %_docsize table */ int bDescIdx = 0; /* True to store descending indexes */ char *zPrefix = 0; /* Prefix parameter value (or NULL) */ char *zCompress = 0; /* compress=? parameter (or NULL) */ char *zUncompress = 0; /* uncompress=? parameter (or NULL) */ char *zContent = 0; /* content=? parameter (or NULL) */ char *zLanguageid = 0; /* languageid=? parameter (or NULL) */ char **azNotindexed = 0; /* The set of notindexed= columns */ int nNotindexed = 0; /* Size of azNotindexed[] array */ assert( strlen(argv[0])==4 ); assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4) || (sqlite3_strnicmp(argv[0], "fts3", 4)==0 && !isFts4) ); nDb = (int)strlen(argv[1]) + 1; nName = (int)strlen(argv[2]) + 1; nByte = sizeof(const char *) * (argc-2); aCol = (const char **)sqlite3_malloc(nByte); if( aCol ){ memset((void*)aCol, 0, nByte); azNotindexed = (char **)sqlite3_malloc(nByte); } if( azNotindexed ){ memset(azNotindexed, 0, nByte); } if( !aCol || !azNotindexed ){ rc = SQLITE_NOMEM; goto fts3_init_out; } /* Loop through all of the arguments passed by the user to the FTS3/4 ** module (i.e. all the column names and special arguments). This loop ** does the following: ** ** + Figures out the number of columns the FTSX table will have, and ** the number of bytes of space that must be allocated to store copies ** of the column names. ** ** + If there is a tokenizer specification included in the arguments, ** initializes the tokenizer pTokenizer. */ for(i=3; rc==SQLITE_OK && i8 && 0==sqlite3_strnicmp(z, "tokenize", 8) && 0==sqlite3Fts3IsIdChar(z[8]) ){ rc = sqlite3Fts3InitTokenizer(pHash, &z[9], &pTokenizer, pzErr); } /* Check if it is an FTS4 special argument. */ else if( isFts4 && fts3IsSpecialColumn(z, &nKey, &zVal) ){ struct Fts4Option { const char *zOpt; int nOpt; } aFts4Opt[] = { { "matchinfo", 9 }, /* 0 -> MATCHINFO */ { "prefix", 6 }, /* 1 -> PREFIX */ { "compress", 8 }, /* 2 -> COMPRESS */ { "uncompress", 10 }, /* 3 -> UNCOMPRESS */ { "order", 5 }, /* 4 -> ORDER */ { "content", 7 }, /* 5 -> CONTENT */ { "languageid", 10 }, /* 6 -> LANGUAGEID */ { "notindexed", 10 } /* 7 -> NOTINDEXED */ }; int iOpt; if( !zVal ){ rc = SQLITE_NOMEM; }else{ for(iOpt=0; iOptnOpt && !sqlite3_strnicmp(z, pOp->zOpt, pOp->nOpt) ){ break; } } if( iOpt==SizeofArray(aFts4Opt) ){ sqlite3Fts3ErrMsg(pzErr, "unrecognized parameter: %s", z); rc = SQLITE_ERROR; }else{ switch( iOpt ){ case 0: /* MATCHINFO */ if( strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "fts3", 4) ){ sqlite3Fts3ErrMsg(pzErr, "unrecognized matchinfo: %s", zVal); rc = SQLITE_ERROR; } bNoDocsize = 1; break; case 1: /* PREFIX */ sqlite3_free(zPrefix); zPrefix = zVal; zVal = 0; break; case 2: /* COMPRESS */ sqlite3_free(zCompress); zCompress = zVal; zVal = 0; break; case 3: /* UNCOMPRESS */ sqlite3_free(zUncompress); zUncompress = zVal; zVal = 0; break; case 4: /* ORDER */ if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3)) && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 4)) ){ sqlite3Fts3ErrMsg(pzErr, "unrecognized order: %s", zVal); rc = SQLITE_ERROR; } bDescIdx = (zVal[0]=='d' || zVal[0]=='D'); break; case 5: /* CONTENT */ sqlite3_free(zContent); zContent = zVal; zVal = 0; break; case 6: /* LANGUAGEID */ assert( iOpt==6 ); sqlite3_free(zLanguageid); zLanguageid = zVal; zVal = 0; break; case 7: /* NOTINDEXED */ azNotindexed[nNotindexed++] = zVal; zVal = 0; break; } } sqlite3_free(zVal); } } /* Otherwise, the argument is a column name. */ else { nString += (int)(strlen(z) + 1); aCol[nCol++] = z; } } /* If a content=xxx option was specified, the following: ** ** 1. Ignore any compress= and uncompress= options. ** ** 2. If no column names were specified as part of the CREATE VIRTUAL ** TABLE statement, use all columns from the content table. */ if( rc==SQLITE_OK && zContent ){ sqlite3_free(zCompress); sqlite3_free(zUncompress); zCompress = 0; zUncompress = 0; if( nCol==0 ){ sqlite3_free((void*)aCol); aCol = 0; rc = fts3ContentColumns(db, argv[1], zContent,&aCol,&nCol,&nString,pzErr); /* If a languageid= option was specified, remove the language id ** column from the aCol[] array. */ if( rc==SQLITE_OK && zLanguageid ){ int j; for(j=0; jdb = db; p->nColumn = nCol; p->nPendingData = 0; p->azColumn = (char **)&p[1]; p->pTokenizer = pTokenizer; p->nMaxPendingData = FTS3_MAX_PENDING_DATA; p->bHasDocsize = (isFts4 && bNoDocsize==0); p->bHasStat = isFts4; p->bFts4 = isFts4; p->bDescIdx = bDescIdx; p->nAutoincrmerge = 0xff; /* 0xff means setting unknown */ p->zContentTbl = zContent; p->zLanguageid = zLanguageid; zContent = 0; zLanguageid = 0; TESTONLY( p->inTransaction = -1 ); TESTONLY( p->mxSavepoint = -1 ); p->aIndex = (struct Fts3Index *)&p->azColumn[nCol]; memcpy(p->aIndex, aIndex, sizeof(struct Fts3Index) * nIndex); p->nIndex = nIndex; for(i=0; iaIndex[i].hPending, FTS3_HASH_STRING, 1); } p->abNotindexed = (u8 *)&p->aIndex[nIndex]; /* Fill in the zName and zDb fields of the vtab structure. */ zCsr = (char *)&p->abNotindexed[nCol]; p->zName = zCsr; memcpy(zCsr, argv[2], nName); zCsr += nName; p->zDb = zCsr; memcpy(zCsr, argv[1], nDb); zCsr += nDb; /* Fill in the azColumn array */ for(iCol=0; iColazColumn[iCol] = zCsr; zCsr += n+1; assert( zCsr <= &((char *)p)[nByte] ); } /* Fill in the abNotindexed array */ for(iCol=0; iColazColumn[iCol]); for(i=0; iazColumn[iCol], zNot, n) ){ p->abNotindexed[iCol] = 1; sqlite3_free(zNot); azNotindexed[i] = 0; } } } for(i=0; izReadExprlist = fts3ReadExprList(p, zUncompress, &rc); p->zWriteExprlist = fts3WriteExprList(p, zCompress, &rc); if( rc!=SQLITE_OK ) goto fts3_init_out; /* If this is an xCreate call, create the underlying tables in the ** database. TODO: For xConnect(), it could verify that said tables exist. */ if( isCreate ){ rc = fts3CreateTables(p); } /* Check to see if a legacy fts3 table has been "upgraded" by the ** addition of a %_stat table so that it can use incremental merge. */ if( !isFts4 && !isCreate ){ p->bHasStat = 2; } /* Figure out the page-size for the database. This is required in order to ** estimate the cost of loading large doclists from the database. */ fts3DatabasePageSize(&rc, p); p->nNodeSize = p->nPgsz-35; /* Declare the table schema to SQLite. */ fts3DeclareVtab(&rc, p); fts3_init_out: sqlite3_free(zPrefix); sqlite3_free(aIndex); sqlite3_free(zCompress); sqlite3_free(zUncompress); sqlite3_free(zContent); sqlite3_free(zLanguageid); for(i=0; ipModule->xDestroy(pTokenizer); } }else{ assert( p->pSegments==0 ); *ppVTab = &p->base; } return rc; } /* ** The xConnect() and xCreate() methods for the virtual table. All the ** work is done in function fts3InitVtab(). */ static int fts3ConnectMethod( sqlite3 *db, /* Database connection */ void *pAux, /* Pointer to tokenizer hash table */ int argc, /* Number of elements in argv array */ const char * const *argv, /* xCreate/xConnect argument array */ sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ char **pzErr /* OUT: sqlite3_malloc'd error message */ ){ return fts3InitVtab(0, db, pAux, argc, argv, ppVtab, pzErr); } static int fts3CreateMethod( sqlite3 *db, /* Database connection */ void *pAux, /* Pointer to tokenizer hash table */ int argc, /* Number of elements in argv array */ const char * const *argv, /* xCreate/xConnect argument array */ sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ char **pzErr /* OUT: sqlite3_malloc'd error message */ ){ return fts3InitVtab(1, db, pAux, argc, argv, ppVtab, pzErr); } /* ** Set the pIdxInfo->estimatedRows variable to nRow. Unless this ** extension is currently being used by a version of SQLite too old to ** support estimatedRows. In that case this function is a no-op. */ static void fts3SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){ #if SQLITE_VERSION_NUMBER>=3008002 if( sqlite3_libversion_number()>=3008002 ){ pIdxInfo->estimatedRows = nRow; } #endif } /* ** Set the SQLITE_INDEX_SCAN_UNIQUE flag in pIdxInfo->flags. Unless this ** extension is currently being used by a version of SQLite too old to ** support index-info flags. In that case this function is a no-op. */ static void fts3SetUniqueFlag(sqlite3_index_info *pIdxInfo){ #if SQLITE_VERSION_NUMBER>=3008012 if( sqlite3_libversion_number()>=3008012 ){ pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE; } #endif } /* ** Implementation of the xBestIndex method for FTS3 tables. There ** are three possible strategies, in order of preference: ** ** 1. Direct lookup by rowid or docid. ** 2. Full-text search using a MATCH operator on a non-docid column. ** 3. Linear scan of %_content table. */ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ Fts3Table *p = (Fts3Table *)pVTab; int i; /* Iterator variable */ int iCons = -1; /* Index of constraint to use */ int iLangidCons = -1; /* Index of langid=x constraint, if present */ int iDocidGe = -1; /* Index of docid>=x constraint, if present */ int iDocidLe = -1; /* Index of docid<=x constraint, if present */ int iIdx; /* By default use a full table scan. This is an expensive option, ** so search through the constraints to see if a more efficient ** strategy is possible. */ pInfo->idxNum = FTS3_FULLSCAN_SEARCH; pInfo->estimatedCost = 5000000; for(i=0; inConstraint; i++){ int bDocid; /* True if this constraint is on docid */ struct sqlite3_index_constraint *pCons = &pInfo->aConstraint[i]; if( pCons->usable==0 ){ if( pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH ){ /* There exists an unusable MATCH constraint. This means that if ** the planner does elect to use the results of this call as part ** of the overall query plan the user will see an "unable to use ** function MATCH in the requested context" error. To discourage ** this, return a very high cost here. */ pInfo->idxNum = FTS3_FULLSCAN_SEARCH; pInfo->estimatedCost = 1e50; fts3SetEstimatedRows(pInfo, ((sqlite3_int64)1) << 50); return SQLITE_OK; } continue; } bDocid = (pCons->iColumn<0 || pCons->iColumn==p->nColumn+1); /* A direct lookup on the rowid or docid column. Assign a cost of 1.0. */ if( iCons<0 && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ && bDocid ){ pInfo->idxNum = FTS3_DOCID_SEARCH; pInfo->estimatedCost = 1.0; iCons = i; } /* A MATCH constraint. Use a full-text search. ** ** If there is more than one MATCH constraint available, use the first ** one encountered. If there is both a MATCH constraint and a direct ** rowid/docid lookup, prefer the MATCH strategy. This is done even ** though the rowid/docid lookup is faster than a MATCH query, selecting ** it would lead to an "unable to use function MATCH in the requested ** context" error. */ if( pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH && pCons->iColumn>=0 && pCons->iColumn<=p->nColumn ){ pInfo->idxNum = FTS3_FULLTEXT_SEARCH + pCons->iColumn; pInfo->estimatedCost = 2.0; iCons = i; } /* Equality constraint on the langid column */ if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ && pCons->iColumn==p->nColumn + 2 ){ iLangidCons = i; } if( bDocid ){ switch( pCons->op ){ case SQLITE_INDEX_CONSTRAINT_GE: case SQLITE_INDEX_CONSTRAINT_GT: iDocidGe = i; break; case SQLITE_INDEX_CONSTRAINT_LE: case SQLITE_INDEX_CONSTRAINT_LT: iDocidLe = i; break; } } } /* If using a docid=? or rowid=? strategy, set the UNIQUE flag. */ if( pInfo->idxNum==FTS3_DOCID_SEARCH ) fts3SetUniqueFlag(pInfo); iIdx = 1; if( iCons>=0 ){ pInfo->aConstraintUsage[iCons].argvIndex = iIdx++; pInfo->aConstraintUsage[iCons].omit = 1; } if( iLangidCons>=0 ){ pInfo->idxNum |= FTS3_HAVE_LANGID; pInfo->aConstraintUsage[iLangidCons].argvIndex = iIdx++; } if( iDocidGe>=0 ){ pInfo->idxNum |= FTS3_HAVE_DOCID_GE; pInfo->aConstraintUsage[iDocidGe].argvIndex = iIdx++; } if( iDocidLe>=0 ){ pInfo->idxNum |= FTS3_HAVE_DOCID_LE; pInfo->aConstraintUsage[iDocidLe].argvIndex = iIdx++; } /* Regardless of the strategy selected, FTS can deliver rows in rowid (or ** docid) order. Both ascending and descending are possible. */ if( pInfo->nOrderBy==1 ){ struct sqlite3_index_orderby *pOrder = &pInfo->aOrderBy[0]; if( pOrder->iColumn<0 || pOrder->iColumn==p->nColumn+1 ){ if( pOrder->desc ){ pInfo->idxStr = "DESC"; }else{ pInfo->idxStr = "ASC"; } pInfo->orderByConsumed = 1; } } assert( p->pSegments==0 ); return SQLITE_OK; } /* ** Implementation of xOpen method. */ static int fts3OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ sqlite3_vtab_cursor *pCsr; /* Allocated cursor */ UNUSED_PARAMETER(pVTab); /* Allocate a buffer large enough for an Fts3Cursor structure. If the ** allocation succeeds, zero it and return SQLITE_OK. Otherwise, ** if the allocation fails, return SQLITE_NOMEM. */ *ppCsr = pCsr = (sqlite3_vtab_cursor *)sqlite3_malloc(sizeof(Fts3Cursor)); if( !pCsr ){ return SQLITE_NOMEM; } memset(pCsr, 0, sizeof(Fts3Cursor)); return SQLITE_OK; } /* ** Close the cursor. For additional information see the documentation ** on the xClose method of the virtual table interface. */ static int fts3CloseMethod(sqlite3_vtab_cursor *pCursor){ Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); sqlite3_finalize(pCsr->pStmt); sqlite3Fts3ExprFree(pCsr->pExpr); sqlite3Fts3FreeDeferredTokens(pCsr); sqlite3_free(pCsr->aDoclist); sqlite3Fts3MIBufferFree(pCsr->pMIBuffer); assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); sqlite3_free(pCsr); return SQLITE_OK; } /* ** If pCsr->pStmt has not been prepared (i.e. if pCsr->pStmt==0), then ** compose and prepare an SQL statement of the form: ** ** "SELECT FROM %_content WHERE rowid = ?" ** ** (or the equivalent for a content=xxx table) and set pCsr->pStmt to ** it. If an error occurs, return an SQLite error code. ** ** Otherwise, set *ppStmt to point to pCsr->pStmt and return SQLITE_OK. */ static int fts3CursorSeekStmt(Fts3Cursor *pCsr, sqlite3_stmt **ppStmt){ int rc = SQLITE_OK; if( pCsr->pStmt==0 ){ Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; char *zSql; zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist); if( !zSql ) return SQLITE_NOMEM; rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); sqlite3_free(zSql); } *ppStmt = pCsr->pStmt; return rc; } /* ** Position the pCsr->pStmt statement so that it is on the row ** of the %_content table that contains the last match. Return ** SQLITE_OK on success. */ static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){ int rc = SQLITE_OK; if( pCsr->isRequireSeek ){ sqlite3_stmt *pStmt = 0; rc = fts3CursorSeekStmt(pCsr, &pStmt); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId); pCsr->isRequireSeek = 0; if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){ return SQLITE_OK; }else{ rc = sqlite3_reset(pCsr->pStmt); if( rc==SQLITE_OK && ((Fts3Table *)pCsr->base.pVtab)->zContentTbl==0 ){ /* If no row was found and no error has occurred, then the %_content ** table is missing a row that is present in the full-text index. ** The data structures are corrupt. */ rc = FTS_CORRUPT_VTAB; pCsr->isEof = 1; } } } } if( rc!=SQLITE_OK && pContext ){ sqlite3_result_error_code(pContext, rc); } return rc; } /* ** This function is used to process a single interior node when searching ** a b-tree for a term or term prefix. The node data is passed to this ** function via the zNode/nNode parameters. The term to search for is ** passed in zTerm/nTerm. ** ** If piFirst is not NULL, then this function sets *piFirst to the blockid ** of the child node that heads the sub-tree that may contain the term. ** ** If piLast is not NULL, then *piLast is set to the right-most child node ** that heads a sub-tree that may contain a term for which zTerm/nTerm is ** a prefix. ** ** If an OOM error occurs, SQLITE_NOMEM is returned. Otherwise, SQLITE_OK. */ static int fts3ScanInteriorNode( const char *zTerm, /* Term to select leaves for */ int nTerm, /* Size of term zTerm in bytes */ const char *zNode, /* Buffer containing segment interior node */ int nNode, /* Size of buffer at zNode */ sqlite3_int64 *piFirst, /* OUT: Selected child node */ sqlite3_int64 *piLast /* OUT: Selected child node */ ){ int rc = SQLITE_OK; /* Return code */ const char *zCsr = zNode; /* Cursor to iterate through node */ const char *zEnd = &zCsr[nNode];/* End of interior node buffer */ char *zBuffer = 0; /* Buffer to load terms into */ int nAlloc = 0; /* Size of allocated buffer */ int isFirstTerm = 1; /* True when processing first term on page */ sqlite3_int64 iChild; /* Block id of child node to descend to */ /* Skip over the 'height' varint that occurs at the start of every ** interior node. Then load the blockid of the left-child of the b-tree ** node into variable iChild. ** ** Even if the data structure on disk is corrupted, this (reading two ** varints from the buffer) does not risk an overread. If zNode is a ** root node, then the buffer comes from a SELECT statement. SQLite does ** not make this guarantee explicitly, but in practice there are always ** either more than 20 bytes of allocated space following the nNode bytes of ** contents, or two zero bytes. Or, if the node is read from the %_segments ** table, then there are always 20 bytes of zeroed padding following the ** nNode bytes of content (see sqlite3Fts3ReadBlock() for details). */ zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); if( zCsr>zEnd ){ return FTS_CORRUPT_VTAB; } while( zCsrzEnd ){ rc = FTS_CORRUPT_VTAB; goto finish_scan; } if( nPrefix+nSuffix>nAlloc ){ char *zNew; nAlloc = (nPrefix+nSuffix) * 2; zNew = (char *)sqlite3_realloc(zBuffer, nAlloc); if( !zNew ){ rc = SQLITE_NOMEM; goto finish_scan; } zBuffer = zNew; } assert( zBuffer ); memcpy(&zBuffer[nPrefix], zCsr, nSuffix); nBuffer = nPrefix + nSuffix; zCsr += nSuffix; /* Compare the term we are searching for with the term just loaded from ** the interior node. If the specified term is greater than or equal ** to the term from the interior node, then all terms on the sub-tree ** headed by node iChild are smaller than zTerm. No need to search ** iChild. ** ** If the interior node term is larger than the specified term, then ** the tree headed by iChild may contain the specified term. */ cmp = memcmp(zTerm, zBuffer, (nBuffer>nTerm ? nTerm : nBuffer)); if( piFirst && (cmp<0 || (cmp==0 && nBuffer>nTerm)) ){ *piFirst = iChild; piFirst = 0; } if( piLast && cmp<0 ){ *piLast = iChild; piLast = 0; } iChild++; }; if( piFirst ) *piFirst = iChild; if( piLast ) *piLast = iChild; finish_scan: sqlite3_free(zBuffer); return rc; } /* ** The buffer pointed to by argument zNode (size nNode bytes) contains an ** interior node of a b-tree segment. The zTerm buffer (size nTerm bytes) ** contains a term. This function searches the sub-tree headed by the zNode ** node for the range of leaf nodes that may contain the specified term ** or terms for which the specified term is a prefix. ** ** If piLeaf is not NULL, then *piLeaf is set to the blockid of the ** left-most leaf node in the tree that may contain the specified term. ** If piLeaf2 is not NULL, then *piLeaf2 is set to the blockid of the ** right-most leaf node that may contain a term for which the specified ** term is a prefix. ** ** It is possible that the range of returned leaf nodes does not contain ** the specified term or any terms for which it is a prefix. However, if the ** segment does contain any such terms, they are stored within the identified ** range. Because this function only inspects interior segment nodes (and ** never loads leaf nodes into memory), it is not possible to be sure. ** ** If an error occurs, an error code other than SQLITE_OK is returned. */ static int fts3SelectLeaf( Fts3Table *p, /* Virtual table handle */ const char *zTerm, /* Term to select leaves for */ int nTerm, /* Size of term zTerm in bytes */ const char *zNode, /* Buffer containing segment interior node */ int nNode, /* Size of buffer at zNode */ sqlite3_int64 *piLeaf, /* Selected leaf node */ sqlite3_int64 *piLeaf2 /* Selected leaf node */ ){ int rc = SQLITE_OK; /* Return code */ int iHeight; /* Height of this node in tree */ assert( piLeaf || piLeaf2 ); fts3GetVarint32(zNode, &iHeight); rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2); assert( !piLeaf2 || !piLeaf || rc!=SQLITE_OK || (*piLeaf<=*piLeaf2) ); if( rc==SQLITE_OK && iHeight>1 ){ char *zBlob = 0; /* Blob read from %_segments table */ int nBlob = 0; /* Size of zBlob in bytes */ if( piLeaf && piLeaf2 && (*piLeaf!=*piLeaf2) ){ rc = sqlite3Fts3ReadBlock(p, *piLeaf, &zBlob, &nBlob, 0); if( rc==SQLITE_OK ){ rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, 0); } sqlite3_free(zBlob); piLeaf = 0; zBlob = 0; } if( rc==SQLITE_OK ){ rc = sqlite3Fts3ReadBlock(p, piLeaf?*piLeaf:*piLeaf2, &zBlob, &nBlob, 0); } if( rc==SQLITE_OK ){ rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, piLeaf2); } sqlite3_free(zBlob); } return rc; } /* ** This function is used to create delta-encoded serialized lists of FTS3 ** varints. Each call to this function appends a single varint to a list. */ static void fts3PutDeltaVarint( char **pp, /* IN/OUT: Output pointer */ sqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */ sqlite3_int64 iVal /* Write this value to the list */ ){ assert( iVal-*piPrev > 0 || (*piPrev==0 && iVal==0) ); *pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev); *piPrev = iVal; } /* ** When this function is called, *ppPoslist is assumed to point to the ** start of a position-list. After it returns, *ppPoslist points to the ** first byte after the position-list. ** ** A position list is list of positions (delta encoded) and columns for ** a single document record of a doclist. So, in other words, this ** routine advances *ppPoslist so that it points to the next docid in ** the doclist, or to the first byte past the end of the doclist. ** ** If pp is not NULL, then the contents of the position list are copied ** to *pp. *pp is set to point to the first byte past the last byte copied ** before this function returns. */ static void fts3PoslistCopy(char **pp, char **ppPoslist){ char *pEnd = *ppPoslist; char c = 0; /* The end of a position list is marked by a zero encoded as an FTS3 ** varint. A single POS_END (0) byte. Except, if the 0 byte is preceded by ** a byte with the 0x80 bit set, then it is not a varint 0, but the tail ** of some other, multi-byte, value. ** ** The following while-loop moves pEnd to point to the first byte that is not ** immediately preceded by a byte with the 0x80 bit set. Then increments ** pEnd once more so that it points to the byte immediately following the ** last byte in the position-list. */ while( *pEnd | c ){ c = *pEnd++ & 0x80; testcase( c!=0 && (*pEnd)==0 ); } pEnd++; /* Advance past the POS_END terminator byte */ if( pp ){ int n = (int)(pEnd - *ppPoslist); char *p = *pp; memcpy(p, *ppPoslist, n); p += n; *pp = p; } *ppPoslist = pEnd; } /* ** When this function is called, *ppPoslist is assumed to point to the ** start of a column-list. After it returns, *ppPoslist points to the ** to the terminator (POS_COLUMN or POS_END) byte of the column-list. ** ** A column-list is list of delta-encoded positions for a single column ** within a single document within a doclist. ** ** The column-list is terminated either by a POS_COLUMN varint (1) or ** a POS_END varint (0). This routine leaves *ppPoslist pointing to ** the POS_COLUMN or POS_END that terminates the column-list. ** ** If pp is not NULL, then the contents of the column-list are copied ** to *pp. *pp is set to point to the first byte past the last byte copied ** before this function returns. The POS_COLUMN or POS_END terminator ** is not copied into *pp. */ static void fts3ColumnlistCopy(char **pp, char **ppPoslist){ char *pEnd = *ppPoslist; char c = 0; /* A column-list is terminated by either a 0x01 or 0x00 byte that is ** not part of a multi-byte varint. */ while( 0xFE & (*pEnd | c) ){ c = *pEnd++ & 0x80; testcase( c!=0 && ((*pEnd)&0xfe)==0 ); } if( pp ){ int n = (int)(pEnd - *ppPoslist); char *p = *pp; memcpy(p, *ppPoslist, n); p += n; *pp = p; } *ppPoslist = pEnd; } /* ** Value used to signify the end of an position-list. This is safe because ** it is not possible to have a document with 2^31 terms. */ #define POSITION_LIST_END 0x7fffffff /* ** This function is used to help parse position-lists. When this function is ** called, *pp may point to the start of the next varint in the position-list ** being parsed, or it may point to 1 byte past the end of the position-list ** (in which case **pp will be a terminator bytes POS_END (0) or ** (1)). ** ** If *pp points past the end of the current position-list, set *pi to ** POSITION_LIST_END and return. Otherwise, read the next varint from *pp, ** increment the current value of *pi by the value read, and set *pp to ** point to the next value before returning. ** ** Before calling this routine *pi must be initialized to the value of ** the previous position, or zero if we are reading the first position ** in the position-list. Because positions are delta-encoded, the value ** of the previous position is needed in order to compute the value of ** the next position. */ static void fts3ReadNextPos( char **pp, /* IN/OUT: Pointer into position-list buffer */ sqlite3_int64 *pi /* IN/OUT: Value read from position-list */ ){ if( (**pp)&0xFE ){ fts3GetDeltaVarint(pp, pi); *pi -= 2; }else{ *pi = POSITION_LIST_END; } } /* ** If parameter iCol is not 0, write an POS_COLUMN (1) byte followed by ** the value of iCol encoded as a varint to *pp. This will start a new ** column list. ** ** Set *pp to point to the byte just after the last byte written before ** returning (do not modify it if iCol==0). Return the total number of bytes ** written (0 if iCol==0). */ static int fts3PutColNumber(char **pp, int iCol){ int n = 0; /* Number of bytes written */ if( iCol ){ char *p = *pp; /* Output pointer */ n = 1 + sqlite3Fts3PutVarint(&p[1], iCol); *p = 0x01; *pp = &p[n]; } return n; } /* ** Compute the union of two position lists. The output written ** into *pp contains all positions of both *pp1 and *pp2 in sorted ** order and with any duplicates removed. All pointers are ** updated appropriately. The caller is responsible for insuring ** that there is enough space in *pp to hold the complete output. */ static void fts3PoslistMerge( char **pp, /* Output buffer */ char **pp1, /* Left input list */ char **pp2 /* Right input list */ ){ char *p = *pp; char *p1 = *pp1; char *p2 = *pp2; while( *p1 || *p2 ){ int iCol1; /* The current column index in pp1 */ int iCol2; /* The current column index in pp2 */ if( *p1==POS_COLUMN ) fts3GetVarint32(&p1[1], &iCol1); else if( *p1==POS_END ) iCol1 = POSITION_LIST_END; else iCol1 = 0; if( *p2==POS_COLUMN ) fts3GetVarint32(&p2[1], &iCol2); else if( *p2==POS_END ) iCol2 = POSITION_LIST_END; else iCol2 = 0; if( iCol1==iCol2 ){ sqlite3_int64 i1 = 0; /* Last position from pp1 */ sqlite3_int64 i2 = 0; /* Last position from pp2 */ sqlite3_int64 iPrev = 0; int n = fts3PutColNumber(&p, iCol1); p1 += n; p2 += n; /* At this point, both p1 and p2 point to the start of column-lists ** for the same column (the column with index iCol1 and iCol2). ** A column-list is a list of non-negative delta-encoded varints, each ** incremented by 2 before being stored. Each list is terminated by a ** POS_END (0) or POS_COLUMN (1). The following block merges the two lists ** and writes the results to buffer p. p is left pointing to the byte ** after the list written. No terminator (POS_END or POS_COLUMN) is ** written to the output. */ fts3GetDeltaVarint(&p1, &i1); fts3GetDeltaVarint(&p2, &i2); do { fts3PutDeltaVarint(&p, &iPrev, (i1pos(*pp1) && pos(*pp2)-pos(*pp1)<=nToken). i.e. ** when the *pp1 token appears before the *pp2 token, but not more than nToken ** slots before it. ** ** e.g. nToken==1 searches for adjacent positions. */ static int fts3PoslistPhraseMerge( char **pp, /* IN/OUT: Preallocated output buffer */ int nToken, /* Maximum difference in token positions */ int isSaveLeft, /* Save the left position */ int isExact, /* If *pp1 is exactly nTokens before *pp2 */ char **pp1, /* IN/OUT: Left input list */ char **pp2 /* IN/OUT: Right input list */ ){ char *p = *pp; char *p1 = *pp1; char *p2 = *pp2; int iCol1 = 0; int iCol2 = 0; /* Never set both isSaveLeft and isExact for the same invocation. */ assert( isSaveLeft==0 || isExact==0 ); assert( p!=0 && *p1!=0 && *p2!=0 ); if( *p1==POS_COLUMN ){ p1++; p1 += fts3GetVarint32(p1, &iCol1); } if( *p2==POS_COLUMN ){ p2++; p2 += fts3GetVarint32(p2, &iCol2); } while( 1 ){ if( iCol1==iCol2 ){ char *pSave = p; sqlite3_int64 iPrev = 0; sqlite3_int64 iPos1 = 0; sqlite3_int64 iPos2 = 0; if( iCol1 ){ *p++ = POS_COLUMN; p += sqlite3Fts3PutVarint(p, iCol1); } assert( *p1!=POS_END && *p1!=POS_COLUMN ); assert( *p2!=POS_END && *p2!=POS_COLUMN ); fts3GetDeltaVarint(&p1, &iPos1); iPos1 -= 2; fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2; while( 1 ){ if( iPos2==iPos1+nToken || (isExact==0 && iPos2>iPos1 && iPos2<=iPos1+nToken) ){ sqlite3_int64 iSave; iSave = isSaveLeft ? iPos1 : iPos2; fts3PutDeltaVarint(&p, &iPrev, iSave+2); iPrev -= 2; pSave = 0; assert( p ); } if( (!isSaveLeft && iPos2<=(iPos1+nToken)) || iPos2<=iPos1 ){ if( (*p2&0xFE)==0 ) break; fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2; }else{ if( (*p1&0xFE)==0 ) break; fts3GetDeltaVarint(&p1, &iPos1); iPos1 -= 2; } } if( pSave ){ assert( pp && p ); p = pSave; } fts3ColumnlistCopy(0, &p1); fts3ColumnlistCopy(0, &p2); assert( (*p1&0xFE)==0 && (*p2&0xFE)==0 ); if( 0==*p1 || 0==*p2 ) break; p1++; p1 += fts3GetVarint32(p1, &iCol1); p2++; p2 += fts3GetVarint32(p2, &iCol2); } /* Advance pointer p1 or p2 (whichever corresponds to the smaller of ** iCol1 and iCol2) so that it points to either the 0x00 that marks the ** end of the position list, or the 0x01 that precedes the next ** column-number in the position list. */ else if( iCol1=pEnd ){ *pp = 0; }else{ sqlite3_int64 iVal; *pp += sqlite3Fts3GetVarint(*pp, &iVal); if( bDescIdx ){ *pVal -= iVal; }else{ *pVal += iVal; } } } /* ** This function is used to write a single varint to a buffer. The varint ** is written to *pp. Before returning, *pp is set to point 1 byte past the ** end of the value written. ** ** If *pbFirst is zero when this function is called, the value written to ** the buffer is that of parameter iVal. ** ** If *pbFirst is non-zero when this function is called, then the value ** written is either (iVal-*piPrev) (if bDescIdx is zero) or (*piPrev-iVal) ** (if bDescIdx is non-zero). ** ** Before returning, this function always sets *pbFirst to 1 and *piPrev ** to the value of parameter iVal. */ static void fts3PutDeltaVarint3( char **pp, /* IN/OUT: Output pointer */ int bDescIdx, /* True for descending docids */ sqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */ int *pbFirst, /* IN/OUT: True after first int written */ sqlite3_int64 iVal /* Write this value to the list */ ){ sqlite3_int64 iWrite; if( bDescIdx==0 || *pbFirst==0 ){ iWrite = iVal - *piPrev; }else{ iWrite = *piPrev - iVal; } assert( *pbFirst || *piPrev==0 ); assert( *pbFirst==0 || iWrite>0 ); *pp += sqlite3Fts3PutVarint(*pp, iWrite); *piPrev = iVal; *pbFirst = 1; } /* ** This macro is used by various functions that merge doclists. The two ** arguments are 64-bit docid values. If the value of the stack variable ** bDescDoclist is 0 when this macro is invoked, then it returns (i1-i2). ** Otherwise, (i2-i1). ** ** Using this makes it easier to write code that can merge doclists that are ** sorted in either ascending or descending order. */ #define DOCID_CMP(i1, i2) ((bDescDoclist?-1:1) * (i1-i2)) /* ** This function does an "OR" merge of two doclists (output contains all ** positions contained in either argument doclist). If the docids in the ** input doclists are sorted in ascending order, parameter bDescDoclist ** should be false. If they are sorted in ascending order, it should be ** passed a non-zero value. ** ** If no error occurs, *paOut is set to point at an sqlite3_malloc'd buffer ** containing the output doclist and SQLITE_OK is returned. In this case ** *pnOut is set to the number of bytes in the output doclist. ** ** If an error occurs, an SQLite error code is returned. The output values ** are undefined in this case. */ static int fts3DoclistOrMerge( int bDescDoclist, /* True if arguments are desc */ char *a1, int n1, /* First doclist */ char *a2, int n2, /* Second doclist */ char **paOut, int *pnOut /* OUT: Malloc'd doclist */ ){ sqlite3_int64 i1 = 0; sqlite3_int64 i2 = 0; sqlite3_int64 iPrev = 0; char *pEnd1 = &a1[n1]; char *pEnd2 = &a2[n2]; char *p1 = a1; char *p2 = a2; char *p; char *aOut; int bFirstOut = 0; *paOut = 0; *pnOut = 0; /* Allocate space for the output. Both the input and output doclists ** are delta encoded. If they are in ascending order (bDescDoclist==0), ** then the first docid in each list is simply encoded as a varint. For ** each subsequent docid, the varint stored is the difference between the ** current and previous docid (a positive number - since the list is in ** ascending order). ** ** The first docid written to the output is therefore encoded using the ** same number of bytes as it is in whichever of the input lists it is ** read from. And each subsequent docid read from the same input list ** consumes either the same or less bytes as it did in the input (since ** the difference between it and the previous value in the output must ** be a positive value less than or equal to the delta value read from ** the input list). The same argument applies to all but the first docid ** read from the 'other' list. And to the contents of all position lists ** that will be copied and merged from the input to the output. ** ** However, if the first docid copied to the output is a negative number, ** then the encoding of the first docid from the 'other' input list may ** be larger in the output than it was in the input (since the delta value ** may be a larger positive integer than the actual docid). ** ** The space required to store the output is therefore the sum of the ** sizes of the two inputs, plus enough space for exactly one of the input ** docids to grow. ** ** A symetric argument may be made if the doclists are in descending ** order. */ aOut = sqlite3_malloc(n1+n2+FTS3_VARINT_MAX-1); if( !aOut ) return SQLITE_NOMEM; p = aOut; fts3GetDeltaVarint3(&p1, pEnd1, 0, &i1); fts3GetDeltaVarint3(&p2, pEnd2, 0, &i2); while( p1 || p2 ){ sqlite3_int64 iDiff = DOCID_CMP(i1, i2); if( p2 && p1 && iDiff==0 ){ fts3PutDeltaVarint3(&p, bDescDoclist, &iPrev, &bFirstOut, i1); fts3PoslistMerge(&p, &p1, &p2); fts3GetDeltaVarint3(&p1, pEnd1, bDescDoclist, &i1); fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2); }else if( !p2 || (p1 && iDiff<0) ){ fts3PutDeltaVarint3(&p, bDescDoclist, &iPrev, &bFirstOut, i1); fts3PoslistCopy(&p, &p1); fts3GetDeltaVarint3(&p1, pEnd1, bDescDoclist, &i1); }else{ fts3PutDeltaVarint3(&p, bDescDoclist, &iPrev, &bFirstOut, i2); fts3PoslistCopy(&p, &p2); fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2); } } *paOut = aOut; *pnOut = (int)(p-aOut); assert( *pnOut<=n1+n2+FTS3_VARINT_MAX-1 ); return SQLITE_OK; } /* ** This function does a "phrase" merge of two doclists. In a phrase merge, ** the output contains a copy of each position from the right-hand input ** doclist for which there is a position in the left-hand input doclist ** exactly nDist tokens before it. ** ** If the docids in the input doclists are sorted in ascending order, ** parameter bDescDoclist should be false. If they are sorted in ascending ** order, it should be passed a non-zero value. ** ** The right-hand input doclist is overwritten by this function. */ static int fts3DoclistPhraseMerge( int bDescDoclist, /* True if arguments are desc */ int nDist, /* Distance from left to right (1=adjacent) */ char *aLeft, int nLeft, /* Left doclist */ char **paRight, int *pnRight /* IN/OUT: Right/output doclist */ ){ sqlite3_int64 i1 = 0; sqlite3_int64 i2 = 0; sqlite3_int64 iPrev = 0; char *aRight = *paRight; char *pEnd1 = &aLeft[nLeft]; char *pEnd2 = &aRight[*pnRight]; char *p1 = aLeft; char *p2 = aRight; char *p; int bFirstOut = 0; char *aOut; assert( nDist>0 ); if( bDescDoclist ){ aOut = sqlite3_malloc(*pnRight + FTS3_VARINT_MAX); if( aOut==0 ) return SQLITE_NOMEM; }else{ aOut = aRight; } p = aOut; fts3GetDeltaVarint3(&p1, pEnd1, 0, &i1); fts3GetDeltaVarint3(&p2, pEnd2, 0, &i2); while( p1 && p2 ){ sqlite3_int64 iDiff = DOCID_CMP(i1, i2); if( iDiff==0 ){ char *pSave = p; sqlite3_int64 iPrevSave = iPrev; int bFirstOutSave = bFirstOut; fts3PutDeltaVarint3(&p, bDescDoclist, &iPrev, &bFirstOut, i1); if( 0==fts3PoslistPhraseMerge(&p, nDist, 0, 1, &p1, &p2) ){ p = pSave; iPrev = iPrevSave; bFirstOut = bFirstOutSave; } fts3GetDeltaVarint3(&p1, pEnd1, bDescDoclist, &i1); fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2); }else if( iDiff<0 ){ fts3PoslistCopy(0, &p1); fts3GetDeltaVarint3(&p1, pEnd1, bDescDoclist, &i1); }else{ fts3PoslistCopy(0, &p2); fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2); } } *pnRight = (int)(p - aOut); if( bDescDoclist ){ sqlite3_free(aRight); *paRight = aOut; } return SQLITE_OK; } /* ** Argument pList points to a position list nList bytes in size. This ** function checks to see if the position list contains any entries for ** a token in position 0 (of any column). If so, it writes argument iDelta ** to the output buffer pOut, followed by a position list consisting only ** of the entries from pList at position 0, and terminated by an 0x00 byte. ** The value returned is the number of bytes written to pOut (if any). */ int sqlite3Fts3FirstFilter( sqlite3_int64 iDelta, /* Varint that may be written to pOut */ char *pList, /* Position list (no 0x00 term) */ int nList, /* Size of pList in bytes */ char *pOut /* Write output here */ ){ int nOut = 0; int bWritten = 0; /* True once iDelta has been written */ char *p = pList; char *pEnd = &pList[nList]; if( *p!=0x01 ){ if( *p==0x02 ){ nOut += sqlite3Fts3PutVarint(&pOut[nOut], iDelta); pOut[nOut++] = 0x02; bWritten = 1; } fts3ColumnlistCopy(0, &p); } while( paaOutput); i++){ if( pTS->aaOutput[i] ){ if( !aOut ){ aOut = pTS->aaOutput[i]; nOut = pTS->anOutput[i]; pTS->aaOutput[i] = 0; }else{ int nNew; char *aNew; int rc = fts3DoclistOrMerge(p->bDescIdx, pTS->aaOutput[i], pTS->anOutput[i], aOut, nOut, &aNew, &nNew ); if( rc!=SQLITE_OK ){ sqlite3_free(aOut); return rc; } sqlite3_free(pTS->aaOutput[i]); sqlite3_free(aOut); pTS->aaOutput[i] = 0; aOut = aNew; nOut = nNew; } } } pTS->aaOutput[0] = aOut; pTS->anOutput[0] = nOut; return SQLITE_OK; } /* ** Merge the doclist aDoclist/nDoclist into the TermSelect object passed ** as the first argument. The merge is an "OR" merge (see function ** fts3DoclistOrMerge() for details). ** ** This function is called with the doclist for each term that matches ** a queried prefix. It merges all these doclists into one, the doclist ** for the specified prefix. Since there can be a very large number of ** doclists to merge, the merging is done pair-wise using the TermSelect ** object. ** ** This function returns SQLITE_OK if the merge is successful, or an ** SQLite error code (SQLITE_NOMEM) if an error occurs. */ static int fts3TermSelectMerge( Fts3Table *p, /* FTS table handle */ TermSelect *pTS, /* TermSelect object to merge into */ char *aDoclist, /* Pointer to doclist */ int nDoclist /* Size of aDoclist in bytes */ ){ if( pTS->aaOutput[0]==0 ){ /* If this is the first term selected, copy the doclist to the output ** buffer using memcpy(). ** ** Add FTS3_VARINT_MAX bytes of unused space to the end of the ** allocation. This is so as to ensure that the buffer is big enough ** to hold the current doclist AND'd with any other doclist. If the ** doclists are stored in order=ASC order, this padding would not be ** required (since the size of [doclistA AND doclistB] is always less ** than or equal to the size of [doclistA] in that case). But this is ** not true for order=DESC. For example, a doclist containing (1, -1) ** may be smaller than (-1), as in the first example the -1 may be stored ** as a single-byte delta, whereas in the second it must be stored as a ** FTS3_VARINT_MAX byte varint. ** ** Similar padding is added in the fts3DoclistOrMerge() function. */ pTS->aaOutput[0] = sqlite3_malloc(nDoclist + FTS3_VARINT_MAX + 1); pTS->anOutput[0] = nDoclist; if( pTS->aaOutput[0] ){ memcpy(pTS->aaOutput[0], aDoclist, nDoclist); }else{ return SQLITE_NOMEM; } }else{ char *aMerge = aDoclist; int nMerge = nDoclist; int iOut; for(iOut=0; iOutaaOutput); iOut++){ if( pTS->aaOutput[iOut]==0 ){ assert( iOut>0 ); pTS->aaOutput[iOut] = aMerge; pTS->anOutput[iOut] = nMerge; break; }else{ char *aNew; int nNew; int rc = fts3DoclistOrMerge(p->bDescIdx, aMerge, nMerge, pTS->aaOutput[iOut], pTS->anOutput[iOut], &aNew, &nNew ); if( rc!=SQLITE_OK ){ if( aMerge!=aDoclist ) sqlite3_free(aMerge); return rc; } if( aMerge!=aDoclist ) sqlite3_free(aMerge); sqlite3_free(pTS->aaOutput[iOut]); pTS->aaOutput[iOut] = 0; aMerge = aNew; nMerge = nNew; if( (iOut+1)==SizeofArray(pTS->aaOutput) ){ pTS->aaOutput[iOut] = aMerge; pTS->anOutput[iOut] = nMerge; } } } } return SQLITE_OK; } /* ** Append SegReader object pNew to the end of the pCsr->apSegment[] array. */ static int fts3SegReaderCursorAppend( Fts3MultiSegReader *pCsr, Fts3SegReader *pNew ){ if( (pCsr->nSegment%16)==0 ){ Fts3SegReader **apNew; int nByte = (pCsr->nSegment + 16)*sizeof(Fts3SegReader*); apNew = (Fts3SegReader **)sqlite3_realloc(pCsr->apSegment, nByte); if( !apNew ){ sqlite3Fts3SegReaderFree(pNew); return SQLITE_NOMEM; } pCsr->apSegment = apNew; } pCsr->apSegment[pCsr->nSegment++] = pNew; return SQLITE_OK; } /* ** Add seg-reader objects to the Fts3MultiSegReader object passed as the ** 8th argument. ** ** This function returns SQLITE_OK if successful, or an SQLite error code ** otherwise. */ static int fts3SegReaderCursor( Fts3Table *p, /* FTS3 table handle */ int iLangid, /* Language id */ int iIndex, /* Index to search (from 0 to p->nIndex-1) */ int iLevel, /* Level of segments to scan */ const char *zTerm, /* Term to query for */ int nTerm, /* Size of zTerm in bytes */ int isPrefix, /* True for a prefix search */ int isScan, /* True to scan from zTerm to EOF */ Fts3MultiSegReader *pCsr /* Cursor object to populate */ ){ int rc = SQLITE_OK; /* Error code */ sqlite3_stmt *pStmt = 0; /* Statement to iterate through segments */ int rc2; /* Result of sqlite3_reset() */ /* If iLevel is less than 0 and this is not a scan, include a seg-reader ** for the pending-terms. If this is a scan, then this call must be being ** made by an fts4aux module, not an FTS table. In this case calling ** Fts3SegReaderPending might segfault, as the data structures used by ** fts4aux are not completely populated. So it's easiest to filter these ** calls out here. */ if( iLevel<0 && p->aIndex ){ Fts3SegReader *pSeg = 0; rc = sqlite3Fts3SegReaderPending(p, iIndex, zTerm, nTerm, isPrefix||isScan, &pSeg); if( rc==SQLITE_OK && pSeg ){ rc = fts3SegReaderCursorAppend(pCsr, pSeg); } } if( iLevel!=FTS3_SEGCURSOR_PENDING ){ if( rc==SQLITE_OK ){ rc = sqlite3Fts3AllSegdirs(p, iLangid, iIndex, iLevel, &pStmt); } while( rc==SQLITE_OK && SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){ Fts3SegReader *pSeg = 0; /* Read the values returned by the SELECT into local variables. */ sqlite3_int64 iStartBlock = sqlite3_column_int64(pStmt, 1); sqlite3_int64 iLeavesEndBlock = sqlite3_column_int64(pStmt, 2); sqlite3_int64 iEndBlock = sqlite3_column_int64(pStmt, 3); int nRoot = sqlite3_column_bytes(pStmt, 4); char const *zRoot = sqlite3_column_blob(pStmt, 4); /* If zTerm is not NULL, and this segment is not stored entirely on its ** root node, the range of leaves scanned can be reduced. Do this. */ if( iStartBlock && zTerm ){ sqlite3_int64 *pi = (isPrefix ? &iLeavesEndBlock : 0); rc = fts3SelectLeaf(p, zTerm, nTerm, zRoot, nRoot, &iStartBlock, pi); if( rc!=SQLITE_OK ) goto finished; if( isPrefix==0 && isScan==0 ) iLeavesEndBlock = iStartBlock; } rc = sqlite3Fts3SegReaderNew(pCsr->nSegment+1, (isPrefix==0 && isScan==0), iStartBlock, iLeavesEndBlock, iEndBlock, zRoot, nRoot, &pSeg ); if( rc!=SQLITE_OK ) goto finished; rc = fts3SegReaderCursorAppend(pCsr, pSeg); } } finished: rc2 = sqlite3_reset(pStmt); if( rc==SQLITE_DONE ) rc = rc2; return rc; } /* ** Set up a cursor object for iterating through a full-text index or a ** single level therein. */ int sqlite3Fts3SegReaderCursor( Fts3Table *p, /* FTS3 table handle */ int iLangid, /* Language-id to search */ int iIndex, /* Index to search (from 0 to p->nIndex-1) */ int iLevel, /* Level of segments to scan */ const char *zTerm, /* Term to query for */ int nTerm, /* Size of zTerm in bytes */ int isPrefix, /* True for a prefix search */ int isScan, /* True to scan from zTerm to EOF */ Fts3MultiSegReader *pCsr /* Cursor object to populate */ ){ assert( iIndex>=0 && iIndexnIndex ); assert( iLevel==FTS3_SEGCURSOR_ALL || iLevel==FTS3_SEGCURSOR_PENDING || iLevel>=0 ); assert( iLevelbase.pVtab; if( isPrefix ){ for(i=1; bFound==0 && inIndex; i++){ if( p->aIndex[i].nPrefix==nTerm ){ bFound = 1; rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, i, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 0, 0, pSegcsr ); pSegcsr->bLookup = 1; } } for(i=1; bFound==0 && inIndex; i++){ if( p->aIndex[i].nPrefix==nTerm+1 ){ bFound = 1; rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, i, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 1, 0, pSegcsr ); if( rc==SQLITE_OK ){ rc = fts3SegReaderCursorAddZero( p, pCsr->iLangid, zTerm, nTerm, pSegcsr ); } } } } if( bFound==0 ){ rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, 0, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, 0, pSegcsr ); pSegcsr->bLookup = !isPrefix; } } *ppSegcsr = pSegcsr; return rc; } /* ** Free an Fts3MultiSegReader allocated by fts3TermSegReaderCursor(). */ static void fts3SegReaderCursorFree(Fts3MultiSegReader *pSegcsr){ sqlite3Fts3SegReaderFinish(pSegcsr); sqlite3_free(pSegcsr); } /* ** This function retrieves the doclist for the specified term (or term ** prefix) from the database. */ static int fts3TermSelect( Fts3Table *p, /* Virtual table handle */ Fts3PhraseToken *pTok, /* Token to query for */ int iColumn, /* Column to query (or -ve for all columns) */ int *pnOut, /* OUT: Size of buffer at *ppOut */ char **ppOut /* OUT: Malloced result buffer */ ){ int rc; /* Return code */ Fts3MultiSegReader *pSegcsr; /* Seg-reader cursor for this term */ TermSelect tsc; /* Object for pair-wise doclist merging */ Fts3SegFilter filter; /* Segment term filter configuration */ pSegcsr = pTok->pSegcsr; memset(&tsc, 0, sizeof(TermSelect)); filter.flags = FTS3_SEGMENT_IGNORE_EMPTY | FTS3_SEGMENT_REQUIRE_POS | (pTok->isPrefix ? FTS3_SEGMENT_PREFIX : 0) | (pTok->bFirst ? FTS3_SEGMENT_FIRST : 0) | (iColumnnColumn ? FTS3_SEGMENT_COLUMN_FILTER : 0); filter.iCol = iColumn; filter.zTerm = pTok->z; filter.nTerm = pTok->n; rc = sqlite3Fts3SegReaderStart(p, pSegcsr, &filter); while( SQLITE_OK==rc && SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, pSegcsr)) ){ rc = fts3TermSelectMerge(p, &tsc, pSegcsr->aDoclist, pSegcsr->nDoclist); } if( rc==SQLITE_OK ){ rc = fts3TermSelectFinishMerge(p, &tsc); } if( rc==SQLITE_OK ){ *ppOut = tsc.aaOutput[0]; *pnOut = tsc.anOutput[0]; }else{ int i; for(i=0; ipSegcsr = 0; return rc; } /* ** This function counts the total number of docids in the doclist stored ** in buffer aList[], size nList bytes. ** ** If the isPoslist argument is true, then it is assumed that the doclist ** contains a position-list following each docid. Otherwise, it is assumed ** that the doclist is simply a list of docids stored as delta encoded ** varints. */ static int fts3DoclistCountDocids(char *aList, int nList){ int nDoc = 0; /* Return value */ if( aList ){ char *aEnd = &aList[nList]; /* Pointer to one byte after EOF */ char *p = aList; /* Cursor */ while( peSearch==FTS3_DOCID_SEARCH || pCsr->eSearch==FTS3_FULLSCAN_SEARCH ){ if( SQLITE_ROW!=sqlite3_step(pCsr->pStmt) ){ pCsr->isEof = 1; rc = sqlite3_reset(pCsr->pStmt); }else{ pCsr->iPrevId = sqlite3_column_int64(pCsr->pStmt, 0); rc = SQLITE_OK; } }else{ rc = fts3EvalNext((Fts3Cursor *)pCursor); } assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); return rc; } /* ** The following are copied from sqliteInt.h. ** ** Constants for the largest and smallest possible 64-bit signed integers. ** These macros are designed to work correctly on both 32-bit and 64-bit ** compilers. */ #ifndef SQLITE_AMALGAMATION # define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32)) # define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) #endif /* ** If the numeric type of argument pVal is "integer", then return it ** converted to a 64-bit signed integer. Otherwise, return a copy of ** the second parameter, iDefault. */ static sqlite3_int64 fts3DocidRange(sqlite3_value *pVal, i64 iDefault){ if( pVal ){ int eType = sqlite3_value_numeric_type(pVal); if( eType==SQLITE_INTEGER ){ return sqlite3_value_int64(pVal); } } return iDefault; } /* ** This is the xFilter interface for the virtual table. See ** the virtual table xFilter method documentation for additional ** information. ** ** If idxNum==FTS3_FULLSCAN_SEARCH then do a full table scan against ** the %_content table. ** ** If idxNum==FTS3_DOCID_SEARCH then do a docid lookup for a single entry ** in the %_content table. ** ** If idxNum>=FTS3_FULLTEXT_SEARCH then use the full text index. The ** column on the left-hand side of the MATCH operator is column ** number idxNum-FTS3_FULLTEXT_SEARCH, 0 indexed. argv[0] is the right-hand ** side of the MATCH operator. */ static int fts3FilterMethod( sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ int idxNum, /* Strategy index */ const char *idxStr, /* Unused */ int nVal, /* Number of elements in apVal */ sqlite3_value **apVal /* Arguments for the indexing scheme */ ){ int rc = SQLITE_OK; char *zSql; /* SQL statement used to access %_content */ int eSearch; Fts3Table *p = (Fts3Table *)pCursor->pVtab; Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; sqlite3_value *pCons = 0; /* The MATCH or rowid constraint, if any */ sqlite3_value *pLangid = 0; /* The "langid = ?" constraint, if any */ sqlite3_value *pDocidGe = 0; /* The "docid >= ?" constraint, if any */ sqlite3_value *pDocidLe = 0; /* The "docid <= ?" constraint, if any */ int iIdx; UNUSED_PARAMETER(idxStr); UNUSED_PARAMETER(nVal); eSearch = (idxNum & 0x0000FFFF); assert( eSearch>=0 && eSearch<=(FTS3_FULLTEXT_SEARCH+p->nColumn) ); assert( p->pSegments==0 ); /* Collect arguments into local variables */ iIdx = 0; if( eSearch!=FTS3_FULLSCAN_SEARCH ) pCons = apVal[iIdx++]; if( idxNum & FTS3_HAVE_LANGID ) pLangid = apVal[iIdx++]; if( idxNum & FTS3_HAVE_DOCID_GE ) pDocidGe = apVal[iIdx++]; if( idxNum & FTS3_HAVE_DOCID_LE ) pDocidLe = apVal[iIdx++]; assert( iIdx==nVal ); /* In case the cursor has been used before, clear it now. */ sqlite3_finalize(pCsr->pStmt); sqlite3_free(pCsr->aDoclist); sqlite3Fts3MIBufferFree(pCsr->pMIBuffer); sqlite3Fts3ExprFree(pCsr->pExpr); memset(&pCursor[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor)); /* Set the lower and upper bounds on docids to return */ pCsr->iMinDocid = fts3DocidRange(pDocidGe, SMALLEST_INT64); pCsr->iMaxDocid = fts3DocidRange(pDocidLe, LARGEST_INT64); if( idxStr ){ pCsr->bDesc = (idxStr[0]=='D'); }else{ pCsr->bDesc = p->bDescIdx; } pCsr->eSearch = (i16)eSearch; if( eSearch!=FTS3_DOCID_SEARCH && eSearch!=FTS3_FULLSCAN_SEARCH ){ int iCol = eSearch-FTS3_FULLTEXT_SEARCH; const char *zQuery = (const char *)sqlite3_value_text(pCons); if( zQuery==0 && sqlite3_value_type(pCons)!=SQLITE_NULL ){ return SQLITE_NOMEM; } pCsr->iLangid = 0; if( pLangid ) pCsr->iLangid = sqlite3_value_int(pLangid); assert( p->base.zErrMsg==0 ); rc = sqlite3Fts3ExprParse(p->pTokenizer, pCsr->iLangid, p->azColumn, p->bFts4, p->nColumn, iCol, zQuery, -1, &pCsr->pExpr, &p->base.zErrMsg ); if( rc!=SQLITE_OK ){ return rc; } rc = fts3EvalStart(pCsr); sqlite3Fts3SegmentsClose(p); if( rc!=SQLITE_OK ) return rc; pCsr->pNextId = pCsr->aDoclist; pCsr->iPrevId = 0; } /* Compile a SELECT statement for this cursor. For a full-table-scan, the ** statement loops through all rows of the %_content table. For a ** full-text query or docid lookup, the statement retrieves a single ** row by docid. */ if( eSearch==FTS3_FULLSCAN_SEARCH ){ if( pDocidGe || pDocidLe ){ zSql = sqlite3_mprintf( "SELECT %s WHERE rowid BETWEEN %lld AND %lld ORDER BY rowid %s", p->zReadExprlist, pCsr->iMinDocid, pCsr->iMaxDocid, (pCsr->bDesc ? "DESC" : "ASC") ); }else{ zSql = sqlite3_mprintf("SELECT %s ORDER BY rowid %s", p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC") ); } if( zSql ){ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); sqlite3_free(zSql); }else{ rc = SQLITE_NOMEM; } }else if( eSearch==FTS3_DOCID_SEARCH ){ rc = fts3CursorSeekStmt(pCsr, &pCsr->pStmt); if( rc==SQLITE_OK ){ rc = sqlite3_bind_value(pCsr->pStmt, 1, pCons); } } if( rc!=SQLITE_OK ) return rc; return fts3NextMethod(pCursor); } /* ** This is the xEof method of the virtual table. SQLite calls this ** routine to find out if it has reached the end of a result set. */ static int fts3EofMethod(sqlite3_vtab_cursor *pCursor){ return ((Fts3Cursor *)pCursor)->isEof; } /* ** This is the xRowid method. The SQLite core calls this routine to ** retrieve the rowid for the current row of the result set. fts3 ** exposes %_content.docid as the rowid for the virtual table. The ** rowid should be written to *pRowid. */ static int fts3RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ Fts3Cursor *pCsr = (Fts3Cursor *) pCursor; *pRowid = pCsr->iPrevId; return SQLITE_OK; } /* ** This is the xColumn method, called by SQLite to request a value from ** the row that the supplied cursor currently points to. ** ** If: ** ** (iCol < p->nColumn) -> The value of the iCol'th user column. ** (iCol == p->nColumn) -> Magic column with the same name as the table. ** (iCol == p->nColumn+1) -> Docid column ** (iCol == p->nColumn+2) -> Langid column */ static int fts3ColumnMethod( sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ int iCol /* Index of column to read value from */ ){ int rc = SQLITE_OK; /* Return Code */ Fts3Cursor *pCsr = (Fts3Cursor *) pCursor; Fts3Table *p = (Fts3Table *)pCursor->pVtab; /* The column value supplied by SQLite must be in range. */ assert( iCol>=0 && iCol<=p->nColumn+2 ); if( iCol==p->nColumn+1 ){ /* This call is a request for the "docid" column. Since "docid" is an ** alias for "rowid", use the xRowid() method to obtain the value. */ sqlite3_result_int64(pCtx, pCsr->iPrevId); }else if( iCol==p->nColumn ){ /* The extra column whose name is the same as the table. ** Return a blob which is a pointer to the cursor. */ sqlite3_result_blob(pCtx, &pCsr, sizeof(pCsr), SQLITE_TRANSIENT); }else if( iCol==p->nColumn+2 && pCsr->pExpr ){ sqlite3_result_int64(pCtx, pCsr->iLangid); }else{ /* The requested column is either a user column (one that contains ** indexed data), or the language-id column. */ rc = fts3CursorSeek(0, pCsr); if( rc==SQLITE_OK ){ if( iCol==p->nColumn+2 ){ int iLangid = 0; if( p->zLanguageid ){ iLangid = sqlite3_column_int(pCsr->pStmt, p->nColumn+1); } sqlite3_result_int(pCtx, iLangid); }else if( sqlite3_data_count(pCsr->pStmt)>(iCol+1) ){ sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); } } } assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); return rc; } /* ** This function is the implementation of the xUpdate callback used by ** FTS3 virtual tables. It is invoked by SQLite each time a row is to be ** inserted, updated or deleted. */ static int fts3UpdateMethod( sqlite3_vtab *pVtab, /* Virtual table handle */ int nArg, /* Size of argument array */ sqlite3_value **apVal, /* Array of arguments */ sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */ ){ return sqlite3Fts3UpdateMethod(pVtab, nArg, apVal, pRowid); } /* ** Implementation of xSync() method. Flush the contents of the pending-terms ** hash-table to the database. */ static int fts3SyncMethod(sqlite3_vtab *pVtab){ /* Following an incremental-merge operation, assuming that the input ** segments are not completely consumed (the usual case), they are updated ** in place to remove the entries that have already been merged. This ** involves updating the leaf block that contains the smallest unmerged ** entry and each block (if any) between the leaf and the root node. So ** if the height of the input segment b-trees is N, and input segments ** are merged eight at a time, updating the input segments at the end ** of an incremental-merge requires writing (8*(1+N)) blocks. N is usually ** small - often between 0 and 2. So the overhead of the incremental ** merge is somewhere between 8 and 24 blocks. To avoid this overhead ** dwarfing the actual productive work accomplished, the incremental merge ** is only attempted if it will write at least 64 leaf blocks. Hence ** nMinMerge. ** ** Of course, updating the input segments also involves deleting a bunch ** of blocks from the segments table. But this is not considered overhead ** as it would also be required by a crisis-merge that used the same input ** segments. */ const u32 nMinMerge = 64; /* Minimum amount of incr-merge work to do */ Fts3Table *p = (Fts3Table*)pVtab; int rc = sqlite3Fts3PendingTermsFlush(p); if( rc==SQLITE_OK && p->nLeafAdd>(nMinMerge/16) && p->nAutoincrmerge && p->nAutoincrmerge!=0xff ){ int mxLevel = 0; /* Maximum relative level value in db */ int A; /* Incr-merge parameter A */ rc = sqlite3Fts3MaxLevel(p, &mxLevel); assert( rc==SQLITE_OK || mxLevel==0 ); A = p->nLeafAdd * mxLevel; A += (A/2); if( A>(int)nMinMerge ) rc = sqlite3Fts3Incrmerge(p, A, p->nAutoincrmerge); } sqlite3Fts3SegmentsClose(p); return rc; } /* ** If it is currently unknown whether or not the FTS table has an %_stat ** table (if p->bHasStat==2), attempt to determine this (set p->bHasStat ** to 0 or 1). Return SQLITE_OK if successful, or an SQLite error code ** if an error occurs. */ static int fts3SetHasStat(Fts3Table *p){ int rc = SQLITE_OK; if( p->bHasStat==2 ){ const char *zFmt ="SELECT 1 FROM %Q.sqlite_master WHERE tbl_name='%q_stat'"; char *zSql = sqlite3_mprintf(zFmt, p->zDb, p->zName); if( zSql ){ sqlite3_stmt *pStmt = 0; rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); if( rc==SQLITE_OK ){ int bHasStat = (sqlite3_step(pStmt)==SQLITE_ROW); rc = sqlite3_finalize(pStmt); if( rc==SQLITE_OK ) p->bHasStat = bHasStat; } sqlite3_free(zSql); }else{ rc = SQLITE_NOMEM; } } return rc; } /* ** Implementation of xBegin() method. */ static int fts3BeginMethod(sqlite3_vtab *pVtab){ Fts3Table *p = (Fts3Table*)pVtab; UNUSED_PARAMETER(pVtab); assert( p->pSegments==0 ); assert( p->nPendingData==0 ); assert( p->inTransaction!=1 ); TESTONLY( p->inTransaction = 1 ); TESTONLY( p->mxSavepoint = -1; ); p->nLeafAdd = 0; return fts3SetHasStat(p); } /* ** Implementation of xCommit() method. This is a no-op. The contents of ** the pending-terms hash-table have already been flushed into the database ** by fts3SyncMethod(). */ static int fts3CommitMethod(sqlite3_vtab *pVtab){ TESTONLY( Fts3Table *p = (Fts3Table*)pVtab ); UNUSED_PARAMETER(pVtab); assert( p->nPendingData==0 ); assert( p->inTransaction!=0 ); assert( p->pSegments==0 ); TESTONLY( p->inTransaction = 0 ); TESTONLY( p->mxSavepoint = -1; ); return SQLITE_OK; } /* ** Implementation of xRollback(). Discard the contents of the pending-terms ** hash-table. Any changes made to the database are reverted by SQLite. */ static int fts3RollbackMethod(sqlite3_vtab *pVtab){ Fts3Table *p = (Fts3Table*)pVtab; sqlite3Fts3PendingTermsClear(p); assert( p->inTransaction!=0 ); TESTONLY( p->inTransaction = 0 ); TESTONLY( p->mxSavepoint = -1; ); return SQLITE_OK; } /* ** When called, *ppPoslist must point to the byte immediately following the ** end of a position-list. i.e. ( (*ppPoslist)[-1]==POS_END ). This function ** moves *ppPoslist so that it instead points to the first byte of the ** same position list. */ static void fts3ReversePoslist(char *pStart, char **ppPoslist){ char *p = &(*ppPoslist)[-2]; char c = 0; /* Skip backwards passed any trailing 0x00 bytes added by NearTrim() */ while( p>pStart && (c=*p--)==0 ); /* Search backwards for a varint with value zero (the end of the previous ** poslist). This is an 0x00 byte preceded by some byte that does not ** have the 0x80 bit set. */ while( p>pStart && (*p & 0x80) | c ){ c = *p--; } assert( p==pStart || c==0 ); /* At this point p points to that preceding byte without the 0x80 bit ** set. So to find the start of the poslist, skip forward 2 bytes then ** over a varint. ** ** Normally. The other case is that p==pStart and the poslist to return ** is the first in the doclist. In this case do not skip forward 2 bytes. ** The second part of the if condition (c==0 && *ppPoslist>&p[2]) ** is required for cases where the first byte of a doclist and the ** doclist is empty. For example, if the first docid is 10, a doclist ** that begins with: ** ** 0x0A 0x00 */ if( p>pStart || (c==0 && *ppPoslist>&p[2]) ){ p = &p[2]; } while( *p++&0x80 ); *ppPoslist = p; } /* ** Helper function used by the implementation of the overloaded snippet(), ** offsets() and optimize() SQL functions. ** ** If the value passed as the third argument is a blob of size ** sizeof(Fts3Cursor*), then the blob contents are copied to the ** output variable *ppCsr and SQLITE_OK is returned. Otherwise, an error ** message is written to context pContext and SQLITE_ERROR returned. The ** string passed via zFunc is used as part of the error message. */ static int fts3FunctionArg( sqlite3_context *pContext, /* SQL function call context */ const char *zFunc, /* Function name */ sqlite3_value *pVal, /* argv[0] passed to function */ Fts3Cursor **ppCsr /* OUT: Store cursor handle here */ ){ Fts3Cursor *pRet; if( sqlite3_value_type(pVal)!=SQLITE_BLOB || sqlite3_value_bytes(pVal)!=sizeof(Fts3Cursor *) ){ char *zErr = sqlite3_mprintf("illegal first argument to %s", zFunc); sqlite3_result_error(pContext, zErr, -1); sqlite3_free(zErr); return SQLITE_ERROR; } memcpy(&pRet, sqlite3_value_blob(pVal), sizeof(Fts3Cursor *)); *ppCsr = pRet; return SQLITE_OK; } /* ** Implementation of the snippet() function for FTS3 */ static void fts3SnippetFunc( sqlite3_context *pContext, /* SQLite function call context */ int nVal, /* Size of apVal[] array */ sqlite3_value **apVal /* Array of arguments */ ){ Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */ const char *zStart = ""; const char *zEnd = ""; const char *zEllipsis = "..."; int iCol = -1; int nToken = 15; /* Default number of tokens in snippet */ /* There must be at least one argument passed to this function (otherwise ** the non-overloaded version would have been called instead of this one). */ assert( nVal>=1 ); if( nVal>6 ){ sqlite3_result_error(pContext, "wrong number of arguments to function snippet()", -1); return; } if( fts3FunctionArg(pContext, "snippet", apVal[0], &pCsr) ) return; switch( nVal ){ case 6: nToken = sqlite3_value_int(apVal[5]); case 5: iCol = sqlite3_value_int(apVal[4]); case 4: zEllipsis = (const char*)sqlite3_value_text(apVal[3]); case 3: zEnd = (const char*)sqlite3_value_text(apVal[2]); case 2: zStart = (const char*)sqlite3_value_text(apVal[1]); } if( !zEllipsis || !zEnd || !zStart ){ sqlite3_result_error_nomem(pContext); }else if( nToken==0 ){ sqlite3_result_text(pContext, "", -1, SQLITE_STATIC); }else if( SQLITE_OK==fts3CursorSeek(pContext, pCsr) ){ sqlite3Fts3Snippet(pContext, pCsr, zStart, zEnd, zEllipsis, iCol, nToken); } } /* ** Implementation of the offsets() function for FTS3 */ static void fts3OffsetsFunc( sqlite3_context *pContext, /* SQLite function call context */ int nVal, /* Size of argument array */ sqlite3_value **apVal /* Array of arguments */ ){ Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */ UNUSED_PARAMETER(nVal); assert( nVal==1 ); if( fts3FunctionArg(pContext, "offsets", apVal[0], &pCsr) ) return; assert( pCsr ); if( SQLITE_OK==fts3CursorSeek(pContext, pCsr) ){ sqlite3Fts3Offsets(pContext, pCsr); } } /* ** Implementation of the special optimize() function for FTS3. This ** function merges all segments in the database to a single segment. ** Example usage is: ** ** SELECT optimize(t) FROM t LIMIT 1; ** ** where 't' is the name of an FTS3 table. */ static void fts3OptimizeFunc( sqlite3_context *pContext, /* SQLite function call context */ int nVal, /* Size of argument array */ sqlite3_value **apVal /* Array of arguments */ ){ int rc; /* Return code */ Fts3Table *p; /* Virtual table handle */ Fts3Cursor *pCursor; /* Cursor handle passed through apVal[0] */ UNUSED_PARAMETER(nVal); assert( nVal==1 ); if( fts3FunctionArg(pContext, "optimize", apVal[0], &pCursor) ) return; p = (Fts3Table *)pCursor->base.pVtab; assert( p ); rc = sqlite3Fts3Optimize(p); switch( rc ){ case SQLITE_OK: sqlite3_result_text(pContext, "Index optimized", -1, SQLITE_STATIC); break; case SQLITE_DONE: sqlite3_result_text(pContext, "Index already optimal", -1, SQLITE_STATIC); break; default: sqlite3_result_error_code(pContext, rc); break; } } /* ** Implementation of the matchinfo() function for FTS3 */ static void fts3MatchinfoFunc( sqlite3_context *pContext, /* SQLite function call context */ int nVal, /* Size of argument array */ sqlite3_value **apVal /* Array of arguments */ ){ Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */ assert( nVal==1 || nVal==2 ); if( SQLITE_OK==fts3FunctionArg(pContext, "matchinfo", apVal[0], &pCsr) ){ const char *zArg = 0; if( nVal>1 ){ zArg = (const char *)sqlite3_value_text(apVal[1]); } sqlite3Fts3Matchinfo(pContext, pCsr, zArg); } } /* ** This routine implements the xFindFunction method for the FTS3 ** virtual table. */ static int fts3FindFunctionMethod( sqlite3_vtab *pVtab, /* Virtual table handle */ int nArg, /* Number of SQL function arguments */ const char *zName, /* Name of SQL function */ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */ void **ppArg /* Unused */ ){ struct Overloaded { const char *zName; void (*xFunc)(sqlite3_context*,int,sqlite3_value**); } aOverload[] = { { "snippet", fts3SnippetFunc }, { "offsets", fts3OffsetsFunc }, { "optimize", fts3OptimizeFunc }, { "matchinfo", fts3MatchinfoFunc }, }; int i; /* Iterator variable */ UNUSED_PARAMETER(pVtab); UNUSED_PARAMETER(nArg); UNUSED_PARAMETER(ppArg); for(i=0; idb; /* Database connection */ int rc; /* Return Code */ /* At this point it must be known if the %_stat table exists or not. ** So bHasStat may not be 2. */ rc = fts3SetHasStat(p); /* As it happens, the pending terms table is always empty here. This is ** because an "ALTER TABLE RENAME TABLE" statement inside a transaction ** always opens a savepoint transaction. And the xSavepoint() method ** flushes the pending terms table. But leave the (no-op) call to ** PendingTermsFlush() in in case that changes. */ assert( p->nPendingData==0 ); if( rc==SQLITE_OK ){ rc = sqlite3Fts3PendingTermsFlush(p); } if( p->zContentTbl==0 ){ fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';", p->zDb, p->zName, zName ); } if( p->bHasDocsize ){ fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_docsize' RENAME TO '%q_docsize';", p->zDb, p->zName, zName ); } if( p->bHasStat ){ fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_stat' RENAME TO '%q_stat';", p->zDb, p->zName, zName ); } fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_segments' RENAME TO '%q_segments';", p->zDb, p->zName, zName ); fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_segdir' RENAME TO '%q_segdir';", p->zDb, p->zName, zName ); return rc; } /* ** The xSavepoint() method. ** ** Flush the contents of the pending-terms table to disk. */ static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ int rc = SQLITE_OK; UNUSED_PARAMETER(iSavepoint); assert( ((Fts3Table *)pVtab)->inTransaction ); assert( ((Fts3Table *)pVtab)->mxSavepoint < iSavepoint ); TESTONLY( ((Fts3Table *)pVtab)->mxSavepoint = iSavepoint ); if( ((Fts3Table *)pVtab)->bIgnoreSavepoint==0 ){ rc = fts3SyncMethod(pVtab); } return rc; } /* ** The xRelease() method. ** ** This is a no-op. */ static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ TESTONLY( Fts3Table *p = (Fts3Table*)pVtab ); UNUSED_PARAMETER(iSavepoint); UNUSED_PARAMETER(pVtab); assert( p->inTransaction ); assert( p->mxSavepoint >= iSavepoint ); TESTONLY( p->mxSavepoint = iSavepoint-1 ); return SQLITE_OK; } /* ** The xRollbackTo() method. ** ** Discard the contents of the pending terms table. */ static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ Fts3Table *p = (Fts3Table*)pVtab; UNUSED_PARAMETER(iSavepoint); assert( p->inTransaction ); assert( p->mxSavepoint >= iSavepoint ); TESTONLY( p->mxSavepoint = iSavepoint ); sqlite3Fts3PendingTermsClear(p); return SQLITE_OK; } static const sqlite3_module fts3Module = { /* iVersion */ 2, /* xCreate */ fts3CreateMethod, /* xConnect */ fts3ConnectMethod, /* xBestIndex */ fts3BestIndexMethod, /* xDisconnect */ fts3DisconnectMethod, /* xDestroy */ fts3DestroyMethod, /* xOpen */ fts3OpenMethod, /* xClose */ fts3CloseMethod, /* xFilter */ fts3FilterMethod, /* xNext */ fts3NextMethod, /* xEof */ fts3EofMethod, /* xColumn */ fts3ColumnMethod, /* xRowid */ fts3RowidMethod, /* xUpdate */ fts3UpdateMethod, /* xBegin */ fts3BeginMethod, /* xSync */ fts3SyncMethod, /* xCommit */ fts3CommitMethod, /* xRollback */ fts3RollbackMethod, /* xFindFunction */ fts3FindFunctionMethod, /* xRename */ fts3RenameMethod, /* xSavepoint */ fts3SavepointMethod, /* xRelease */ fts3ReleaseMethod, /* xRollbackTo */ fts3RollbackToMethod, }; /* ** This function is registered as the module destructor (called when an ** FTS3 enabled database connection is closed). It frees the memory ** allocated for the tokenizer hash table. */ static void hashDestroy(void *p){ Fts3Hash *pHash = (Fts3Hash *)p; sqlite3Fts3HashClear(pHash); sqlite3_free(pHash); } /* ** The fts3 built-in tokenizers - "simple", "porter" and "icu"- are ** implemented in files fts3_tokenizer1.c, fts3_porter.c and fts3_icu.c ** respectively. The following three forward declarations are for functions ** declared in these files used to retrieve the respective implementations. ** ** Calling sqlite3Fts3SimpleTokenizerModule() sets the value pointed ** to by the argument to point to the "simple" tokenizer implementation. ** And so on. */ void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule); void sqlite3Fts3PorterTokenizerModule(sqlite3_tokenizer_module const**ppModule); #ifndef SQLITE_DISABLE_FTS3_UNICODE void sqlite3Fts3UnicodeTokenizer(sqlite3_tokenizer_module const**ppModule); #endif #ifdef SQLITE_ENABLE_ICU void sqlite3Fts3IcuTokenizerModule(sqlite3_tokenizer_module const**ppModule); #endif /* ** Initialize the fts3 extension. If this extension is built as part ** of the sqlite library, then this function is called directly by ** SQLite. If fts3 is built as a dynamically loadable extension, this ** function is called by the sqlite3_extension_init() entry point. */ int sqlite3Fts3Init(sqlite3 *db){ int rc = SQLITE_OK; Fts3Hash *pHash = 0; const sqlite3_tokenizer_module *pSimple = 0; const sqlite3_tokenizer_module *pPorter = 0; #ifndef SQLITE_DISABLE_FTS3_UNICODE const sqlite3_tokenizer_module *pUnicode = 0; #endif #ifdef SQLITE_ENABLE_ICU const sqlite3_tokenizer_module *pIcu = 0; sqlite3Fts3IcuTokenizerModule(&pIcu); #endif #ifndef SQLITE_DISABLE_FTS3_UNICODE sqlite3Fts3UnicodeTokenizer(&pUnicode); #endif #ifdef SQLITE_TEST rc = sqlite3Fts3InitTerm(db); if( rc!=SQLITE_OK ) return rc; #endif rc = sqlite3Fts3InitAux(db); if( rc!=SQLITE_OK ) return rc; sqlite3Fts3SimpleTokenizerModule(&pSimple); sqlite3Fts3PorterTokenizerModule(&pPorter); /* Allocate and initialize the hash-table used to store tokenizers. */ pHash = sqlite3_malloc(sizeof(Fts3Hash)); if( !pHash ){ rc = SQLITE_NOMEM; }else{ sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1); } /* Load the built-in tokenizers into the hash table */ if( rc==SQLITE_OK ){ if( sqlite3Fts3HashInsert(pHash, "simple", 7, (void *)pSimple) || sqlite3Fts3HashInsert(pHash, "porter", 7, (void *)pPorter) #ifndef SQLITE_DISABLE_FTS3_UNICODE || sqlite3Fts3HashInsert(pHash, "unicode61", 10, (void *)pUnicode) #endif #ifdef SQLITE_ENABLE_ICU || (pIcu && sqlite3Fts3HashInsert(pHash, "icu", 4, (void *)pIcu)) #endif ){ rc = SQLITE_NOMEM; } } #ifdef SQLITE_TEST if( rc==SQLITE_OK ){ rc = sqlite3Fts3ExprInitTestInterface(db); } #endif /* Create the virtual table wrapper around the hash-table and overload ** the two scalar functions. If this is successful, register the ** module with sqlite. */ if( SQLITE_OK==rc && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer")) && SQLITE_OK==(rc = sqlite3_overload_function(db, "snippet", -1)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "offsets", 1)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 1)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 2)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "optimize", 1)) ){ rc = sqlite3_create_module_v2( db, "fts3", &fts3Module, (void *)pHash, hashDestroy ); if( rc==SQLITE_OK ){ rc = sqlite3_create_module_v2( db, "fts4", &fts3Module, (void *)pHash, 0 ); } if( rc==SQLITE_OK ){ rc = sqlite3Fts3InitTok(db, (void *)pHash); } return rc; } /* An error has occurred. Delete the hash table and return the error code. */ assert( rc!=SQLITE_OK ); if( pHash ){ sqlite3Fts3HashClear(pHash); sqlite3_free(pHash); } return rc; } /* ** Allocate an Fts3MultiSegReader for each token in the expression headed ** by pExpr. ** ** An Fts3SegReader object is a cursor that can seek or scan a range of ** entries within a single segment b-tree. An Fts3MultiSegReader uses multiple ** Fts3SegReader objects internally to provide an interface to seek or scan ** within the union of all segments of a b-tree. Hence the name. ** ** If the allocated Fts3MultiSegReader just seeks to a single entry in a ** segment b-tree (if the term is not a prefix or it is a prefix for which ** there exists prefix b-tree of the right length) then it may be traversed ** and merged incrementally. Otherwise, it has to be merged into an in-memory ** doclist and then traversed. */ static void fts3EvalAllocateReaders( Fts3Cursor *pCsr, /* FTS cursor handle */ Fts3Expr *pExpr, /* Allocate readers for this expression */ int *pnToken, /* OUT: Total number of tokens in phrase. */ int *pnOr, /* OUT: Total number of OR nodes in expr. */ int *pRc /* IN/OUT: Error code */ ){ if( pExpr && SQLITE_OK==*pRc ){ if( pExpr->eType==FTSQUERY_PHRASE ){ int i; int nToken = pExpr->pPhrase->nToken; *pnToken += nToken; for(i=0; ipPhrase->aToken[i]; int rc = fts3TermSegReaderCursor(pCsr, pToken->z, pToken->n, pToken->isPrefix, &pToken->pSegcsr ); if( rc!=SQLITE_OK ){ *pRc = rc; return; } } assert( pExpr->pPhrase->iDoclistToken==0 ); pExpr->pPhrase->iDoclistToken = -1; }else{ *pnOr += (pExpr->eType==FTSQUERY_OR); fts3EvalAllocateReaders(pCsr, pExpr->pLeft, pnToken, pnOr, pRc); fts3EvalAllocateReaders(pCsr, pExpr->pRight, pnToken, pnOr, pRc); } } } /* ** Arguments pList/nList contain the doclist for token iToken of phrase p. ** It is merged into the main doclist stored in p->doclist.aAll/nAll. ** ** This function assumes that pList points to a buffer allocated using ** sqlite3_malloc(). This function takes responsibility for eventually ** freeing the buffer. ** ** SQLITE_OK is returned if successful, or SQLITE_NOMEM if an error occurs. */ static int fts3EvalPhraseMergeToken( Fts3Table *pTab, /* FTS Table pointer */ Fts3Phrase *p, /* Phrase to merge pList/nList into */ int iToken, /* Token pList/nList corresponds to */ char *pList, /* Pointer to doclist */ int nList /* Number of bytes in pList */ ){ int rc = SQLITE_OK; assert( iToken!=p->iDoclistToken ); if( pList==0 ){ sqlite3_free(p->doclist.aAll); p->doclist.aAll = 0; p->doclist.nAll = 0; } else if( p->iDoclistToken<0 ){ p->doclist.aAll = pList; p->doclist.nAll = nList; } else if( p->doclist.aAll==0 ){ sqlite3_free(pList); } else { char *pLeft; char *pRight; int nLeft; int nRight; int nDiff; if( p->iDoclistTokendoclist.aAll; nLeft = p->doclist.nAll; pRight = pList; nRight = nList; nDiff = iToken - p->iDoclistToken; }else{ pRight = p->doclist.aAll; nRight = p->doclist.nAll; pLeft = pList; nLeft = nList; nDiff = p->iDoclistToken - iToken; } rc = fts3DoclistPhraseMerge( pTab->bDescIdx, nDiff, pLeft, nLeft, &pRight, &nRight ); sqlite3_free(pLeft); p->doclist.aAll = pRight; p->doclist.nAll = nRight; } if( iToken>p->iDoclistToken ) p->iDoclistToken = iToken; return rc; } /* ** Load the doclist for phrase p into p->doclist.aAll/nAll. The loaded doclist ** does not take deferred tokens into account. ** ** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code. */ static int fts3EvalPhraseLoad( Fts3Cursor *pCsr, /* FTS Cursor handle */ Fts3Phrase *p /* Phrase object */ ){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; int iToken; int rc = SQLITE_OK; for(iToken=0; rc==SQLITE_OK && iTokennToken; iToken++){ Fts3PhraseToken *pToken = &p->aToken[iToken]; assert( pToken->pDeferred==0 || pToken->pSegcsr==0 ); if( pToken->pSegcsr ){ int nThis = 0; char *pThis = 0; rc = fts3TermSelect(pTab, pToken, p->iColumn, &nThis, &pThis); if( rc==SQLITE_OK ){ rc = fts3EvalPhraseMergeToken(pTab, p, iToken, pThis, nThis); } } assert( pToken->pSegcsr==0 ); } return rc; } /* ** This function is called on each phrase after the position lists for ** any deferred tokens have been loaded into memory. It updates the phrases ** current position list to include only those positions that are really ** instances of the phrase (after considering deferred tokens). If this ** means that the phrase does not appear in the current row, doclist.pList ** and doclist.nList are both zeroed. ** ** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code. */ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ int iToken; /* Used to iterate through phrase tokens */ char *aPoslist = 0; /* Position list for deferred tokens */ int nPoslist = 0; /* Number of bytes in aPoslist */ int iPrev = -1; /* Token number of previous deferred token */ assert( pPhrase->doclist.bFreeList==0 ); for(iToken=0; iTokennToken; iToken++){ Fts3PhraseToken *pToken = &pPhrase->aToken[iToken]; Fts3DeferredToken *pDeferred = pToken->pDeferred; if( pDeferred ){ char *pList; int nList; int rc = sqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList); if( rc!=SQLITE_OK ) return rc; if( pList==0 ){ sqlite3_free(aPoslist); pPhrase->doclist.pList = 0; pPhrase->doclist.nList = 0; return SQLITE_OK; }else if( aPoslist==0 ){ aPoslist = pList; nPoslist = nList; }else{ char *aOut = pList; char *p1 = aPoslist; char *p2 = aOut; assert( iPrev>=0 ); fts3PoslistPhraseMerge(&aOut, iToken-iPrev, 0, 1, &p1, &p2); sqlite3_free(aPoslist); aPoslist = pList; nPoslist = (int)(aOut - aPoslist); if( nPoslist==0 ){ sqlite3_free(aPoslist); pPhrase->doclist.pList = 0; pPhrase->doclist.nList = 0; return SQLITE_OK; } } iPrev = iToken; } } if( iPrev>=0 ){ int nMaxUndeferred = pPhrase->iDoclistToken; if( nMaxUndeferred<0 ){ pPhrase->doclist.pList = aPoslist; pPhrase->doclist.nList = nPoslist; pPhrase->doclist.iDocid = pCsr->iPrevId; pPhrase->doclist.bFreeList = 1; }else{ int nDistance; char *p1; char *p2; char *aOut; if( nMaxUndeferred>iPrev ){ p1 = aPoslist; p2 = pPhrase->doclist.pList; nDistance = nMaxUndeferred - iPrev; }else{ p1 = pPhrase->doclist.pList; p2 = aPoslist; nDistance = iPrev - nMaxUndeferred; } aOut = (char *)sqlite3_malloc(nPoslist+8); if( !aOut ){ sqlite3_free(aPoslist); return SQLITE_NOMEM; } pPhrase->doclist.pList = aOut; if( fts3PoslistPhraseMerge(&aOut, nDistance, 0, 1, &p1, &p2) ){ pPhrase->doclist.bFreeList = 1; pPhrase->doclist.nList = (int)(aOut - pPhrase->doclist.pList); }else{ sqlite3_free(aOut); pPhrase->doclist.pList = 0; pPhrase->doclist.nList = 0; } sqlite3_free(aPoslist); } } return SQLITE_OK; } /* ** Maximum number of tokens a phrase may have to be considered for the ** incremental doclists strategy. */ #define MAX_INCR_PHRASE_TOKENS 4 /* ** This function is called for each Fts3Phrase in a full-text query ** expression to initialize the mechanism for returning rows. Once this ** function has been called successfully on an Fts3Phrase, it may be ** used with fts3EvalPhraseNext() to iterate through the matching docids. ** ** If parameter bOptOk is true, then the phrase may (or may not) use the ** incremental loading strategy. Otherwise, the entire doclist is loaded into ** memory within this call. ** ** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code. */ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; int rc = SQLITE_OK; /* Error code */ int i; /* Determine if doclists may be loaded from disk incrementally. This is ** possible if the bOptOk argument is true, the FTS doclists will be ** scanned in forward order, and the phrase consists of ** MAX_INCR_PHRASE_TOKENS or fewer tokens, none of which are are "^first" ** tokens or prefix tokens that cannot use a prefix-index. */ int bHaveIncr = 0; int bIncrOk = (bOptOk && pCsr->bDesc==pTab->bDescIdx && p->nToken<=MAX_INCR_PHRASE_TOKENS && p->nToken>0 #ifdef SQLITE_TEST && pTab->bNoIncrDoclist==0 #endif ); for(i=0; bIncrOk==1 && inToken; i++){ Fts3PhraseToken *pToken = &p->aToken[i]; if( pToken->bFirst || (pToken->pSegcsr!=0 && !pToken->pSegcsr->bLookup) ){ bIncrOk = 0; } if( pToken->pSegcsr ) bHaveIncr = 1; } if( bIncrOk && bHaveIncr ){ /* Use the incremental approach. */ int iCol = (p->iColumn >= pTab->nColumn ? -1 : p->iColumn); for(i=0; rc==SQLITE_OK && inToken; i++){ Fts3PhraseToken *pToken = &p->aToken[i]; Fts3MultiSegReader *pSegcsr = pToken->pSegcsr; if( pSegcsr ){ rc = sqlite3Fts3MsrIncrStart(pTab, pSegcsr, iCol, pToken->z, pToken->n); } } p->bIncr = 1; }else{ /* Load the full doclist for the phrase into memory. */ rc = fts3EvalPhraseLoad(pCsr, p); p->bIncr = 0; } assert( rc!=SQLITE_OK || p->nToken<1 || p->aToken[0].pSegcsr==0 || p->bIncr ); return rc; } /* ** This function is used to iterate backwards (from the end to start) ** through doclists. It is used by this module to iterate through phrase ** doclists in reverse and by the fts3_write.c module to iterate through ** pending-terms lists when writing to databases with "order=desc". ** ** The doclist may be sorted in ascending (parameter bDescIdx==0) or ** descending (parameter bDescIdx==1) order of docid. Regardless, this ** function iterates from the end of the doclist to the beginning. */ void sqlite3Fts3DoclistPrev( int bDescIdx, /* True if the doclist is desc */ char *aDoclist, /* Pointer to entire doclist */ int nDoclist, /* Length of aDoclist in bytes */ char **ppIter, /* IN/OUT: Iterator pointer */ sqlite3_int64 *piDocid, /* IN/OUT: Docid pointer */ int *pnList, /* OUT: List length pointer */ u8 *pbEof /* OUT: End-of-file flag */ ){ char *p = *ppIter; assert( nDoclist>0 ); assert( *pbEof==0 ); assert( p || *piDocid==0 ); assert( !p || (p>aDoclist && p<&aDoclist[nDoclist]) ); if( p==0 ){ sqlite3_int64 iDocid = 0; char *pNext = 0; char *pDocid = aDoclist; char *pEnd = &aDoclist[nDoclist]; int iMul = 1; while( pDocid0 ); assert( *pbEof==0 ); assert( p || *piDocid==0 ); assert( !p || (p>=aDoclist && p<=&aDoclist[nDoclist]) ); if( p==0 ){ p = aDoclist; p += sqlite3Fts3GetVarint(p, piDocid); }else{ fts3PoslistCopy(0, &p); while( p<&aDoclist[nDoclist] && *p==0 ) p++; if( p>=&aDoclist[nDoclist] ){ *pbEof = 1; }else{ sqlite3_int64 iVar; p += sqlite3Fts3GetVarint(p, &iVar); *piDocid += ((bDescIdx ? -1 : 1) * iVar); } } *ppIter = p; } /* ** Advance the iterator pDL to the next entry in pDL->aAll/nAll. Set *pbEof ** to true if EOF is reached. */ static void fts3EvalDlPhraseNext( Fts3Table *pTab, Fts3Doclist *pDL, u8 *pbEof ){ char *pIter; /* Used to iterate through aAll */ char *pEnd = &pDL->aAll[pDL->nAll]; /* 1 byte past end of aAll */ if( pDL->pNextDocid ){ pIter = pDL->pNextDocid; }else{ pIter = pDL->aAll; } if( pIter>=pEnd ){ /* We have already reached the end of this doclist. EOF. */ *pbEof = 1; }else{ sqlite3_int64 iDelta; pIter += sqlite3Fts3GetVarint(pIter, &iDelta); if( pTab->bDescIdx==0 || pDL->pNextDocid==0 ){ pDL->iDocid += iDelta; }else{ pDL->iDocid -= iDelta; } pDL->pList = pIter; fts3PoslistCopy(0, &pIter); pDL->nList = (int)(pIter - pDL->pList); /* pIter now points just past the 0x00 that terminates the position- ** list for document pDL->iDocid. However, if this position-list was ** edited in place by fts3EvalNearTrim(), then pIter may not actually ** point to the start of the next docid value. The following line deals ** with this case by advancing pIter past the zero-padding added by ** fts3EvalNearTrim(). */ while( pIterpNextDocid = pIter; assert( pIter>=&pDL->aAll[pDL->nAll] || *pIter ); *pbEof = 0; } } /* ** Helper type used by fts3EvalIncrPhraseNext() and incrPhraseTokenNext(). */ typedef struct TokenDoclist TokenDoclist; struct TokenDoclist { int bIgnore; sqlite3_int64 iDocid; char *pList; int nList; }; /* ** Token pToken is an incrementally loaded token that is part of a ** multi-token phrase. Advance it to the next matching document in the ** database and populate output variable *p with the details of the new ** entry. Or, if the iterator has reached EOF, set *pbEof to true. ** ** If an error occurs, return an SQLite error code. Otherwise, return ** SQLITE_OK. */ static int incrPhraseTokenNext( Fts3Table *pTab, /* Virtual table handle */ Fts3Phrase *pPhrase, /* Phrase to advance token of */ int iToken, /* Specific token to advance */ TokenDoclist *p, /* OUT: Docid and doclist for new entry */ u8 *pbEof /* OUT: True if iterator is at EOF */ ){ int rc = SQLITE_OK; if( pPhrase->iDoclistToken==iToken ){ assert( p->bIgnore==0 ); assert( pPhrase->aToken[iToken].pSegcsr==0 ); fts3EvalDlPhraseNext(pTab, &pPhrase->doclist, pbEof); p->pList = pPhrase->doclist.pList; p->nList = pPhrase->doclist.nList; p->iDocid = pPhrase->doclist.iDocid; }else{ Fts3PhraseToken *pToken = &pPhrase->aToken[iToken]; assert( pToken->pDeferred==0 ); assert( pToken->pSegcsr || pPhrase->iDoclistToken>=0 ); if( pToken->pSegcsr ){ assert( p->bIgnore==0 ); rc = sqlite3Fts3MsrIncrNext( pTab, pToken->pSegcsr, &p->iDocid, &p->pList, &p->nList ); if( p->pList==0 ) *pbEof = 1; }else{ p->bIgnore = 1; } } return rc; } /* ** The phrase iterator passed as the second argument: ** ** * features at least one token that uses an incremental doclist, and ** ** * does not contain any deferred tokens. ** ** Advance it to the next matching documnent in the database and populate ** the Fts3Doclist.pList and nList fields. ** ** If there is no "next" entry and no error occurs, then *pbEof is set to ** 1 before returning. Otherwise, if no error occurs and the iterator is ** successfully advanced, *pbEof is set to 0. ** ** If an error occurs, return an SQLite error code. Otherwise, return ** SQLITE_OK. */ static int fts3EvalIncrPhraseNext( Fts3Cursor *pCsr, /* FTS Cursor handle */ Fts3Phrase *p, /* Phrase object to advance to next docid */ u8 *pbEof /* OUT: Set to 1 if EOF */ ){ int rc = SQLITE_OK; Fts3Doclist *pDL = &p->doclist; Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; u8 bEof = 0; /* This is only called if it is guaranteed that the phrase has at least ** one incremental token. In which case the bIncr flag is set. */ assert( p->bIncr==1 ); if( p->nToken==1 && p->bIncr ){ rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr, &pDL->iDocid, &pDL->pList, &pDL->nList ); if( pDL->pList==0 ) bEof = 1; }else{ int bDescDoclist = pCsr->bDesc; struct TokenDoclist a[MAX_INCR_PHRASE_TOKENS]; memset(a, 0, sizeof(a)); assert( p->nToken<=MAX_INCR_PHRASE_TOKENS ); assert( p->iDoclistTokennToken && bEof==0; i++){ rc = incrPhraseTokenNext(pTab, p, i, &a[i], &bEof); if( a[i].bIgnore==0 && (bMaxSet==0 || DOCID_CMP(iMax, a[i].iDocid)<0) ){ iMax = a[i].iDocid; bMaxSet = 1; } } assert( rc!=SQLITE_OK || (p->nToken>=1 && a[p->nToken-1].bIgnore==0) ); assert( rc!=SQLITE_OK || bMaxSet ); /* Keep advancing iterators until they all point to the same document */ for(i=0; inToken; i++){ while( rc==SQLITE_OK && bEof==0 && a[i].bIgnore==0 && DOCID_CMP(a[i].iDocid, iMax)<0 ){ rc = incrPhraseTokenNext(pTab, p, i, &a[i], &bEof); if( DOCID_CMP(a[i].iDocid, iMax)>0 ){ iMax = a[i].iDocid; i = 0; } } } /* Check if the current entries really are a phrase match */ if( bEof==0 ){ int nList = 0; int nByte = a[p->nToken-1].nList; char *aDoclist = sqlite3_malloc(nByte+1); if( !aDoclist ) return SQLITE_NOMEM; memcpy(aDoclist, a[p->nToken-1].pList, nByte+1); for(i=0; i<(p->nToken-1); i++){ if( a[i].bIgnore==0 ){ char *pL = a[i].pList; char *pR = aDoclist; char *pOut = aDoclist; int nDist = p->nToken-1-i; int res = fts3PoslistPhraseMerge(&pOut, nDist, 0, 1, &pL, &pR); if( res==0 ) break; nList = (int)(pOut - aDoclist); } } if( i==(p->nToken-1) ){ pDL->iDocid = iMax; pDL->pList = aDoclist; pDL->nList = nList; pDL->bFreeList = 1; break; } sqlite3_free(aDoclist); } } } *pbEof = bEof; return rc; } /* ** Attempt to move the phrase iterator to point to the next matching docid. ** If an error occurs, return an SQLite error code. Otherwise, return ** SQLITE_OK. ** ** If there is no "next" entry and no error occurs, then *pbEof is set to ** 1 before returning. Otherwise, if no error occurs and the iterator is ** successfully advanced, *pbEof is set to 0. */ static int fts3EvalPhraseNext( Fts3Cursor *pCsr, /* FTS Cursor handle */ Fts3Phrase *p, /* Phrase object to advance to next docid */ u8 *pbEof /* OUT: Set to 1 if EOF */ ){ int rc = SQLITE_OK; Fts3Doclist *pDL = &p->doclist; Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; if( p->bIncr ){ rc = fts3EvalIncrPhraseNext(pCsr, p, pbEof); }else if( pCsr->bDesc!=pTab->bDescIdx && pDL->nAll ){ sqlite3Fts3DoclistPrev(pTab->bDescIdx, pDL->aAll, pDL->nAll, &pDL->pNextDocid, &pDL->iDocid, &pDL->nList, pbEof ); pDL->pList = pDL->pNextDocid; }else{ fts3EvalDlPhraseNext(pTab, pDL, pbEof); } return rc; } /* ** ** If *pRc is not SQLITE_OK when this function is called, it is a no-op. ** Otherwise, fts3EvalPhraseStart() is called on all phrases within the ** expression. Also the Fts3Expr.bDeferred variable is set to true for any ** expressions for which all descendent tokens are deferred. ** ** If parameter bOptOk is zero, then it is guaranteed that the ** Fts3Phrase.doclist.aAll/nAll variables contain the entire doclist for ** each phrase in the expression (subject to deferred token processing). ** Or, if bOptOk is non-zero, then one or more tokens within the expression ** may be loaded incrementally, meaning doclist.aAll/nAll is not available. ** ** If an error occurs within this function, *pRc is set to an SQLite error ** code before returning. */ static void fts3EvalStartReaders( Fts3Cursor *pCsr, /* FTS Cursor handle */ Fts3Expr *pExpr, /* Expression to initialize phrases in */ int *pRc /* IN/OUT: Error code */ ){ if( pExpr && SQLITE_OK==*pRc ){ if( pExpr->eType==FTSQUERY_PHRASE ){ int nToken = pExpr->pPhrase->nToken; if( nToken ){ int i; for(i=0; ipPhrase->aToken[i].pDeferred==0 ) break; } pExpr->bDeferred = (i==nToken); } *pRc = fts3EvalPhraseStart(pCsr, 1, pExpr->pPhrase); }else{ fts3EvalStartReaders(pCsr, pExpr->pLeft, pRc); fts3EvalStartReaders(pCsr, pExpr->pRight, pRc); pExpr->bDeferred = (pExpr->pLeft->bDeferred && pExpr->pRight->bDeferred); } } } /* ** An array of the following structures is assembled as part of the process ** of selecting tokens to defer before the query starts executing (as part ** of the xFilter() method). There is one element in the array for each ** token in the FTS expression. ** ** Tokens are divided into AND/NEAR clusters. All tokens in a cluster belong ** to phrases that are connected only by AND and NEAR operators (not OR or ** NOT). When determining tokens to defer, each AND/NEAR cluster is considered ** separately. The root of a tokens AND/NEAR cluster is stored in ** Fts3TokenAndCost.pRoot. */ typedef struct Fts3TokenAndCost Fts3TokenAndCost; struct Fts3TokenAndCost { Fts3Phrase *pPhrase; /* The phrase the token belongs to */ int iToken; /* Position of token in phrase */ Fts3PhraseToken *pToken; /* The token itself */ Fts3Expr *pRoot; /* Root of NEAR/AND cluster */ int nOvfl; /* Number of overflow pages to load doclist */ int iCol; /* The column the token must match */ }; /* ** This function is used to populate an allocated Fts3TokenAndCost array. ** ** If *pRc is not SQLITE_OK when this function is called, it is a no-op. ** Otherwise, if an error occurs during execution, *pRc is set to an ** SQLite error code. */ static void fts3EvalTokenCosts( Fts3Cursor *pCsr, /* FTS Cursor handle */ Fts3Expr *pRoot, /* Root of current AND/NEAR cluster */ Fts3Expr *pExpr, /* Expression to consider */ Fts3TokenAndCost **ppTC, /* Write new entries to *(*ppTC)++ */ Fts3Expr ***ppOr, /* Write new OR root to *(*ppOr)++ */ int *pRc /* IN/OUT: Error code */ ){ if( *pRc==SQLITE_OK ){ if( pExpr->eType==FTSQUERY_PHRASE ){ Fts3Phrase *pPhrase = pExpr->pPhrase; int i; for(i=0; *pRc==SQLITE_OK && inToken; i++){ Fts3TokenAndCost *pTC = (*ppTC)++; pTC->pPhrase = pPhrase; pTC->iToken = i; pTC->pRoot = pRoot; pTC->pToken = &pPhrase->aToken[i]; pTC->iCol = pPhrase->iColumn; *pRc = sqlite3Fts3MsrOvfl(pCsr, pTC->pToken->pSegcsr, &pTC->nOvfl); } }else if( pExpr->eType!=FTSQUERY_NOT ){ assert( pExpr->eType==FTSQUERY_OR || pExpr->eType==FTSQUERY_AND || pExpr->eType==FTSQUERY_NEAR ); assert( pExpr->pLeft && pExpr->pRight ); if( pExpr->eType==FTSQUERY_OR ){ pRoot = pExpr->pLeft; **ppOr = pRoot; (*ppOr)++; } fts3EvalTokenCosts(pCsr, pRoot, pExpr->pLeft, ppTC, ppOr, pRc); if( pExpr->eType==FTSQUERY_OR ){ pRoot = pExpr->pRight; **ppOr = pRoot; (*ppOr)++; } fts3EvalTokenCosts(pCsr, pRoot, pExpr->pRight, ppTC, ppOr, pRc); } } } /* ** Determine the average document (row) size in pages. If successful, ** write this value to *pnPage and return SQLITE_OK. Otherwise, return ** an SQLite error code. ** ** The average document size in pages is calculated by first calculating ** determining the average size in bytes, B. If B is less than the amount ** of data that will fit on a single leaf page of an intkey table in ** this database, then the average docsize is 1. Otherwise, it is 1 plus ** the number of overflow pages consumed by a record B bytes in size. */ static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){ if( pCsr->nRowAvg==0 ){ /* The average document size, which is required to calculate the cost ** of each doclist, has not yet been determined. Read the required ** data from the %_stat table to calculate it. ** ** Entry 0 of the %_stat table is a blob containing (nCol+1) FTS3 ** varints, where nCol is the number of columns in the FTS3 table. ** The first varint is the number of documents currently stored in ** the table. The following nCol varints contain the total amount of ** data stored in all rows of each column of the table, from left ** to right. */ int rc; Fts3Table *p = (Fts3Table*)pCsr->base.pVtab; sqlite3_stmt *pStmt; sqlite3_int64 nDoc = 0; sqlite3_int64 nByte = 0; const char *pEnd; const char *a; rc = sqlite3Fts3SelectDoctotal(p, &pStmt); if( rc!=SQLITE_OK ) return rc; a = sqlite3_column_blob(pStmt, 0); assert( a ); pEnd = &a[sqlite3_column_bytes(pStmt, 0)]; a += sqlite3Fts3GetVarint(a, &nDoc); while( anDoc = nDoc; pCsr->nRowAvg = (int)(((nByte / nDoc) + p->nPgsz) / p->nPgsz); assert( pCsr->nRowAvg>0 ); rc = sqlite3_reset(pStmt); if( rc!=SQLITE_OK ) return rc; } *pnPage = pCsr->nRowAvg; return SQLITE_OK; } /* ** This function is called to select the tokens (if any) that will be ** deferred. The array aTC[] has already been populated when this is ** called. ** ** This function is called once for each AND/NEAR cluster in the ** expression. Each invocation determines which tokens to defer within ** the cluster with root node pRoot. See comments above the definition ** of struct Fts3TokenAndCost for more details. ** ** If no error occurs, SQLITE_OK is returned and sqlite3Fts3DeferToken() ** called on each token to defer. Otherwise, an SQLite error code is ** returned. */ static int fts3EvalSelectDeferred( Fts3Cursor *pCsr, /* FTS Cursor handle */ Fts3Expr *pRoot, /* Consider tokens with this root node */ Fts3TokenAndCost *aTC, /* Array of expression tokens and costs */ int nTC /* Number of entries in aTC[] */ ){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; int nDocSize = 0; /* Number of pages per doc loaded */ int rc = SQLITE_OK; /* Return code */ int ii; /* Iterator variable for various purposes */ int nOvfl = 0; /* Total overflow pages used by doclists */ int nToken = 0; /* Total number of tokens in cluster */ int nMinEst = 0; /* The minimum count for any phrase so far. */ int nLoad4 = 1; /* (Phrases that will be loaded)^4. */ /* Tokens are never deferred for FTS tables created using the content=xxx ** option. The reason being that it is not guaranteed that the content ** table actually contains the same data as the index. To prevent this from ** causing any problems, the deferred token optimization is completely ** disabled for content=xxx tables. */ if( pTab->zContentTbl ){ return SQLITE_OK; } /* Count the tokens in this AND/NEAR cluster. If none of the doclists ** associated with the tokens spill onto overflow pages, or if there is ** only 1 token, exit early. No tokens to defer in this case. */ for(ii=0; ii0 ); /* Iterate through all tokens in this AND/NEAR cluster, in ascending order ** of the number of overflow pages that will be loaded by the pager layer ** to retrieve the entire doclist for the token from the full-text index. ** Load the doclists for tokens that are either: ** ** a. The cheapest token in the entire query (i.e. the one visited by the ** first iteration of this loop), or ** ** b. Part of a multi-token phrase. ** ** After each token doclist is loaded, merge it with the others from the ** same phrase and count the number of documents that the merged doclist ** contains. Set variable "nMinEst" to the smallest number of documents in ** any phrase doclist for which 1 or more token doclists have been loaded. ** Let nOther be the number of other phrases for which it is certain that ** one or more tokens will not be deferred. ** ** Then, for each token, defer it if loading the doclist would result in ** loading N or more overflow pages into memory, where N is computed as: ** ** (nMinEst + 4^nOther - 1) / (4^nOther) */ for(ii=0; iinOvfl) ){ pTC = &aTC[iTC]; } } assert( pTC ); if( ii && pTC->nOvfl>=((nMinEst+(nLoad4/4)-1)/(nLoad4/4))*nDocSize ){ /* The number of overflow pages to load for this (and therefore all ** subsequent) tokens is greater than the estimated number of pages ** that will be loaded if all subsequent tokens are deferred. */ Fts3PhraseToken *pToken = pTC->pToken; rc = sqlite3Fts3DeferToken(pCsr, pToken, pTC->iCol); fts3SegReaderCursorFree(pToken->pSegcsr); pToken->pSegcsr = 0; }else{ /* Set nLoad4 to the value of (4^nOther) for the next iteration of the ** for-loop. Except, limit the value to 2^24 to prevent it from ** overflowing the 32-bit integer it is stored in. */ if( ii<12 ) nLoad4 = nLoad4*4; if( ii==0 || (pTC->pPhrase->nToken>1 && ii!=nToken-1) ){ /* Either this is the cheapest token in the entire query, or it is ** part of a multi-token phrase. Either way, the entire doclist will ** (eventually) be loaded into memory. It may as well be now. */ Fts3PhraseToken *pToken = pTC->pToken; int nList = 0; char *pList = 0; rc = fts3TermSelect(pTab, pToken, pTC->iCol, &nList, &pList); assert( rc==SQLITE_OK || pList==0 ); if( rc==SQLITE_OK ){ rc = fts3EvalPhraseMergeToken( pTab, pTC->pPhrase, pTC->iToken,pList,nList ); } if( rc==SQLITE_OK ){ int nCount; nCount = fts3DoclistCountDocids( pTC->pPhrase->doclist.aAll, pTC->pPhrase->doclist.nAll ); if( ii==0 || nCountpToken = 0; } return rc; } /* ** This function is called from within the xFilter method. It initializes ** the full-text query currently stored in pCsr->pExpr. To iterate through ** the results of a query, the caller does: ** ** fts3EvalStart(pCsr); ** while( 1 ){ ** fts3EvalNext(pCsr); ** if( pCsr->bEof ) break; ** ... return row pCsr->iPrevId to the caller ... ** } */ static int fts3EvalStart(Fts3Cursor *pCsr){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; int rc = SQLITE_OK; int nToken = 0; int nOr = 0; /* Allocate a MultiSegReader for each token in the expression. */ fts3EvalAllocateReaders(pCsr, pCsr->pExpr, &nToken, &nOr, &rc); /* Determine which, if any, tokens in the expression should be deferred. */ #ifndef SQLITE_DISABLE_FTS4_DEFERRED if( rc==SQLITE_OK && nToken>1 && pTab->bFts4 ){ Fts3TokenAndCost *aTC; Fts3Expr **apOr; aTC = (Fts3TokenAndCost *)sqlite3_malloc( sizeof(Fts3TokenAndCost) * nToken + sizeof(Fts3Expr *) * nOr * 2 ); apOr = (Fts3Expr **)&aTC[nToken]; if( !aTC ){ rc = SQLITE_NOMEM; }else{ int ii; Fts3TokenAndCost *pTC = aTC; Fts3Expr **ppOr = apOr; fts3EvalTokenCosts(pCsr, 0, pCsr->pExpr, &pTC, &ppOr, &rc); nToken = (int)(pTC-aTC); nOr = (int)(ppOr-apOr); if( rc==SQLITE_OK ){ rc = fts3EvalSelectDeferred(pCsr, 0, aTC, nToken); for(ii=0; rc==SQLITE_OK && iipExpr, &rc); return rc; } /* ** Invalidate the current position list for phrase pPhrase. */ static void fts3EvalInvalidatePoslist(Fts3Phrase *pPhrase){ if( pPhrase->doclist.bFreeList ){ sqlite3_free(pPhrase->doclist.pList); } pPhrase->doclist.pList = 0; pPhrase->doclist.nList = 0; pPhrase->doclist.bFreeList = 0; } /* ** This function is called to edit the position list associated with ** the phrase object passed as the fifth argument according to a NEAR ** condition. For example: ** ** abc NEAR/5 "def ghi" ** ** Parameter nNear is passed the NEAR distance of the expression (5 in ** the example above). When this function is called, *paPoslist points to ** the position list, and *pnToken is the number of phrase tokens in, the ** phrase on the other side of the NEAR operator to pPhrase. For example, ** if pPhrase refers to the "def ghi" phrase, then *paPoslist points to ** the position list associated with phrase "abc". ** ** All positions in the pPhrase position list that are not sufficiently ** close to a position in the *paPoslist position list are removed. If this ** leaves 0 positions, zero is returned. Otherwise, non-zero. ** ** Before returning, *paPoslist is set to point to the position lsit ** associated with pPhrase. And *pnToken is set to the number of tokens in ** pPhrase. */ static int fts3EvalNearTrim( int nNear, /* NEAR distance. As in "NEAR/nNear". */ char *aTmp, /* Temporary space to use */ char **paPoslist, /* IN/OUT: Position list */ int *pnToken, /* IN/OUT: Tokens in phrase of *paPoslist */ Fts3Phrase *pPhrase /* The phrase object to trim the doclist of */ ){ int nParam1 = nNear + pPhrase->nToken; int nParam2 = nNear + *pnToken; int nNew; char *p2; char *pOut; int res; assert( pPhrase->doclist.pList ); p2 = pOut = pPhrase->doclist.pList; res = fts3PoslistNearMerge( &pOut, aTmp, nParam1, nParam2, paPoslist, &p2 ); if( res ){ nNew = (int)(pOut - pPhrase->doclist.pList) - 1; assert( pPhrase->doclist.pList[nNew]=='\0' ); assert( nNew<=pPhrase->doclist.nList && nNew>0 ); memset(&pPhrase->doclist.pList[nNew], 0, pPhrase->doclist.nList - nNew); pPhrase->doclist.nList = nNew; *paPoslist = pPhrase->doclist.pList; *pnToken = pPhrase->nToken; } return res; } /* ** This function is a no-op if *pRc is other than SQLITE_OK when it is called. ** Otherwise, it advances the expression passed as the second argument to ** point to the next matching row in the database. Expressions iterate through ** matching rows in docid order. Ascending order if Fts3Cursor.bDesc is zero, ** or descending if it is non-zero. ** ** If an error occurs, *pRc is set to an SQLite error code. Otherwise, if ** successful, the following variables in pExpr are set: ** ** Fts3Expr.bEof (non-zero if EOF - there is no next row) ** Fts3Expr.iDocid (valid if bEof==0. The docid of the next row) ** ** If the expression is of type FTSQUERY_PHRASE, and the expression is not ** at EOF, then the following variables are populated with the position list ** for the phrase for the visited row: ** ** FTs3Expr.pPhrase->doclist.nList (length of pList in bytes) ** FTs3Expr.pPhrase->doclist.pList (pointer to position list) ** ** It says above that this function advances the expression to the next ** matching row. This is usually true, but there are the following exceptions: ** ** 1. Deferred tokens are not taken into account. If a phrase consists ** entirely of deferred tokens, it is assumed to match every row in ** the db. In this case the position-list is not populated at all. ** ** Or, if a phrase contains one or more deferred tokens and one or ** more non-deferred tokens, then the expression is advanced to the ** next possible match, considering only non-deferred tokens. In other ** words, if the phrase is "A B C", and "B" is deferred, the expression ** is advanced to the next row that contains an instance of "A * C", ** where "*" may match any single token. The position list in this case ** is populated as for "A * C" before returning. ** ** 2. NEAR is treated as AND. If the expression is "x NEAR y", it is ** advanced to point to the next row that matches "x AND y". ** ** See sqlite3Fts3EvalTestDeferred() for details on testing if a row is ** really a match, taking into account deferred tokens and NEAR operators. */ static void fts3EvalNextRow( Fts3Cursor *pCsr, /* FTS Cursor handle */ Fts3Expr *pExpr, /* Expr. to advance to next matching row */ int *pRc /* IN/OUT: Error code */ ){ if( *pRc==SQLITE_OK ){ int bDescDoclist = pCsr->bDesc; /* Used by DOCID_CMP() macro */ assert( pExpr->bEof==0 ); pExpr->bStart = 1; switch( pExpr->eType ){ case FTSQUERY_NEAR: case FTSQUERY_AND: { Fts3Expr *pLeft = pExpr->pLeft; Fts3Expr *pRight = pExpr->pRight; assert( !pLeft->bDeferred || !pRight->bDeferred ); if( pLeft->bDeferred ){ /* LHS is entirely deferred. So we assume it matches every row. ** Advance the RHS iterator to find the next row visited. */ fts3EvalNextRow(pCsr, pRight, pRc); pExpr->iDocid = pRight->iDocid; pExpr->bEof = pRight->bEof; }else if( pRight->bDeferred ){ /* RHS is entirely deferred. So we assume it matches every row. ** Advance the LHS iterator to find the next row visited. */ fts3EvalNextRow(pCsr, pLeft, pRc); pExpr->iDocid = pLeft->iDocid; pExpr->bEof = pLeft->bEof; }else{ /* Neither the RHS or LHS are deferred. */ fts3EvalNextRow(pCsr, pLeft, pRc); fts3EvalNextRow(pCsr, pRight, pRc); while( !pLeft->bEof && !pRight->bEof && *pRc==SQLITE_OK ){ sqlite3_int64 iDiff = DOCID_CMP(pLeft->iDocid, pRight->iDocid); if( iDiff==0 ) break; if( iDiff<0 ){ fts3EvalNextRow(pCsr, pLeft, pRc); }else{ fts3EvalNextRow(pCsr, pRight, pRc); } } pExpr->iDocid = pLeft->iDocid; pExpr->bEof = (pLeft->bEof || pRight->bEof); if( pExpr->eType==FTSQUERY_NEAR && pExpr->bEof ){ if( pRight->pPhrase && pRight->pPhrase->doclist.aAll ){ Fts3Doclist *pDl = &pRight->pPhrase->doclist; while( *pRc==SQLITE_OK && pRight->bEof==0 ){ memset(pDl->pList, 0, pDl->nList); fts3EvalNextRow(pCsr, pRight, pRc); } } if( pLeft->pPhrase && pLeft->pPhrase->doclist.aAll ){ Fts3Doclist *pDl = &pLeft->pPhrase->doclist; while( *pRc==SQLITE_OK && pLeft->bEof==0 ){ memset(pDl->pList, 0, pDl->nList); fts3EvalNextRow(pCsr, pLeft, pRc); } } } } break; } case FTSQUERY_OR: { Fts3Expr *pLeft = pExpr->pLeft; Fts3Expr *pRight = pExpr->pRight; sqlite3_int64 iCmp = DOCID_CMP(pLeft->iDocid, pRight->iDocid); assert( pLeft->bStart || pLeft->iDocid==pRight->iDocid ); assert( pRight->bStart || pLeft->iDocid==pRight->iDocid ); if( pRight->bEof || (pLeft->bEof==0 && iCmp<0) ){ fts3EvalNextRow(pCsr, pLeft, pRc); }else if( pLeft->bEof || (pRight->bEof==0 && iCmp>0) ){ fts3EvalNextRow(pCsr, pRight, pRc); }else{ fts3EvalNextRow(pCsr, pLeft, pRc); fts3EvalNextRow(pCsr, pRight, pRc); } pExpr->bEof = (pLeft->bEof && pRight->bEof); iCmp = DOCID_CMP(pLeft->iDocid, pRight->iDocid); if( pRight->bEof || (pLeft->bEof==0 && iCmp<0) ){ pExpr->iDocid = pLeft->iDocid; }else{ pExpr->iDocid = pRight->iDocid; } break; } case FTSQUERY_NOT: { Fts3Expr *pLeft = pExpr->pLeft; Fts3Expr *pRight = pExpr->pRight; if( pRight->bStart==0 ){ fts3EvalNextRow(pCsr, pRight, pRc); assert( *pRc!=SQLITE_OK || pRight->bStart ); } fts3EvalNextRow(pCsr, pLeft, pRc); if( pLeft->bEof==0 ){ while( !*pRc && !pRight->bEof && DOCID_CMP(pLeft->iDocid, pRight->iDocid)>0 ){ fts3EvalNextRow(pCsr, pRight, pRc); } } pExpr->iDocid = pLeft->iDocid; pExpr->bEof = pLeft->bEof; break; } default: { Fts3Phrase *pPhrase = pExpr->pPhrase; fts3EvalInvalidatePoslist(pPhrase); *pRc = fts3EvalPhraseNext(pCsr, pPhrase, &pExpr->bEof); pExpr->iDocid = pPhrase->doclist.iDocid; break; } } } } /* ** If *pRc is not SQLITE_OK, or if pExpr is not the root node of a NEAR ** cluster, then this function returns 1 immediately. ** ** Otherwise, it checks if the current row really does match the NEAR ** expression, using the data currently stored in the position lists ** (Fts3Expr->pPhrase.doclist.pList/nList) for each phrase in the expression. ** ** If the current row is a match, the position list associated with each ** phrase in the NEAR expression is edited in place to contain only those ** phrase instances sufficiently close to their peers to satisfy all NEAR ** constraints. In this case it returns 1. If the NEAR expression does not ** match the current row, 0 is returned. The position lists may or may not ** be edited if 0 is returned. */ static int fts3EvalNearTest(Fts3Expr *pExpr, int *pRc){ int res = 1; /* The following block runs if pExpr is the root of a NEAR query. ** For example, the query: ** ** "w" NEAR "x" NEAR "y" NEAR "z" ** ** which is represented in tree form as: ** ** | ** +--NEAR--+ <-- root of NEAR query ** | | ** +--NEAR--+ "z" ** | | ** +--NEAR--+ "y" ** | | ** "w" "x" ** ** The right-hand child of a NEAR node is always a phrase. The ** left-hand child may be either a phrase or a NEAR node. There are ** no exceptions to this - it's the way the parser in fts3_expr.c works. */ if( *pRc==SQLITE_OK && pExpr->eType==FTSQUERY_NEAR && pExpr->bEof==0 && (pExpr->pParent==0 || pExpr->pParent->eType!=FTSQUERY_NEAR) ){ Fts3Expr *p; int nTmp = 0; /* Bytes of temp space */ char *aTmp; /* Temp space for PoslistNearMerge() */ /* Allocate temporary working space. */ for(p=pExpr; p->pLeft; p=p->pLeft){ nTmp += p->pRight->pPhrase->doclist.nList; } nTmp += p->pPhrase->doclist.nList; if( nTmp==0 ){ res = 0; }else{ aTmp = sqlite3_malloc(nTmp*2); if( !aTmp ){ *pRc = SQLITE_NOMEM; res = 0; }else{ char *aPoslist = p->pPhrase->doclist.pList; int nToken = p->pPhrase->nToken; for(p=p->pParent;res && p && p->eType==FTSQUERY_NEAR; p=p->pParent){ Fts3Phrase *pPhrase = p->pRight->pPhrase; int nNear = p->nNear; res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); } aPoslist = pExpr->pRight->pPhrase->doclist.pList; nToken = pExpr->pRight->pPhrase->nToken; for(p=pExpr->pLeft; p && res; p=p->pLeft){ int nNear; Fts3Phrase *pPhrase; assert( p->pParent && p->pParent->pLeft==p ); nNear = p->pParent->nNear; pPhrase = ( p->eType==FTSQUERY_NEAR ? p->pRight->pPhrase : p->pPhrase ); res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); } } sqlite3_free(aTmp); } } return res; } /* ** This function is a helper function for sqlite3Fts3EvalTestDeferred(). ** Assuming no error occurs or has occurred, It returns non-zero if the ** expression passed as the second argument matches the row that pCsr ** currently points to, or zero if it does not. ** ** If *pRc is not SQLITE_OK when this function is called, it is a no-op. ** If an error occurs during execution of this function, *pRc is set to ** the appropriate SQLite error code. In this case the returned value is ** undefined. */ static int fts3EvalTestExpr( Fts3Cursor *pCsr, /* FTS cursor handle */ Fts3Expr *pExpr, /* Expr to test. May or may not be root. */ int *pRc /* IN/OUT: Error code */ ){ int bHit = 1; /* Return value */ if( *pRc==SQLITE_OK ){ switch( pExpr->eType ){ case FTSQUERY_NEAR: case FTSQUERY_AND: bHit = ( fts3EvalTestExpr(pCsr, pExpr->pLeft, pRc) && fts3EvalTestExpr(pCsr, pExpr->pRight, pRc) && fts3EvalNearTest(pExpr, pRc) ); /* If the NEAR expression does not match any rows, zero the doclist for ** all phrases involved in the NEAR. This is because the snippet(), ** offsets() and matchinfo() functions are not supposed to recognize ** any instances of phrases that are part of unmatched NEAR queries. ** For example if this expression: ** ** ... MATCH 'a OR (b NEAR c)' ** ** is matched against a row containing: ** ** 'a b d e' ** ** then any snippet() should ony highlight the "a" term, not the "b" ** (as "b" is part of a non-matching NEAR clause). */ if( bHit==0 && pExpr->eType==FTSQUERY_NEAR && (pExpr->pParent==0 || pExpr->pParent->eType!=FTSQUERY_NEAR) ){ Fts3Expr *p; for(p=pExpr; p->pPhrase==0; p=p->pLeft){ if( p->pRight->iDocid==pCsr->iPrevId ){ fts3EvalInvalidatePoslist(p->pRight->pPhrase); } } if( p->iDocid==pCsr->iPrevId ){ fts3EvalInvalidatePoslist(p->pPhrase); } } break; case FTSQUERY_OR: { int bHit1 = fts3EvalTestExpr(pCsr, pExpr->pLeft, pRc); int bHit2 = fts3EvalTestExpr(pCsr, pExpr->pRight, pRc); bHit = bHit1 || bHit2; break; } case FTSQUERY_NOT: bHit = ( fts3EvalTestExpr(pCsr, pExpr->pLeft, pRc) && !fts3EvalTestExpr(pCsr, pExpr->pRight, pRc) ); break; default: { #ifndef SQLITE_DISABLE_FTS4_DEFERRED if( pCsr->pDeferred && (pExpr->iDocid==pCsr->iPrevId || pExpr->bDeferred) ){ Fts3Phrase *pPhrase = pExpr->pPhrase; assert( pExpr->bDeferred || pPhrase->doclist.bFreeList==0 ); if( pExpr->bDeferred ){ fts3EvalInvalidatePoslist(pPhrase); } *pRc = fts3EvalDeferredPhrase(pCsr, pPhrase); bHit = (pPhrase->doclist.pList!=0); pExpr->iDocid = pCsr->iPrevId; }else #endif { bHit = (pExpr->bEof==0 && pExpr->iDocid==pCsr->iPrevId); } break; } } } return bHit; } /* ** This function is called as the second part of each xNext operation when ** iterating through the results of a full-text query. At this point the ** cursor points to a row that matches the query expression, with the ** following caveats: ** ** * Up until this point, "NEAR" operators in the expression have been ** treated as "AND". ** ** * Deferred tokens have not yet been considered. ** ** If *pRc is not SQLITE_OK when this function is called, it immediately ** returns 0. Otherwise, it tests whether or not after considering NEAR ** operators and deferred tokens the current row is still a match for the ** expression. It returns 1 if both of the following are true: ** ** 1. *pRc is SQLITE_OK when this function returns, and ** ** 2. After scanning the current FTS table row for the deferred tokens, ** it is determined that the row does *not* match the query. ** ** Or, if no error occurs and it seems the current row does match the FTS ** query, return 0. */ int sqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc){ int rc = *pRc; int bMiss = 0; if( rc==SQLITE_OK ){ /* If there are one or more deferred tokens, load the current row into ** memory and scan it to determine the position list for each deferred ** token. Then, see if this row is really a match, considering deferred ** tokens and NEAR operators (neither of which were taken into account ** earlier, by fts3EvalNextRow()). */ if( pCsr->pDeferred ){ rc = fts3CursorSeek(0, pCsr); if( rc==SQLITE_OK ){ rc = sqlite3Fts3CacheDeferredDoclists(pCsr); } } bMiss = (0==fts3EvalTestExpr(pCsr, pCsr->pExpr, &rc)); /* Free the position-lists accumulated for each deferred token above. */ sqlite3Fts3FreeDeferredDoclists(pCsr); *pRc = rc; } return (rc==SQLITE_OK && bMiss); } /* ** Advance to the next document that matches the FTS expression in ** Fts3Cursor.pExpr. */ static int fts3EvalNext(Fts3Cursor *pCsr){ int rc = SQLITE_OK; /* Return Code */ Fts3Expr *pExpr = pCsr->pExpr; assert( pCsr->isEof==0 ); if( pExpr==0 ){ pCsr->isEof = 1; }else{ do { if( pCsr->isRequireSeek==0 ){ sqlite3_reset(pCsr->pStmt); } assert( sqlite3_data_count(pCsr->pStmt)==0 ); fts3EvalNextRow(pCsr, pExpr, &rc); pCsr->isEof = pExpr->bEof; pCsr->isRequireSeek = 1; pCsr->isMatchinfoNeeded = 1; pCsr->iPrevId = pExpr->iDocid; }while( pCsr->isEof==0 && sqlite3Fts3EvalTestDeferred(pCsr, &rc) ); } /* Check if the cursor is past the end of the docid range specified ** by Fts3Cursor.iMinDocid/iMaxDocid. If so, set the EOF flag. */ if( rc==SQLITE_OK && ( (pCsr->bDesc==0 && pCsr->iPrevId>pCsr->iMaxDocid) || (pCsr->bDesc!=0 && pCsr->iPrevIdiMinDocid) )){ pCsr->isEof = 1; } return rc; } /* ** Restart interation for expression pExpr so that the next call to ** fts3EvalNext() visits the first row. Do not allow incremental ** loading or merging of phrase doclists for this iteration. ** ** If *pRc is other than SQLITE_OK when this function is called, it is ** a no-op. If an error occurs within this function, *pRc is set to an ** SQLite error code before returning. */ static void fts3EvalRestart( Fts3Cursor *pCsr, Fts3Expr *pExpr, int *pRc ){ if( pExpr && *pRc==SQLITE_OK ){ Fts3Phrase *pPhrase = pExpr->pPhrase; if( pPhrase ){ fts3EvalInvalidatePoslist(pPhrase); if( pPhrase->bIncr ){ int i; for(i=0; inToken; i++){ Fts3PhraseToken *pToken = &pPhrase->aToken[i]; assert( pToken->pDeferred==0 ); if( pToken->pSegcsr ){ sqlite3Fts3MsrIncrRestart(pToken->pSegcsr); } } *pRc = fts3EvalPhraseStart(pCsr, 0, pPhrase); } pPhrase->doclist.pNextDocid = 0; pPhrase->doclist.iDocid = 0; pPhrase->pOrPoslist = 0; } pExpr->iDocid = 0; pExpr->bEof = 0; pExpr->bStart = 0; fts3EvalRestart(pCsr, pExpr->pLeft, pRc); fts3EvalRestart(pCsr, pExpr->pRight, pRc); } } /* ** After allocating the Fts3Expr.aMI[] array for each phrase in the ** expression rooted at pExpr, the cursor iterates through all rows matched ** by pExpr, calling this function for each row. This function increments ** the values in Fts3Expr.aMI[] according to the position-list currently ** found in Fts3Expr.pPhrase->doclist.pList for each of the phrase ** expression nodes. */ static void fts3EvalUpdateCounts(Fts3Expr *pExpr){ if( pExpr ){ Fts3Phrase *pPhrase = pExpr->pPhrase; if( pPhrase && pPhrase->doclist.pList ){ int iCol = 0; char *p = pPhrase->doclist.pList; assert( *p ); while( 1 ){ u8 c = 0; int iCnt = 0; while( 0xFE & (*p | c) ){ if( (c&0x80)==0 ) iCnt++; c = *p++ & 0x80; } /* aMI[iCol*3 + 1] = Number of occurrences ** aMI[iCol*3 + 2] = Number of rows containing at least one instance */ pExpr->aMI[iCol*3 + 1] += iCnt; pExpr->aMI[iCol*3 + 2] += (iCnt>0); if( *p==0x00 ) break; p++; p += fts3GetVarint32(p, &iCol); } } fts3EvalUpdateCounts(pExpr->pLeft); fts3EvalUpdateCounts(pExpr->pRight); } } /* ** Expression pExpr must be of type FTSQUERY_PHRASE. ** ** If it is not already allocated and populated, this function allocates and ** populates the Fts3Expr.aMI[] array for expression pExpr. If pExpr is part ** of a NEAR expression, then it also allocates and populates the same array ** for all other phrases that are part of the NEAR expression. ** ** SQLITE_OK is returned if the aMI[] array is successfully allocated and ** populated. Otherwise, if an error occurs, an SQLite error code is returned. */ static int fts3EvalGatherStats( Fts3Cursor *pCsr, /* Cursor object */ Fts3Expr *pExpr /* FTSQUERY_PHRASE expression */ ){ int rc = SQLITE_OK; /* Return code */ assert( pExpr->eType==FTSQUERY_PHRASE ); if( pExpr->aMI==0 ){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; Fts3Expr *pRoot; /* Root of NEAR expression */ Fts3Expr *p; /* Iterator used for several purposes */ sqlite3_int64 iPrevId = pCsr->iPrevId; sqlite3_int64 iDocid; u8 bEof; /* Find the root of the NEAR expression */ pRoot = pExpr; while( pRoot->pParent && pRoot->pParent->eType==FTSQUERY_NEAR ){ pRoot = pRoot->pParent; } iDocid = pRoot->iDocid; bEof = pRoot->bEof; assert( pRoot->bStart ); /* Allocate space for the aMSI[] array of each FTSQUERY_PHRASE node */ for(p=pRoot; p; p=p->pLeft){ Fts3Expr *pE = (p->eType==FTSQUERY_PHRASE?p:p->pRight); assert( pE->aMI==0 ); pE->aMI = (u32 *)sqlite3_malloc(pTab->nColumn * 3 * sizeof(u32)); if( !pE->aMI ) return SQLITE_NOMEM; memset(pE->aMI, 0, pTab->nColumn * 3 * sizeof(u32)); } fts3EvalRestart(pCsr, pRoot, &rc); while( pCsr->isEof==0 && rc==SQLITE_OK ){ do { /* Ensure the %_content statement is reset. */ if( pCsr->isRequireSeek==0 ) sqlite3_reset(pCsr->pStmt); assert( sqlite3_data_count(pCsr->pStmt)==0 ); /* Advance to the next document */ fts3EvalNextRow(pCsr, pRoot, &rc); pCsr->isEof = pRoot->bEof; pCsr->isRequireSeek = 1; pCsr->isMatchinfoNeeded = 1; pCsr->iPrevId = pRoot->iDocid; }while( pCsr->isEof==0 && pRoot->eType==FTSQUERY_NEAR && sqlite3Fts3EvalTestDeferred(pCsr, &rc) ); if( rc==SQLITE_OK && pCsr->isEof==0 ){ fts3EvalUpdateCounts(pRoot); } } pCsr->isEof = 0; pCsr->iPrevId = iPrevId; if( bEof ){ pRoot->bEof = bEof; }else{ /* Caution: pRoot may iterate through docids in ascending or descending ** order. For this reason, even though it seems more defensive, the ** do loop can not be written: ** ** do {...} while( pRoot->iDocidbEof==0 ); }while( pRoot->iDocid!=iDocid && rc==SQLITE_OK ); } } return rc; } /* ** This function is used by the matchinfo() module to query a phrase ** expression node for the following information: ** ** 1. The total number of occurrences of the phrase in each column of ** the FTS table (considering all rows), and ** ** 2. For each column, the number of rows in the table for which the ** column contains at least one instance of the phrase. ** ** If no error occurs, SQLITE_OK is returned and the values for each column ** written into the array aiOut as follows: ** ** aiOut[iCol*3 + 1] = Number of occurrences ** aiOut[iCol*3 + 2] = Number of rows containing at least one instance ** ** Caveats: ** ** * If a phrase consists entirely of deferred tokens, then all output ** values are set to the number of documents in the table. In other ** words we assume that very common tokens occur exactly once in each ** column of each row of the table. ** ** * If a phrase contains some deferred tokens (and some non-deferred ** tokens), count the potential occurrence identified by considering ** the non-deferred tokens instead of actual phrase occurrences. ** ** * If the phrase is part of a NEAR expression, then only phrase instances ** that meet the NEAR constraint are included in the counts. */ int sqlite3Fts3EvalPhraseStats( Fts3Cursor *pCsr, /* FTS cursor handle */ Fts3Expr *pExpr, /* Phrase expression */ u32 *aiOut /* Array to write results into (see above) */ ){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; int rc = SQLITE_OK; int iCol; if( pExpr->bDeferred && pExpr->pParent->eType!=FTSQUERY_NEAR ){ assert( pCsr->nDoc>0 ); for(iCol=0; iColnColumn; iCol++){ aiOut[iCol*3 + 1] = (u32)pCsr->nDoc; aiOut[iCol*3 + 2] = (u32)pCsr->nDoc; } }else{ rc = fts3EvalGatherStats(pCsr, pExpr); if( rc==SQLITE_OK ){ assert( pExpr->aMI ); for(iCol=0; iColnColumn; iCol++){ aiOut[iCol*3 + 1] = pExpr->aMI[iCol*3 + 1]; aiOut[iCol*3 + 2] = pExpr->aMI[iCol*3 + 2]; } } } return rc; } /* ** The expression pExpr passed as the second argument to this function ** must be of type FTSQUERY_PHRASE. ** ** The returned value is either NULL or a pointer to a buffer containing ** a position-list indicating the occurrences of the phrase in column iCol ** of the current row. ** ** More specifically, the returned buffer contains 1 varint for each ** occurrence of the phrase in the column, stored using the normal (delta+2) ** compression and is terminated by either an 0x01 or 0x00 byte. For example, ** if the requested column contains "a b X c d X X" and the position-list ** for 'X' is requested, the buffer returned may contain: ** ** 0x04 0x05 0x03 0x01 or 0x04 0x05 0x03 0x00 ** ** This function works regardless of whether or not the phrase is deferred, ** incremental, or neither. */ int sqlite3Fts3EvalPhrasePoslist( Fts3Cursor *pCsr, /* FTS3 cursor object */ Fts3Expr *pExpr, /* Phrase to return doclist for */ int iCol, /* Column to return position list for */ char **ppOut /* OUT: Pointer to position list */ ){ Fts3Phrase *pPhrase = pExpr->pPhrase; Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; char *pIter; int iThis; sqlite3_int64 iDocid; /* If this phrase is applies specifically to some column other than ** column iCol, return a NULL pointer. */ *ppOut = 0; assert( iCol>=0 && iColnColumn ); if( (pPhrase->iColumnnColumn && pPhrase->iColumn!=iCol) ){ return SQLITE_OK; } iDocid = pExpr->iDocid; pIter = pPhrase->doclist.pList; if( iDocid!=pCsr->iPrevId || pExpr->bEof ){ int rc = SQLITE_OK; int bDescDoclist = pTab->bDescIdx; /* For DOCID_CMP macro */ int bOr = 0; u8 bTreeEof = 0; Fts3Expr *p; /* Used to iterate from pExpr to root */ Fts3Expr *pNear; /* Most senior NEAR ancestor (or pExpr) */ int bMatch; /* Check if this phrase descends from an OR expression node. If not, ** return NULL. Otherwise, the entry that corresponds to docid ** pCsr->iPrevId may lie earlier in the doclist buffer. Or, if the ** tree that the node is part of has been marked as EOF, but the node ** itself is not EOF, then it may point to an earlier entry. */ pNear = pExpr; for(p=pExpr->pParent; p; p=p->pParent){ if( p->eType==FTSQUERY_OR ) bOr = 1; if( p->eType==FTSQUERY_NEAR ) pNear = p; if( p->bEof ) bTreeEof = 1; } if( bOr==0 ) return SQLITE_OK; /* This is the descendent of an OR node. In this case we cannot use ** an incremental phrase. Load the entire doclist for the phrase ** into memory in this case. */ if( pPhrase->bIncr ){ int bEofSave = pNear->bEof; fts3EvalRestart(pCsr, pNear, &rc); while( rc==SQLITE_OK && !pNear->bEof ){ fts3EvalNextRow(pCsr, pNear, &rc); if( bEofSave==0 && pNear->iDocid==iDocid ) break; } assert( rc!=SQLITE_OK || pPhrase->bIncr==0 ); } if( bTreeEof ){ while( rc==SQLITE_OK && !pNear->bEof ){ fts3EvalNextRow(pCsr, pNear, &rc); } } if( rc!=SQLITE_OK ) return rc; bMatch = 1; for(p=pNear; p; p=p->pLeft){ u8 bEof = 0; Fts3Expr *pTest = p; Fts3Phrase *pPh; assert( pTest->eType==FTSQUERY_NEAR || pTest->eType==FTSQUERY_PHRASE ); if( pTest->eType==FTSQUERY_NEAR ) pTest = pTest->pRight; assert( pTest->eType==FTSQUERY_PHRASE ); pPh = pTest->pPhrase; pIter = pPh->pOrPoslist; iDocid = pPh->iOrDocid; if( pCsr->bDesc==bDescDoclist ){ bEof = !pPh->doclist.nAll || (pIter >= (pPh->doclist.aAll + pPh->doclist.nAll)); while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)<0 ) && bEof==0 ){ sqlite3Fts3DoclistNext( bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll, &pIter, &iDocid, &bEof ); } }else{ bEof = !pPh->doclist.nAll || (pIter && pIter<=pPh->doclist.aAll); while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)>0 ) && bEof==0 ){ int dummy; sqlite3Fts3DoclistPrev( bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll, &pIter, &iDocid, &dummy, &bEof ); } } pPh->pOrPoslist = pIter; pPh->iOrDocid = iDocid; if( bEof || iDocid!=pCsr->iPrevId ) bMatch = 0; } if( bMatch ){ pIter = pPhrase->pOrPoslist; }else{ pIter = 0; } } if( pIter==0 ) return SQLITE_OK; if( *pIter==0x01 ){ pIter++; pIter += fts3GetVarint32(pIter, &iThis); }else{ iThis = 0; } while( iThisdoclist, and ** * any Fts3MultiSegReader objects held by phrase tokens. */ void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *pPhrase){ if( pPhrase ){ int i; sqlite3_free(pPhrase->doclist.aAll); fts3EvalInvalidatePoslist(pPhrase); memset(&pPhrase->doclist, 0, sizeof(Fts3Doclist)); for(i=0; inToken; i++){ fts3SegReaderCursorFree(pPhrase->aToken[i].pSegcsr); pPhrase->aToken[i].pSegcsr = 0; } } } /* ** Return SQLITE_CORRUPT_VTAB. */ #ifdef SQLITE_DEBUG int sqlite3Fts3Corrupt(){ return SQLITE_CORRUPT_VTAB; } #endif #if !SQLITE_CORE /* ** Initialize API pointer table, if required. */ #ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_fts3_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ SQLITE_EXTENSION_INIT2(pApi) return sqlite3Fts3Init(db); } #endif #endif ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3.h ================================================ /* ** 2006 Oct 10 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This header file is used by programs that want to link against the ** FTS3 library. All it does is declare the sqlite3Fts3Init() interface. */ #include "sqlite3.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ int sqlite3Fts3Init(sqlite3 *db); #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3Int.h ================================================ /* ** 2009 Nov 12 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** */ #ifndef _FTSINT_H #define _FTSINT_H #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) # define NDEBUG 1 #endif /* FTS3/FTS4 require virtual tables */ #ifdef SQLITE_OMIT_VIRTUALTABLE # undef SQLITE_ENABLE_FTS3 # undef SQLITE_ENABLE_FTS4 #endif /* ** FTS4 is really an extension for FTS3. It is enabled using the ** SQLITE_ENABLE_FTS3 macro. But to avoid confusion we also all ** the SQLITE_ENABLE_FTS4 macro to serve as an alisse for SQLITE_ENABLE_FTS3. */ #if defined(SQLITE_ENABLE_FTS4) && !defined(SQLITE_ENABLE_FTS3) # define SQLITE_ENABLE_FTS3 #endif #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) /* If not building as part of the core, include sqlite3ext.h. */ #ifndef SQLITE_CORE # include "sqlite3ext.h" SQLITE_EXTENSION_INIT3 #endif #include "sqlite3.h" #include "fts3_tokenizer.h" #include "fts3_hash.h" /* ** This constant determines the maximum depth of an FTS expression tree ** that the library will create and use. FTS uses recursion to perform ** various operations on the query tree, so the disadvantage of a large ** limit is that it may allow very large queries to use large amounts ** of stack space (perhaps causing a stack overflow). */ #ifndef SQLITE_FTS3_MAX_EXPR_DEPTH # define SQLITE_FTS3_MAX_EXPR_DEPTH 12 #endif /* ** This constant controls how often segments are merged. Once there are ** FTS3_MERGE_COUNT segments of level N, they are merged into a single ** segment of level N+1. */ #define FTS3_MERGE_COUNT 16 /* ** This is the maximum amount of data (in bytes) to store in the ** Fts3Table.pendingTerms hash table. Normally, the hash table is ** populated as documents are inserted/updated/deleted in a transaction ** and used to create a new segment when the transaction is committed. ** However if this limit is reached midway through a transaction, a new ** segment is created and the hash table cleared immediately. */ #define FTS3_MAX_PENDING_DATA (1*1024*1024) /* ** Macro to return the number of elements in an array. SQLite has a ** similar macro called ArraySize(). Use a different name to avoid ** a collision when building an amalgamation with built-in FTS3. */ #define SizeofArray(X) ((int)(sizeof(X)/sizeof(X[0]))) #ifndef MIN # define MIN(x,y) ((x)<(y)?(x):(y)) #endif #ifndef MAX # define MAX(x,y) ((x)>(y)?(x):(y)) #endif /* ** Maximum length of a varint encoded integer. The varint format is different ** from that used by SQLite, so the maximum length is 10, not 9. */ #define FTS3_VARINT_MAX 10 /* ** FTS4 virtual tables may maintain multiple indexes - one index of all terms ** in the document set and zero or more prefix indexes. All indexes are stored ** as one or more b+-trees in the %_segments and %_segdir tables. ** ** It is possible to determine which index a b+-tree belongs to based on the ** value stored in the "%_segdir.level" column. Given this value L, the index ** that the b+-tree belongs to is (L<<10). In other words, all b+-trees with ** level values between 0 and 1023 (inclusive) belong to index 0, all levels ** between 1024 and 2047 to index 1, and so on. ** ** It is considered impossible for an index to use more than 1024 levels. In ** theory though this may happen, but only after at least ** (FTS3_MERGE_COUNT^1024) separate flushes of the pending-terms tables. */ #define FTS3_SEGDIR_MAXLEVEL 1024 #define FTS3_SEGDIR_MAXLEVEL_STR "1024" /* ** The testcase() macro is only used by the amalgamation. If undefined, ** make it a no-op. */ #ifndef testcase # define testcase(X) #endif /* ** Terminator values for position-lists and column-lists. */ #define POS_COLUMN (1) /* Column-list terminator */ #define POS_END (0) /* Position-list terminator */ /* ** This section provides definitions to allow the ** FTS3 extension to be compiled outside of the ** amalgamation. */ #ifndef SQLITE_AMALGAMATION /* ** Macros indicating that conditional expressions are always true or ** false. */ #ifdef SQLITE_COVERAGE_TEST # define ALWAYS(x) (1) # define NEVER(X) (0) #elif defined(SQLITE_DEBUG) # define ALWAYS(x) sqlite3Fts3Always((x)!=0) # define NEVER(x) sqlite3Fts3Never((x)!=0) int sqlite3Fts3Always(int b); int sqlite3Fts3Never(int b); #else # define ALWAYS(x) (x) # define NEVER(x) (x) #endif /* ** Internal types used by SQLite. */ typedef unsigned char u8; /* 1-byte (or larger) unsigned integer */ typedef short int i16; /* 2-byte (or larger) signed integer */ typedef unsigned int u32; /* 4-byte unsigned integer */ typedef sqlite3_uint64 u64; /* 8-byte unsigned integer */ typedef sqlite3_int64 i64; /* 8-byte signed integer */ /* ** Macro used to suppress compiler warnings for unused parameters. */ #define UNUSED_PARAMETER(x) (void)(x) /* ** Activate assert() only if SQLITE_TEST is enabled. */ #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) # define NDEBUG 1 #endif /* ** The TESTONLY macro is used to enclose variable declarations or ** other bits of code that are needed to support the arguments ** within testcase() and assert() macros. */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) # define TESTONLY(X) X #else # define TESTONLY(X) #endif #endif /* SQLITE_AMALGAMATION */ #ifdef SQLITE_DEBUG int sqlite3Fts3Corrupt(void); # define FTS_CORRUPT_VTAB sqlite3Fts3Corrupt() #else # define FTS_CORRUPT_VTAB SQLITE_CORRUPT_VTAB #endif typedef struct Fts3Table Fts3Table; typedef struct Fts3Cursor Fts3Cursor; typedef struct Fts3Expr Fts3Expr; typedef struct Fts3Phrase Fts3Phrase; typedef struct Fts3PhraseToken Fts3PhraseToken; typedef struct Fts3Doclist Fts3Doclist; typedef struct Fts3SegFilter Fts3SegFilter; typedef struct Fts3DeferredToken Fts3DeferredToken; typedef struct Fts3SegReader Fts3SegReader; typedef struct Fts3MultiSegReader Fts3MultiSegReader; typedef struct MatchinfoBuffer MatchinfoBuffer; /* ** A connection to a fulltext index is an instance of the following ** structure. The xCreate and xConnect methods create an instance ** of this structure and xDestroy and xDisconnect free that instance. ** All other methods receive a pointer to the structure as one of their ** arguments. */ struct Fts3Table { sqlite3_vtab base; /* Base class used by SQLite core */ sqlite3 *db; /* The database connection */ const char *zDb; /* logical database name */ const char *zName; /* virtual table name */ int nColumn; /* number of named columns in virtual table */ char **azColumn; /* column names. malloced */ u8 *abNotindexed; /* True for 'notindexed' columns */ sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */ char *zContentTbl; /* content=xxx option, or NULL */ char *zLanguageid; /* languageid=xxx option, or NULL */ int nAutoincrmerge; /* Value configured by 'automerge' */ u32 nLeafAdd; /* Number of leaf blocks added this trans */ /* Precompiled statements used by the implementation. Each of these ** statements is run and reset within a single virtual table API call. */ sqlite3_stmt *aStmt[40]; char *zReadExprlist; char *zWriteExprlist; int nNodeSize; /* Soft limit for node size */ u8 bFts4; /* True for FTS4, false for FTS3 */ u8 bHasStat; /* True if %_stat table exists (2==unknown) */ u8 bHasDocsize; /* True if %_docsize table exists */ u8 bDescIdx; /* True if doclists are in reverse order */ u8 bIgnoreSavepoint; /* True to ignore xSavepoint invocations */ int nPgsz; /* Page size for host database */ char *zSegmentsTbl; /* Name of %_segments table */ sqlite3_blob *pSegments; /* Blob handle open on %_segments table */ /* ** The following array of hash tables is used to buffer pending index ** updates during transactions. All pending updates buffered at any one ** time must share a common language-id (see the FTS4 langid= feature). ** The current language id is stored in variable iPrevLangid. ** ** A single FTS4 table may have multiple full-text indexes. For each index ** there is an entry in the aIndex[] array. Index 0 is an index of all the ** terms that appear in the document set. Each subsequent index in aIndex[] ** is an index of prefixes of a specific length. ** ** Variable nPendingData contains an estimate the memory consumed by the ** pending data structures, including hash table overhead, but not including ** malloc overhead. When nPendingData exceeds nMaxPendingData, all hash ** tables are flushed to disk. Variable iPrevDocid is the docid of the most ** recently inserted record. */ int nIndex; /* Size of aIndex[] */ struct Fts3Index { int nPrefix; /* Prefix length (0 for main terms index) */ Fts3Hash hPending; /* Pending terms table for this index */ } *aIndex; int nMaxPendingData; /* Max pending data before flush to disk */ int nPendingData; /* Current bytes of pending data */ sqlite_int64 iPrevDocid; /* Docid of most recently inserted document */ int iPrevLangid; /* Langid of recently inserted document */ int bPrevDelete; /* True if last operation was a delete */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) /* State variables used for validating that the transaction control ** methods of the virtual table are called at appropriate times. These ** values do not contribute to FTS functionality; they are used for ** verifying the operation of the SQLite core. */ int inTransaction; /* True after xBegin but before xCommit/xRollback */ int mxSavepoint; /* Largest valid xSavepoint integer */ #endif #ifdef SQLITE_TEST /* True to disable the incremental doclist optimization. This is controled ** by special insert command 'test-no-incr-doclist'. */ int bNoIncrDoclist; #endif }; /* ** When the core wants to read from the virtual table, it creates a ** virtual table cursor (an instance of the following structure) using ** the xOpen method. Cursors are destroyed using the xClose method. */ struct Fts3Cursor { sqlite3_vtab_cursor base; /* Base class used by SQLite core */ i16 eSearch; /* Search strategy (see below) */ u8 isEof; /* True if at End Of Results */ u8 isRequireSeek; /* True if must seek pStmt to %_content row */ sqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */ Fts3Expr *pExpr; /* Parsed MATCH query string */ int iLangid; /* Language being queried for */ int nPhrase; /* Number of matchable phrases in query */ Fts3DeferredToken *pDeferred; /* Deferred search tokens, if any */ sqlite3_int64 iPrevId; /* Previous id read from aDoclist */ char *pNextId; /* Pointer into the body of aDoclist */ char *aDoclist; /* List of docids for full-text queries */ int nDoclist; /* Size of buffer at aDoclist */ u8 bDesc; /* True to sort in descending order */ int eEvalmode; /* An FTS3_EVAL_XX constant */ int nRowAvg; /* Average size of database rows, in pages */ sqlite3_int64 nDoc; /* Documents in table */ i64 iMinDocid; /* Minimum docid to return */ i64 iMaxDocid; /* Maximum docid to return */ int isMatchinfoNeeded; /* True when aMatchinfo[] needs filling in */ MatchinfoBuffer *pMIBuffer; /* Buffer for matchinfo data */ }; #define FTS3_EVAL_FILTER 0 #define FTS3_EVAL_NEXT 1 #define FTS3_EVAL_MATCHINFO 2 /* ** The Fts3Cursor.eSearch member is always set to one of the following. ** Actualy, Fts3Cursor.eSearch can be greater than or equal to ** FTS3_FULLTEXT_SEARCH. If so, then Fts3Cursor.eSearch - 2 is the index ** of the column to be searched. For example, in ** ** CREATE VIRTUAL TABLE ex1 USING fts3(a,b,c,d); ** SELECT docid FROM ex1 WHERE b MATCH 'one two three'; ** ** Because the LHS of the MATCH operator is 2nd column "b", ** Fts3Cursor.eSearch will be set to FTS3_FULLTEXT_SEARCH+1. (+0 for a, ** +1 for b, +2 for c, +3 for d.) If the LHS of MATCH were "ex1" ** indicating that all columns should be searched, ** then eSearch would be set to FTS3_FULLTEXT_SEARCH+4. */ #define FTS3_FULLSCAN_SEARCH 0 /* Linear scan of %_content table */ #define FTS3_DOCID_SEARCH 1 /* Lookup by rowid on %_content table */ #define FTS3_FULLTEXT_SEARCH 2 /* Full-text index search */ /* ** The lower 16-bits of the sqlite3_index_info.idxNum value set by ** the xBestIndex() method contains the Fts3Cursor.eSearch value described ** above. The upper 16-bits contain a combination of the following ** bits, used to describe extra constraints on full-text searches. */ #define FTS3_HAVE_LANGID 0x00010000 /* languageid=? */ #define FTS3_HAVE_DOCID_GE 0x00020000 /* docid>=? */ #define FTS3_HAVE_DOCID_LE 0x00040000 /* docid<=? */ struct Fts3Doclist { char *aAll; /* Array containing doclist (or NULL) */ int nAll; /* Size of a[] in bytes */ char *pNextDocid; /* Pointer to next docid */ sqlite3_int64 iDocid; /* Current docid (if pList!=0) */ int bFreeList; /* True if pList should be sqlite3_free()d */ char *pList; /* Pointer to position list following iDocid */ int nList; /* Length of position list */ }; /* ** A "phrase" is a sequence of one or more tokens that must match in ** sequence. A single token is the base case and the most common case. ** For a sequence of tokens contained in double-quotes (i.e. "one two three") ** nToken will be the number of tokens in the string. */ struct Fts3PhraseToken { char *z; /* Text of the token */ int n; /* Number of bytes in buffer z */ int isPrefix; /* True if token ends with a "*" character */ int bFirst; /* True if token must appear at position 0 */ /* Variables above this point are populated when the expression is ** parsed (by code in fts3_expr.c). Below this point the variables are ** used when evaluating the expression. */ Fts3DeferredToken *pDeferred; /* Deferred token object for this token */ Fts3MultiSegReader *pSegcsr; /* Segment-reader for this token */ }; struct Fts3Phrase { /* Cache of doclist for this phrase. */ Fts3Doclist doclist; int bIncr; /* True if doclist is loaded incrementally */ int iDoclistToken; /* Used by sqlite3Fts3EvalPhrasePoslist() if this is a descendent of an ** OR condition. */ char *pOrPoslist; i64 iOrDocid; /* Variables below this point are populated by fts3_expr.c when parsing ** a MATCH expression. Everything above is part of the evaluation phase. */ int nToken; /* Number of tokens in the phrase */ int iColumn; /* Index of column this phrase must match */ Fts3PhraseToken aToken[1]; /* One entry for each token in the phrase */ }; /* ** A tree of these objects forms the RHS of a MATCH operator. ** ** If Fts3Expr.eType is FTSQUERY_PHRASE and isLoaded is true, then aDoclist ** points to a malloced buffer, size nDoclist bytes, containing the results ** of this phrase query in FTS3 doclist format. As usual, the initial ** "Length" field found in doclists stored on disk is omitted from this ** buffer. ** ** Variable aMI is used only for FTSQUERY_NEAR nodes to store the global ** matchinfo data. If it is not NULL, it points to an array of size nCol*3, ** where nCol is the number of columns in the queried FTS table. The array ** is populated as follows: ** ** aMI[iCol*3 + 0] = Undefined ** aMI[iCol*3 + 1] = Number of occurrences ** aMI[iCol*3 + 2] = Number of rows containing at least one instance ** ** The aMI array is allocated using sqlite3_malloc(). It should be freed ** when the expression node is. */ struct Fts3Expr { int eType; /* One of the FTSQUERY_XXX values defined below */ int nNear; /* Valid if eType==FTSQUERY_NEAR */ Fts3Expr *pParent; /* pParent->pLeft==this or pParent->pRight==this */ Fts3Expr *pLeft; /* Left operand */ Fts3Expr *pRight; /* Right operand */ Fts3Phrase *pPhrase; /* Valid if eType==FTSQUERY_PHRASE */ /* The following are used by the fts3_eval.c module. */ sqlite3_int64 iDocid; /* Current docid */ u8 bEof; /* True this expression is at EOF already */ u8 bStart; /* True if iDocid is valid */ u8 bDeferred; /* True if this expression is entirely deferred */ /* The following are used by the fts3_snippet.c module. */ int iPhrase; /* Index of this phrase in matchinfo() results */ u32 *aMI; /* See above */ }; /* ** Candidate values for Fts3Query.eType. Note that the order of the first ** four values is in order of precedence when parsing expressions. For ** example, the following: ** ** "a OR b AND c NOT d NEAR e" ** ** is equivalent to: ** ** "a OR (b AND (c NOT (d NEAR e)))" */ #define FTSQUERY_NEAR 1 #define FTSQUERY_NOT 2 #define FTSQUERY_AND 3 #define FTSQUERY_OR 4 #define FTSQUERY_PHRASE 5 /* fts3_write.c */ int sqlite3Fts3UpdateMethod(sqlite3_vtab*,int,sqlite3_value**,sqlite3_int64*); int sqlite3Fts3PendingTermsFlush(Fts3Table *); void sqlite3Fts3PendingTermsClear(Fts3Table *); int sqlite3Fts3Optimize(Fts3Table *); int sqlite3Fts3SegReaderNew(int, int, sqlite3_int64, sqlite3_int64, sqlite3_int64, const char *, int, Fts3SegReader**); int sqlite3Fts3SegReaderPending( Fts3Table*,int,const char*,int,int,Fts3SegReader**); void sqlite3Fts3SegReaderFree(Fts3SegReader *); int sqlite3Fts3AllSegdirs(Fts3Table*, int, int, int, sqlite3_stmt **); int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char **, int*, int*); int sqlite3Fts3SelectDoctotal(Fts3Table *, sqlite3_stmt **); int sqlite3Fts3SelectDocsize(Fts3Table *, sqlite3_int64, sqlite3_stmt **); #ifndef SQLITE_DISABLE_FTS4_DEFERRED void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *); int sqlite3Fts3DeferToken(Fts3Cursor *, Fts3PhraseToken *, int); int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *); void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *); int sqlite3Fts3DeferredTokenList(Fts3DeferredToken *, char **, int *); #else # define sqlite3Fts3FreeDeferredTokens(x) # define sqlite3Fts3DeferToken(x,y,z) SQLITE_OK # define sqlite3Fts3CacheDeferredDoclists(x) SQLITE_OK # define sqlite3Fts3FreeDeferredDoclists(x) # define sqlite3Fts3DeferredTokenList(x,y,z) SQLITE_OK #endif void sqlite3Fts3SegmentsClose(Fts3Table *); int sqlite3Fts3MaxLevel(Fts3Table *, int *); /* Special values interpreted by sqlite3SegReaderCursor() */ #define FTS3_SEGCURSOR_PENDING -1 #define FTS3_SEGCURSOR_ALL -2 int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3MultiSegReader*, Fts3SegFilter*); int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3MultiSegReader *); void sqlite3Fts3SegReaderFinish(Fts3MultiSegReader *); int sqlite3Fts3SegReaderCursor(Fts3Table *, int, int, int, const char *, int, int, int, Fts3MultiSegReader *); /* Flags allowed as part of the 4th argument to SegmentReaderIterate() */ #define FTS3_SEGMENT_REQUIRE_POS 0x00000001 #define FTS3_SEGMENT_IGNORE_EMPTY 0x00000002 #define FTS3_SEGMENT_COLUMN_FILTER 0x00000004 #define FTS3_SEGMENT_PREFIX 0x00000008 #define FTS3_SEGMENT_SCAN 0x00000010 #define FTS3_SEGMENT_FIRST 0x00000020 /* Type passed as 4th argument to SegmentReaderIterate() */ struct Fts3SegFilter { const char *zTerm; int nTerm; int iCol; int flags; }; struct Fts3MultiSegReader { /* Used internally by sqlite3Fts3SegReaderXXX() calls */ Fts3SegReader **apSegment; /* Array of Fts3SegReader objects */ int nSegment; /* Size of apSegment array */ int nAdvance; /* How many seg-readers to advance */ Fts3SegFilter *pFilter; /* Pointer to filter object */ char *aBuffer; /* Buffer to merge doclists in */ int nBuffer; /* Allocated size of aBuffer[] in bytes */ int iColFilter; /* If >=0, filter for this column */ int bRestart; /* Used by fts3.c only. */ int nCost; /* Cost of running iterator */ int bLookup; /* True if a lookup of a single entry. */ /* Output values. Valid only after Fts3SegReaderStep() returns SQLITE_ROW. */ char *zTerm; /* Pointer to term buffer */ int nTerm; /* Size of zTerm in bytes */ char *aDoclist; /* Pointer to doclist buffer */ int nDoclist; /* Size of aDoclist[] in bytes */ }; int sqlite3Fts3Incrmerge(Fts3Table*,int,int); #define fts3GetVarint32(p, piVal) ( \ (*(u8*)(p)&0x80) ? sqlite3Fts3GetVarint32(p, piVal) : (*piVal=*(u8*)(p), 1) \ ) /* fts3.c */ void sqlite3Fts3ErrMsg(char**,const char*,...); int sqlite3Fts3PutVarint(char *, sqlite3_int64); int sqlite3Fts3GetVarint(const char *, sqlite_int64 *); int sqlite3Fts3GetVarint32(const char *, int *); int sqlite3Fts3VarintLen(sqlite3_uint64); void sqlite3Fts3Dequote(char *); void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*); int sqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *); int sqlite3Fts3FirstFilter(sqlite3_int64, char *, int, char *); void sqlite3Fts3CreateStatTable(int*, Fts3Table*); int sqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc); /* fts3_tokenizer.c */ const char *sqlite3Fts3NextToken(const char *, int *); int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *); int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *, sqlite3_tokenizer **, char ** ); int sqlite3Fts3IsIdChar(char); /* fts3_snippet.c */ void sqlite3Fts3Offsets(sqlite3_context*, Fts3Cursor*); void sqlite3Fts3Snippet(sqlite3_context *, Fts3Cursor *, const char *, const char *, const char *, int, int ); void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *, const char *); void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p); /* fts3_expr.c */ int sqlite3Fts3ExprParse(sqlite3_tokenizer *, int, char **, int, int, int, const char *, int, Fts3Expr **, char ** ); void sqlite3Fts3ExprFree(Fts3Expr *); #ifdef SQLITE_TEST int sqlite3Fts3ExprInitTestInterface(sqlite3 *db); int sqlite3Fts3InitTerm(sqlite3 *db); #endif int sqlite3Fts3OpenTokenizer(sqlite3_tokenizer *, int, const char *, int, sqlite3_tokenizer_cursor ** ); /* fts3_aux.c */ int sqlite3Fts3InitAux(sqlite3 *db); void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *); int sqlite3Fts3MsrIncrStart( Fts3Table*, Fts3MultiSegReader*, int, const char*, int); int sqlite3Fts3MsrIncrNext( Fts3Table *, Fts3MultiSegReader *, sqlite3_int64 *, char **, int *); int sqlite3Fts3EvalPhrasePoslist(Fts3Cursor *, Fts3Expr *, int iCol, char **); int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *); int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr); /* fts3_tokenize_vtab.c */ int sqlite3Fts3InitTok(sqlite3*, Fts3Hash *); /* fts3_unicode2.c (functions generated by parsing unicode text files) */ #ifndef SQLITE_DISABLE_FTS3_UNICODE int sqlite3FtsUnicodeFold(int, int); int sqlite3FtsUnicodeIsalnum(int); int sqlite3FtsUnicodeIsdiacritic(int); #endif #endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */ #endif /* _FTSINT_H */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_aux.c ================================================ /* ** 2011 Jan 27 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #include #include typedef struct Fts3auxTable Fts3auxTable; typedef struct Fts3auxCursor Fts3auxCursor; struct Fts3auxTable { sqlite3_vtab base; /* Base class used by SQLite core */ Fts3Table *pFts3Tab; }; struct Fts3auxCursor { sqlite3_vtab_cursor base; /* Base class used by SQLite core */ Fts3MultiSegReader csr; /* Must be right after "base" */ Fts3SegFilter filter; char *zStop; int nStop; /* Byte-length of string zStop */ int iLangid; /* Language id to query */ int isEof; /* True if cursor is at EOF */ sqlite3_int64 iRowid; /* Current rowid */ int iCol; /* Current value of 'col' column */ int nStat; /* Size of aStat[] array */ struct Fts3auxColstats { sqlite3_int64 nDoc; /* 'documents' values for current csr row */ sqlite3_int64 nOcc; /* 'occurrences' values for current csr row */ } *aStat; }; /* ** Schema of the terms table. */ #define FTS3_AUX_SCHEMA \ "CREATE TABLE x(term, col, documents, occurrences, languageid HIDDEN)" /* ** This function does all the work for both the xConnect and xCreate methods. ** These tables have no persistent representation of their own, so xConnect ** and xCreate are identical operations. */ static int fts3auxConnectMethod( sqlite3 *db, /* Database connection */ void *pUnused, /* Unused */ int argc, /* Number of elements in argv array */ const char * const *argv, /* xCreate/xConnect argument array */ sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ char **pzErr /* OUT: sqlite3_malloc'd error message */ ){ char const *zDb; /* Name of database (e.g. "main") */ char const *zFts3; /* Name of fts3 table */ int nDb; /* Result of strlen(zDb) */ int nFts3; /* Result of strlen(zFts3) */ int nByte; /* Bytes of space to allocate here */ int rc; /* value returned by declare_vtab() */ Fts3auxTable *p; /* Virtual table object to return */ UNUSED_PARAMETER(pUnused); /* The user should invoke this in one of two forms: ** ** CREATE VIRTUAL TABLE xxx USING fts4aux(fts4-table); ** CREATE VIRTUAL TABLE xxx USING fts4aux(fts4-table-db, fts4-table); */ if( argc!=4 && argc!=5 ) goto bad_args; zDb = argv[1]; nDb = (int)strlen(zDb); if( argc==5 ){ if( nDb==4 && 0==sqlite3_strnicmp("temp", zDb, 4) ){ zDb = argv[3]; nDb = (int)strlen(zDb); zFts3 = argv[4]; }else{ goto bad_args; } }else{ zFts3 = argv[3]; } nFts3 = (int)strlen(zFts3); rc = sqlite3_declare_vtab(db, FTS3_AUX_SCHEMA); if( rc!=SQLITE_OK ) return rc; nByte = sizeof(Fts3auxTable) + sizeof(Fts3Table) + nDb + nFts3 + 2; p = (Fts3auxTable *)sqlite3_malloc(nByte); if( !p ) return SQLITE_NOMEM; memset(p, 0, nByte); p->pFts3Tab = (Fts3Table *)&p[1]; p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1]; p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1]; p->pFts3Tab->db = db; p->pFts3Tab->nIndex = 1; memcpy((char *)p->pFts3Tab->zDb, zDb, nDb); memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3); sqlite3Fts3Dequote((char *)p->pFts3Tab->zName); *ppVtab = (sqlite3_vtab *)p; return SQLITE_OK; bad_args: sqlite3Fts3ErrMsg(pzErr, "invalid arguments to fts4aux constructor"); return SQLITE_ERROR; } /* ** This function does the work for both the xDisconnect and xDestroy methods. ** These tables have no persistent representation of their own, so xDisconnect ** and xDestroy are identical operations. */ static int fts3auxDisconnectMethod(sqlite3_vtab *pVtab){ Fts3auxTable *p = (Fts3auxTable *)pVtab; Fts3Table *pFts3 = p->pFts3Tab; int i; /* Free any prepared statements held */ for(i=0; iaStmt); i++){ sqlite3_finalize(pFts3->aStmt[i]); } sqlite3_free(pFts3->zSegmentsTbl); sqlite3_free(p); return SQLITE_OK; } #define FTS4AUX_EQ_CONSTRAINT 1 #define FTS4AUX_GE_CONSTRAINT 2 #define FTS4AUX_LE_CONSTRAINT 4 /* ** xBestIndex - Analyze a WHERE and ORDER BY clause. */ static int fts3auxBestIndexMethod( sqlite3_vtab *pVTab, sqlite3_index_info *pInfo ){ int i; int iEq = -1; int iGe = -1; int iLe = -1; int iLangid = -1; int iNext = 1; /* Next free argvIndex value */ UNUSED_PARAMETER(pVTab); /* This vtab delivers always results in "ORDER BY term ASC" order. */ if( pInfo->nOrderBy==1 && pInfo->aOrderBy[0].iColumn==0 && pInfo->aOrderBy[0].desc==0 ){ pInfo->orderByConsumed = 1; } /* Search for equality and range constraints on the "term" column. ** And equality constraints on the hidden "languageid" column. */ for(i=0; inConstraint; i++){ if( pInfo->aConstraint[i].usable ){ int op = pInfo->aConstraint[i].op; int iCol = pInfo->aConstraint[i].iColumn; if( iCol==0 ){ if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iEq = i; if( op==SQLITE_INDEX_CONSTRAINT_LT ) iLe = i; if( op==SQLITE_INDEX_CONSTRAINT_LE ) iLe = i; if( op==SQLITE_INDEX_CONSTRAINT_GT ) iGe = i; if( op==SQLITE_INDEX_CONSTRAINT_GE ) iGe = i; } if( iCol==4 ){ if( op==SQLITE_INDEX_CONSTRAINT_EQ ) iLangid = i; } } } if( iEq>=0 ){ pInfo->idxNum = FTS4AUX_EQ_CONSTRAINT; pInfo->aConstraintUsage[iEq].argvIndex = iNext++; pInfo->estimatedCost = 5; }else{ pInfo->idxNum = 0; pInfo->estimatedCost = 20000; if( iGe>=0 ){ pInfo->idxNum += FTS4AUX_GE_CONSTRAINT; pInfo->aConstraintUsage[iGe].argvIndex = iNext++; pInfo->estimatedCost /= 2; } if( iLe>=0 ){ pInfo->idxNum += FTS4AUX_LE_CONSTRAINT; pInfo->aConstraintUsage[iLe].argvIndex = iNext++; pInfo->estimatedCost /= 2; } } if( iLangid>=0 ){ pInfo->aConstraintUsage[iLangid].argvIndex = iNext++; pInfo->estimatedCost--; } return SQLITE_OK; } /* ** xOpen - Open a cursor. */ static int fts3auxOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ Fts3auxCursor *pCsr; /* Pointer to cursor object to return */ UNUSED_PARAMETER(pVTab); pCsr = (Fts3auxCursor *)sqlite3_malloc(sizeof(Fts3auxCursor)); if( !pCsr ) return SQLITE_NOMEM; memset(pCsr, 0, sizeof(Fts3auxCursor)); *ppCsr = (sqlite3_vtab_cursor *)pCsr; return SQLITE_OK; } /* ** xClose - Close a cursor. */ static int fts3auxCloseMethod(sqlite3_vtab_cursor *pCursor){ Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab; Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; sqlite3Fts3SegmentsClose(pFts3); sqlite3Fts3SegReaderFinish(&pCsr->csr); sqlite3_free((void *)pCsr->filter.zTerm); sqlite3_free(pCsr->zStop); sqlite3_free(pCsr->aStat); sqlite3_free(pCsr); return SQLITE_OK; } static int fts3auxGrowStatArray(Fts3auxCursor *pCsr, int nSize){ if( nSize>pCsr->nStat ){ struct Fts3auxColstats *aNew; aNew = (struct Fts3auxColstats *)sqlite3_realloc(pCsr->aStat, sizeof(struct Fts3auxColstats) * nSize ); if( aNew==0 ) return SQLITE_NOMEM; memset(&aNew[pCsr->nStat], 0, sizeof(struct Fts3auxColstats) * (nSize - pCsr->nStat) ); pCsr->aStat = aNew; pCsr->nStat = nSize; } return SQLITE_OK; } /* ** xNext - Advance the cursor to the next row, if any. */ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab; int rc; /* Increment our pretend rowid value. */ pCsr->iRowid++; for(pCsr->iCol++; pCsr->iColnStat; pCsr->iCol++){ if( pCsr->aStat[pCsr->iCol].nDoc>0 ) return SQLITE_OK; } rc = sqlite3Fts3SegReaderStep(pFts3, &pCsr->csr); if( rc==SQLITE_ROW ){ int i = 0; int nDoclist = pCsr->csr.nDoclist; char *aDoclist = pCsr->csr.aDoclist; int iCol; int eState = 0; if( pCsr->zStop ){ int n = (pCsr->nStopcsr.nTerm) ? pCsr->nStop : pCsr->csr.nTerm; int mc = memcmp(pCsr->zStop, pCsr->csr.zTerm, n); if( mc<0 || (mc==0 && pCsr->csr.nTerm>pCsr->nStop) ){ pCsr->isEof = 1; return SQLITE_OK; } } if( fts3auxGrowStatArray(pCsr, 2) ) return SQLITE_NOMEM; memset(pCsr->aStat, 0, sizeof(struct Fts3auxColstats) * pCsr->nStat); iCol = 0; while( iaStat[0].nDoc++; eState = 1; iCol = 0; break; /* State 1. In this state we are expecting either a 1, indicating ** that the following integer will be a column number, or the ** start of a position list for column 0. ** ** The only difference between state 1 and state 2 is that if the ** integer encountered in state 1 is not 0 or 1, then we need to ** increment the column 0 "nDoc" count for this term. */ case 1: assert( iCol==0 ); if( v>1 ){ pCsr->aStat[1].nDoc++; } eState = 2; /* fall through */ case 2: if( v==0 ){ /* 0x00. Next integer will be a docid. */ eState = 0; }else if( v==1 ){ /* 0x01. Next integer will be a column number. */ eState = 3; }else{ /* 2 or greater. A position. */ pCsr->aStat[iCol+1].nOcc++; pCsr->aStat[0].nOcc++; } break; /* State 3. The integer just read is a column number. */ default: assert( eState==3 ); iCol = (int)v; if( fts3auxGrowStatArray(pCsr, iCol+2) ) return SQLITE_NOMEM; pCsr->aStat[iCol+1].nDoc++; eState = 2; break; } } pCsr->iCol = 0; rc = SQLITE_OK; }else{ pCsr->isEof = 1; } return rc; } /* ** xFilter - Initialize a cursor to point at the start of its data. */ static int fts3auxFilterMethod( sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ int idxNum, /* Strategy index */ const char *idxStr, /* Unused */ int nVal, /* Number of elements in apVal */ sqlite3_value **apVal /* Arguments for the indexing scheme */ ){ Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab; int rc; int isScan = 0; int iLangVal = 0; /* Language id to query */ int iEq = -1; /* Index of term=? value in apVal */ int iGe = -1; /* Index of term>=? value in apVal */ int iLe = -1; /* Index of term<=? value in apVal */ int iLangid = -1; /* Index of languageid=? value in apVal */ int iNext = 0; UNUSED_PARAMETER(nVal); UNUSED_PARAMETER(idxStr); assert( idxStr==0 ); assert( idxNum==FTS4AUX_EQ_CONSTRAINT || idxNum==0 || idxNum==FTS4AUX_LE_CONSTRAINT || idxNum==FTS4AUX_GE_CONSTRAINT || idxNum==(FTS4AUX_LE_CONSTRAINT|FTS4AUX_GE_CONSTRAINT) ); if( idxNum==FTS4AUX_EQ_CONSTRAINT ){ iEq = iNext++; }else{ isScan = 1; if( idxNum & FTS4AUX_GE_CONSTRAINT ){ iGe = iNext++; } if( idxNum & FTS4AUX_LE_CONSTRAINT ){ iLe = iNext++; } } if( iNextfilter.zTerm); sqlite3Fts3SegReaderFinish(&pCsr->csr); sqlite3_free((void *)pCsr->filter.zTerm); sqlite3_free(pCsr->aStat); memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr); pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY; if( isScan ) pCsr->filter.flags |= FTS3_SEGMENT_SCAN; if( iEq>=0 || iGe>=0 ){ const unsigned char *zStr = sqlite3_value_text(apVal[0]); assert( (iEq==0 && iGe==-1) || (iEq==-1 && iGe==0) ); if( zStr ){ pCsr->filter.zTerm = sqlite3_mprintf("%s", zStr); pCsr->filter.nTerm = sqlite3_value_bytes(apVal[0]); if( pCsr->filter.zTerm==0 ) return SQLITE_NOMEM; } } if( iLe>=0 ){ pCsr->zStop = sqlite3_mprintf("%s", sqlite3_value_text(apVal[iLe])); pCsr->nStop = sqlite3_value_bytes(apVal[iLe]); if( pCsr->zStop==0 ) return SQLITE_NOMEM; } if( iLangid>=0 ){ iLangVal = sqlite3_value_int(apVal[iLangid]); /* If the user specified a negative value for the languageid, use zero ** instead. This works, as the "languageid=?" constraint will also ** be tested by the VDBE layer. The test will always be false (since ** this module will not return a row with a negative languageid), and ** so the overall query will return zero rows. */ if( iLangVal<0 ) iLangVal = 0; } pCsr->iLangid = iLangVal; rc = sqlite3Fts3SegReaderCursor(pFts3, iLangVal, 0, FTS3_SEGCURSOR_ALL, pCsr->filter.zTerm, pCsr->filter.nTerm, 0, isScan, &pCsr->csr ); if( rc==SQLITE_OK ){ rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter); } if( rc==SQLITE_OK ) rc = fts3auxNextMethod(pCursor); return rc; } /* ** xEof - Return true if the cursor is at EOF, or false otherwise. */ static int fts3auxEofMethod(sqlite3_vtab_cursor *pCursor){ Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; return pCsr->isEof; } /* ** xColumn - Return a column value. */ static int fts3auxColumnMethod( sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ int iCol /* Index of column to read value from */ ){ Fts3auxCursor *p = (Fts3auxCursor *)pCursor; assert( p->isEof==0 ); switch( iCol ){ case 0: /* term */ sqlite3_result_text(pCtx, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT); break; case 1: /* col */ if( p->iCol ){ sqlite3_result_int(pCtx, p->iCol-1); }else{ sqlite3_result_text(pCtx, "*", -1, SQLITE_STATIC); } break; case 2: /* documents */ sqlite3_result_int64(pCtx, p->aStat[p->iCol].nDoc); break; case 3: /* occurrences */ sqlite3_result_int64(pCtx, p->aStat[p->iCol].nOcc); break; default: /* languageid */ assert( iCol==4 ); sqlite3_result_int(pCtx, p->iLangid); break; } return SQLITE_OK; } /* ** xRowid - Return the current rowid for the cursor. */ static int fts3auxRowidMethod( sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ sqlite_int64 *pRowid /* OUT: Rowid value */ ){ Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor; *pRowid = pCsr->iRowid; return SQLITE_OK; } /* ** Register the fts3aux module with database connection db. Return SQLITE_OK ** if successful or an error code if sqlite3_create_module() fails. */ int sqlite3Fts3InitAux(sqlite3 *db){ static const sqlite3_module fts3aux_module = { 0, /* iVersion */ fts3auxConnectMethod, /* xCreate */ fts3auxConnectMethod, /* xConnect */ fts3auxBestIndexMethod, /* xBestIndex */ fts3auxDisconnectMethod, /* xDisconnect */ fts3auxDisconnectMethod, /* xDestroy */ fts3auxOpenMethod, /* xOpen */ fts3auxCloseMethod, /* xClose */ fts3auxFilterMethod, /* xFilter */ fts3auxNextMethod, /* xNext */ fts3auxEofMethod, /* xEof */ fts3auxColumnMethod, /* xColumn */ fts3auxRowidMethod, /* xRowid */ 0, /* xUpdate */ 0, /* xBegin */ 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ 0, /* xFindFunction */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0 /* xRollbackTo */ }; int rc; /* Return code */ rc = sqlite3_create_module(db, "fts4aux", &fts3aux_module, 0); return rc; } #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_expr.c ================================================ /* ** 2008 Nov 28 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This module contains code that implements a parser for fts3 query strings ** (the right-hand argument to the MATCH operator). Because the supported ** syntax is relatively simple, the whole tokenizer/parser system is ** hand-coded. */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) /* ** By default, this module parses the legacy syntax that has been ** traditionally used by fts3. Or, if SQLITE_ENABLE_FTS3_PARENTHESIS ** is defined, then it uses the new syntax. The differences between ** the new and the old syntaxes are: ** ** a) The new syntax supports parenthesis. The old does not. ** ** b) The new syntax supports the AND and NOT operators. The old does not. ** ** c) The old syntax supports the "-" token qualifier. This is not ** supported by the new syntax (it is replaced by the NOT operator). ** ** d) When using the old syntax, the OR operator has a greater precedence ** than an implicit AND. When using the new, both implicity and explicit ** AND operators have a higher precedence than OR. ** ** If compiled with SQLITE_TEST defined, then this module exports the ** symbol "int sqlite3_fts3_enable_parentheses". Setting this variable ** to zero causes the module to use the old syntax. If it is set to ** non-zero the new syntax is activated. This is so both syntaxes can ** be tested using a single build of testfixture. ** ** The following describes the syntax supported by the fts3 MATCH ** operator in a similar format to that used by the lemon parser ** generator. This module does not use actually lemon, it uses a ** custom parser. ** ** query ::= andexpr (OR andexpr)*. ** ** andexpr ::= notexpr (AND? notexpr)*. ** ** notexpr ::= nearexpr (NOT nearexpr|-TOKEN)*. ** notexpr ::= LP query RP. ** ** nearexpr ::= phrase (NEAR distance_opt nearexpr)*. ** ** distance_opt ::= . ** distance_opt ::= / INTEGER. ** ** phrase ::= TOKEN. ** phrase ::= COLUMN:TOKEN. ** phrase ::= "TOKEN TOKEN TOKEN...". */ #ifdef SQLITE_TEST int sqlite3_fts3_enable_parentheses = 0; #else # ifdef SQLITE_ENABLE_FTS3_PARENTHESIS # define sqlite3_fts3_enable_parentheses 1 # else # define sqlite3_fts3_enable_parentheses 0 # endif #endif /* ** Default span for NEAR operators. */ #define SQLITE_FTS3_DEFAULT_NEAR_PARAM 10 #include #include /* ** isNot: ** This variable is used by function getNextNode(). When getNextNode() is ** called, it sets ParseContext.isNot to true if the 'next node' is a ** FTSQUERY_PHRASE with a unary "-" attached to it. i.e. "mysql" in the ** FTS3 query "sqlite -mysql". Otherwise, ParseContext.isNot is set to ** zero. */ typedef struct ParseContext ParseContext; struct ParseContext { sqlite3_tokenizer *pTokenizer; /* Tokenizer module */ int iLangid; /* Language id used with tokenizer */ const char **azCol; /* Array of column names for fts3 table */ int bFts4; /* True to allow FTS4-only syntax */ int nCol; /* Number of entries in azCol[] */ int iDefaultCol; /* Default column to query */ int isNot; /* True if getNextNode() sees a unary - */ sqlite3_context *pCtx; /* Write error message here */ int nNest; /* Number of nested brackets */ }; /* ** This function is equivalent to the standard isspace() function. ** ** The standard isspace() can be awkward to use safely, because although it ** is defined to accept an argument of type int, its behavior when passed ** an integer that falls outside of the range of the unsigned char type ** is undefined (and sometimes, "undefined" means segfault). This wrapper ** is defined to accept an argument of type char, and always returns 0 for ** any values that fall outside of the range of the unsigned char type (i.e. ** negative values). */ static int fts3isspace(char c){ return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f'; } /* ** Allocate nByte bytes of memory using sqlite3_malloc(). If successful, ** zero the memory before returning a pointer to it. If unsuccessful, ** return NULL. */ static void *fts3MallocZero(int nByte){ void *pRet = sqlite3_malloc(nByte); if( pRet ) memset(pRet, 0, nByte); return pRet; } int sqlite3Fts3OpenTokenizer( sqlite3_tokenizer *pTokenizer, int iLangid, const char *z, int n, sqlite3_tokenizer_cursor **ppCsr ){ sqlite3_tokenizer_module const *pModule = pTokenizer->pModule; sqlite3_tokenizer_cursor *pCsr = 0; int rc; rc = pModule->xOpen(pTokenizer, z, n, &pCsr); assert( rc==SQLITE_OK || pCsr==0 ); if( rc==SQLITE_OK ){ pCsr->pTokenizer = pTokenizer; if( pModule->iVersion>=1 ){ rc = pModule->xLanguageid(pCsr, iLangid); if( rc!=SQLITE_OK ){ pModule->xClose(pCsr); pCsr = 0; } } } *ppCsr = pCsr; return rc; } /* ** Function getNextNode(), which is called by fts3ExprParse(), may itself ** call fts3ExprParse(). So this forward declaration is required. */ static int fts3ExprParse(ParseContext *, const char *, int, Fts3Expr **, int *); /* ** Extract the next token from buffer z (length n) using the tokenizer ** and other information (column names etc.) in pParse. Create an Fts3Expr ** structure of type FTSQUERY_PHRASE containing a phrase consisting of this ** single token and set *ppExpr to point to it. If the end of the buffer is ** reached before a token is found, set *ppExpr to zero. It is the ** responsibility of the caller to eventually deallocate the allocated ** Fts3Expr structure (if any) by passing it to sqlite3_free(). ** ** Return SQLITE_OK if successful, or SQLITE_NOMEM if a memory allocation ** fails. */ static int getNextToken( ParseContext *pParse, /* fts3 query parse context */ int iCol, /* Value for Fts3Phrase.iColumn */ const char *z, int n, /* Input string */ Fts3Expr **ppExpr, /* OUT: expression */ int *pnConsumed /* OUT: Number of bytes consumed */ ){ sqlite3_tokenizer *pTokenizer = pParse->pTokenizer; sqlite3_tokenizer_module const *pModule = pTokenizer->pModule; int rc; sqlite3_tokenizer_cursor *pCursor; Fts3Expr *pRet = 0; int i = 0; /* Set variable i to the maximum number of bytes of input to tokenize. */ for(i=0; iiLangid, z, i, &pCursor); if( rc==SQLITE_OK ){ const char *zToken; int nToken = 0, iStart = 0, iEnd = 0, iPosition = 0; int nByte; /* total space to allocate */ rc = pModule->xNext(pCursor, &zToken, &nToken, &iStart, &iEnd, &iPosition); if( rc==SQLITE_OK ){ nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase) + nToken; pRet = (Fts3Expr *)fts3MallocZero(nByte); if( !pRet ){ rc = SQLITE_NOMEM; }else{ pRet->eType = FTSQUERY_PHRASE; pRet->pPhrase = (Fts3Phrase *)&pRet[1]; pRet->pPhrase->nToken = 1; pRet->pPhrase->iColumn = iCol; pRet->pPhrase->aToken[0].n = nToken; pRet->pPhrase->aToken[0].z = (char *)&pRet->pPhrase[1]; memcpy(pRet->pPhrase->aToken[0].z, zToken, nToken); if( iEndpPhrase->aToken[0].isPrefix = 1; iEnd++; } while( 1 ){ if( !sqlite3_fts3_enable_parentheses && iStart>0 && z[iStart-1]=='-' ){ pParse->isNot = 1; iStart--; }else if( pParse->bFts4 && iStart>0 && z[iStart-1]=='^' ){ pRet->pPhrase->aToken[0].bFirst = 1; iStart--; }else{ break; } } } *pnConsumed = iEnd; }else if( i && rc==SQLITE_DONE ){ rc = SQLITE_OK; } pModule->xClose(pCursor); } *ppExpr = pRet; return rc; } /* ** Enlarge a memory allocation. If an out-of-memory allocation occurs, ** then free the old allocation. */ static void *fts3ReallocOrFree(void *pOrig, int nNew){ void *pRet = sqlite3_realloc(pOrig, nNew); if( !pRet ){ sqlite3_free(pOrig); } return pRet; } /* ** Buffer zInput, length nInput, contains the contents of a quoted string ** that appeared as part of an fts3 query expression. Neither quote character ** is included in the buffer. This function attempts to tokenize the entire ** input buffer and create an Fts3Expr structure of type FTSQUERY_PHRASE ** containing the results. ** ** If successful, SQLITE_OK is returned and *ppExpr set to point at the ** allocated Fts3Expr structure. Otherwise, either SQLITE_NOMEM (out of memory ** error) or SQLITE_ERROR (tokenization error) is returned and *ppExpr set ** to 0. */ static int getNextString( ParseContext *pParse, /* fts3 query parse context */ const char *zInput, int nInput, /* Input string */ Fts3Expr **ppExpr /* OUT: expression */ ){ sqlite3_tokenizer *pTokenizer = pParse->pTokenizer; sqlite3_tokenizer_module const *pModule = pTokenizer->pModule; int rc; Fts3Expr *p = 0; sqlite3_tokenizer_cursor *pCursor = 0; char *zTemp = 0; int nTemp = 0; const int nSpace = sizeof(Fts3Expr) + sizeof(Fts3Phrase); int nToken = 0; /* The final Fts3Expr data structure, including the Fts3Phrase, ** Fts3PhraseToken structures token buffers are all stored as a single ** allocation so that the expression can be freed with a single call to ** sqlite3_free(). Setting this up requires a two pass approach. ** ** The first pass, in the block below, uses a tokenizer cursor to iterate ** through the tokens in the expression. This pass uses fts3ReallocOrFree() ** to assemble data in two dynamic buffers: ** ** Buffer p: Points to the Fts3Expr structure, followed by the Fts3Phrase ** structure, followed by the array of Fts3PhraseToken ** structures. This pass only populates the Fts3PhraseToken array. ** ** Buffer zTemp: Contains copies of all tokens. ** ** The second pass, in the block that begins "if( rc==SQLITE_DONE )" below, ** appends buffer zTemp to buffer p, and fills in the Fts3Expr and Fts3Phrase ** structures. */ rc = sqlite3Fts3OpenTokenizer( pTokenizer, pParse->iLangid, zInput, nInput, &pCursor); if( rc==SQLITE_OK ){ int ii; for(ii=0; rc==SQLITE_OK; ii++){ const char *zByte; int nByte = 0, iBegin = 0, iEnd = 0, iPos = 0; rc = pModule->xNext(pCursor, &zByte, &nByte, &iBegin, &iEnd, &iPos); if( rc==SQLITE_OK ){ Fts3PhraseToken *pToken; p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken)); if( !p ) goto no_mem; zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte); if( !zTemp ) goto no_mem; assert( nToken==ii ); pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii]; memset(pToken, 0, sizeof(Fts3PhraseToken)); memcpy(&zTemp[nTemp], zByte, nByte); nTemp += nByte; pToken->n = nByte; pToken->isPrefix = (iEndbFirst = (iBegin>0 && zInput[iBegin-1]=='^'); nToken = ii+1; } } pModule->xClose(pCursor); pCursor = 0; } if( rc==SQLITE_DONE ){ int jj; char *zBuf = 0; p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp); if( !p ) goto no_mem; memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p); p->eType = FTSQUERY_PHRASE; p->pPhrase = (Fts3Phrase *)&p[1]; p->pPhrase->iColumn = pParse->iDefaultCol; p->pPhrase->nToken = nToken; zBuf = (char *)&p->pPhrase->aToken[nToken]; if( zTemp ){ memcpy(zBuf, zTemp, nTemp); sqlite3_free(zTemp); }else{ assert( nTemp==0 ); } for(jj=0; jjpPhrase->nToken; jj++){ p->pPhrase->aToken[jj].z = zBuf; zBuf += p->pPhrase->aToken[jj].n; } rc = SQLITE_OK; } *ppExpr = p; return rc; no_mem: if( pCursor ){ pModule->xClose(pCursor); } sqlite3_free(zTemp); sqlite3_free(p); *ppExpr = 0; return SQLITE_NOMEM; } /* ** The output variable *ppExpr is populated with an allocated Fts3Expr ** structure, or set to 0 if the end of the input buffer is reached. ** ** Returns an SQLite error code. SQLITE_OK if everything works, SQLITE_NOMEM ** if a malloc failure occurs, or SQLITE_ERROR if a parse error is encountered. ** If SQLITE_ERROR is returned, pContext is populated with an error message. */ static int getNextNode( ParseContext *pParse, /* fts3 query parse context */ const char *z, int n, /* Input string */ Fts3Expr **ppExpr, /* OUT: expression */ int *pnConsumed /* OUT: Number of bytes consumed */ ){ static const struct Fts3Keyword { char *z; /* Keyword text */ unsigned char n; /* Length of the keyword */ unsigned char parenOnly; /* Only valid in paren mode */ unsigned char eType; /* Keyword code */ } aKeyword[] = { { "OR" , 2, 0, FTSQUERY_OR }, { "AND", 3, 1, FTSQUERY_AND }, { "NOT", 3, 1, FTSQUERY_NOT }, { "NEAR", 4, 0, FTSQUERY_NEAR } }; int ii; int iCol; int iColLen; int rc; Fts3Expr *pRet = 0; const char *zInput = z; int nInput = n; pParse->isNot = 0; /* Skip over any whitespace before checking for a keyword, an open or ** close bracket, or a quoted string. */ while( nInput>0 && fts3isspace(*zInput) ){ nInput--; zInput++; } if( nInput==0 ){ return SQLITE_DONE; } /* See if we are dealing with a keyword. */ for(ii=0; ii<(int)(sizeof(aKeyword)/sizeof(struct Fts3Keyword)); ii++){ const struct Fts3Keyword *pKey = &aKeyword[ii]; if( (pKey->parenOnly & ~sqlite3_fts3_enable_parentheses)!=0 ){ continue; } if( nInput>=pKey->n && 0==memcmp(zInput, pKey->z, pKey->n) ){ int nNear = SQLITE_FTS3_DEFAULT_NEAR_PARAM; int nKey = pKey->n; char cNext; /* If this is a "NEAR" keyword, check for an explicit nearness. */ if( pKey->eType==FTSQUERY_NEAR ){ assert( nKey==4 ); if( zInput[4]=='/' && zInput[5]>='0' && zInput[5]<='9' ){ nNear = 0; for(nKey=5; zInput[nKey]>='0' && zInput[nKey]<='9'; nKey++){ nNear = nNear * 10 + (zInput[nKey] - '0'); } } } /* At this point this is probably a keyword. But for that to be true, ** the next byte must contain either whitespace, an open or close ** parenthesis, a quote character, or EOF. */ cNext = zInput[nKey]; if( fts3isspace(cNext) || cNext=='"' || cNext=='(' || cNext==')' || cNext==0 ){ pRet = (Fts3Expr *)fts3MallocZero(sizeof(Fts3Expr)); if( !pRet ){ return SQLITE_NOMEM; } pRet->eType = pKey->eType; pRet->nNear = nNear; *ppExpr = pRet; *pnConsumed = (int)((zInput - z) + nKey); return SQLITE_OK; } /* Turns out that wasn't a keyword after all. This happens if the ** user has supplied a token such as "ORacle". Continue. */ } } /* See if we are dealing with a quoted phrase. If this is the case, then ** search for the closing quote and pass the whole string to getNextString() ** for processing. This is easy to do, as fts3 has no syntax for escaping ** a quote character embedded in a string. */ if( *zInput=='"' ){ for(ii=1; iinNest++; rc = fts3ExprParse(pParse, zInput+1, nInput-1, ppExpr, &nConsumed); if( rc==SQLITE_OK && !*ppExpr ){ rc = SQLITE_DONE; } *pnConsumed = (int)(zInput - z) + 1 + nConsumed; return rc; }else if( *zInput==')' ){ pParse->nNest--; *pnConsumed = (int)((zInput - z) + 1); *ppExpr = 0; return SQLITE_DONE; } } /* If control flows to this point, this must be a regular token, or ** the end of the input. Read a regular token using the sqlite3_tokenizer ** interface. Before doing so, figure out if there is an explicit ** column specifier for the token. ** ** TODO: Strangely, it is not possible to associate a column specifier ** with a quoted phrase, only with a single token. Not sure if this was ** an implementation artifact or an intentional decision when fts3 was ** first implemented. Whichever it was, this module duplicates the ** limitation. */ iCol = pParse->iDefaultCol; iColLen = 0; for(ii=0; iinCol; ii++){ const char *zStr = pParse->azCol[ii]; int nStr = (int)strlen(zStr); if( nInput>nStr && zInput[nStr]==':' && sqlite3_strnicmp(zStr, zInput, nStr)==0 ){ iCol = ii; iColLen = (int)((zInput - z) + nStr + 1); break; } } rc = getNextToken(pParse, iCol, &z[iColLen], n-iColLen, ppExpr, pnConsumed); *pnConsumed += iColLen; return rc; } /* ** The argument is an Fts3Expr structure for a binary operator (any type ** except an FTSQUERY_PHRASE). Return an integer value representing the ** precedence of the operator. Lower values have a higher precedence (i.e. ** group more tightly). For example, in the C language, the == operator ** groups more tightly than ||, and would therefore have a higher precedence. ** ** When using the new fts3 query syntax (when SQLITE_ENABLE_FTS3_PARENTHESIS ** is defined), the order of the operators in precedence from highest to ** lowest is: ** ** NEAR ** NOT ** AND (including implicit ANDs) ** OR ** ** Note that when using the old query syntax, the OR operator has a higher ** precedence than the AND operator. */ static int opPrecedence(Fts3Expr *p){ assert( p->eType!=FTSQUERY_PHRASE ); if( sqlite3_fts3_enable_parentheses ){ return p->eType; }else if( p->eType==FTSQUERY_NEAR ){ return 1; }else if( p->eType==FTSQUERY_OR ){ return 2; } assert( p->eType==FTSQUERY_AND ); return 3; } /* ** Argument ppHead contains a pointer to the current head of a query ** expression tree being parsed. pPrev is the expression node most recently ** inserted into the tree. This function adds pNew, which is always a binary ** operator node, into the expression tree based on the relative precedence ** of pNew and the existing nodes of the tree. This may result in the head ** of the tree changing, in which case *ppHead is set to the new root node. */ static void insertBinaryOperator( Fts3Expr **ppHead, /* Pointer to the root node of a tree */ Fts3Expr *pPrev, /* Node most recently inserted into the tree */ Fts3Expr *pNew /* New binary node to insert into expression tree */ ){ Fts3Expr *pSplit = pPrev; while( pSplit->pParent && opPrecedence(pSplit->pParent)<=opPrecedence(pNew) ){ pSplit = pSplit->pParent; } if( pSplit->pParent ){ assert( pSplit->pParent->pRight==pSplit ); pSplit->pParent->pRight = pNew; pNew->pParent = pSplit->pParent; }else{ *ppHead = pNew; } pNew->pLeft = pSplit; pSplit->pParent = pNew; } /* ** Parse the fts3 query expression found in buffer z, length n. This function ** returns either when the end of the buffer is reached or an unmatched ** closing bracket - ')' - is encountered. ** ** If successful, SQLITE_OK is returned, *ppExpr is set to point to the ** parsed form of the expression and *pnConsumed is set to the number of ** bytes read from buffer z. Otherwise, *ppExpr is set to 0 and SQLITE_NOMEM ** (out of memory error) or SQLITE_ERROR (parse error) is returned. */ static int fts3ExprParse( ParseContext *pParse, /* fts3 query parse context */ const char *z, int n, /* Text of MATCH query */ Fts3Expr **ppExpr, /* OUT: Parsed query structure */ int *pnConsumed /* OUT: Number of bytes consumed */ ){ Fts3Expr *pRet = 0; Fts3Expr *pPrev = 0; Fts3Expr *pNotBranch = 0; /* Only used in legacy parse mode */ int nIn = n; const char *zIn = z; int rc = SQLITE_OK; int isRequirePhrase = 1; while( rc==SQLITE_OK ){ Fts3Expr *p = 0; int nByte = 0; rc = getNextNode(pParse, zIn, nIn, &p, &nByte); assert( nByte>0 || (rc!=SQLITE_OK && p==0) ); if( rc==SQLITE_OK ){ if( p ){ int isPhrase; if( !sqlite3_fts3_enable_parentheses && p->eType==FTSQUERY_PHRASE && pParse->isNot ){ /* Create an implicit NOT operator. */ Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr)); if( !pNot ){ sqlite3Fts3ExprFree(p); rc = SQLITE_NOMEM; goto exprparse_out; } pNot->eType = FTSQUERY_NOT; pNot->pRight = p; p->pParent = pNot; if( pNotBranch ){ pNot->pLeft = pNotBranch; pNotBranch->pParent = pNot; } pNotBranch = pNot; p = pPrev; }else{ int eType = p->eType; isPhrase = (eType==FTSQUERY_PHRASE || p->pLeft); /* The isRequirePhrase variable is set to true if a phrase or ** an expression contained in parenthesis is required. If a ** binary operator (AND, OR, NOT or NEAR) is encounted when ** isRequirePhrase is set, this is a syntax error. */ if( !isPhrase && isRequirePhrase ){ sqlite3Fts3ExprFree(p); rc = SQLITE_ERROR; goto exprparse_out; } if( isPhrase && !isRequirePhrase ){ /* Insert an implicit AND operator. */ Fts3Expr *pAnd; assert( pRet && pPrev ); pAnd = fts3MallocZero(sizeof(Fts3Expr)); if( !pAnd ){ sqlite3Fts3ExprFree(p); rc = SQLITE_NOMEM; goto exprparse_out; } pAnd->eType = FTSQUERY_AND; insertBinaryOperator(&pRet, pPrev, pAnd); pPrev = pAnd; } /* This test catches attempts to make either operand of a NEAR ** operator something other than a phrase. For example, either of ** the following: ** ** (bracketed expression) NEAR phrase ** phrase NEAR (bracketed expression) ** ** Return an error in either case. */ if( pPrev && ( (eType==FTSQUERY_NEAR && !isPhrase && pPrev->eType!=FTSQUERY_PHRASE) || (eType!=FTSQUERY_PHRASE && isPhrase && pPrev->eType==FTSQUERY_NEAR) )){ sqlite3Fts3ExprFree(p); rc = SQLITE_ERROR; goto exprparse_out; } if( isPhrase ){ if( pRet ){ assert( pPrev && pPrev->pLeft && pPrev->pRight==0 ); pPrev->pRight = p; p->pParent = pPrev; }else{ pRet = p; } }else{ insertBinaryOperator(&pRet, pPrev, p); } isRequirePhrase = !isPhrase; } pPrev = p; } assert( nByte>0 ); } assert( rc!=SQLITE_OK || (nByte>0 && nByte<=nIn) ); nIn -= nByte; zIn += nByte; } if( rc==SQLITE_DONE && pRet && isRequirePhrase ){ rc = SQLITE_ERROR; } if( rc==SQLITE_DONE ){ rc = SQLITE_OK; if( !sqlite3_fts3_enable_parentheses && pNotBranch ){ if( !pRet ){ rc = SQLITE_ERROR; }else{ Fts3Expr *pIter = pNotBranch; while( pIter->pLeft ){ pIter = pIter->pLeft; } pIter->pLeft = pRet; pRet->pParent = pIter; pRet = pNotBranch; } } } *pnConsumed = n - nIn; exprparse_out: if( rc!=SQLITE_OK ){ sqlite3Fts3ExprFree(pRet); sqlite3Fts3ExprFree(pNotBranch); pRet = 0; } *ppExpr = pRet; return rc; } /* ** Return SQLITE_ERROR if the maximum depth of the expression tree passed ** as the only argument is more than nMaxDepth. */ static int fts3ExprCheckDepth(Fts3Expr *p, int nMaxDepth){ int rc = SQLITE_OK; if( p ){ if( nMaxDepth<0 ){ rc = SQLITE_TOOBIG; }else{ rc = fts3ExprCheckDepth(p->pLeft, nMaxDepth-1); if( rc==SQLITE_OK ){ rc = fts3ExprCheckDepth(p->pRight, nMaxDepth-1); } } } return rc; } /* ** This function attempts to transform the expression tree at (*pp) to ** an equivalent but more balanced form. The tree is modified in place. ** If successful, SQLITE_OK is returned and (*pp) set to point to the ** new root expression node. ** ** nMaxDepth is the maximum allowable depth of the balanced sub-tree. ** ** Otherwise, if an error occurs, an SQLite error code is returned and ** expression (*pp) freed. */ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){ int rc = SQLITE_OK; /* Return code */ Fts3Expr *pRoot = *pp; /* Initial root node */ Fts3Expr *pFree = 0; /* List of free nodes. Linked by pParent. */ int eType = pRoot->eType; /* Type of node in this tree */ if( nMaxDepth==0 ){ rc = SQLITE_ERROR; } if( rc==SQLITE_OK ){ if( (eType==FTSQUERY_AND || eType==FTSQUERY_OR) ){ Fts3Expr **apLeaf; apLeaf = (Fts3Expr **)sqlite3_malloc(sizeof(Fts3Expr *) * nMaxDepth); if( 0==apLeaf ){ rc = SQLITE_NOMEM; }else{ memset(apLeaf, 0, sizeof(Fts3Expr *) * nMaxDepth); } if( rc==SQLITE_OK ){ int i; Fts3Expr *p; /* Set $p to point to the left-most leaf in the tree of eType nodes. */ for(p=pRoot; p->eType==eType; p=p->pLeft){ assert( p->pParent==0 || p->pParent->pLeft==p ); assert( p->pLeft && p->pRight ); } /* This loop runs once for each leaf in the tree of eType nodes. */ while( 1 ){ int iLvl; Fts3Expr *pParent = p->pParent; /* Current parent of p */ assert( pParent==0 || pParent->pLeft==p ); p->pParent = 0; if( pParent ){ pParent->pLeft = 0; }else{ pRoot = 0; } rc = fts3ExprBalance(&p, nMaxDepth-1); if( rc!=SQLITE_OK ) break; for(iLvl=0; p && iLvlpLeft = apLeaf[iLvl]; pFree->pRight = p; pFree->pLeft->pParent = pFree; pFree->pRight->pParent = pFree; p = pFree; pFree = pFree->pParent; p->pParent = 0; apLeaf[iLvl] = 0; } } if( p ){ sqlite3Fts3ExprFree(p); rc = SQLITE_TOOBIG; break; } /* If that was the last leaf node, break out of the loop */ if( pParent==0 ) break; /* Set $p to point to the next leaf in the tree of eType nodes */ for(p=pParent->pRight; p->eType==eType; p=p->pLeft); /* Remove pParent from the original tree. */ assert( pParent->pParent==0 || pParent->pParent->pLeft==pParent ); pParent->pRight->pParent = pParent->pParent; if( pParent->pParent ){ pParent->pParent->pLeft = pParent->pRight; }else{ assert( pParent==pRoot ); pRoot = pParent->pRight; } /* Link pParent into the free node list. It will be used as an ** internal node of the new tree. */ pParent->pParent = pFree; pFree = pParent; } if( rc==SQLITE_OK ){ p = 0; for(i=0; ipParent = 0; }else{ assert( pFree!=0 ); pFree->pRight = p; pFree->pLeft = apLeaf[i]; pFree->pLeft->pParent = pFree; pFree->pRight->pParent = pFree; p = pFree; pFree = pFree->pParent; p->pParent = 0; } } } pRoot = p; }else{ /* An error occurred. Delete the contents of the apLeaf[] array ** and pFree list. Everything else is cleaned up by the call to ** sqlite3Fts3ExprFree(pRoot) below. */ Fts3Expr *pDel; for(i=0; ipParent; sqlite3_free(pDel); } } assert( pFree==0 ); sqlite3_free( apLeaf ); } }else if( eType==FTSQUERY_NOT ){ Fts3Expr *pLeft = pRoot->pLeft; Fts3Expr *pRight = pRoot->pRight; pRoot->pLeft = 0; pRoot->pRight = 0; pLeft->pParent = 0; pRight->pParent = 0; rc = fts3ExprBalance(&pLeft, nMaxDepth-1); if( rc==SQLITE_OK ){ rc = fts3ExprBalance(&pRight, nMaxDepth-1); } if( rc!=SQLITE_OK ){ sqlite3Fts3ExprFree(pRight); sqlite3Fts3ExprFree(pLeft); }else{ assert( pLeft && pRight ); pRoot->pLeft = pLeft; pLeft->pParent = pRoot; pRoot->pRight = pRight; pRight->pParent = pRoot; } } } if( rc!=SQLITE_OK ){ sqlite3Fts3ExprFree(pRoot); pRoot = 0; } *pp = pRoot; return rc; } /* ** This function is similar to sqlite3Fts3ExprParse(), with the following ** differences: ** ** 1. It does not do expression rebalancing. ** 2. It does not check that the expression does not exceed the ** maximum allowable depth. ** 3. Even if it fails, *ppExpr may still be set to point to an ** expression tree. It should be deleted using sqlite3Fts3ExprFree() ** in this case. */ static int fts3ExprParseUnbalanced( sqlite3_tokenizer *pTokenizer, /* Tokenizer module */ int iLangid, /* Language id for tokenizer */ char **azCol, /* Array of column names for fts3 table */ int bFts4, /* True to allow FTS4-only syntax */ int nCol, /* Number of entries in azCol[] */ int iDefaultCol, /* Default column to query */ const char *z, int n, /* Text of MATCH query */ Fts3Expr **ppExpr /* OUT: Parsed query structure */ ){ int nParsed; int rc; ParseContext sParse; memset(&sParse, 0, sizeof(ParseContext)); sParse.pTokenizer = pTokenizer; sParse.iLangid = iLangid; sParse.azCol = (const char **)azCol; sParse.nCol = nCol; sParse.iDefaultCol = iDefaultCol; sParse.bFts4 = bFts4; if( z==0 ){ *ppExpr = 0; return SQLITE_OK; } if( n<0 ){ n = (int)strlen(z); } rc = fts3ExprParse(&sParse, z, n, ppExpr, &nParsed); assert( rc==SQLITE_OK || *ppExpr==0 ); /* Check for mismatched parenthesis */ if( rc==SQLITE_OK && sParse.nNest ){ rc = SQLITE_ERROR; } return rc; } /* ** Parameters z and n contain a pointer to and length of a buffer containing ** an fts3 query expression, respectively. This function attempts to parse the ** query expression and create a tree of Fts3Expr structures representing the ** parsed expression. If successful, *ppExpr is set to point to the head ** of the parsed expression tree and SQLITE_OK is returned. If an error ** occurs, either SQLITE_NOMEM (out-of-memory error) or SQLITE_ERROR (parse ** error) is returned and *ppExpr is set to 0. ** ** If parameter n is a negative number, then z is assumed to point to a ** nul-terminated string and the length is determined using strlen(). ** ** The first parameter, pTokenizer, is passed the fts3 tokenizer module to ** use to normalize query tokens while parsing the expression. The azCol[] ** array, which is assumed to contain nCol entries, should contain the names ** of each column in the target fts3 table, in order from left to right. ** Column names must be nul-terminated strings. ** ** The iDefaultCol parameter should be passed the index of the table column ** that appears on the left-hand-side of the MATCH operator (the default ** column to match against for tokens for which a column name is not explicitly ** specified as part of the query string), or -1 if tokens may by default ** match any table column. */ int sqlite3Fts3ExprParse( sqlite3_tokenizer *pTokenizer, /* Tokenizer module */ int iLangid, /* Language id for tokenizer */ char **azCol, /* Array of column names for fts3 table */ int bFts4, /* True to allow FTS4-only syntax */ int nCol, /* Number of entries in azCol[] */ int iDefaultCol, /* Default column to query */ const char *z, int n, /* Text of MATCH query */ Fts3Expr **ppExpr, /* OUT: Parsed query structure */ char **pzErr /* OUT: Error message (sqlite3_malloc) */ ){ int rc = fts3ExprParseUnbalanced( pTokenizer, iLangid, azCol, bFts4, nCol, iDefaultCol, z, n, ppExpr ); /* Rebalance the expression. And check that its depth does not exceed ** SQLITE_FTS3_MAX_EXPR_DEPTH. */ if( rc==SQLITE_OK && *ppExpr ){ rc = fts3ExprBalance(ppExpr, SQLITE_FTS3_MAX_EXPR_DEPTH); if( rc==SQLITE_OK ){ rc = fts3ExprCheckDepth(*ppExpr, SQLITE_FTS3_MAX_EXPR_DEPTH); } } if( rc!=SQLITE_OK ){ sqlite3Fts3ExprFree(*ppExpr); *ppExpr = 0; if( rc==SQLITE_TOOBIG ){ sqlite3Fts3ErrMsg(pzErr, "FTS expression tree is too large (maximum depth %d)", SQLITE_FTS3_MAX_EXPR_DEPTH ); rc = SQLITE_ERROR; }else if( rc==SQLITE_ERROR ){ sqlite3Fts3ErrMsg(pzErr, "malformed MATCH expression: [%s]", z); } } return rc; } /* ** Free a single node of an expression tree. */ static void fts3FreeExprNode(Fts3Expr *p){ assert( p->eType==FTSQUERY_PHRASE || p->pPhrase==0 ); sqlite3Fts3EvalPhraseCleanup(p->pPhrase); sqlite3_free(p->aMI); sqlite3_free(p); } /* ** Free a parsed fts3 query expression allocated by sqlite3Fts3ExprParse(). ** ** This function would be simpler if it recursively called itself. But ** that would mean passing a sufficiently large expression to ExprParse() ** could cause a stack overflow. */ void sqlite3Fts3ExprFree(Fts3Expr *pDel){ Fts3Expr *p; assert( pDel==0 || pDel->pParent==0 ); for(p=pDel; p && (p->pLeft||p->pRight); p=(p->pLeft ? p->pLeft : p->pRight)){ assert( p->pParent==0 || p==p->pParent->pRight || p==p->pParent->pLeft ); } while( p ){ Fts3Expr *pParent = p->pParent; fts3FreeExprNode(p); if( pParent && p==pParent->pLeft && pParent->pRight ){ p = pParent->pRight; while( p && (p->pLeft || p->pRight) ){ assert( p==p->pParent->pRight || p==p->pParent->pLeft ); p = (p->pLeft ? p->pLeft : p->pRight); } }else{ p = pParent; } } } /**************************************************************************** ***************************************************************************** ** Everything after this point is just test code. */ #ifdef SQLITE_TEST #include /* ** Function to query the hash-table of tokenizers (see README.tokenizers). */ static int queryTestTokenizer( sqlite3 *db, const char *zName, const sqlite3_tokenizer_module **pp ){ int rc; sqlite3_stmt *pStmt; const char zSql[] = "SELECT fts3_tokenizer(?)"; *pp = 0; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); if( rc!=SQLITE_OK ){ return rc; } sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); if( SQLITE_ROW==sqlite3_step(pStmt) ){ if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){ memcpy((void *)pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); } } return sqlite3_finalize(pStmt); } /* ** Return a pointer to a buffer containing a text representation of the ** expression passed as the first argument. The buffer is obtained from ** sqlite3_malloc(). It is the responsibility of the caller to use ** sqlite3_free() to release the memory. If an OOM condition is encountered, ** NULL is returned. ** ** If the second argument is not NULL, then its contents are prepended to ** the returned expression text and then freed using sqlite3_free(). */ static char *exprToString(Fts3Expr *pExpr, char *zBuf){ if( pExpr==0 ){ return sqlite3_mprintf(""); } switch( pExpr->eType ){ case FTSQUERY_PHRASE: { Fts3Phrase *pPhrase = pExpr->pPhrase; int i; zBuf = sqlite3_mprintf( "%zPHRASE %d 0", zBuf, pPhrase->iColumn); for(i=0; zBuf && inToken; i++){ zBuf = sqlite3_mprintf("%z %.*s%s", zBuf, pPhrase->aToken[i].n, pPhrase->aToken[i].z, (pPhrase->aToken[i].isPrefix?"+":"") ); } return zBuf; } case FTSQUERY_NEAR: zBuf = sqlite3_mprintf("%zNEAR/%d ", zBuf, pExpr->nNear); break; case FTSQUERY_NOT: zBuf = sqlite3_mprintf("%zNOT ", zBuf); break; case FTSQUERY_AND: zBuf = sqlite3_mprintf("%zAND ", zBuf); break; case FTSQUERY_OR: zBuf = sqlite3_mprintf("%zOR ", zBuf); break; } if( zBuf ) zBuf = sqlite3_mprintf("%z{", zBuf); if( zBuf ) zBuf = exprToString(pExpr->pLeft, zBuf); if( zBuf ) zBuf = sqlite3_mprintf("%z} {", zBuf); if( zBuf ) zBuf = exprToString(pExpr->pRight, zBuf); if( zBuf ) zBuf = sqlite3_mprintf("%z}", zBuf); return zBuf; } /* ** This is the implementation of a scalar SQL function used to test the ** expression parser. It should be called as follows: ** ** fts3_exprtest(, , , ...); ** ** The first argument, , is the name of the fts3 tokenizer used ** to parse the query expression (see README.tokenizers). The second argument ** is the query expression to parse. Each subsequent argument is the name ** of a column of the fts3 table that the query expression may refer to. ** For example: ** ** SELECT fts3_exprtest('simple', 'Bill col2:Bloggs', 'col1', 'col2'); */ static void fts3ExprTest( sqlite3_context *context, int argc, sqlite3_value **argv ){ sqlite3_tokenizer_module const *pModule = 0; sqlite3_tokenizer *pTokenizer = 0; int rc; char **azCol = 0; const char *zExpr; int nExpr; int nCol; int ii; Fts3Expr *pExpr; char *zBuf = 0; sqlite3 *db = sqlite3_context_db_handle(context); if( argc<3 ){ sqlite3_result_error(context, "Usage: fts3_exprtest(tokenizer, expr, col1, ...", -1 ); return; } rc = queryTestTokenizer(db, (const char *)sqlite3_value_text(argv[0]), &pModule); if( rc==SQLITE_NOMEM ){ sqlite3_result_error_nomem(context); goto exprtest_out; }else if( !pModule ){ sqlite3_result_error(context, "No such tokenizer module", -1); goto exprtest_out; } rc = pModule->xCreate(0, 0, &pTokenizer); assert( rc==SQLITE_NOMEM || rc==SQLITE_OK ); if( rc==SQLITE_NOMEM ){ sqlite3_result_error_nomem(context); goto exprtest_out; } pTokenizer->pModule = pModule; zExpr = (const char *)sqlite3_value_text(argv[1]); nExpr = sqlite3_value_bytes(argv[1]); nCol = argc-2; azCol = (char **)sqlite3_malloc(nCol*sizeof(char *)); if( !azCol ){ sqlite3_result_error_nomem(context); goto exprtest_out; } for(ii=0; iixDestroy(pTokenizer); } sqlite3_free(azCol); } /* ** Register the query expression parser test function fts3_exprtest() ** with database connection db. */ int sqlite3Fts3ExprInitTestInterface(sqlite3* db){ int rc = sqlite3_create_function( db, "fts3_exprtest", -1, SQLITE_UTF8, 0, fts3ExprTest, 0, 0 ); if( rc==SQLITE_OK ){ rc = sqlite3_create_function(db, "fts3_exprtest_rebalance", -1, SQLITE_UTF8, (void *)1, fts3ExprTest, 0, 0 ); } return rc; } #endif #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_hash.c ================================================ /* ** 2001 September 22 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This is the implementation of generic hash-tables used in SQLite. ** We've modified it slightly to serve as a standalone hash table ** implementation for the full-text indexing module. */ /* ** The code in this file is only compiled if: ** ** * The FTS3 module is being built as an extension ** (in which case SQLITE_CORE is not defined), or ** ** * The FTS3 module is being built into the core of ** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #include #include #include #include "fts3_hash.h" /* ** Malloc and Free functions */ static void *fts3HashMalloc(int n){ void *p = sqlite3_malloc(n); if( p ){ memset(p, 0, n); } return p; } static void fts3HashFree(void *p){ sqlite3_free(p); } /* Turn bulk memory into a hash table object by initializing the ** fields of the Hash structure. ** ** "pNew" is a pointer to the hash table that is to be initialized. ** keyClass is one of the constants ** FTS3_HASH_BINARY or FTS3_HASH_STRING. The value of keyClass ** determines what kind of key the hash table will use. "copyKey" is ** true if the hash table should make its own private copy of keys and ** false if it should just use the supplied pointer. */ void sqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copyKey){ assert( pNew!=0 ); assert( keyClass>=FTS3_HASH_STRING && keyClass<=FTS3_HASH_BINARY ); pNew->keyClass = keyClass; pNew->copyKey = copyKey; pNew->first = 0; pNew->count = 0; pNew->htsize = 0; pNew->ht = 0; } /* Remove all entries from a hash table. Reclaim all memory. ** Call this routine to delete a hash table or to reset a hash table ** to the empty state. */ void sqlite3Fts3HashClear(Fts3Hash *pH){ Fts3HashElem *elem; /* For looping over all elements of the table */ assert( pH!=0 ); elem = pH->first; pH->first = 0; fts3HashFree(pH->ht); pH->ht = 0; pH->htsize = 0; while( elem ){ Fts3HashElem *next_elem = elem->next; if( pH->copyKey && elem->pKey ){ fts3HashFree(elem->pKey); } fts3HashFree(elem); elem = next_elem; } pH->count = 0; } /* ** Hash and comparison functions when the mode is FTS3_HASH_STRING */ static int fts3StrHash(const void *pKey, int nKey){ const char *z = (const char *)pKey; unsigned h = 0; if( nKey<=0 ) nKey = (int) strlen(z); while( nKey > 0 ){ h = (h<<3) ^ h ^ *z++; nKey--; } return (int)(h & 0x7fffffff); } static int fts3StrCompare(const void *pKey1, int n1, const void *pKey2, int n2){ if( n1!=n2 ) return 1; return strncmp((const char*)pKey1,(const char*)pKey2,n1); } /* ** Hash and comparison functions when the mode is FTS3_HASH_BINARY */ static int fts3BinHash(const void *pKey, int nKey){ int h = 0; const char *z = (const char *)pKey; while( nKey-- > 0 ){ h = (h<<3) ^ h ^ *(z++); } return h & 0x7fffffff; } static int fts3BinCompare(const void *pKey1, int n1, const void *pKey2, int n2){ if( n1!=n2 ) return 1; return memcmp(pKey1,pKey2,n1); } /* ** Return a pointer to the appropriate hash function given the key class. ** ** The C syntax in this function definition may be unfamilar to some ** programmers, so we provide the following additional explanation: ** ** The name of the function is "ftsHashFunction". The function takes a ** single parameter "keyClass". The return value of ftsHashFunction() ** is a pointer to another function. Specifically, the return value ** of ftsHashFunction() is a pointer to a function that takes two parameters ** with types "const void*" and "int" and returns an "int". */ static int (*ftsHashFunction(int keyClass))(const void*,int){ if( keyClass==FTS3_HASH_STRING ){ return &fts3StrHash; }else{ assert( keyClass==FTS3_HASH_BINARY ); return &fts3BinHash; } } /* ** Return a pointer to the appropriate hash function given the key class. ** ** For help in interpreted the obscure C code in the function definition, ** see the header comment on the previous function. */ static int (*ftsCompareFunction(int keyClass))(const void*,int,const void*,int){ if( keyClass==FTS3_HASH_STRING ){ return &fts3StrCompare; }else{ assert( keyClass==FTS3_HASH_BINARY ); return &fts3BinCompare; } } /* Link an element into the hash table */ static void fts3HashInsertElement( Fts3Hash *pH, /* The complete hash table */ struct _fts3ht *pEntry, /* The entry into which pNew is inserted */ Fts3HashElem *pNew /* The element to be inserted */ ){ Fts3HashElem *pHead; /* First element already in pEntry */ pHead = pEntry->chain; if( pHead ){ pNew->next = pHead; pNew->prev = pHead->prev; if( pHead->prev ){ pHead->prev->next = pNew; } else { pH->first = pNew; } pHead->prev = pNew; }else{ pNew->next = pH->first; if( pH->first ){ pH->first->prev = pNew; } pNew->prev = 0; pH->first = pNew; } pEntry->count++; pEntry->chain = pNew; } /* Resize the hash table so that it cantains "new_size" buckets. ** "new_size" must be a power of 2. The hash table might fail ** to resize if sqliteMalloc() fails. ** ** Return non-zero if a memory allocation error occurs. */ static int fts3Rehash(Fts3Hash *pH, int new_size){ struct _fts3ht *new_ht; /* The new hash table */ Fts3HashElem *elem, *next_elem; /* For looping over existing elements */ int (*xHash)(const void*,int); /* The hash function */ assert( (new_size & (new_size-1))==0 ); new_ht = (struct _fts3ht *)fts3HashMalloc( new_size*sizeof(struct _fts3ht) ); if( new_ht==0 ) return 1; fts3HashFree(pH->ht); pH->ht = new_ht; pH->htsize = new_size; xHash = ftsHashFunction(pH->keyClass); for(elem=pH->first, pH->first=0; elem; elem = next_elem){ int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1); next_elem = elem->next; fts3HashInsertElement(pH, &new_ht[h], elem); } return 0; } /* This function (for internal use only) locates an element in an ** hash table that matches the given key. The hash for this key has ** already been computed and is passed as the 4th parameter. */ static Fts3HashElem *fts3FindElementByHash( const Fts3Hash *pH, /* The pH to be searched */ const void *pKey, /* The key we are searching for */ int nKey, int h /* The hash for this key. */ ){ Fts3HashElem *elem; /* Used to loop thru the element list */ int count; /* Number of elements left to test */ int (*xCompare)(const void*,int,const void*,int); /* comparison function */ if( pH->ht ){ struct _fts3ht *pEntry = &pH->ht[h]; elem = pEntry->chain; count = pEntry->count; xCompare = ftsCompareFunction(pH->keyClass); while( count-- && elem ){ if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){ return elem; } elem = elem->next; } } return 0; } /* Remove a single entry from the hash table given a pointer to that ** element and a hash on the element's key. */ static void fts3RemoveElementByHash( Fts3Hash *pH, /* The pH containing "elem" */ Fts3HashElem* elem, /* The element to be removed from the pH */ int h /* Hash value for the element */ ){ struct _fts3ht *pEntry; if( elem->prev ){ elem->prev->next = elem->next; }else{ pH->first = elem->next; } if( elem->next ){ elem->next->prev = elem->prev; } pEntry = &pH->ht[h]; if( pEntry->chain==elem ){ pEntry->chain = elem->next; } pEntry->count--; if( pEntry->count<=0 ){ pEntry->chain = 0; } if( pH->copyKey && elem->pKey ){ fts3HashFree(elem->pKey); } fts3HashFree( elem ); pH->count--; if( pH->count<=0 ){ assert( pH->first==0 ); assert( pH->count==0 ); fts3HashClear(pH); } } Fts3HashElem *sqlite3Fts3HashFindElem( const Fts3Hash *pH, const void *pKey, int nKey ){ int h; /* A hash on key */ int (*xHash)(const void*,int); /* The hash function */ if( pH==0 || pH->ht==0 ) return 0; xHash = ftsHashFunction(pH->keyClass); assert( xHash!=0 ); h = (*xHash)(pKey,nKey); assert( (pH->htsize & (pH->htsize-1))==0 ); return fts3FindElementByHash(pH,pKey,nKey, h & (pH->htsize-1)); } /* ** Attempt to locate an element of the hash table pH with a key ** that matches pKey,nKey. Return the data for this element if it is ** found, or NULL if there is no match. */ void *sqlite3Fts3HashFind(const Fts3Hash *pH, const void *pKey, int nKey){ Fts3HashElem *pElem; /* The element that matches key (if any) */ pElem = sqlite3Fts3HashFindElem(pH, pKey, nKey); return pElem ? pElem->data : 0; } /* Insert an element into the hash table pH. The key is pKey,nKey ** and the data is "data". ** ** If no element exists with a matching key, then a new ** element is created. A copy of the key is made if the copyKey ** flag is set. NULL is returned. ** ** If another element already exists with the same key, then the ** new data replaces the old data and the old data is returned. ** The key is not copied in this instance. If a malloc fails, then ** the new data is returned and the hash table is unchanged. ** ** If the "data" parameter to this function is NULL, then the ** element corresponding to "key" is removed from the hash table. */ void *sqlite3Fts3HashInsert( Fts3Hash *pH, /* The hash table to insert into */ const void *pKey, /* The key */ int nKey, /* Number of bytes in the key */ void *data /* The data */ ){ int hraw; /* Raw hash value of the key */ int h; /* the hash of the key modulo hash table size */ Fts3HashElem *elem; /* Used to loop thru the element list */ Fts3HashElem *new_elem; /* New element added to the pH */ int (*xHash)(const void*,int); /* The hash function */ assert( pH!=0 ); xHash = ftsHashFunction(pH->keyClass); assert( xHash!=0 ); hraw = (*xHash)(pKey, nKey); assert( (pH->htsize & (pH->htsize-1))==0 ); h = hraw & (pH->htsize-1); elem = fts3FindElementByHash(pH,pKey,nKey,h); if( elem ){ void *old_data = elem->data; if( data==0 ){ fts3RemoveElementByHash(pH,elem,h); }else{ elem->data = data; } return old_data; } if( data==0 ) return 0; if( (pH->htsize==0 && fts3Rehash(pH,8)) || (pH->count>=pH->htsize && fts3Rehash(pH, pH->htsize*2)) ){ pH->count = 0; return data; } assert( pH->htsize>0 ); new_elem = (Fts3HashElem*)fts3HashMalloc( sizeof(Fts3HashElem) ); if( new_elem==0 ) return data; if( pH->copyKey && pKey!=0 ){ new_elem->pKey = fts3HashMalloc( nKey ); if( new_elem->pKey==0 ){ fts3HashFree(new_elem); return data; } memcpy((void*)new_elem->pKey, pKey, nKey); }else{ new_elem->pKey = (void*)pKey; } new_elem->nKey = nKey; pH->count++; assert( pH->htsize>0 ); assert( (pH->htsize & (pH->htsize-1))==0 ); h = hraw & (pH->htsize-1); fts3HashInsertElement(pH, &pH->ht[h], new_elem); new_elem->data = data; return 0; } #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_hash.h ================================================ /* ** 2001 September 22 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This is the header file for the generic hash-table implementation ** used in SQLite. We've modified it slightly to serve as a standalone ** hash table implementation for the full-text indexing module. ** */ #ifndef _FTS3_HASH_H_ #define _FTS3_HASH_H_ /* Forward declarations of structures. */ typedef struct Fts3Hash Fts3Hash; typedef struct Fts3HashElem Fts3HashElem; /* A complete hash table is an instance of the following structure. ** The internals of this structure are intended to be opaque -- client ** code should not attempt to access or modify the fields of this structure ** directly. Change this structure only by using the routines below. ** However, many of the "procedures" and "functions" for modifying and ** accessing this structure are really macros, so we can't really make ** this structure opaque. */ struct Fts3Hash { char keyClass; /* HASH_INT, _POINTER, _STRING, _BINARY */ char copyKey; /* True if copy of key made on insert */ int count; /* Number of entries in this table */ Fts3HashElem *first; /* The first element of the array */ int htsize; /* Number of buckets in the hash table */ struct _fts3ht { /* the hash table */ int count; /* Number of entries with this hash */ Fts3HashElem *chain; /* Pointer to first entry with this hash */ } *ht; }; /* Each element in the hash table is an instance of the following ** structure. All elements are stored on a single doubly-linked list. ** ** Again, this structure is intended to be opaque, but it can't really ** be opaque because it is used by macros. */ struct Fts3HashElem { Fts3HashElem *next, *prev; /* Next and previous elements in the table */ void *data; /* Data associated with this element */ void *pKey; int nKey; /* Key associated with this element */ }; /* ** There are 2 different modes of operation for a hash table: ** ** FTS3_HASH_STRING pKey points to a string that is nKey bytes long ** (including the null-terminator, if any). Case ** is respected in comparisons. ** ** FTS3_HASH_BINARY pKey points to binary data nKey bytes long. ** memcmp() is used to compare keys. ** ** A copy of the key is made if the copyKey parameter to fts3HashInit is 1. */ #define FTS3_HASH_STRING 1 #define FTS3_HASH_BINARY 2 /* ** Access routines. To delete, insert a NULL pointer. */ void sqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copyKey); void *sqlite3Fts3HashInsert(Fts3Hash*, const void *pKey, int nKey, void *pData); void *sqlite3Fts3HashFind(const Fts3Hash*, const void *pKey, int nKey); void sqlite3Fts3HashClear(Fts3Hash*); Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const void *, int); /* ** Shorthand for the functions above */ #define fts3HashInit sqlite3Fts3HashInit #define fts3HashInsert sqlite3Fts3HashInsert #define fts3HashFind sqlite3Fts3HashFind #define fts3HashClear sqlite3Fts3HashClear #define fts3HashFindElem sqlite3Fts3HashFindElem /* ** Macros for looping over all elements of a hash table. The idiom is ** like this: ** ** Fts3Hash h; ** Fts3HashElem *p; ** ... ** for(p=fts3HashFirst(&h); p; p=fts3HashNext(p)){ ** SomeStructure *pData = fts3HashData(p); ** // do something with pData ** } */ #define fts3HashFirst(H) ((H)->first) #define fts3HashNext(E) ((E)->next) #define fts3HashData(E) ((E)->data) #define fts3HashKey(E) ((E)->pKey) #define fts3HashKeysize(E) ((E)->nKey) /* ** Number of entries in a hash table */ #define fts3HashCount(H) ((H)->count) #endif /* _FTS3_HASH_H_ */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_icu.c ================================================ /* ** 2007 June 22 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file implements a tokenizer for fts3 based on the ICU library. */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #ifdef SQLITE_ENABLE_ICU #include #include #include "fts3_tokenizer.h" #include #include #include #include typedef struct IcuTokenizer IcuTokenizer; typedef struct IcuCursor IcuCursor; struct IcuTokenizer { sqlite3_tokenizer base; char *zLocale; }; struct IcuCursor { sqlite3_tokenizer_cursor base; UBreakIterator *pIter; /* ICU break-iterator object */ int nChar; /* Number of UChar elements in pInput */ UChar *aChar; /* Copy of input using utf-16 encoding */ int *aOffset; /* Offsets of each character in utf-8 input */ int nBuffer; char *zBuffer; int iToken; }; /* ** Create a new tokenizer instance. */ static int icuCreate( int argc, /* Number of entries in argv[] */ const char * const *argv, /* Tokenizer creation arguments */ sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ ){ IcuTokenizer *p; int n = 0; if( argc>0 ){ n = strlen(argv[0])+1; } p = (IcuTokenizer *)sqlite3_malloc(sizeof(IcuTokenizer)+n); if( !p ){ return SQLITE_NOMEM; } memset(p, 0, sizeof(IcuTokenizer)); if( n ){ p->zLocale = (char *)&p[1]; memcpy(p->zLocale, argv[0], n); } *ppTokenizer = (sqlite3_tokenizer *)p; return SQLITE_OK; } /* ** Destroy a tokenizer */ static int icuDestroy(sqlite3_tokenizer *pTokenizer){ IcuTokenizer *p = (IcuTokenizer *)pTokenizer; sqlite3_free(p); return SQLITE_OK; } /* ** Prepare to begin tokenizing a particular string. The input ** string to be tokenized is pInput[0..nBytes-1]. A cursor ** used to incrementally tokenize this string is returned in ** *ppCursor. */ static int icuOpen( sqlite3_tokenizer *pTokenizer, /* The tokenizer */ const char *zInput, /* Input string */ int nInput, /* Length of zInput in bytes */ sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ ){ IcuTokenizer *p = (IcuTokenizer *)pTokenizer; IcuCursor *pCsr; const int32_t opt = U_FOLD_CASE_DEFAULT; UErrorCode status = U_ZERO_ERROR; int nChar; UChar32 c; int iInput = 0; int iOut = 0; *ppCursor = 0; if( zInput==0 ){ nInput = 0; zInput = ""; }else if( nInput<0 ){ nInput = strlen(zInput); } nChar = nInput+1; pCsr = (IcuCursor *)sqlite3_malloc( sizeof(IcuCursor) + /* IcuCursor */ ((nChar+3)&~3) * sizeof(UChar) + /* IcuCursor.aChar[] */ (nChar+1) * sizeof(int) /* IcuCursor.aOffset[] */ ); if( !pCsr ){ return SQLITE_NOMEM; } memset(pCsr, 0, sizeof(IcuCursor)); pCsr->aChar = (UChar *)&pCsr[1]; pCsr->aOffset = (int *)&pCsr->aChar[(nChar+3)&~3]; pCsr->aOffset[iOut] = iInput; U8_NEXT(zInput, iInput, nInput, c); while( c>0 ){ int isError = 0; c = u_foldCase(c, opt); U16_APPEND(pCsr->aChar, iOut, nChar, c, isError); if( isError ){ sqlite3_free(pCsr); return SQLITE_ERROR; } pCsr->aOffset[iOut] = iInput; if( iInputpIter = ubrk_open(UBRK_WORD, p->zLocale, pCsr->aChar, iOut, &status); if( !U_SUCCESS(status) ){ sqlite3_free(pCsr); return SQLITE_ERROR; } pCsr->nChar = iOut; ubrk_first(pCsr->pIter); *ppCursor = (sqlite3_tokenizer_cursor *)pCsr; return SQLITE_OK; } /* ** Close a tokenization cursor previously opened by a call to icuOpen(). */ static int icuClose(sqlite3_tokenizer_cursor *pCursor){ IcuCursor *pCsr = (IcuCursor *)pCursor; ubrk_close(pCsr->pIter); sqlite3_free(pCsr->zBuffer); sqlite3_free(pCsr); return SQLITE_OK; } /* ** Extract the next token from a tokenization cursor. */ static int icuNext( sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */ const char **ppToken, /* OUT: *ppToken is the token text */ int *pnBytes, /* OUT: Number of bytes in token */ int *piStartOffset, /* OUT: Starting offset of token */ int *piEndOffset, /* OUT: Ending offset of token */ int *piPosition /* OUT: Position integer of token */ ){ IcuCursor *pCsr = (IcuCursor *)pCursor; int iStart = 0; int iEnd = 0; int nByte = 0; while( iStart==iEnd ){ UChar32 c; iStart = ubrk_current(pCsr->pIter); iEnd = ubrk_next(pCsr->pIter); if( iEnd==UBRK_DONE ){ return SQLITE_DONE; } while( iStartaChar, iWhite, pCsr->nChar, c); if( u_isspace(c) ){ iStart = iWhite; }else{ break; } } assert(iStart<=iEnd); } do { UErrorCode status = U_ZERO_ERROR; if( nByte ){ char *zNew = sqlite3_realloc(pCsr->zBuffer, nByte); if( !zNew ){ return SQLITE_NOMEM; } pCsr->zBuffer = zNew; pCsr->nBuffer = nByte; } u_strToUTF8( pCsr->zBuffer, pCsr->nBuffer, &nByte, /* Output vars */ &pCsr->aChar[iStart], iEnd-iStart, /* Input vars */ &status /* Output success/failure */ ); } while( nByte>pCsr->nBuffer ); *ppToken = pCsr->zBuffer; *pnBytes = nByte; *piStartOffset = pCsr->aOffset[iStart]; *piEndOffset = pCsr->aOffset[iEnd]; *piPosition = pCsr->iToken++; return SQLITE_OK; } /* ** The set of routines that implement the simple tokenizer */ static const sqlite3_tokenizer_module icuTokenizerModule = { 0, /* iVersion */ icuCreate, /* xCreate */ icuDestroy, /* xCreate */ icuOpen, /* xOpen */ icuClose, /* xClose */ icuNext, /* xNext */ 0, /* xLanguageid */ }; /* ** Set *ppModule to point at the implementation of the ICU tokenizer. */ void sqlite3Fts3IcuTokenizerModule( sqlite3_tokenizer_module const**ppModule ){ *ppModule = &icuTokenizerModule; } #endif /* defined(SQLITE_ENABLE_ICU) */ #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_porter.c ================================================ /* ** 2006 September 30 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** Implementation of the full-text-search tokenizer that implements ** a Porter stemmer. */ /* ** The code in this file is only compiled if: ** ** * The FTS3 module is being built as an extension ** (in which case SQLITE_CORE is not defined), or ** ** * The FTS3 module is being built into the core of ** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #include #include #include #include #include "fts3_tokenizer.h" /* ** Class derived from sqlite3_tokenizer */ typedef struct porter_tokenizer { sqlite3_tokenizer base; /* Base class */ } porter_tokenizer; /* ** Class derived from sqlite3_tokenizer_cursor */ typedef struct porter_tokenizer_cursor { sqlite3_tokenizer_cursor base; const char *zInput; /* input we are tokenizing */ int nInput; /* size of the input */ int iOffset; /* current position in zInput */ int iToken; /* index of next token to be returned */ char *zToken; /* storage for current token */ int nAllocated; /* space allocated to zToken buffer */ } porter_tokenizer_cursor; /* ** Create a new tokenizer instance. */ static int porterCreate( int argc, const char * const *argv, sqlite3_tokenizer **ppTokenizer ){ porter_tokenizer *t; UNUSED_PARAMETER(argc); UNUSED_PARAMETER(argv); t = (porter_tokenizer *) sqlite3_malloc(sizeof(*t)); if( t==NULL ) return SQLITE_NOMEM; memset(t, 0, sizeof(*t)); *ppTokenizer = &t->base; return SQLITE_OK; } /* ** Destroy a tokenizer */ static int porterDestroy(sqlite3_tokenizer *pTokenizer){ sqlite3_free(pTokenizer); return SQLITE_OK; } /* ** Prepare to begin tokenizing a particular string. The input ** string to be tokenized is zInput[0..nInput-1]. A cursor ** used to incrementally tokenize this string is returned in ** *ppCursor. */ static int porterOpen( sqlite3_tokenizer *pTokenizer, /* The tokenizer */ const char *zInput, int nInput, /* String to be tokenized */ sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ ){ porter_tokenizer_cursor *c; UNUSED_PARAMETER(pTokenizer); c = (porter_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); if( c==NULL ) return SQLITE_NOMEM; c->zInput = zInput; if( zInput==0 ){ c->nInput = 0; }else if( nInput<0 ){ c->nInput = (int)strlen(zInput); }else{ c->nInput = nInput; } c->iOffset = 0; /* start tokenizing at the beginning */ c->iToken = 0; c->zToken = NULL; /* no space allocated, yet. */ c->nAllocated = 0; *ppCursor = &c->base; return SQLITE_OK; } /* ** Close a tokenization cursor previously opened by a call to ** porterOpen() above. */ static int porterClose(sqlite3_tokenizer_cursor *pCursor){ porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; sqlite3_free(c->zToken); sqlite3_free(c); return SQLITE_OK; } /* ** Vowel or consonant */ static const char cType[] = { 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 2, 1 }; /* ** isConsonant() and isVowel() determine if their first character in ** the string they point to is a consonant or a vowel, according ** to Porter ruls. ** ** A consonate is any letter other than 'a', 'e', 'i', 'o', or 'u'. ** 'Y' is a consonant unless it follows another consonant, ** in which case it is a vowel. ** ** In these routine, the letters are in reverse order. So the 'y' rule ** is that 'y' is a consonant unless it is followed by another ** consonent. */ static int isVowel(const char*); static int isConsonant(const char *z){ int j; char x = *z; if( x==0 ) return 0; assert( x>='a' && x<='z' ); j = cType[x-'a']; if( j<2 ) return j; return z[1]==0 || isVowel(z + 1); } static int isVowel(const char *z){ int j; char x = *z; if( x==0 ) return 0; assert( x>='a' && x<='z' ); j = cType[x-'a']; if( j<2 ) return 1-j; return isConsonant(z + 1); } /* ** Let any sequence of one or more vowels be represented by V and let ** C be sequence of one or more consonants. Then every word can be ** represented as: ** ** [C] (VC){m} [V] ** ** In prose: A word is an optional consonant followed by zero or ** vowel-consonant pairs followed by an optional vowel. "m" is the ** number of vowel consonant pairs. This routine computes the value ** of m for the first i bytes of a word. ** ** Return true if the m-value for z is 1 or more. In other words, ** return true if z contains at least one vowel that is followed ** by a consonant. ** ** In this routine z[] is in reverse order. So we are really looking ** for an instance of a consonant followed by a vowel. */ static int m_gt_0(const char *z){ while( isVowel(z) ){ z++; } if( *z==0 ) return 0; while( isConsonant(z) ){ z++; } return *z!=0; } /* Like mgt0 above except we are looking for a value of m which is ** exactly 1 */ static int m_eq_1(const char *z){ while( isVowel(z) ){ z++; } if( *z==0 ) return 0; while( isConsonant(z) ){ z++; } if( *z==0 ) return 0; while( isVowel(z) ){ z++; } if( *z==0 ) return 1; while( isConsonant(z) ){ z++; } return *z==0; } /* Like mgt0 above except we are looking for a value of m>1 instead ** or m>0 */ static int m_gt_1(const char *z){ while( isVowel(z) ){ z++; } if( *z==0 ) return 0; while( isConsonant(z) ){ z++; } if( *z==0 ) return 0; while( isVowel(z) ){ z++; } if( *z==0 ) return 0; while( isConsonant(z) ){ z++; } return *z!=0; } /* ** Return TRUE if there is a vowel anywhere within z[0..n-1] */ static int hasVowel(const char *z){ while( isConsonant(z) ){ z++; } return *z!=0; } /* ** Return TRUE if the word ends in a double consonant. ** ** The text is reversed here. So we are really looking at ** the first two characters of z[]. */ static int doubleConsonant(const char *z){ return isConsonant(z) && z[0]==z[1]; } /* ** Return TRUE if the word ends with three letters which ** are consonant-vowel-consonent and where the final consonant ** is not 'w', 'x', or 'y'. ** ** The word is reversed here. So we are really checking the ** first three letters and the first one cannot be in [wxy]. */ static int star_oh(const char *z){ return isConsonant(z) && z[0]!='w' && z[0]!='x' && z[0]!='y' && isVowel(z+1) && isConsonant(z+2); } /* ** If the word ends with zFrom and xCond() is true for the stem ** of the word that preceeds the zFrom ending, then change the ** ending to zTo. ** ** The input word *pz and zFrom are both in reverse order. zTo ** is in normal order. ** ** Return TRUE if zFrom matches. Return FALSE if zFrom does not ** match. Not that TRUE is returned even if xCond() fails and ** no substitution occurs. */ static int stem( char **pz, /* The word being stemmed (Reversed) */ const char *zFrom, /* If the ending matches this... (Reversed) */ const char *zTo, /* ... change the ending to this (not reversed) */ int (*xCond)(const char*) /* Condition that must be true */ ){ char *z = *pz; while( *zFrom && *zFrom==*z ){ z++; zFrom++; } if( *zFrom!=0 ) return 0; if( xCond && !xCond(z) ) return 1; while( *zTo ){ *(--z) = *(zTo++); } *pz = z; return 1; } /* ** This is the fallback stemmer used when the porter stemmer is ** inappropriate. The input word is copied into the output with ** US-ASCII case folding. If the input word is too long (more ** than 20 bytes if it contains no digits or more than 6 bytes if ** it contains digits) then word is truncated to 20 or 6 bytes ** by taking 10 or 3 bytes from the beginning and end. */ static void copy_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ int i, mx, j; int hasDigit = 0; for(i=0; i='A' && c<='Z' ){ zOut[i] = c - 'A' + 'a'; }else{ if( c>='0' && c<='9' ) hasDigit = 1; zOut[i] = c; } } mx = hasDigit ? 3 : 10; if( nIn>mx*2 ){ for(j=mx, i=nIn-mx; i=(int)sizeof(zReverse)-7 ){ /* The word is too big or too small for the porter stemmer. ** Fallback to the copy stemmer */ copy_stemmer(zIn, nIn, zOut, pnOut); return; } for(i=0, j=sizeof(zReverse)-6; i='A' && c<='Z' ){ zReverse[j] = c + 'a' - 'A'; }else if( c>='a' && c<='z' ){ zReverse[j] = c; }else{ /* The use of a character not in [a-zA-Z] means that we fallback ** to the copy stemmer */ copy_stemmer(zIn, nIn, zOut, pnOut); return; } } memset(&zReverse[sizeof(zReverse)-5], 0, 5); z = &zReverse[j+1]; /* Step 1a */ if( z[0]=='s' ){ if( !stem(&z, "sess", "ss", 0) && !stem(&z, "sei", "i", 0) && !stem(&z, "ss", "ss", 0) ){ z++; } } /* Step 1b */ z2 = z; if( stem(&z, "dee", "ee", m_gt_0) ){ /* Do nothing. The work was all in the test */ }else if( (stem(&z, "gni", "", hasVowel) || stem(&z, "de", "", hasVowel)) && z!=z2 ){ if( stem(&z, "ta", "ate", 0) || stem(&z, "lb", "ble", 0) || stem(&z, "zi", "ize", 0) ){ /* Do nothing. The work was all in the test */ }else if( doubleConsonant(z) && (*z!='l' && *z!='s' && *z!='z') ){ z++; }else if( m_eq_1(z) && star_oh(z) ){ *(--z) = 'e'; } } /* Step 1c */ if( z[0]=='y' && hasVowel(z+1) ){ z[0] = 'i'; } /* Step 2 */ switch( z[1] ){ case 'a': if( !stem(&z, "lanoita", "ate", m_gt_0) ){ stem(&z, "lanoit", "tion", m_gt_0); } break; case 'c': if( !stem(&z, "icne", "ence", m_gt_0) ){ stem(&z, "icna", "ance", m_gt_0); } break; case 'e': stem(&z, "rezi", "ize", m_gt_0); break; case 'g': stem(&z, "igol", "log", m_gt_0); break; case 'l': if( !stem(&z, "ilb", "ble", m_gt_0) && !stem(&z, "illa", "al", m_gt_0) && !stem(&z, "iltne", "ent", m_gt_0) && !stem(&z, "ile", "e", m_gt_0) ){ stem(&z, "ilsuo", "ous", m_gt_0); } break; case 'o': if( !stem(&z, "noitazi", "ize", m_gt_0) && !stem(&z, "noita", "ate", m_gt_0) ){ stem(&z, "rota", "ate", m_gt_0); } break; case 's': if( !stem(&z, "msila", "al", m_gt_0) && !stem(&z, "ssenevi", "ive", m_gt_0) && !stem(&z, "ssenluf", "ful", m_gt_0) ){ stem(&z, "ssensuo", "ous", m_gt_0); } break; case 't': if( !stem(&z, "itila", "al", m_gt_0) && !stem(&z, "itivi", "ive", m_gt_0) ){ stem(&z, "itilib", "ble", m_gt_0); } break; } /* Step 3 */ switch( z[0] ){ case 'e': if( !stem(&z, "etaci", "ic", m_gt_0) && !stem(&z, "evita", "", m_gt_0) ){ stem(&z, "ezila", "al", m_gt_0); } break; case 'i': stem(&z, "itici", "ic", m_gt_0); break; case 'l': if( !stem(&z, "laci", "ic", m_gt_0) ){ stem(&z, "luf", "", m_gt_0); } break; case 's': stem(&z, "ssen", "", m_gt_0); break; } /* Step 4 */ switch( z[1] ){ case 'a': if( z[0]=='l' && m_gt_1(z+2) ){ z += 2; } break; case 'c': if( z[0]=='e' && z[2]=='n' && (z[3]=='a' || z[3]=='e') && m_gt_1(z+4) ){ z += 4; } break; case 'e': if( z[0]=='r' && m_gt_1(z+2) ){ z += 2; } break; case 'i': if( z[0]=='c' && m_gt_1(z+2) ){ z += 2; } break; case 'l': if( z[0]=='e' && z[2]=='b' && (z[3]=='a' || z[3]=='i') && m_gt_1(z+4) ){ z += 4; } break; case 'n': if( z[0]=='t' ){ if( z[2]=='a' ){ if( m_gt_1(z+3) ){ z += 3; } }else if( z[2]=='e' ){ if( !stem(&z, "tneme", "", m_gt_1) && !stem(&z, "tnem", "", m_gt_1) ){ stem(&z, "tne", "", m_gt_1); } } } break; case 'o': if( z[0]=='u' ){ if( m_gt_1(z+2) ){ z += 2; } }else if( z[3]=='s' || z[3]=='t' ){ stem(&z, "noi", "", m_gt_1); } break; case 's': if( z[0]=='m' && z[2]=='i' && m_gt_1(z+3) ){ z += 3; } break; case 't': if( !stem(&z, "eta", "", m_gt_1) ){ stem(&z, "iti", "", m_gt_1); } break; case 'u': if( z[0]=='s' && z[2]=='o' && m_gt_1(z+3) ){ z += 3; } break; case 'v': case 'z': if( z[0]=='e' && z[2]=='i' && m_gt_1(z+3) ){ z += 3; } break; } /* Step 5a */ if( z[0]=='e' ){ if( m_gt_1(z+1) ){ z++; }else if( m_eq_1(z+1) && !star_oh(z+1) ){ z++; } } /* Step 5b */ if( m_gt_1(z) && z[0]=='l' && z[1]=='l' ){ z++; } /* z[] is now the stemmed word in reverse order. Flip it back ** around into forward order and return. */ *pnOut = i = (int)strlen(z); zOut[i] = 0; while( *z ){ zOut[--i] = *(z++); } } /* ** Characters that can be part of a token. We assume any character ** whose value is greater than 0x80 (any UTF character) can be ** part of a token. In other words, delimiters all must have ** values of 0x7f or lower. */ static const char porterIdChar[] = { /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ }; #define isDelim(C) (((ch=C)&0x80)==0 && (ch<0x30 || !porterIdChar[ch-0x30])) /* ** Extract the next token from a tokenization cursor. The cursor must ** have been opened by a prior call to porterOpen(). */ static int porterNext( sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by porterOpen */ const char **pzToken, /* OUT: *pzToken is the token text */ int *pnBytes, /* OUT: Number of bytes in token */ int *piStartOffset, /* OUT: Starting offset of token */ int *piEndOffset, /* OUT: Ending offset of token */ int *piPosition /* OUT: Position integer of token */ ){ porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; const char *z = c->zInput; while( c->iOffsetnInput ){ int iStartOffset, ch; /* Scan past delimiter characters */ while( c->iOffsetnInput && isDelim(z[c->iOffset]) ){ c->iOffset++; } /* Count non-delimiter characters. */ iStartOffset = c->iOffset; while( c->iOffsetnInput && !isDelim(z[c->iOffset]) ){ c->iOffset++; } if( c->iOffset>iStartOffset ){ int n = c->iOffset-iStartOffset; if( n>c->nAllocated ){ char *pNew; c->nAllocated = n+20; pNew = sqlite3_realloc(c->zToken, c->nAllocated); if( !pNew ) return SQLITE_NOMEM; c->zToken = pNew; } porter_stemmer(&z[iStartOffset], n, c->zToken, pnBytes); *pzToken = c->zToken; *piStartOffset = iStartOffset; *piEndOffset = c->iOffset; *piPosition = c->iToken++; return SQLITE_OK; } } return SQLITE_DONE; } /* ** The set of routines that implement the porter-stemmer tokenizer */ static const sqlite3_tokenizer_module porterTokenizerModule = { 0, porterCreate, porterDestroy, porterOpen, porterClose, porterNext, 0 }; /* ** Allocate a new porter tokenizer. Return a pointer to the new ** tokenizer in *ppModule */ void sqlite3Fts3PorterTokenizerModule( sqlite3_tokenizer_module const**ppModule ){ *ppModule = &porterTokenizerModule; } #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_snippet.c ================================================ /* ** 2009 Oct 23 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #include #include /* ** Characters that may appear in the second argument to matchinfo(). */ #define FTS3_MATCHINFO_NPHRASE 'p' /* 1 value */ #define FTS3_MATCHINFO_NCOL 'c' /* 1 value */ #define FTS3_MATCHINFO_NDOC 'n' /* 1 value */ #define FTS3_MATCHINFO_AVGLENGTH 'a' /* nCol values */ #define FTS3_MATCHINFO_LENGTH 'l' /* nCol values */ #define FTS3_MATCHINFO_LCS 's' /* nCol values */ #define FTS3_MATCHINFO_HITS 'x' /* 3*nCol*nPhrase values */ #define FTS3_MATCHINFO_LHITS 'y' /* nCol*nPhrase values */ #define FTS3_MATCHINFO_LHITS_BM 'b' /* nCol*nPhrase values */ /* ** The default value for the second argument to matchinfo(). */ #define FTS3_MATCHINFO_DEFAULT "pcx" /* ** Used as an fts3ExprIterate() context when loading phrase doclists to ** Fts3Expr.aDoclist[]/nDoclist. */ typedef struct LoadDoclistCtx LoadDoclistCtx; struct LoadDoclistCtx { Fts3Cursor *pCsr; /* FTS3 Cursor */ int nPhrase; /* Number of phrases seen so far */ int nToken; /* Number of tokens seen so far */ }; /* ** The following types are used as part of the implementation of the ** fts3BestSnippet() routine. */ typedef struct SnippetIter SnippetIter; typedef struct SnippetPhrase SnippetPhrase; typedef struct SnippetFragment SnippetFragment; struct SnippetIter { Fts3Cursor *pCsr; /* Cursor snippet is being generated from */ int iCol; /* Extract snippet from this column */ int nSnippet; /* Requested snippet length (in tokens) */ int nPhrase; /* Number of phrases in query */ SnippetPhrase *aPhrase; /* Array of size nPhrase */ int iCurrent; /* First token of current snippet */ }; struct SnippetPhrase { int nToken; /* Number of tokens in phrase */ char *pList; /* Pointer to start of phrase position list */ int iHead; /* Next value in position list */ char *pHead; /* Position list data following iHead */ int iTail; /* Next value in trailing position list */ char *pTail; /* Position list data following iTail */ }; struct SnippetFragment { int iCol; /* Column snippet is extracted from */ int iPos; /* Index of first token in snippet */ u64 covered; /* Mask of query phrases covered */ u64 hlmask; /* Mask of snippet terms to highlight */ }; /* ** This type is used as an fts3ExprIterate() context object while ** accumulating the data returned by the matchinfo() function. */ typedef struct MatchInfo MatchInfo; struct MatchInfo { Fts3Cursor *pCursor; /* FTS3 Cursor */ int nCol; /* Number of columns in table */ int nPhrase; /* Number of matchable phrases in query */ sqlite3_int64 nDoc; /* Number of docs in database */ char flag; u32 *aMatchinfo; /* Pre-allocated buffer */ }; /* ** An instance of this structure is used to manage a pair of buffers, each ** (nElem * sizeof(u32)) bytes in size. See the MatchinfoBuffer code below ** for details. */ struct MatchinfoBuffer { u8 aRef[3]; int nElem; int bGlobal; /* Set if global data is loaded */ char *zMatchinfo; u32 aMatchinfo[1]; }; /* ** The snippet() and offsets() functions both return text values. An instance ** of the following structure is used to accumulate those values while the ** functions are running. See fts3StringAppend() for details. */ typedef struct StrBuffer StrBuffer; struct StrBuffer { char *z; /* Pointer to buffer containing string */ int n; /* Length of z in bytes (excl. nul-term) */ int nAlloc; /* Allocated size of buffer z in bytes */ }; /************************************************************************* ** Start of MatchinfoBuffer code. */ /* ** Allocate a two-slot MatchinfoBuffer object. */ static MatchinfoBuffer *fts3MIBufferNew(int nElem, const char *zMatchinfo){ MatchinfoBuffer *pRet; int nByte = sizeof(u32) * (2*nElem + 1) + sizeof(MatchinfoBuffer); int nStr = (int)strlen(zMatchinfo); pRet = sqlite3_malloc(nByte + nStr+1); if( pRet ){ memset(pRet, 0, nByte); pRet->aMatchinfo[0] = (u8*)(&pRet->aMatchinfo[1]) - (u8*)pRet; pRet->aMatchinfo[1+nElem] = pRet->aMatchinfo[0] + sizeof(u32)*(nElem+1); pRet->nElem = nElem; pRet->zMatchinfo = ((char*)pRet) + nByte; memcpy(pRet->zMatchinfo, zMatchinfo, nStr+1); pRet->aRef[0] = 1; } return pRet; } static void fts3MIBufferFree(void *p){ MatchinfoBuffer *pBuf = (MatchinfoBuffer*)((u8*)p - ((u32*)p)[-1]); assert( (u32*)p==&pBuf->aMatchinfo[1] || (u32*)p==&pBuf->aMatchinfo[pBuf->nElem+2] ); if( (u32*)p==&pBuf->aMatchinfo[1] ){ pBuf->aRef[1] = 0; }else{ pBuf->aRef[2] = 0; } if( pBuf->aRef[0]==0 && pBuf->aRef[1]==0 && pBuf->aRef[2]==0 ){ sqlite3_free(pBuf); } } static void (*fts3MIBufferAlloc(MatchinfoBuffer *p, u32 **paOut))(void*){ void (*xRet)(void*) = 0; u32 *aOut = 0; if( p->aRef[1]==0 ){ p->aRef[1] = 1; aOut = &p->aMatchinfo[1]; xRet = fts3MIBufferFree; } else if( p->aRef[2]==0 ){ p->aRef[2] = 1; aOut = &p->aMatchinfo[p->nElem+2]; xRet = fts3MIBufferFree; }else{ aOut = (u32*)sqlite3_malloc(p->nElem * sizeof(u32)); if( aOut ){ xRet = sqlite3_free; if( p->bGlobal ) memcpy(aOut, &p->aMatchinfo[1], p->nElem*sizeof(u32)); } } *paOut = aOut; return xRet; } static void fts3MIBufferSetGlobal(MatchinfoBuffer *p){ p->bGlobal = 1; memcpy(&p->aMatchinfo[2+p->nElem], &p->aMatchinfo[1], p->nElem*sizeof(u32)); } /* ** Free a MatchinfoBuffer object allocated using fts3MIBufferNew() */ void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p){ if( p ){ assert( p->aRef[0]==1 ); p->aRef[0] = 0; if( p->aRef[0]==0 && p->aRef[1]==0 && p->aRef[2]==0 ){ sqlite3_free(p); } } } /* ** End of MatchinfoBuffer code. *************************************************************************/ /* ** This function is used to help iterate through a position-list. A position ** list is a list of unique integers, sorted from smallest to largest. Each ** element of the list is represented by an FTS3 varint that takes the value ** of the difference between the current element and the previous one plus ** two. For example, to store the position-list: ** ** 4 9 113 ** ** the three varints: ** ** 6 7 106 ** ** are encoded. ** ** When this function is called, *pp points to the start of an element of ** the list. *piPos contains the value of the previous entry in the list. ** After it returns, *piPos contains the value of the next element of the ** list and *pp is advanced to the following varint. */ static void fts3GetDeltaPosition(char **pp, int *piPos){ int iVal; *pp += fts3GetVarint32(*pp, &iVal); *piPos += (iVal-2); } /* ** Helper function for fts3ExprIterate() (see below). */ static int fts3ExprIterate2( Fts3Expr *pExpr, /* Expression to iterate phrases of */ int *piPhrase, /* Pointer to phrase counter */ int (*x)(Fts3Expr*,int,void*), /* Callback function to invoke for phrases */ void *pCtx /* Second argument to pass to callback */ ){ int rc; /* Return code */ int eType = pExpr->eType; /* Type of expression node pExpr */ if( eType!=FTSQUERY_PHRASE ){ assert( pExpr->pLeft && pExpr->pRight ); rc = fts3ExprIterate2(pExpr->pLeft, piPhrase, x, pCtx); if( rc==SQLITE_OK && eType!=FTSQUERY_NOT ){ rc = fts3ExprIterate2(pExpr->pRight, piPhrase, x, pCtx); } }else{ rc = x(pExpr, *piPhrase, pCtx); (*piPhrase)++; } return rc; } /* ** Iterate through all phrase nodes in an FTS3 query, except those that ** are part of a sub-tree that is the right-hand-side of a NOT operator. ** For each phrase node found, the supplied callback function is invoked. ** ** If the callback function returns anything other than SQLITE_OK, ** the iteration is abandoned and the error code returned immediately. ** Otherwise, SQLITE_OK is returned after a callback has been made for ** all eligible phrase nodes. */ static int fts3ExprIterate( Fts3Expr *pExpr, /* Expression to iterate phrases of */ int (*x)(Fts3Expr*,int,void*), /* Callback function to invoke for phrases */ void *pCtx /* Second argument to pass to callback */ ){ int iPhrase = 0; /* Variable used as the phrase counter */ return fts3ExprIterate2(pExpr, &iPhrase, x, pCtx); } /* ** This is an fts3ExprIterate() callback used while loading the doclists ** for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also ** fts3ExprLoadDoclists(). */ static int fts3ExprLoadDoclistsCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ int rc = SQLITE_OK; Fts3Phrase *pPhrase = pExpr->pPhrase; LoadDoclistCtx *p = (LoadDoclistCtx *)ctx; UNUSED_PARAMETER(iPhrase); p->nPhrase++; p->nToken += pPhrase->nToken; return rc; } /* ** Load the doclists for each phrase in the query associated with FTS3 cursor ** pCsr. ** ** If pnPhrase is not NULL, then *pnPhrase is set to the number of matchable ** phrases in the expression (all phrases except those directly or ** indirectly descended from the right-hand-side of a NOT operator). If ** pnToken is not NULL, then it is set to the number of tokens in all ** matchable phrases of the expression. */ static int fts3ExprLoadDoclists( Fts3Cursor *pCsr, /* Fts3 cursor for current query */ int *pnPhrase, /* OUT: Number of phrases in query */ int *pnToken /* OUT: Number of tokens in query */ ){ int rc; /* Return Code */ LoadDoclistCtx sCtx = {0,0,0}; /* Context for fts3ExprIterate() */ sCtx.pCsr = pCsr; rc = fts3ExprIterate(pCsr->pExpr, fts3ExprLoadDoclistsCb, (void *)&sCtx); if( pnPhrase ) *pnPhrase = sCtx.nPhrase; if( pnToken ) *pnToken = sCtx.nToken; return rc; } static int fts3ExprPhraseCountCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ (*(int *)ctx)++; pExpr->iPhrase = iPhrase; return SQLITE_OK; } static int fts3ExprPhraseCount(Fts3Expr *pExpr){ int nPhrase = 0; (void)fts3ExprIterate(pExpr, fts3ExprPhraseCountCb, (void *)&nPhrase); return nPhrase; } /* ** Advance the position list iterator specified by the first two ** arguments so that it points to the first element with a value greater ** than or equal to parameter iNext. */ static void fts3SnippetAdvance(char **ppIter, int *piIter, int iNext){ char *pIter = *ppIter; if( pIter ){ int iIter = *piIter; while( iIteriCurrent<0 ){ /* The SnippetIter object has just been initialized. The first snippet ** candidate always starts at offset 0 (even if this candidate has a ** score of 0.0). */ pIter->iCurrent = 0; /* Advance the 'head' iterator of each phrase to the first offset that ** is greater than or equal to (iNext+nSnippet). */ for(i=0; inPhrase; i++){ SnippetPhrase *pPhrase = &pIter->aPhrase[i]; fts3SnippetAdvance(&pPhrase->pHead, &pPhrase->iHead, pIter->nSnippet); } }else{ int iStart; int iEnd = 0x7FFFFFFF; for(i=0; inPhrase; i++){ SnippetPhrase *pPhrase = &pIter->aPhrase[i]; if( pPhrase->pHead && pPhrase->iHeadiHead; } } if( iEnd==0x7FFFFFFF ){ return 1; } pIter->iCurrent = iStart = iEnd - pIter->nSnippet + 1; for(i=0; inPhrase; i++){ SnippetPhrase *pPhrase = &pIter->aPhrase[i]; fts3SnippetAdvance(&pPhrase->pHead, &pPhrase->iHead, iEnd+1); fts3SnippetAdvance(&pPhrase->pTail, &pPhrase->iTail, iStart); } } return 0; } /* ** Retrieve information about the current candidate snippet of snippet ** iterator pIter. */ static void fts3SnippetDetails( SnippetIter *pIter, /* Snippet iterator */ u64 mCovered, /* Bitmask of phrases already covered */ int *piToken, /* OUT: First token of proposed snippet */ int *piScore, /* OUT: "Score" for this snippet */ u64 *pmCover, /* OUT: Bitmask of phrases covered */ u64 *pmHighlight /* OUT: Bitmask of terms to highlight */ ){ int iStart = pIter->iCurrent; /* First token of snippet */ int iScore = 0; /* Score of this snippet */ int i; /* Loop counter */ u64 mCover = 0; /* Mask of phrases covered by this snippet */ u64 mHighlight = 0; /* Mask of tokens to highlight in snippet */ for(i=0; inPhrase; i++){ SnippetPhrase *pPhrase = &pIter->aPhrase[i]; if( pPhrase->pTail ){ char *pCsr = pPhrase->pTail; int iCsr = pPhrase->iTail; while( iCsr<(iStart+pIter->nSnippet) ){ int j; u64 mPhrase = (u64)1 << i; u64 mPos = (u64)1 << (iCsr - iStart); assert( iCsr>=iStart ); if( (mCover|mCovered)&mPhrase ){ iScore++; }else{ iScore += 1000; } mCover |= mPhrase; for(j=0; jnToken; j++){ mHighlight |= (mPos>>j); } if( 0==(*pCsr & 0x0FE) ) break; fts3GetDeltaPosition(&pCsr, &iCsr); } } } /* Set the output variables before returning. */ *piToken = iStart; *piScore = iScore; *pmCover = mCover; *pmHighlight = mHighlight; } /* ** This function is an fts3ExprIterate() callback used by fts3BestSnippet(). ** Each invocation populates an element of the SnippetIter.aPhrase[] array. */ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){ SnippetIter *p = (SnippetIter *)ctx; SnippetPhrase *pPhrase = &p->aPhrase[iPhrase]; char *pCsr; int rc; pPhrase->nToken = pExpr->pPhrase->nToken; rc = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pCsr); assert( rc==SQLITE_OK || pCsr==0 ); if( pCsr ){ int iFirst = 0; pPhrase->pList = pCsr; fts3GetDeltaPosition(&pCsr, &iFirst); assert( iFirst>=0 ); pPhrase->pHead = pCsr; pPhrase->pTail = pCsr; pPhrase->iHead = iFirst; pPhrase->iTail = iFirst; }else{ assert( rc!=SQLITE_OK || ( pPhrase->pList==0 && pPhrase->pHead==0 && pPhrase->pTail==0 )); } return rc; } /* ** Select the fragment of text consisting of nFragment contiguous tokens ** from column iCol that represent the "best" snippet. The best snippet ** is the snippet with the highest score, where scores are calculated ** by adding: ** ** (a) +1 point for each occurrence of a matchable phrase in the snippet. ** ** (b) +1000 points for the first occurrence of each matchable phrase in ** the snippet for which the corresponding mCovered bit is not set. ** ** The selected snippet parameters are stored in structure *pFragment before ** returning. The score of the selected snippet is stored in *piScore ** before returning. */ static int fts3BestSnippet( int nSnippet, /* Desired snippet length */ Fts3Cursor *pCsr, /* Cursor to create snippet for */ int iCol, /* Index of column to create snippet from */ u64 mCovered, /* Mask of phrases already covered */ u64 *pmSeen, /* IN/OUT: Mask of phrases seen */ SnippetFragment *pFragment, /* OUT: Best snippet found */ int *piScore /* OUT: Score of snippet pFragment */ ){ int rc; /* Return Code */ int nList; /* Number of phrases in expression */ SnippetIter sIter; /* Iterates through snippet candidates */ int nByte; /* Number of bytes of space to allocate */ int iBestScore = -1; /* Best snippet score found so far */ int i; /* Loop counter */ memset(&sIter, 0, sizeof(sIter)); /* Iterate through the phrases in the expression to count them. The same ** callback makes sure the doclists are loaded for each phrase. */ rc = fts3ExprLoadDoclists(pCsr, &nList, 0); if( rc!=SQLITE_OK ){ return rc; } /* Now that it is known how many phrases there are, allocate and zero ** the required space using malloc(). */ nByte = sizeof(SnippetPhrase) * nList; sIter.aPhrase = (SnippetPhrase *)sqlite3_malloc(nByte); if( !sIter.aPhrase ){ return SQLITE_NOMEM; } memset(sIter.aPhrase, 0, nByte); /* Initialize the contents of the SnippetIter object. Then iterate through ** the set of phrases in the expression to populate the aPhrase[] array. */ sIter.pCsr = pCsr; sIter.iCol = iCol; sIter.nSnippet = nSnippet; sIter.nPhrase = nList; sIter.iCurrent = -1; rc = fts3ExprIterate(pCsr->pExpr, fts3SnippetFindPositions, (void*)&sIter); if( rc==SQLITE_OK ){ /* Set the *pmSeen output variable. */ for(i=0; iiCol = iCol; while( !fts3SnippetNextCandidate(&sIter) ){ int iPos; int iScore; u64 mCover; u64 mHighlite; fts3SnippetDetails(&sIter, mCovered, &iPos, &iScore, &mCover,&mHighlite); assert( iScore>=0 ); if( iScore>iBestScore ){ pFragment->iPos = iPos; pFragment->hlmask = mHighlite; pFragment->covered = mCover; iBestScore = iScore; } } *piScore = iBestScore; } sqlite3_free(sIter.aPhrase); return rc; } /* ** Append a string to the string-buffer passed as the first argument. ** ** If nAppend is negative, then the length of the string zAppend is ** determined using strlen(). */ static int fts3StringAppend( StrBuffer *pStr, /* Buffer to append to */ const char *zAppend, /* Pointer to data to append to buffer */ int nAppend /* Size of zAppend in bytes (or -1) */ ){ if( nAppend<0 ){ nAppend = (int)strlen(zAppend); } /* If there is insufficient space allocated at StrBuffer.z, use realloc() ** to grow the buffer until so that it is big enough to accomadate the ** appended data. */ if( pStr->n+nAppend+1>=pStr->nAlloc ){ int nAlloc = pStr->nAlloc+nAppend+100; char *zNew = sqlite3_realloc(pStr->z, nAlloc); if( !zNew ){ return SQLITE_NOMEM; } pStr->z = zNew; pStr->nAlloc = nAlloc; } assert( pStr->z!=0 && (pStr->nAlloc >= pStr->n+nAppend+1) ); /* Append the data to the string buffer. */ memcpy(&pStr->z[pStr->n], zAppend, nAppend); pStr->n += nAppend; pStr->z[pStr->n] = '\0'; return SQLITE_OK; } /* ** The fts3BestSnippet() function often selects snippets that end with a ** query term. That is, the final term of the snippet is always a term ** that requires highlighting. For example, if 'X' is a highlighted term ** and '.' is a non-highlighted term, BestSnippet() may select: ** ** ........X.....X ** ** This function "shifts" the beginning of the snippet forward in the ** document so that there are approximately the same number of ** non-highlighted terms to the right of the final highlighted term as there ** are to the left of the first highlighted term. For example, to this: ** ** ....X.....X.... ** ** This is done as part of extracting the snippet text, not when selecting ** the snippet. Snippet selection is done based on doclists only, so there ** is no way for fts3BestSnippet() to know whether or not the document ** actually contains terms that follow the final highlighted term. */ static int fts3SnippetShift( Fts3Table *pTab, /* FTS3 table snippet comes from */ int iLangid, /* Language id to use in tokenizing */ int nSnippet, /* Number of tokens desired for snippet */ const char *zDoc, /* Document text to extract snippet from */ int nDoc, /* Size of buffer zDoc in bytes */ int *piPos, /* IN/OUT: First token of snippet */ u64 *pHlmask /* IN/OUT: Mask of tokens to highlight */ ){ u64 hlmask = *pHlmask; /* Local copy of initial highlight-mask */ if( hlmask ){ int nLeft; /* Tokens to the left of first highlight */ int nRight; /* Tokens to the right of last highlight */ int nDesired; /* Ideal number of tokens to shift forward */ for(nLeft=0; !(hlmask & ((u64)1 << nLeft)); nLeft++); for(nRight=0; !(hlmask & ((u64)1 << (nSnippet-1-nRight))); nRight++); nDesired = (nLeft-nRight)/2; /* Ideally, the start of the snippet should be pushed forward in the ** document nDesired tokens. This block checks if there are actually ** nDesired tokens to the right of the snippet. If so, *piPos and ** *pHlMask are updated to shift the snippet nDesired tokens to the ** right. Otherwise, the snippet is shifted by the number of tokens ** available. */ if( nDesired>0 ){ int nShift; /* Number of tokens to shift snippet by */ int iCurrent = 0; /* Token counter */ int rc; /* Return Code */ sqlite3_tokenizer_module *pMod; sqlite3_tokenizer_cursor *pC; pMod = (sqlite3_tokenizer_module *)pTab->pTokenizer->pModule; /* Open a cursor on zDoc/nDoc. Check if there are (nSnippet+nDesired) ** or more tokens in zDoc/nDoc. */ rc = sqlite3Fts3OpenTokenizer(pTab->pTokenizer, iLangid, zDoc, nDoc, &pC); if( rc!=SQLITE_OK ){ return rc; } while( rc==SQLITE_OK && iCurrent<(nSnippet+nDesired) ){ const char *ZDUMMY; int DUMMY1 = 0, DUMMY2 = 0, DUMMY3 = 0; rc = pMod->xNext(pC, &ZDUMMY, &DUMMY1, &DUMMY2, &DUMMY3, &iCurrent); } pMod->xClose(pC); if( rc!=SQLITE_OK && rc!=SQLITE_DONE ){ return rc; } nShift = (rc==SQLITE_DONE)+iCurrent-nSnippet; assert( nShift<=nDesired ); if( nShift>0 ){ *piPos += nShift; *pHlmask = hlmask >> nShift; } } } return SQLITE_OK; } /* ** Extract the snippet text for fragment pFragment from cursor pCsr and ** append it to string buffer pOut. */ static int fts3SnippetText( Fts3Cursor *pCsr, /* FTS3 Cursor */ SnippetFragment *pFragment, /* Snippet to extract */ int iFragment, /* Fragment number */ int isLast, /* True for final fragment in snippet */ int nSnippet, /* Number of tokens in extracted snippet */ const char *zOpen, /* String inserted before highlighted term */ const char *zClose, /* String inserted after highlighted term */ const char *zEllipsis, /* String inserted between snippets */ StrBuffer *pOut /* Write output here */ ){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; int rc; /* Return code */ const char *zDoc; /* Document text to extract snippet from */ int nDoc; /* Size of zDoc in bytes */ int iCurrent = 0; /* Current token number of document */ int iEnd = 0; /* Byte offset of end of current token */ int isShiftDone = 0; /* True after snippet is shifted */ int iPos = pFragment->iPos; /* First token of snippet */ u64 hlmask = pFragment->hlmask; /* Highlight-mask for snippet */ int iCol = pFragment->iCol+1; /* Query column to extract text from */ sqlite3_tokenizer_module *pMod; /* Tokenizer module methods object */ sqlite3_tokenizer_cursor *pC; /* Tokenizer cursor open on zDoc/nDoc */ zDoc = (const char *)sqlite3_column_text(pCsr->pStmt, iCol); if( zDoc==0 ){ if( sqlite3_column_type(pCsr->pStmt, iCol)!=SQLITE_NULL ){ return SQLITE_NOMEM; } return SQLITE_OK; } nDoc = sqlite3_column_bytes(pCsr->pStmt, iCol); /* Open a token cursor on the document. */ pMod = (sqlite3_tokenizer_module *)pTab->pTokenizer->pModule; rc = sqlite3Fts3OpenTokenizer(pTab->pTokenizer, pCsr->iLangid, zDoc,nDoc,&pC); if( rc!=SQLITE_OK ){ return rc; } while( rc==SQLITE_OK ){ const char *ZDUMMY; /* Dummy argument used with tokenizer */ int DUMMY1 = -1; /* Dummy argument used with tokenizer */ int iBegin = 0; /* Offset in zDoc of start of token */ int iFin = 0; /* Offset in zDoc of end of token */ int isHighlight = 0; /* True for highlighted terms */ /* Variable DUMMY1 is initialized to a negative value above. Elsewhere ** in the FTS code the variable that the third argument to xNext points to ** is initialized to zero before the first (*but not necessarily ** subsequent*) call to xNext(). This is done for a particular application ** that needs to know whether or not the tokenizer is being used for ** snippet generation or for some other purpose. ** ** Extreme care is required when writing code to depend on this ** initialization. It is not a documented part of the tokenizer interface. ** If a tokenizer is used directly by any code outside of FTS, this ** convention might not be respected. */ rc = pMod->xNext(pC, &ZDUMMY, &DUMMY1, &iBegin, &iFin, &iCurrent); if( rc!=SQLITE_OK ){ if( rc==SQLITE_DONE ){ /* Special case - the last token of the snippet is also the last token ** of the column. Append any punctuation that occurred between the end ** of the previous token and the end of the document to the output. ** Then break out of the loop. */ rc = fts3StringAppend(pOut, &zDoc[iEnd], -1); } break; } if( iCurrentiLangid, nSnippet, &zDoc[iBegin], n, &iPos, &hlmask ); isShiftDone = 1; /* Now that the shift has been done, check if the initial "..." are ** required. They are required if (a) this is not the first fragment, ** or (b) this fragment does not begin at position 0 of its column. */ if( rc==SQLITE_OK ){ if( iPos>0 || iFragment>0 ){ rc = fts3StringAppend(pOut, zEllipsis, -1); }else if( iBegin ){ rc = fts3StringAppend(pOut, zDoc, iBegin); } } if( rc!=SQLITE_OK || iCurrent=(iPos+nSnippet) ){ if( isLast ){ rc = fts3StringAppend(pOut, zEllipsis, -1); } break; } /* Set isHighlight to true if this term should be highlighted. */ isHighlight = (hlmask & ((u64)1 << (iCurrent-iPos)))!=0; if( iCurrent>iPos ) rc = fts3StringAppend(pOut, &zDoc[iEnd], iBegin-iEnd); if( rc==SQLITE_OK && isHighlight ) rc = fts3StringAppend(pOut, zOpen, -1); if( rc==SQLITE_OK ) rc = fts3StringAppend(pOut, &zDoc[iBegin], iFin-iBegin); if( rc==SQLITE_OK && isHighlight ) rc = fts3StringAppend(pOut, zClose, -1); iEnd = iFin; } pMod->xClose(pC); return rc; } /* ** This function is used to count the entries in a column-list (a ** delta-encoded list of term offsets within a single column of a single ** row). When this function is called, *ppCollist should point to the ** beginning of the first varint in the column-list (the varint that ** contains the position of the first matching term in the column data). ** Before returning, *ppCollist is set to point to the first byte after ** the last varint in the column-list (either the 0x00 signifying the end ** of the position-list, or the 0x01 that precedes the column number of ** the next column in the position-list). ** ** The number of elements in the column-list is returned. */ static int fts3ColumnlistCount(char **ppCollist){ char *pEnd = *ppCollist; char c = 0; int nEntry = 0; /* A column-list is terminated by either a 0x01 or 0x00. */ while( 0xFE & (*pEnd | c) ){ c = *pEnd++ & 0x80; if( !c ) nEntry++; } *ppCollist = pEnd; return nEntry; } /* ** This function gathers 'y' or 'b' data for a single phrase. */ static void fts3ExprLHits( Fts3Expr *pExpr, /* Phrase expression node */ MatchInfo *p /* Matchinfo context */ ){ Fts3Table *pTab = (Fts3Table *)p->pCursor->base.pVtab; int iStart; Fts3Phrase *pPhrase = pExpr->pPhrase; char *pIter = pPhrase->doclist.pList; int iCol = 0; assert( p->flag==FTS3_MATCHINFO_LHITS_BM || p->flag==FTS3_MATCHINFO_LHITS ); if( p->flag==FTS3_MATCHINFO_LHITS ){ iStart = pExpr->iPhrase * p->nCol; }else{ iStart = pExpr->iPhrase * ((p->nCol + 31) / 32); } while( 1 ){ int nHit = fts3ColumnlistCount(&pIter); if( (pPhrase->iColumn>=pTab->nColumn || pPhrase->iColumn==iCol) ){ if( p->flag==FTS3_MATCHINFO_LHITS ){ p->aMatchinfo[iStart + iCol] = (u32)nHit; }else if( nHit ){ p->aMatchinfo[iStart + (iCol+1)/32] |= (1 << (iCol&0x1F)); } } assert( *pIter==0x00 || *pIter==0x01 ); if( *pIter!=0x01 ) break; pIter++; pIter += fts3GetVarint32(pIter, &iCol); } } /* ** Gather the results for matchinfo directives 'y' and 'b'. */ static void fts3ExprLHitGather( Fts3Expr *pExpr, MatchInfo *p ){ assert( (pExpr->pLeft==0)==(pExpr->pRight==0) ); if( pExpr->bEof==0 && pExpr->iDocid==p->pCursor->iPrevId ){ if( pExpr->pLeft ){ fts3ExprLHitGather(pExpr->pLeft, p); fts3ExprLHitGather(pExpr->pRight, p); }else{ fts3ExprLHits(pExpr, p); } } } /* ** fts3ExprIterate() callback used to collect the "global" matchinfo stats ** for a single query. ** ** fts3ExprIterate() callback to load the 'global' elements of a ** FTS3_MATCHINFO_HITS matchinfo array. The global stats are those elements ** of the matchinfo array that are constant for all rows returned by the ** current query. ** ** Argument pCtx is actually a pointer to a struct of type MatchInfo. This ** function populates Matchinfo.aMatchinfo[] as follows: ** ** for(iCol=0; iColpCursor, pExpr, &p->aMatchinfo[3*iPhrase*p->nCol] ); } /* ** fts3ExprIterate() callback used to collect the "local" part of the ** FTS3_MATCHINFO_HITS array. The local stats are those elements of the ** array that are different for each row returned by the query. */ static int fts3ExprLocalHitsCb( Fts3Expr *pExpr, /* Phrase expression node */ int iPhrase, /* Phrase number */ void *pCtx /* Pointer to MatchInfo structure */ ){ int rc = SQLITE_OK; MatchInfo *p = (MatchInfo *)pCtx; int iStart = iPhrase * p->nCol * 3; int i; for(i=0; inCol && rc==SQLITE_OK; i++){ char *pCsr; rc = sqlite3Fts3EvalPhrasePoslist(p->pCursor, pExpr, i, &pCsr); if( pCsr ){ p->aMatchinfo[iStart+i*3] = fts3ColumnlistCount(&pCsr); }else{ p->aMatchinfo[iStart+i*3] = 0; } } return rc; } static int fts3MatchinfoCheck( Fts3Table *pTab, char cArg, char **pzErr ){ if( (cArg==FTS3_MATCHINFO_NPHRASE) || (cArg==FTS3_MATCHINFO_NCOL) || (cArg==FTS3_MATCHINFO_NDOC && pTab->bFts4) || (cArg==FTS3_MATCHINFO_AVGLENGTH && pTab->bFts4) || (cArg==FTS3_MATCHINFO_LENGTH && pTab->bHasDocsize) || (cArg==FTS3_MATCHINFO_LCS) || (cArg==FTS3_MATCHINFO_HITS) || (cArg==FTS3_MATCHINFO_LHITS) || (cArg==FTS3_MATCHINFO_LHITS_BM) ){ return SQLITE_OK; } sqlite3Fts3ErrMsg(pzErr, "unrecognized matchinfo request: %c", cArg); return SQLITE_ERROR; } static int fts3MatchinfoSize(MatchInfo *pInfo, char cArg){ int nVal; /* Number of integers output by cArg */ switch( cArg ){ case FTS3_MATCHINFO_NDOC: case FTS3_MATCHINFO_NPHRASE: case FTS3_MATCHINFO_NCOL: nVal = 1; break; case FTS3_MATCHINFO_AVGLENGTH: case FTS3_MATCHINFO_LENGTH: case FTS3_MATCHINFO_LCS: nVal = pInfo->nCol; break; case FTS3_MATCHINFO_LHITS: nVal = pInfo->nCol * pInfo->nPhrase; break; case FTS3_MATCHINFO_LHITS_BM: nVal = pInfo->nPhrase * ((pInfo->nCol + 31) / 32); break; default: assert( cArg==FTS3_MATCHINFO_HITS ); nVal = pInfo->nCol * pInfo->nPhrase * 3; break; } return nVal; } static int fts3MatchinfoSelectDoctotal( Fts3Table *pTab, sqlite3_stmt **ppStmt, sqlite3_int64 *pnDoc, const char **paLen ){ sqlite3_stmt *pStmt; const char *a; sqlite3_int64 nDoc; if( !*ppStmt ){ int rc = sqlite3Fts3SelectDoctotal(pTab, ppStmt); if( rc!=SQLITE_OK ) return rc; } pStmt = *ppStmt; assert( sqlite3_data_count(pStmt)==1 ); a = sqlite3_column_blob(pStmt, 0); a += sqlite3Fts3GetVarint(a, &nDoc); if( nDoc==0 ) return FTS_CORRUPT_VTAB; *pnDoc = (u32)nDoc; if( paLen ) *paLen = a; return SQLITE_OK; } /* ** An instance of the following structure is used to store state while ** iterating through a multi-column position-list corresponding to the ** hits for a single phrase on a single row in order to calculate the ** values for a matchinfo() FTS3_MATCHINFO_LCS request. */ typedef struct LcsIterator LcsIterator; struct LcsIterator { Fts3Expr *pExpr; /* Pointer to phrase expression */ int iPosOffset; /* Tokens count up to end of this phrase */ char *pRead; /* Cursor used to iterate through aDoclist */ int iPos; /* Current position */ }; /* ** If LcsIterator.iCol is set to the following value, the iterator has ** finished iterating through all offsets for all columns. */ #define LCS_ITERATOR_FINISHED 0x7FFFFFFF; static int fts3MatchinfoLcsCb( Fts3Expr *pExpr, /* Phrase expression node */ int iPhrase, /* Phrase number (numbered from zero) */ void *pCtx /* Pointer to MatchInfo structure */ ){ LcsIterator *aIter = (LcsIterator *)pCtx; aIter[iPhrase].pExpr = pExpr; return SQLITE_OK; } /* ** Advance the iterator passed as an argument to the next position. Return ** 1 if the iterator is at EOF or if it now points to the start of the ** position list for the next column. */ static int fts3LcsIteratorAdvance(LcsIterator *pIter){ char *pRead = pIter->pRead; sqlite3_int64 iRead; int rc = 0; pRead += sqlite3Fts3GetVarint(pRead, &iRead); if( iRead==0 || iRead==1 ){ pRead = 0; rc = 1; }else{ pIter->iPos += (int)(iRead-2); } pIter->pRead = pRead; return rc; } /* ** This function implements the FTS3_MATCHINFO_LCS matchinfo() flag. ** ** If the call is successful, the longest-common-substring lengths for each ** column are written into the first nCol elements of the pInfo->aMatchinfo[] ** array before returning. SQLITE_OK is returned in this case. ** ** Otherwise, if an error occurs, an SQLite error code is returned and the ** data written to the first nCol elements of pInfo->aMatchinfo[] is ** undefined. */ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){ LcsIterator *aIter; int i; int iCol; int nToken = 0; /* Allocate and populate the array of LcsIterator objects. The array ** contains one element for each matchable phrase in the query. **/ aIter = sqlite3_malloc(sizeof(LcsIterator) * pCsr->nPhrase); if( !aIter ) return SQLITE_NOMEM; memset(aIter, 0, sizeof(LcsIterator) * pCsr->nPhrase); (void)fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter); for(i=0; inPhrase; i++){ LcsIterator *pIter = &aIter[i]; nToken -= pIter->pExpr->pPhrase->nToken; pIter->iPosOffset = nToken; } for(iCol=0; iColnCol; iCol++){ int nLcs = 0; /* LCS value for this column */ int nLive = 0; /* Number of iterators in aIter not at EOF */ for(i=0; inPhrase; i++){ int rc; LcsIterator *pIt = &aIter[i]; rc = sqlite3Fts3EvalPhrasePoslist(pCsr, pIt->pExpr, iCol, &pIt->pRead); if( rc!=SQLITE_OK ) return rc; if( pIt->pRead ){ pIt->iPos = pIt->iPosOffset; fts3LcsIteratorAdvance(&aIter[i]); nLive++; } } while( nLive>0 ){ LcsIterator *pAdv = 0; /* The iterator to advance by one position */ int nThisLcs = 0; /* LCS for the current iterator positions */ for(i=0; inPhrase; i++){ LcsIterator *pIter = &aIter[i]; if( pIter->pRead==0 ){ /* This iterator is already at EOF for this column. */ nThisLcs = 0; }else{ if( pAdv==0 || pIter->iPosiPos ){ pAdv = pIter; } if( nThisLcs==0 || pIter->iPos==pIter[-1].iPos ){ nThisLcs++; }else{ nThisLcs = 1; } if( nThisLcs>nLcs ) nLcs = nThisLcs; } } if( fts3LcsIteratorAdvance(pAdv) ) nLive--; } pInfo->aMatchinfo[iCol] = nLcs; } sqlite3_free(aIter); return SQLITE_OK; } /* ** Populate the buffer pInfo->aMatchinfo[] with an array of integers to ** be returned by the matchinfo() function. Argument zArg contains the ** format string passed as the second argument to matchinfo (or the ** default value "pcx" if no second argument was specified). The format ** string has already been validated and the pInfo->aMatchinfo[] array ** is guaranteed to be large enough for the output. ** ** If bGlobal is true, then populate all fields of the matchinfo() output. ** If it is false, then assume that those fields that do not change between ** rows (i.e. FTS3_MATCHINFO_NPHRASE, NCOL, NDOC, AVGLENGTH and part of HITS) ** have already been populated. ** ** Return SQLITE_OK if successful, or an SQLite error code if an error ** occurs. If a value other than SQLITE_OK is returned, the state the ** pInfo->aMatchinfo[] buffer is left in is undefined. */ static int fts3MatchinfoValues( Fts3Cursor *pCsr, /* FTS3 cursor object */ int bGlobal, /* True to grab the global stats */ MatchInfo *pInfo, /* Matchinfo context object */ const char *zArg /* Matchinfo format string */ ){ int rc = SQLITE_OK; int i; Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; sqlite3_stmt *pSelect = 0; for(i=0; rc==SQLITE_OK && zArg[i]; i++){ pInfo->flag = zArg[i]; switch( zArg[i] ){ case FTS3_MATCHINFO_NPHRASE: if( bGlobal ) pInfo->aMatchinfo[0] = pInfo->nPhrase; break; case FTS3_MATCHINFO_NCOL: if( bGlobal ) pInfo->aMatchinfo[0] = pInfo->nCol; break; case FTS3_MATCHINFO_NDOC: if( bGlobal ){ sqlite3_int64 nDoc = 0; rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, 0); pInfo->aMatchinfo[0] = (u32)nDoc; } break; case FTS3_MATCHINFO_AVGLENGTH: if( bGlobal ){ sqlite3_int64 nDoc; /* Number of rows in table */ const char *a; /* Aggregate column length array */ rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, &a); if( rc==SQLITE_OK ){ int iCol; for(iCol=0; iColnCol; iCol++){ u32 iVal; sqlite3_int64 nToken; a += sqlite3Fts3GetVarint(a, &nToken); iVal = (u32)(((u32)(nToken&0xffffffff)+nDoc/2)/nDoc); pInfo->aMatchinfo[iCol] = iVal; } } } break; case FTS3_MATCHINFO_LENGTH: { sqlite3_stmt *pSelectDocsize = 0; rc = sqlite3Fts3SelectDocsize(pTab, pCsr->iPrevId, &pSelectDocsize); if( rc==SQLITE_OK ){ int iCol; const char *a = sqlite3_column_blob(pSelectDocsize, 0); for(iCol=0; iColnCol; iCol++){ sqlite3_int64 nToken; a += sqlite3Fts3GetVarint(a, &nToken); pInfo->aMatchinfo[iCol] = (u32)nToken; } } sqlite3_reset(pSelectDocsize); break; } case FTS3_MATCHINFO_LCS: rc = fts3ExprLoadDoclists(pCsr, 0, 0); if( rc==SQLITE_OK ){ rc = fts3MatchinfoLcs(pCsr, pInfo); } break; case FTS3_MATCHINFO_LHITS_BM: case FTS3_MATCHINFO_LHITS: { int nZero = fts3MatchinfoSize(pInfo, zArg[i]) * sizeof(u32); memset(pInfo->aMatchinfo, 0, nZero); fts3ExprLHitGather(pCsr->pExpr, pInfo); break; } default: { Fts3Expr *pExpr; assert( zArg[i]==FTS3_MATCHINFO_HITS ); pExpr = pCsr->pExpr; rc = fts3ExprLoadDoclists(pCsr, 0, 0); if( rc!=SQLITE_OK ) break; if( bGlobal ){ if( pCsr->pDeferred ){ rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &pInfo->nDoc, 0); if( rc!=SQLITE_OK ) break; } rc = fts3ExprIterate(pExpr, fts3ExprGlobalHitsCb,(void*)pInfo); sqlite3Fts3EvalTestDeferred(pCsr, &rc); if( rc!=SQLITE_OK ) break; } (void)fts3ExprIterate(pExpr, fts3ExprLocalHitsCb,(void*)pInfo); break; } } pInfo->aMatchinfo += fts3MatchinfoSize(pInfo, zArg[i]); } sqlite3_reset(pSelect); return rc; } /* ** Populate pCsr->aMatchinfo[] with data for the current row. The ** 'matchinfo' data is an array of 32-bit unsigned integers (C type u32). */ static void fts3GetMatchinfo( sqlite3_context *pCtx, /* Return results here */ Fts3Cursor *pCsr, /* FTS3 Cursor object */ const char *zArg /* Second argument to matchinfo() function */ ){ MatchInfo sInfo; Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; int rc = SQLITE_OK; int bGlobal = 0; /* Collect 'global' stats as well as local */ u32 *aOut = 0; void (*xDestroyOut)(void*) = 0; memset(&sInfo, 0, sizeof(MatchInfo)); sInfo.pCursor = pCsr; sInfo.nCol = pTab->nColumn; /* If there is cached matchinfo() data, but the format string for the ** cache does not match the format string for this request, discard ** the cached data. */ if( pCsr->pMIBuffer && strcmp(pCsr->pMIBuffer->zMatchinfo, zArg) ){ sqlite3Fts3MIBufferFree(pCsr->pMIBuffer); pCsr->pMIBuffer = 0; } /* If Fts3Cursor.pMIBuffer is NULL, then this is the first time the ** matchinfo function has been called for this query. In this case ** allocate the array used to accumulate the matchinfo data and ** initialize those elements that are constant for every row. */ if( pCsr->pMIBuffer==0 ){ int nMatchinfo = 0; /* Number of u32 elements in match-info */ int i; /* Used to iterate through zArg */ /* Determine the number of phrases in the query */ pCsr->nPhrase = fts3ExprPhraseCount(pCsr->pExpr); sInfo.nPhrase = pCsr->nPhrase; /* Determine the number of integers in the buffer returned by this call. */ for(i=0; zArg[i]; i++){ char *zErr = 0; if( fts3MatchinfoCheck(pTab, zArg[i], &zErr) ){ sqlite3_result_error(pCtx, zErr, -1); sqlite3_free(zErr); return; } nMatchinfo += fts3MatchinfoSize(&sInfo, zArg[i]); } /* Allocate space for Fts3Cursor.aMatchinfo[] and Fts3Cursor.zMatchinfo. */ pCsr->pMIBuffer = fts3MIBufferNew(nMatchinfo, zArg); if( !pCsr->pMIBuffer ) rc = SQLITE_NOMEM; pCsr->isMatchinfoNeeded = 1; bGlobal = 1; } if( rc==SQLITE_OK ){ xDestroyOut = fts3MIBufferAlloc(pCsr->pMIBuffer, &aOut); if( xDestroyOut==0 ){ rc = SQLITE_NOMEM; } } if( rc==SQLITE_OK ){ sInfo.aMatchinfo = aOut; sInfo.nPhrase = pCsr->nPhrase; rc = fts3MatchinfoValues(pCsr, bGlobal, &sInfo, zArg); if( bGlobal ){ fts3MIBufferSetGlobal(pCsr->pMIBuffer); } } if( rc!=SQLITE_OK ){ sqlite3_result_error_code(pCtx, rc); if( xDestroyOut ) xDestroyOut(aOut); }else{ int n = pCsr->pMIBuffer->nElem * sizeof(u32); sqlite3_result_blob(pCtx, aOut, n, xDestroyOut); } } /* ** Implementation of snippet() function. */ void sqlite3Fts3Snippet( sqlite3_context *pCtx, /* SQLite function call context */ Fts3Cursor *pCsr, /* Cursor object */ const char *zStart, /* Snippet start text - "" */ const char *zEnd, /* Snippet end text - "" */ const char *zEllipsis, /* Snippet ellipsis text - "..." */ int iCol, /* Extract snippet from this column */ int nToken /* Approximate number of tokens in snippet */ ){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; int rc = SQLITE_OK; int i; StrBuffer res = {0, 0, 0}; /* The returned text includes up to four fragments of text extracted from ** the data in the current row. The first iteration of the for(...) loop ** below attempts to locate a single fragment of text nToken tokens in ** size that contains at least one instance of all phrases in the query ** expression that appear in the current row. If such a fragment of text ** cannot be found, the second iteration of the loop attempts to locate ** a pair of fragments, and so on. */ int nSnippet = 0; /* Number of fragments in this snippet */ SnippetFragment aSnippet[4]; /* Maximum of 4 fragments per snippet */ int nFToken = -1; /* Number of tokens in each fragment */ if( !pCsr->pExpr ){ sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC); return; } for(nSnippet=1; 1; nSnippet++){ int iSnip; /* Loop counter 0..nSnippet-1 */ u64 mCovered = 0; /* Bitmask of phrases covered by snippet */ u64 mSeen = 0; /* Bitmask of phrases seen by BestSnippet() */ if( nToken>=0 ){ nFToken = (nToken+nSnippet-1) / nSnippet; }else{ nFToken = -1 * nToken; } for(iSnip=0; iSnipnColumn; iRead++){ SnippetFragment sF = {0, 0, 0, 0}; int iS = 0; if( iCol>=0 && iRead!=iCol ) continue; /* Find the best snippet of nFToken tokens in column iRead. */ rc = fts3BestSnippet(nFToken, pCsr, iRead, mCovered, &mSeen, &sF, &iS); if( rc!=SQLITE_OK ){ goto snippet_out; } if( iS>iBestScore ){ *pFragment = sF; iBestScore = iS; } } mCovered |= pFragment->covered; } /* If all query phrases seen by fts3BestSnippet() are present in at least ** one of the nSnippet snippet fragments, break out of the loop. */ assert( (mCovered&mSeen)==mCovered ); if( mSeen==mCovered || nSnippet==SizeofArray(aSnippet) ) break; } assert( nFToken>0 ); for(i=0; ipCsr, pExpr, p->iCol, &pList); nTerm = pExpr->pPhrase->nToken; if( pList ){ fts3GetDeltaPosition(&pList, &iPos); assert( iPos>=0 ); } for(iTerm=0; iTermaTerm[p->iTerm++]; pT->iOff = nTerm-iTerm-1; pT->pList = pList; pT->iPos = iPos; } return rc; } /* ** Implementation of offsets() function. */ void sqlite3Fts3Offsets( sqlite3_context *pCtx, /* SQLite function call context */ Fts3Cursor *pCsr /* Cursor object */ ){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; sqlite3_tokenizer_module const *pMod = pTab->pTokenizer->pModule; int rc; /* Return Code */ int nToken; /* Number of tokens in query */ int iCol; /* Column currently being processed */ StrBuffer res = {0, 0, 0}; /* Result string */ TermOffsetCtx sCtx; /* Context for fts3ExprTermOffsetInit() */ if( !pCsr->pExpr ){ sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC); return; } memset(&sCtx, 0, sizeof(sCtx)); assert( pCsr->isRequireSeek==0 ); /* Count the number of terms in the query */ rc = fts3ExprLoadDoclists(pCsr, 0, &nToken); if( rc!=SQLITE_OK ) goto offsets_out; /* Allocate the array of TermOffset iterators. */ sCtx.aTerm = (TermOffset *)sqlite3_malloc(sizeof(TermOffset)*nToken); if( 0==sCtx.aTerm ){ rc = SQLITE_NOMEM; goto offsets_out; } sCtx.iDocid = pCsr->iPrevId; sCtx.pCsr = pCsr; /* Loop through the table columns, appending offset information to ** string-buffer res for each column. */ for(iCol=0; iColnColumn; iCol++){ sqlite3_tokenizer_cursor *pC; /* Tokenizer cursor */ const char *ZDUMMY; /* Dummy argument used with xNext() */ int NDUMMY = 0; /* Dummy argument used with xNext() */ int iStart = 0; int iEnd = 0; int iCurrent = 0; const char *zDoc; int nDoc; /* Initialize the contents of sCtx.aTerm[] for column iCol. There is ** no way that this operation can fail, so the return code from ** fts3ExprIterate() can be discarded. */ sCtx.iCol = iCol; sCtx.iTerm = 0; (void)fts3ExprIterate(pCsr->pExpr, fts3ExprTermOffsetInit, (void*)&sCtx); /* Retreive the text stored in column iCol. If an SQL NULL is stored ** in column iCol, jump immediately to the next iteration of the loop. ** If an OOM occurs while retrieving the data (this can happen if SQLite ** needs to transform the data from utf-16 to utf-8), return SQLITE_NOMEM ** to the caller. */ zDoc = (const char *)sqlite3_column_text(pCsr->pStmt, iCol+1); nDoc = sqlite3_column_bytes(pCsr->pStmt, iCol+1); if( zDoc==0 ){ if( sqlite3_column_type(pCsr->pStmt, iCol+1)==SQLITE_NULL ){ continue; } rc = SQLITE_NOMEM; goto offsets_out; } /* Initialize a tokenizer iterator to iterate through column iCol. */ rc = sqlite3Fts3OpenTokenizer(pTab->pTokenizer, pCsr->iLangid, zDoc, nDoc, &pC ); if( rc!=SQLITE_OK ) goto offsets_out; rc = pMod->xNext(pC, &ZDUMMY, &NDUMMY, &iStart, &iEnd, &iCurrent); while( rc==SQLITE_OK ){ int i; /* Used to loop through terms */ int iMinPos = 0x7FFFFFFF; /* Position of next token */ TermOffset *pTerm = 0; /* TermOffset associated with next token */ for(i=0; ipList && (pT->iPos-pT->iOff)iPos-pT->iOff; pTerm = pT; } } if( !pTerm ){ /* All offsets for this column have been gathered. */ rc = SQLITE_DONE; }else{ assert( iCurrent<=iMinPos ); if( 0==(0xFE&*pTerm->pList) ){ pTerm->pList = 0; }else{ fts3GetDeltaPosition(&pTerm->pList, &pTerm->iPos); } while( rc==SQLITE_OK && iCurrentxNext(pC, &ZDUMMY, &NDUMMY, &iStart, &iEnd, &iCurrent); } if( rc==SQLITE_OK ){ char aBuffer[64]; sqlite3_snprintf(sizeof(aBuffer), aBuffer, "%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart ); rc = fts3StringAppend(&res, aBuffer, -1); }else if( rc==SQLITE_DONE && pTab->zContentTbl==0 ){ rc = FTS_CORRUPT_VTAB; } } } if( rc==SQLITE_DONE ){ rc = SQLITE_OK; } pMod->xClose(pC); if( rc!=SQLITE_OK ) goto offsets_out; } offsets_out: sqlite3_free(sCtx.aTerm); assert( rc!=SQLITE_DONE ); sqlite3Fts3SegmentsClose(pTab); if( rc!=SQLITE_OK ){ sqlite3_result_error_code(pCtx, rc); sqlite3_free(res.z); }else{ sqlite3_result_text(pCtx, res.z, res.n-1, sqlite3_free); } return; } /* ** Implementation of matchinfo() function. */ void sqlite3Fts3Matchinfo( sqlite3_context *pContext, /* Function call context */ Fts3Cursor *pCsr, /* FTS3 table cursor */ const char *zArg /* Second arg to matchinfo() function */ ){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; const char *zFormat; if( zArg ){ zFormat = zArg; }else{ zFormat = FTS3_MATCHINFO_DEFAULT; } if( !pCsr->pExpr ){ sqlite3_result_blob(pContext, "", 0, SQLITE_STATIC); return; }else{ /* Retrieve matchinfo() data. */ fts3GetMatchinfo(pContext, pCsr, zFormat); sqlite3Fts3SegmentsClose(pTab); } } #endif ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_tokenize_vtab.c ================================================ /* ** 2013 Apr 22 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This file contains code for the "fts3tokenize" virtual table module. ** An fts3tokenize virtual table is created as follows: ** ** CREATE VIRTUAL TABLE USING fts3tokenize( ** , , ... ** ); ** ** The table created has the following schema: ** ** CREATE TABLE (input, token, start, end, position) ** ** When queried, the query must include a WHERE clause of type: ** ** input = ** ** The virtual table module tokenizes this , using the FTS3 ** tokenizer specified by the arguments to the CREATE VIRTUAL TABLE ** statement and returns one row for each token in the result. With ** fields set as follows: ** ** input: Always set to a copy of ** token: A token from the input. ** start: Byte offset of the token within the input . ** end: Byte offset of the byte immediately following the end of the ** token within the input string. ** pos: Token offset of token within input. ** */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #include #include typedef struct Fts3tokTable Fts3tokTable; typedef struct Fts3tokCursor Fts3tokCursor; /* ** Virtual table structure. */ struct Fts3tokTable { sqlite3_vtab base; /* Base class used by SQLite core */ const sqlite3_tokenizer_module *pMod; sqlite3_tokenizer *pTok; }; /* ** Virtual table cursor structure. */ struct Fts3tokCursor { sqlite3_vtab_cursor base; /* Base class used by SQLite core */ char *zInput; /* Input string */ sqlite3_tokenizer_cursor *pCsr; /* Cursor to iterate through zInput */ int iRowid; /* Current 'rowid' value */ const char *zToken; /* Current 'token' value */ int nToken; /* Size of zToken in bytes */ int iStart; /* Current 'start' value */ int iEnd; /* Current 'end' value */ int iPos; /* Current 'pos' value */ }; /* ** Query FTS for the tokenizer implementation named zName. */ static int fts3tokQueryTokenizer( Fts3Hash *pHash, const char *zName, const sqlite3_tokenizer_module **pp, char **pzErr ){ sqlite3_tokenizer_module *p; int nName = (int)strlen(zName); p = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, zName, nName+1); if( !p ){ sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer: %s", zName); return SQLITE_ERROR; } *pp = p; return SQLITE_OK; } /* ** The second argument, argv[], is an array of pointers to nul-terminated ** strings. This function makes a copy of the array and strings into a ** single block of memory. It then dequotes any of the strings that appear ** to be quoted. ** ** If successful, output parameter *pazDequote is set to point at the ** array of dequoted strings and SQLITE_OK is returned. The caller is ** responsible for eventually calling sqlite3_free() to free the array ** in this case. Or, if an error occurs, an SQLite error code is returned. ** The final value of *pazDequote is undefined in this case. */ static int fts3tokDequoteArray( int argc, /* Number of elements in argv[] */ const char * const *argv, /* Input array */ char ***pazDequote /* Output array */ ){ int rc = SQLITE_OK; /* Return code */ if( argc==0 ){ *pazDequote = 0; }else{ int i; int nByte = 0; char **azDequote; for(i=0; ixCreate((nDequote>1 ? nDequote-1 : 0), azArg, &pTok); } if( rc==SQLITE_OK ){ pTab = (Fts3tokTable *)sqlite3_malloc(sizeof(Fts3tokTable)); if( pTab==0 ){ rc = SQLITE_NOMEM; } } if( rc==SQLITE_OK ){ memset(pTab, 0, sizeof(Fts3tokTable)); pTab->pMod = pMod; pTab->pTok = pTok; *ppVtab = &pTab->base; }else{ if( pTok ){ pMod->xDestroy(pTok); } } sqlite3_free(azDequote); return rc; } /* ** This function does the work for both the xDisconnect and xDestroy methods. ** These tables have no persistent representation of their own, so xDisconnect ** and xDestroy are identical operations. */ static int fts3tokDisconnectMethod(sqlite3_vtab *pVtab){ Fts3tokTable *pTab = (Fts3tokTable *)pVtab; pTab->pMod->xDestroy(pTab->pTok); sqlite3_free(pTab); return SQLITE_OK; } /* ** xBestIndex - Analyze a WHERE and ORDER BY clause. */ static int fts3tokBestIndexMethod( sqlite3_vtab *pVTab, sqlite3_index_info *pInfo ){ int i; UNUSED_PARAMETER(pVTab); for(i=0; inConstraint; i++){ if( pInfo->aConstraint[i].usable && pInfo->aConstraint[i].iColumn==0 && pInfo->aConstraint[i].op==SQLITE_INDEX_CONSTRAINT_EQ ){ pInfo->idxNum = 1; pInfo->aConstraintUsage[i].argvIndex = 1; pInfo->aConstraintUsage[i].omit = 1; pInfo->estimatedCost = 1; return SQLITE_OK; } } pInfo->idxNum = 0; assert( pInfo->estimatedCost>1000000.0 ); return SQLITE_OK; } /* ** xOpen - Open a cursor. */ static int fts3tokOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ Fts3tokCursor *pCsr; UNUSED_PARAMETER(pVTab); pCsr = (Fts3tokCursor *)sqlite3_malloc(sizeof(Fts3tokCursor)); if( pCsr==0 ){ return SQLITE_NOMEM; } memset(pCsr, 0, sizeof(Fts3tokCursor)); *ppCsr = (sqlite3_vtab_cursor *)pCsr; return SQLITE_OK; } /* ** Reset the tokenizer cursor passed as the only argument. As if it had ** just been returned by fts3tokOpenMethod(). */ static void fts3tokResetCursor(Fts3tokCursor *pCsr){ if( pCsr->pCsr ){ Fts3tokTable *pTab = (Fts3tokTable *)(pCsr->base.pVtab); pTab->pMod->xClose(pCsr->pCsr); pCsr->pCsr = 0; } sqlite3_free(pCsr->zInput); pCsr->zInput = 0; pCsr->zToken = 0; pCsr->nToken = 0; pCsr->iStart = 0; pCsr->iEnd = 0; pCsr->iPos = 0; pCsr->iRowid = 0; } /* ** xClose - Close a cursor. */ static int fts3tokCloseMethod(sqlite3_vtab_cursor *pCursor){ Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; fts3tokResetCursor(pCsr); sqlite3_free(pCsr); return SQLITE_OK; } /* ** xNext - Advance the cursor to the next row, if any. */ static int fts3tokNextMethod(sqlite3_vtab_cursor *pCursor){ Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; Fts3tokTable *pTab = (Fts3tokTable *)(pCursor->pVtab); int rc; /* Return code */ pCsr->iRowid++; rc = pTab->pMod->xNext(pCsr->pCsr, &pCsr->zToken, &pCsr->nToken, &pCsr->iStart, &pCsr->iEnd, &pCsr->iPos ); if( rc!=SQLITE_OK ){ fts3tokResetCursor(pCsr); if( rc==SQLITE_DONE ) rc = SQLITE_OK; } return rc; } /* ** xFilter - Initialize a cursor to point at the start of its data. */ static int fts3tokFilterMethod( sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ int idxNum, /* Strategy index */ const char *idxStr, /* Unused */ int nVal, /* Number of elements in apVal */ sqlite3_value **apVal /* Arguments for the indexing scheme */ ){ int rc = SQLITE_ERROR; Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; Fts3tokTable *pTab = (Fts3tokTable *)(pCursor->pVtab); UNUSED_PARAMETER(idxStr); UNUSED_PARAMETER(nVal); fts3tokResetCursor(pCsr); if( idxNum==1 ){ const char *zByte = (const char *)sqlite3_value_text(apVal[0]); int nByte = sqlite3_value_bytes(apVal[0]); pCsr->zInput = sqlite3_malloc(nByte+1); if( pCsr->zInput==0 ){ rc = SQLITE_NOMEM; }else{ memcpy(pCsr->zInput, zByte, nByte); pCsr->zInput[nByte] = 0; rc = pTab->pMod->xOpen(pTab->pTok, pCsr->zInput, nByte, &pCsr->pCsr); if( rc==SQLITE_OK ){ pCsr->pCsr->pTokenizer = pTab->pTok; } } } if( rc!=SQLITE_OK ) return rc; return fts3tokNextMethod(pCursor); } /* ** xEof - Return true if the cursor is at EOF, or false otherwise. */ static int fts3tokEofMethod(sqlite3_vtab_cursor *pCursor){ Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; return (pCsr->zToken==0); } /* ** xColumn - Return a column value. */ static int fts3tokColumnMethod( sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ int iCol /* Index of column to read value from */ ){ Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; /* CREATE TABLE x(input, token, start, end, position) */ switch( iCol ){ case 0: sqlite3_result_text(pCtx, pCsr->zInput, -1, SQLITE_TRANSIENT); break; case 1: sqlite3_result_text(pCtx, pCsr->zToken, pCsr->nToken, SQLITE_TRANSIENT); break; case 2: sqlite3_result_int(pCtx, pCsr->iStart); break; case 3: sqlite3_result_int(pCtx, pCsr->iEnd); break; default: assert( iCol==4 ); sqlite3_result_int(pCtx, pCsr->iPos); break; } return SQLITE_OK; } /* ** xRowid - Return the current rowid for the cursor. */ static int fts3tokRowidMethod( sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ sqlite_int64 *pRowid /* OUT: Rowid value */ ){ Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; *pRowid = (sqlite3_int64)pCsr->iRowid; return SQLITE_OK; } /* ** Register the fts3tok module with database connection db. Return SQLITE_OK ** if successful or an error code if sqlite3_create_module() fails. */ int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){ static const sqlite3_module fts3tok_module = { 0, /* iVersion */ fts3tokConnectMethod, /* xCreate */ fts3tokConnectMethod, /* xConnect */ fts3tokBestIndexMethod, /* xBestIndex */ fts3tokDisconnectMethod, /* xDisconnect */ fts3tokDisconnectMethod, /* xDestroy */ fts3tokOpenMethod, /* xOpen */ fts3tokCloseMethod, /* xClose */ fts3tokFilterMethod, /* xFilter */ fts3tokNextMethod, /* xNext */ fts3tokEofMethod, /* xEof */ fts3tokColumnMethod, /* xColumn */ fts3tokRowidMethod, /* xRowid */ 0, /* xUpdate */ 0, /* xBegin */ 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ 0, /* xFindFunction */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0 /* xRollbackTo */ }; int rc; /* Return code */ rc = sqlite3_create_module(db, "fts3tokenize", &fts3tok_module, (void*)pHash); return rc; } #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_tokenizer.c ================================================ /* ** 2007 June 22 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This is part of an SQLite module implementing full-text search. ** This particular file implements the generic tokenizer interface. */ /* ** The code in this file is only compiled if: ** ** * The FTS3 module is being built as an extension ** (in which case SQLITE_CORE is not defined), or ** ** * The FTS3 module is being built into the core of ** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #include #include /* ** Return true if the two-argument version of fts3_tokenizer() ** has been activated via a prior call to sqlite3_db_config(db, ** SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0); */ static int fts3TokenizerEnabled(sqlite3_context *context){ sqlite3 *db = sqlite3_context_db_handle(context); int isEnabled = 0; sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,-1,&isEnabled); return isEnabled; } /* ** Implementation of the SQL scalar function for accessing the underlying ** hash table. This function may be called as follows: ** ** SELECT (); ** SELECT (, ); ** ** where is the name passed as the second argument ** to the sqlite3Fts3InitHashTable() function (e.g. 'fts3_tokenizer'). ** ** If the argument is specified, it must be a blob value ** containing a pointer to be stored as the hash data corresponding ** to the string . If is not specified, then ** the string must already exist in the has table. Otherwise, ** an error is returned. ** ** Whether or not the argument is specified, the value returned ** is a blob containing the pointer stored as the hash data corresponding ** to string (after the hash-table is updated, if applicable). */ static void fts3TokenizerFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ Fts3Hash *pHash; void *pPtr = 0; const unsigned char *zName; int nName; assert( argc==1 || argc==2 ); pHash = (Fts3Hash *)sqlite3_user_data(context); zName = sqlite3_value_text(argv[0]); nName = sqlite3_value_bytes(argv[0])+1; if( argc==2 ){ if( fts3TokenizerEnabled(context) ){ void *pOld; int n = sqlite3_value_bytes(argv[1]); if( zName==0 || n!=sizeof(pPtr) ){ sqlite3_result_error(context, "argument type mismatch", -1); return; } pPtr = *(void **)sqlite3_value_blob(argv[1]); pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr); if( pOld==pPtr ){ sqlite3_result_error(context, "out of memory", -1); } }else{ sqlite3_result_error(context, "fts3tokenize disabled", -1); return; } }else{ if( zName ){ pPtr = sqlite3Fts3HashFind(pHash, zName, nName); } if( !pPtr ){ char *zErr = sqlite3_mprintf("unknown tokenizer: %s", zName); sqlite3_result_error(context, zErr, -1); sqlite3_free(zErr); return; } } sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT); } int sqlite3Fts3IsIdChar(char c){ static const char isFtsIdChar[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ }; return (c&0x80 || isFtsIdChar[(int)(c)]); } const char *sqlite3Fts3NextToken(const char *zStr, int *pn){ const char *z1; const char *z2 = 0; /* Find the start of the next token. */ z1 = zStr; while( z2==0 ){ char c = *z1; switch( c ){ case '\0': return 0; /* No more tokens here */ case '\'': case '"': case '`': { z2 = z1; while( *++z2 && (*z2!=c || *++z2==c) ); break; } case '[': z2 = &z1[1]; while( *z2 && z2[0]!=']' ) z2++; if( *z2 ) z2++; break; default: if( sqlite3Fts3IsIdChar(*z1) ){ z2 = &z1[1]; while( sqlite3Fts3IsIdChar(*z2) ) z2++; }else{ z1++; } } } *pn = (int)(z2-z1); return z1; } int sqlite3Fts3InitTokenizer( Fts3Hash *pHash, /* Tokenizer hash table */ const char *zArg, /* Tokenizer name */ sqlite3_tokenizer **ppTok, /* OUT: Tokenizer (if applicable) */ char **pzErr /* OUT: Set to malloced error message */ ){ int rc; char *z = (char *)zArg; int n = 0; char *zCopy; char *zEnd; /* Pointer to nul-term of zCopy */ sqlite3_tokenizer_module *m; zCopy = sqlite3_mprintf("%s", zArg); if( !zCopy ) return SQLITE_NOMEM; zEnd = &zCopy[strlen(zCopy)]; z = (char *)sqlite3Fts3NextToken(zCopy, &n); if( z==0 ){ assert( n==0 ); z = zCopy; } z[n] = '\0'; sqlite3Fts3Dequote(z); m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash,z,(int)strlen(z)+1); if( !m ){ sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer: %s", z); rc = SQLITE_ERROR; }else{ char const **aArg = 0; int iArg = 0; z = &z[n+1]; while( zxCreate(iArg, aArg, ppTok); assert( rc!=SQLITE_OK || *ppTok ); if( rc!=SQLITE_OK ){ sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer"); }else{ (*ppTok)->pModule = m; } sqlite3_free((void *)aArg); } sqlite3_free(zCopy); return rc; } #ifdef SQLITE_TEST #if defined(INCLUDE_SQLITE_TCL_H) # include "sqlite_tcl.h" #else # include "tcl.h" #endif #include /* ** Implementation of a special SQL scalar function for testing tokenizers ** designed to be used in concert with the Tcl testing framework. This ** function must be called with two or more arguments: ** ** SELECT (, ..., ); ** ** where is the name passed as the second argument ** to the sqlite3Fts3InitHashTable() function (e.g. 'fts3_tokenizer') ** concatenated with the string '_test' (e.g. 'fts3_tokenizer_test'). ** ** The return value is a string that may be interpreted as a Tcl ** list. For each token in the , three elements are ** added to the returned list. The first is the token position, the ** second is the token text (folded, stemmed, etc.) and the third is the ** substring of associated with the token. For example, ** using the built-in "simple" tokenizer: ** ** SELECT fts_tokenizer_test('simple', 'I don't see how'); ** ** will return the string: ** ** "{0 i I 1 dont don't 2 see see 3 how how}" ** */ static void testFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ Fts3Hash *pHash; sqlite3_tokenizer_module *p; sqlite3_tokenizer *pTokenizer = 0; sqlite3_tokenizer_cursor *pCsr = 0; const char *zErr = 0; const char *zName; int nName; const char *zInput; int nInput; const char *azArg[64]; const char *zToken; int nToken = 0; int iStart = 0; int iEnd = 0; int iPos = 0; int i; Tcl_Obj *pRet; if( argc<2 ){ sqlite3_result_error(context, "insufficient arguments", -1); return; } nName = sqlite3_value_bytes(argv[0]); zName = (const char *)sqlite3_value_text(argv[0]); nInput = sqlite3_value_bytes(argv[argc-1]); zInput = (const char *)sqlite3_value_text(argv[argc-1]); pHash = (Fts3Hash *)sqlite3_user_data(context); p = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, zName, nName+1); if( !p ){ char *zErr2 = sqlite3_mprintf("unknown tokenizer: %s", zName); sqlite3_result_error(context, zErr2, -1); sqlite3_free(zErr2); return; } pRet = Tcl_NewObj(); Tcl_IncrRefCount(pRet); for(i=1; ixCreate(argc-2, azArg, &pTokenizer) ){ zErr = "error in xCreate()"; goto finish; } pTokenizer->pModule = p; if( sqlite3Fts3OpenTokenizer(pTokenizer, 0, zInput, nInput, &pCsr) ){ zErr = "error in xOpen()"; goto finish; } while( SQLITE_OK==p->xNext(pCsr, &zToken, &nToken, &iStart, &iEnd, &iPos) ){ Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(iPos)); Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zToken, nToken)); zToken = &zInput[iStart]; nToken = iEnd-iStart; Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zToken, nToken)); } if( SQLITE_OK!=p->xClose(pCsr) ){ zErr = "error in xClose()"; goto finish; } if( SQLITE_OK!=p->xDestroy(pTokenizer) ){ zErr = "error in xDestroy()"; goto finish; } finish: if( zErr ){ sqlite3_result_error(context, zErr, -1); }else{ sqlite3_result_text(context, Tcl_GetString(pRet), -1, SQLITE_TRANSIENT); } Tcl_DecrRefCount(pRet); } static int registerTokenizer( sqlite3 *db, char *zName, const sqlite3_tokenizer_module *p ){ int rc; sqlite3_stmt *pStmt; const char zSql[] = "SELECT fts3_tokenizer(?, ?)"; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); if( rc!=SQLITE_OK ){ return rc; } sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); sqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC); sqlite3_step(pStmt); return sqlite3_finalize(pStmt); } static int queryTokenizer( sqlite3 *db, char *zName, const sqlite3_tokenizer_module **pp ){ int rc; sqlite3_stmt *pStmt; const char zSql[] = "SELECT fts3_tokenizer(?)"; *pp = 0; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); if( rc!=SQLITE_OK ){ return rc; } sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); if( SQLITE_ROW==sqlite3_step(pStmt) ){ if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){ memcpy((void *)pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); } } return sqlite3_finalize(pStmt); } void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule); /* ** Implementation of the scalar function fts3_tokenizer_internal_test(). ** This function is used for testing only, it is not included in the ** build unless SQLITE_TEST is defined. ** ** The purpose of this is to test that the fts3_tokenizer() function ** can be used as designed by the C-code in the queryTokenizer and ** registerTokenizer() functions above. These two functions are repeated ** in the README.tokenizer file as an example, so it is important to ** test them. ** ** To run the tests, evaluate the fts3_tokenizer_internal_test() scalar ** function with no arguments. An assert() will fail if a problem is ** detected. i.e.: ** ** SELECT fts3_tokenizer_internal_test(); ** */ static void intTestFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ int rc; const sqlite3_tokenizer_module *p1; const sqlite3_tokenizer_module *p2; sqlite3 *db = (sqlite3 *)sqlite3_user_data(context); UNUSED_PARAMETER(argc); UNUSED_PARAMETER(argv); /* Test the query function */ sqlite3Fts3SimpleTokenizerModule(&p1); rc = queryTokenizer(db, "simple", &p2); assert( rc==SQLITE_OK ); assert( p1==p2 ); rc = queryTokenizer(db, "nosuchtokenizer", &p2); assert( rc==SQLITE_ERROR ); assert( p2==0 ); assert( 0==strcmp(sqlite3_errmsg(db), "unknown tokenizer: nosuchtokenizer") ); /* Test the storage function */ if( fts3TokenizerEnabled(context) ){ rc = registerTokenizer(db, "nosuchtokenizer", p1); assert( rc==SQLITE_OK ); rc = queryTokenizer(db, "nosuchtokenizer", &p2); assert( rc==SQLITE_OK ); assert( p2==p1 ); } sqlite3_result_text(context, "ok", -1, SQLITE_STATIC); } #endif /* ** Set up SQL objects in database db used to access the contents of ** the hash table pointed to by argument pHash. The hash table must ** been initialized to use string keys, and to take a private copy ** of the key when a value is inserted. i.e. by a call similar to: ** ** sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1); ** ** This function adds a scalar function (see header comment above ** fts3TokenizerFunc() in this file for details) and, if ENABLE_TABLE is ** defined at compilation time, a temporary virtual table (see header ** comment above struct HashTableVtab) to the database schema. Both ** provide read/write access to the contents of *pHash. ** ** The third argument to this function, zName, is used as the name ** of both the scalar and, if created, the virtual table. */ int sqlite3Fts3InitHashTable( sqlite3 *db, Fts3Hash *pHash, const char *zName ){ int rc = SQLITE_OK; void *p = (void *)pHash; const int any = SQLITE_ANY; #ifdef SQLITE_TEST char *zTest = 0; char *zTest2 = 0; void *pdb = (void *)db; zTest = sqlite3_mprintf("%s_test", zName); zTest2 = sqlite3_mprintf("%s_internal_test", zName); if( !zTest || !zTest2 ){ rc = SQLITE_NOMEM; } #endif if( SQLITE_OK==rc ){ rc = sqlite3_create_function(db, zName, 1, any, p, fts3TokenizerFunc, 0, 0); } if( SQLITE_OK==rc ){ rc = sqlite3_create_function(db, zName, 2, any, p, fts3TokenizerFunc, 0, 0); } #ifdef SQLITE_TEST if( SQLITE_OK==rc ){ rc = sqlite3_create_function(db, zTest, -1, any, p, testFunc, 0, 0); } if( SQLITE_OK==rc ){ rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0); } #endif #ifdef SQLITE_TEST sqlite3_free(zTest); sqlite3_free(zTest2); #endif return rc; } #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_tokenizer.h ================================================ /* ** 2006 July 10 ** ** The author disclaims copyright to this source code. ** ************************************************************************* ** Defines the interface to tokenizers used by fulltext-search. There ** are three basic components: ** ** sqlite3_tokenizer_module is a singleton defining the tokenizer ** interface functions. This is essentially the class structure for ** tokenizers. ** ** sqlite3_tokenizer is used to define a particular tokenizer, perhaps ** including customization information defined at creation time. ** ** sqlite3_tokenizer_cursor is generated by a tokenizer to generate ** tokens from a particular input. */ #ifndef _FTS3_TOKENIZER_H_ #define _FTS3_TOKENIZER_H_ /* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. ** If tokenizers are to be allowed to call sqlite3_*() functions, then ** we will need a way to register the API consistently. */ #include "sqlite3.h" /* ** Structures used by the tokenizer interface. When a new tokenizer ** implementation is registered, the caller provides a pointer to ** an sqlite3_tokenizer_module containing pointers to the callback ** functions that make up an implementation. ** ** When an fts3 table is created, it passes any arguments passed to ** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the ** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer ** implementation. The xCreate() function in turn returns an ** sqlite3_tokenizer structure representing the specific tokenizer to ** be used for the fts3 table (customized by the tokenizer clause arguments). ** ** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen() ** method is called. It returns an sqlite3_tokenizer_cursor object ** that may be used to tokenize a specific input buffer based on ** the tokenization rules supplied by a specific sqlite3_tokenizer ** object. */ typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; typedef struct sqlite3_tokenizer sqlite3_tokenizer; typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; struct sqlite3_tokenizer_module { /* ** Structure version. Should always be set to 0 or 1. */ int iVersion; /* ** Create a new tokenizer. The values in the argv[] array are the ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL ** TABLE statement that created the fts3 table. For example, if ** the following SQL is executed: ** ** CREATE .. USING fts3( ... , tokenizer arg1 arg2) ** ** then argc is set to 2, and the argv[] array contains pointers ** to the strings "arg1" and "arg2". ** ** This method should return either SQLITE_OK (0), or an SQLite error ** code. If SQLITE_OK is returned, then *ppTokenizer should be set ** to point at the newly created tokenizer structure. The generic ** sqlite3_tokenizer.pModule variable should not be initialized by ** this callback. The caller will do so. */ int (*xCreate)( int argc, /* Size of argv array */ const char *const*argv, /* Tokenizer argument strings */ sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ ); /* ** Destroy an existing tokenizer. The fts3 module calls this method ** exactly once for each successful call to xCreate(). */ int (*xDestroy)(sqlite3_tokenizer *pTokenizer); /* ** Create a tokenizer cursor to tokenize an input buffer. The caller ** is responsible for ensuring that the input buffer remains valid ** until the cursor is closed (using the xClose() method). */ int (*xOpen)( sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ const char *pInput, int nBytes, /* Input buffer */ sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ ); /* ** Destroy an existing tokenizer cursor. The fts3 module calls this ** method exactly once for each successful call to xOpen(). */ int (*xClose)(sqlite3_tokenizer_cursor *pCursor); /* ** Retrieve the next token from the tokenizer cursor pCursor. This ** method should either return SQLITE_OK and set the values of the ** "OUT" variables identified below, or SQLITE_DONE to indicate that ** the end of the buffer has been reached, or an SQLite error code. ** ** *ppToken should be set to point at a buffer containing the ** normalized version of the token (i.e. after any case-folding and/or ** stemming has been performed). *pnBytes should be set to the length ** of this buffer in bytes. The input text that generated the token is ** identified by the byte offsets returned in *piStartOffset and ** *piEndOffset. *piStartOffset should be set to the index of the first ** byte of the token in the input buffer. *piEndOffset should be set ** to the index of the first byte just past the end of the token in ** the input buffer. ** ** The buffer *ppToken is set to point at is managed by the tokenizer ** implementation. It is only required to be valid until the next call ** to xNext() or xClose(). */ /* TODO(shess) current implementation requires pInput to be ** nul-terminated. This should either be fixed, or pInput/nBytes ** should be converted to zInput. */ int (*xNext)( sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */ int *piStartOffset, /* OUT: Byte offset of token in input buffer */ int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ int *piPosition /* OUT: Number of tokens returned before this one */ ); /*********************************************************************** ** Methods below this point are only available if iVersion>=1. */ /* ** Configure the language id of a tokenizer cursor. */ int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid); }; struct sqlite3_tokenizer { const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ /* Tokenizer implementations will typically add additional fields */ }; struct sqlite3_tokenizer_cursor { sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ /* Tokenizer implementations will typically add additional fields */ }; int fts3_global_term_cnt(int iTerm, int iCol); int fts3_term_cnt(int iTerm, int iCol); #endif /* _FTS3_TOKENIZER_H_ */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_tokenizer1.c ================================================ /* ** 2006 Oct 10 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** Implementation of the "simple" full-text-search tokenizer. */ /* ** The code in this file is only compiled if: ** ** * The FTS3 module is being built as an extension ** (in which case SQLITE_CORE is not defined), or ** ** * The FTS3 module is being built into the core of ** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #include #include #include #include #include "fts3_tokenizer.h" typedef struct simple_tokenizer { sqlite3_tokenizer base; char delim[128]; /* flag ASCII delimiters */ } simple_tokenizer; typedef struct simple_tokenizer_cursor { sqlite3_tokenizer_cursor base; const char *pInput; /* input we are tokenizing */ int nBytes; /* size of the input */ int iOffset; /* current position in pInput */ int iToken; /* index of next token to be returned */ char *pToken; /* storage for current token */ int nTokenAllocated; /* space allocated to zToken buffer */ } simple_tokenizer_cursor; static int simpleDelim(simple_tokenizer *t, unsigned char c){ return c<0x80 && t->delim[c]; } static int fts3_isalnum(int x){ return (x>='0' && x<='9') || (x>='A' && x<='Z') || (x>='a' && x<='z'); } /* ** Create a new tokenizer instance. */ static int simpleCreate( int argc, const char * const *argv, sqlite3_tokenizer **ppTokenizer ){ simple_tokenizer *t; t = (simple_tokenizer *) sqlite3_malloc(sizeof(*t)); if( t==NULL ) return SQLITE_NOMEM; memset(t, 0, sizeof(*t)); /* TODO(shess) Delimiters need to remain the same from run to run, ** else we need to reindex. One solution would be a meta-table to ** track such information in the database, then we'd only want this ** information on the initial create. */ if( argc>1 ){ int i, n = (int)strlen(argv[1]); for(i=0; i=0x80 ){ sqlite3_free(t); return SQLITE_ERROR; } t->delim[ch] = 1; } } else { /* Mark non-alphanumeric ASCII characters as delimiters */ int i; for(i=1; i<0x80; i++){ t->delim[i] = !fts3_isalnum(i) ? -1 : 0; } } *ppTokenizer = &t->base; return SQLITE_OK; } /* ** Destroy a tokenizer */ static int simpleDestroy(sqlite3_tokenizer *pTokenizer){ sqlite3_free(pTokenizer); return SQLITE_OK; } /* ** Prepare to begin tokenizing a particular string. The input ** string to be tokenized is pInput[0..nBytes-1]. A cursor ** used to incrementally tokenize this string is returned in ** *ppCursor. */ static int simpleOpen( sqlite3_tokenizer *pTokenizer, /* The tokenizer */ const char *pInput, int nBytes, /* String to be tokenized */ sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ ){ simple_tokenizer_cursor *c; UNUSED_PARAMETER(pTokenizer); c = (simple_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); if( c==NULL ) return SQLITE_NOMEM; c->pInput = pInput; if( pInput==0 ){ c->nBytes = 0; }else if( nBytes<0 ){ c->nBytes = (int)strlen(pInput); }else{ c->nBytes = nBytes; } c->iOffset = 0; /* start tokenizing at the beginning */ c->iToken = 0; c->pToken = NULL; /* no space allocated, yet. */ c->nTokenAllocated = 0; *ppCursor = &c->base; return SQLITE_OK; } /* ** Close a tokenization cursor previously opened by a call to ** simpleOpen() above. */ static int simpleClose(sqlite3_tokenizer_cursor *pCursor){ simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor; sqlite3_free(c->pToken); sqlite3_free(c); return SQLITE_OK; } /* ** Extract the next token from a tokenization cursor. The cursor must ** have been opened by a prior call to simpleOpen(). */ static int simpleNext( sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */ const char **ppToken, /* OUT: *ppToken is the token text */ int *pnBytes, /* OUT: Number of bytes in token */ int *piStartOffset, /* OUT: Starting offset of token */ int *piEndOffset, /* OUT: Ending offset of token */ int *piPosition /* OUT: Position integer of token */ ){ simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor; simple_tokenizer *t = (simple_tokenizer *) pCursor->pTokenizer; unsigned char *p = (unsigned char *)c->pInput; while( c->iOffsetnBytes ){ int iStartOffset; /* Scan past delimiter characters */ while( c->iOffsetnBytes && simpleDelim(t, p[c->iOffset]) ){ c->iOffset++; } /* Count non-delimiter characters. */ iStartOffset = c->iOffset; while( c->iOffsetnBytes && !simpleDelim(t, p[c->iOffset]) ){ c->iOffset++; } if( c->iOffset>iStartOffset ){ int i, n = c->iOffset-iStartOffset; if( n>c->nTokenAllocated ){ char *pNew; c->nTokenAllocated = n+20; pNew = sqlite3_realloc(c->pToken, c->nTokenAllocated); if( !pNew ) return SQLITE_NOMEM; c->pToken = pNew; } for(i=0; ipToken[i] = (char)((ch>='A' && ch<='Z') ? ch-'A'+'a' : ch); } *ppToken = c->pToken; *pnBytes = n; *piStartOffset = iStartOffset; *piEndOffset = c->iOffset; *piPosition = c->iToken++; return SQLITE_OK; } } return SQLITE_DONE; } /* ** The set of routines that implement the simple tokenizer */ static const sqlite3_tokenizer_module simpleTokenizerModule = { 0, simpleCreate, simpleDestroy, simpleOpen, simpleClose, simpleNext, 0, }; /* ** Allocate a new simple tokenizer. Return a pointer to the new ** tokenizer in *ppModule */ void sqlite3Fts3SimpleTokenizerModule( sqlite3_tokenizer_module const**ppModule ){ *ppModule = &simpleTokenizerModule; } #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_unicode.c ================================================ /* ** 2012 May 24 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** Implementation of the "unicode" full-text-search tokenizer. */ #ifndef SQLITE_DISABLE_FTS3_UNICODE #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #include #include #include #include #include "fts3_tokenizer.h" /* ** The following two macros - READ_UTF8 and WRITE_UTF8 - have been copied ** from the sqlite3 source file utf.c. If this file is compiled as part ** of the amalgamation, they are not required. */ #ifndef SQLITE_AMALGAMATION static const unsigned char sqlite3Utf8Trans1[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, }; #define READ_UTF8(zIn, zTerm, c) \ c = *(zIn++); \ if( c>=0xc0 ){ \ c = sqlite3Utf8Trans1[c-0xc0]; \ while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ c = (c<<6) + (0x3f & *(zIn++)); \ } \ if( c<0x80 \ || (c&0xFFFFF800)==0xD800 \ || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ } #define WRITE_UTF8(zOut, c) { \ if( c<0x00080 ){ \ *zOut++ = (u8)(c&0xFF); \ } \ else if( c<0x00800 ){ \ *zOut++ = 0xC0 + (u8)((c>>6)&0x1F); \ *zOut++ = 0x80 + (u8)(c & 0x3F); \ } \ else if( c<0x10000 ){ \ *zOut++ = 0xE0 + (u8)((c>>12)&0x0F); \ *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); \ *zOut++ = 0x80 + (u8)(c & 0x3F); \ }else{ \ *zOut++ = 0xF0 + (u8)((c>>18) & 0x07); \ *zOut++ = 0x80 + (u8)((c>>12) & 0x3F); \ *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); \ *zOut++ = 0x80 + (u8)(c & 0x3F); \ } \ } #endif /* ifndef SQLITE_AMALGAMATION */ typedef struct unicode_tokenizer unicode_tokenizer; typedef struct unicode_cursor unicode_cursor; struct unicode_tokenizer { sqlite3_tokenizer base; int bRemoveDiacritic; int nException; int *aiException; }; struct unicode_cursor { sqlite3_tokenizer_cursor base; const unsigned char *aInput; /* Input text being tokenized */ int nInput; /* Size of aInput[] in bytes */ int iOff; /* Current offset within aInput[] */ int iToken; /* Index of next token to be returned */ char *zToken; /* storage for current token */ int nAlloc; /* space allocated at zToken */ }; /* ** Destroy a tokenizer allocated by unicodeCreate(). */ static int unicodeDestroy(sqlite3_tokenizer *pTokenizer){ if( pTokenizer ){ unicode_tokenizer *p = (unicode_tokenizer *)pTokenizer; sqlite3_free(p->aiException); sqlite3_free(p); } return SQLITE_OK; } /* ** As part of a tokenchars= or separators= option, the CREATE VIRTUAL TABLE ** statement has specified that the tokenizer for this table shall consider ** all characters in string zIn/nIn to be separators (if bAlnum==0) or ** token characters (if bAlnum==1). ** ** For each codepoint in the zIn/nIn string, this function checks if the ** sqlite3FtsUnicodeIsalnum() function already returns the desired result. ** If so, no action is taken. Otherwise, the codepoint is added to the ** unicode_tokenizer.aiException[] array. For the purposes of tokenization, ** the return value of sqlite3FtsUnicodeIsalnum() is inverted for all ** codepoints in the aiException[] array. ** ** If a standalone diacritic mark (one that sqlite3FtsUnicodeIsdiacritic() ** identifies as a diacritic) occurs in the zIn/nIn string it is ignored. ** It is not possible to change the behavior of the tokenizer with respect ** to these codepoints. */ static int unicodeAddExceptions( unicode_tokenizer *p, /* Tokenizer to add exceptions to */ int bAlnum, /* Replace Isalnum() return value with this */ const char *zIn, /* Array of characters to make exceptions */ int nIn /* Length of z in bytes */ ){ const unsigned char *z = (const unsigned char *)zIn; const unsigned char *zTerm = &z[nIn]; int iCode; int nEntry = 0; assert( bAlnum==0 || bAlnum==1 ); while( zaiException, (p->nException+nEntry)*sizeof(int)); if( aNew==0 ) return SQLITE_NOMEM; nNew = p->nException; z = (const unsigned char *)zIn; while( zi; j--) aNew[j] = aNew[j-1]; aNew[i] = iCode; nNew++; } } p->aiException = aNew; p->nException = nNew; } return SQLITE_OK; } /* ** Return true if the p->aiException[] array contains the value iCode. */ static int unicodeIsException(unicode_tokenizer *p, int iCode){ if( p->nException>0 ){ int *a = p->aiException; int iLo = 0; int iHi = p->nException-1; while( iHi>=iLo ){ int iTest = (iHi + iLo) / 2; if( iCode==a[iTest] ){ return 1; }else if( iCode>a[iTest] ){ iLo = iTest+1; }else{ iHi = iTest-1; } } } return 0; } /* ** Return true if, for the purposes of tokenization, codepoint iCode is ** considered a token character (not a separator). */ static int unicodeIsAlnum(unicode_tokenizer *p, int iCode){ assert( (sqlite3FtsUnicodeIsalnum(iCode) & 0xFFFFFFFE)==0 ); return sqlite3FtsUnicodeIsalnum(iCode) ^ unicodeIsException(p, iCode); } /* ** Create a new tokenizer instance. */ static int unicodeCreate( int nArg, /* Size of array argv[] */ const char * const *azArg, /* Tokenizer creation arguments */ sqlite3_tokenizer **pp /* OUT: New tokenizer handle */ ){ unicode_tokenizer *pNew; /* New tokenizer object */ int i; int rc = SQLITE_OK; pNew = (unicode_tokenizer *) sqlite3_malloc(sizeof(unicode_tokenizer)); if( pNew==NULL ) return SQLITE_NOMEM; memset(pNew, 0, sizeof(unicode_tokenizer)); pNew->bRemoveDiacritic = 1; for(i=0; rc==SQLITE_OK && ibRemoveDiacritic = 1; } else if( n==19 && memcmp("remove_diacritics=0", z, 19)==0 ){ pNew->bRemoveDiacritic = 0; } else if( n>=11 && memcmp("tokenchars=", z, 11)==0 ){ rc = unicodeAddExceptions(pNew, 1, &z[11], n-11); } else if( n>=11 && memcmp("separators=", z, 11)==0 ){ rc = unicodeAddExceptions(pNew, 0, &z[11], n-11); } else{ /* Unrecognized argument */ rc = SQLITE_ERROR; } } if( rc!=SQLITE_OK ){ unicodeDestroy((sqlite3_tokenizer *)pNew); pNew = 0; } *pp = (sqlite3_tokenizer *)pNew; return rc; } /* ** Prepare to begin tokenizing a particular string. The input ** string to be tokenized is pInput[0..nBytes-1]. A cursor ** used to incrementally tokenize this string is returned in ** *ppCursor. */ static int unicodeOpen( sqlite3_tokenizer *p, /* The tokenizer */ const char *aInput, /* Input string */ int nInput, /* Size of string aInput in bytes */ sqlite3_tokenizer_cursor **pp /* OUT: New cursor object */ ){ unicode_cursor *pCsr; pCsr = (unicode_cursor *)sqlite3_malloc(sizeof(unicode_cursor)); if( pCsr==0 ){ return SQLITE_NOMEM; } memset(pCsr, 0, sizeof(unicode_cursor)); pCsr->aInput = (const unsigned char *)aInput; if( aInput==0 ){ pCsr->nInput = 0; }else if( nInput<0 ){ pCsr->nInput = (int)strlen(aInput); }else{ pCsr->nInput = nInput; } *pp = &pCsr->base; UNUSED_PARAMETER(p); return SQLITE_OK; } /* ** Close a tokenization cursor previously opened by a call to ** simpleOpen() above. */ static int unicodeClose(sqlite3_tokenizer_cursor *pCursor){ unicode_cursor *pCsr = (unicode_cursor *) pCursor; sqlite3_free(pCsr->zToken); sqlite3_free(pCsr); return SQLITE_OK; } /* ** Extract the next token from a tokenization cursor. The cursor must ** have been opened by a prior call to simpleOpen(). */ static int unicodeNext( sqlite3_tokenizer_cursor *pC, /* Cursor returned by simpleOpen */ const char **paToken, /* OUT: Token text */ int *pnToken, /* OUT: Number of bytes at *paToken */ int *piStart, /* OUT: Starting offset of token */ int *piEnd, /* OUT: Ending offset of token */ int *piPos /* OUT: Position integer of token */ ){ unicode_cursor *pCsr = (unicode_cursor *)pC; unicode_tokenizer *p = ((unicode_tokenizer *)pCsr->base.pTokenizer); int iCode = 0; char *zOut; const unsigned char *z = &pCsr->aInput[pCsr->iOff]; const unsigned char *zStart = z; const unsigned char *zEnd; const unsigned char *zTerm = &pCsr->aInput[pCsr->nInput]; /* Scan past any delimiter characters before the start of the next token. ** Return SQLITE_DONE early if this takes us all the way to the end of ** the input. */ while( z=zTerm ) return SQLITE_DONE; zOut = pCsr->zToken; do { int iOut; /* Grow the output buffer if required. */ if( (zOut-pCsr->zToken)>=(pCsr->nAlloc-4) ){ char *zNew = sqlite3_realloc(pCsr->zToken, pCsr->nAlloc+64); if( !zNew ) return SQLITE_NOMEM; zOut = &zNew[zOut - pCsr->zToken]; pCsr->zToken = zNew; pCsr->nAlloc += 64; } /* Write the folded case of the last character read to the output */ zEnd = z; iOut = sqlite3FtsUnicodeFold(iCode, p->bRemoveDiacritic); if( iOut ){ WRITE_UTF8(zOut, iOut); } /* If the cursor is not at EOF, read the next character */ if( z>=zTerm ) break; READ_UTF8(z, zTerm, iCode); }while( unicodeIsAlnum(p, iCode) || sqlite3FtsUnicodeIsdiacritic(iCode) ); /* Set the output variables and return. */ pCsr->iOff = (int)(z - pCsr->aInput); *paToken = pCsr->zToken; *pnToken = (int)(zOut - pCsr->zToken); *piStart = (int)(zStart - pCsr->aInput); *piEnd = (int)(zEnd - pCsr->aInput); *piPos = pCsr->iToken++; return SQLITE_OK; } /* ** Set *ppModule to a pointer to the sqlite3_tokenizer_module ** structure for the unicode tokenizer. */ void sqlite3Fts3UnicodeTokenizer(sqlite3_tokenizer_module const **ppModule){ static const sqlite3_tokenizer_module module = { 0, unicodeCreate, unicodeDestroy, unicodeOpen, unicodeClose, unicodeNext, 0, }; *ppModule = &module; } #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ #endif /* ifndef SQLITE_DISABLE_FTS3_UNICODE */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_unicode2.c ================================================ /* ** 2012 May 25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** */ /* ** DO NOT EDIT THIS MACHINE GENERATED FILE. */ #ifndef SQLITE_DISABLE_FTS3_UNICODE #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) #include /* ** Return true if the argument corresponds to a unicode codepoint ** classified as either a letter or a number. Otherwise false. ** ** The results are undefined if the value passed to this function ** is less than zero. */ int sqlite3FtsUnicodeIsalnum(int c){ /* Each unsigned integer in the following array corresponds to a contiguous ** range of unicode codepoints that are not either letters or numbers (i.e. ** codepoints for which this function should return 0). ** ** The most significant 22 bits in each 32-bit value contain the first ** codepoint in the range. The least significant 10 bits are used to store ** the size of the range (always at least 1). In other words, the value ** ((C<<22) + N) represents a range of N codepoints starting with codepoint ** C. It is not possible to represent a range larger than 1023 codepoints ** using this format. */ static const unsigned int aEntry[] = { 0x00000030, 0x0000E807, 0x00016C06, 0x0001EC2F, 0x0002AC07, 0x0002D001, 0x0002D803, 0x0002EC01, 0x0002FC01, 0x00035C01, 0x0003DC01, 0x000B0804, 0x000B480E, 0x000B9407, 0x000BB401, 0x000BBC81, 0x000DD401, 0x000DF801, 0x000E1002, 0x000E1C01, 0x000FD801, 0x00120808, 0x00156806, 0x00162402, 0x00163C01, 0x00164437, 0x0017CC02, 0x00180005, 0x00181816, 0x00187802, 0x00192C15, 0x0019A804, 0x0019C001, 0x001B5001, 0x001B580F, 0x001B9C07, 0x001BF402, 0x001C000E, 0x001C3C01, 0x001C4401, 0x001CC01B, 0x001E980B, 0x001FAC09, 0x001FD804, 0x00205804, 0x00206C09, 0x00209403, 0x0020A405, 0x0020C00F, 0x00216403, 0x00217801, 0x0023901B, 0x00240004, 0x0024E803, 0x0024F812, 0x00254407, 0x00258804, 0x0025C001, 0x00260403, 0x0026F001, 0x0026F807, 0x00271C02, 0x00272C03, 0x00275C01, 0x00278802, 0x0027C802, 0x0027E802, 0x00280403, 0x0028F001, 0x0028F805, 0x00291C02, 0x00292C03, 0x00294401, 0x0029C002, 0x0029D401, 0x002A0403, 0x002AF001, 0x002AF808, 0x002B1C03, 0x002B2C03, 0x002B8802, 0x002BC002, 0x002C0403, 0x002CF001, 0x002CF807, 0x002D1C02, 0x002D2C03, 0x002D5802, 0x002D8802, 0x002DC001, 0x002E0801, 0x002EF805, 0x002F1803, 0x002F2804, 0x002F5C01, 0x002FCC08, 0x00300403, 0x0030F807, 0x00311803, 0x00312804, 0x00315402, 0x00318802, 0x0031FC01, 0x00320802, 0x0032F001, 0x0032F807, 0x00331803, 0x00332804, 0x00335402, 0x00338802, 0x00340802, 0x0034F807, 0x00351803, 0x00352804, 0x00355C01, 0x00358802, 0x0035E401, 0x00360802, 0x00372801, 0x00373C06, 0x00375801, 0x00376008, 0x0037C803, 0x0038C401, 0x0038D007, 0x0038FC01, 0x00391C09, 0x00396802, 0x003AC401, 0x003AD006, 0x003AEC02, 0x003B2006, 0x003C041F, 0x003CD00C, 0x003DC417, 0x003E340B, 0x003E6424, 0x003EF80F, 0x003F380D, 0x0040AC14, 0x00412806, 0x00415804, 0x00417803, 0x00418803, 0x00419C07, 0x0041C404, 0x0042080C, 0x00423C01, 0x00426806, 0x0043EC01, 0x004D740C, 0x004E400A, 0x00500001, 0x0059B402, 0x005A0001, 0x005A6C02, 0x005BAC03, 0x005C4803, 0x005CC805, 0x005D4802, 0x005DC802, 0x005ED023, 0x005F6004, 0x005F7401, 0x0060000F, 0x0062A401, 0x0064800C, 0x0064C00C, 0x00650001, 0x00651002, 0x0066C011, 0x00672002, 0x00677822, 0x00685C05, 0x00687802, 0x0069540A, 0x0069801D, 0x0069FC01, 0x006A8007, 0x006AA006, 0x006C0005, 0x006CD011, 0x006D6823, 0x006E0003, 0x006E840D, 0x006F980E, 0x006FF004, 0x00709014, 0x0070EC05, 0x0071F802, 0x00730008, 0x00734019, 0x0073B401, 0x0073C803, 0x00770027, 0x0077F004, 0x007EF401, 0x007EFC03, 0x007F3403, 0x007F7403, 0x007FB403, 0x007FF402, 0x00800065, 0x0081A806, 0x0081E805, 0x00822805, 0x0082801A, 0x00834021, 0x00840002, 0x00840C04, 0x00842002, 0x00845001, 0x00845803, 0x00847806, 0x00849401, 0x00849C01, 0x0084A401, 0x0084B801, 0x0084E802, 0x00850005, 0x00852804, 0x00853C01, 0x00864264, 0x00900027, 0x0091000B, 0x0092704E, 0x00940200, 0x009C0475, 0x009E53B9, 0x00AD400A, 0x00B39406, 0x00B3BC03, 0x00B3E404, 0x00B3F802, 0x00B5C001, 0x00B5FC01, 0x00B7804F, 0x00B8C00C, 0x00BA001A, 0x00BA6C59, 0x00BC00D6, 0x00BFC00C, 0x00C00005, 0x00C02019, 0x00C0A807, 0x00C0D802, 0x00C0F403, 0x00C26404, 0x00C28001, 0x00C3EC01, 0x00C64002, 0x00C6580A, 0x00C70024, 0x00C8001F, 0x00C8A81E, 0x00C94001, 0x00C98020, 0x00CA2827, 0x00CB003F, 0x00CC0100, 0x01370040, 0x02924037, 0x0293F802, 0x02983403, 0x0299BC10, 0x029A7C01, 0x029BC008, 0x029C0017, 0x029C8002, 0x029E2402, 0x02A00801, 0x02A01801, 0x02A02C01, 0x02A08C09, 0x02A0D804, 0x02A1D004, 0x02A20002, 0x02A2D011, 0x02A33802, 0x02A38012, 0x02A3E003, 0x02A4980A, 0x02A51C0D, 0x02A57C01, 0x02A60004, 0x02A6CC1B, 0x02A77802, 0x02A8A40E, 0x02A90C01, 0x02A93002, 0x02A97004, 0x02A9DC03, 0x02A9EC01, 0x02AAC001, 0x02AAC803, 0x02AADC02, 0x02AAF802, 0x02AB0401, 0x02AB7802, 0x02ABAC07, 0x02ABD402, 0x02AF8C0B, 0x03600001, 0x036DFC02, 0x036FFC02, 0x037FFC01, 0x03EC7801, 0x03ECA401, 0x03EEC810, 0x03F4F802, 0x03F7F002, 0x03F8001A, 0x03F88007, 0x03F8C023, 0x03F95013, 0x03F9A004, 0x03FBFC01, 0x03FC040F, 0x03FC6807, 0x03FCEC06, 0x03FD6C0B, 0x03FF8007, 0x03FFA007, 0x03FFE405, 0x04040003, 0x0404DC09, 0x0405E411, 0x0406400C, 0x0407402E, 0x040E7C01, 0x040F4001, 0x04215C01, 0x04247C01, 0x0424FC01, 0x04280403, 0x04281402, 0x04283004, 0x0428E003, 0x0428FC01, 0x04294009, 0x0429FC01, 0x042CE407, 0x04400003, 0x0440E016, 0x04420003, 0x0442C012, 0x04440003, 0x04449C0E, 0x04450004, 0x04460003, 0x0446CC0E, 0x04471404, 0x045AAC0D, 0x0491C004, 0x05BD442E, 0x05BE3C04, 0x074000F6, 0x07440027, 0x0744A4B5, 0x07480046, 0x074C0057, 0x075B0401, 0x075B6C01, 0x075BEC01, 0x075C5401, 0x075CD401, 0x075D3C01, 0x075DBC01, 0x075E2401, 0x075EA401, 0x075F0C01, 0x07BBC002, 0x07C0002C, 0x07C0C064, 0x07C2800F, 0x07C2C40E, 0x07C3040F, 0x07C3440F, 0x07C4401F, 0x07C4C03C, 0x07C5C02B, 0x07C7981D, 0x07C8402B, 0x07C90009, 0x07C94002, 0x07CC0021, 0x07CCC006, 0x07CCDC46, 0x07CE0014, 0x07CE8025, 0x07CF1805, 0x07CF8011, 0x07D0003F, 0x07D10001, 0x07D108B6, 0x07D3E404, 0x07D4003E, 0x07D50004, 0x07D54018, 0x07D7EC46, 0x07D9140B, 0x07DA0046, 0x07DC0074, 0x38000401, 0x38008060, 0x380400F0, }; static const unsigned int aAscii[4] = { 0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001, }; if( c<128 ){ return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 ); }else if( c<(1<<22) ){ unsigned int key = (((unsigned int)c)<<10) | 0x000003FF; int iRes = 0; int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1; int iLo = 0; while( iHi>=iLo ){ int iTest = (iHi + iLo) / 2; if( key >= aEntry[iTest] ){ iRes = iTest; iLo = iTest+1; }else{ iHi = iTest-1; } } assert( aEntry[0]=aEntry[iRes] ); return (((unsigned int)c) >= ((aEntry[iRes]>>10) + (aEntry[iRes]&0x3FF))); } return 1; } /* ** If the argument is a codepoint corresponding to a lowercase letter ** in the ASCII range with a diacritic added, return the codepoint ** of the ASCII letter only. For example, if passed 235 - "LATIN ** SMALL LETTER E WITH DIAERESIS" - return 65 ("LATIN SMALL LETTER ** E"). The resuls of passing a codepoint that corresponds to an ** uppercase letter are undefined. */ static int remove_diacritic(int c){ unsigned short aDia[] = { 0, 1797, 1848, 1859, 1891, 1928, 1940, 1995, 2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286, 2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732, 2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336, 3456, 3696, 3712, 3728, 3744, 3896, 3912, 3928, 3968, 4008, 4040, 4106, 4138, 4170, 4202, 4234, 4266, 4296, 4312, 4344, 4408, 4424, 4472, 4504, 6148, 6198, 6264, 6280, 6360, 6429, 6505, 6529, 61448, 61468, 61534, 61592, 61642, 61688, 61704, 61726, 61784, 61800, 61836, 61880, 61914, 61948, 61998, 62122, 62154, 62200, 62218, 62302, 62364, 62442, 62478, 62536, 62554, 62584, 62604, 62640, 62648, 62656, 62664, 62730, 62924, 63050, 63082, 63274, 63390, }; char aChar[] = { '\0', 'a', 'c', 'e', 'i', 'n', 'o', 'u', 'y', 'y', 'a', 'c', 'd', 'e', 'e', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'o', 'r', 's', 't', 'u', 'u', 'w', 'y', 'z', 'o', 'u', 'a', 'i', 'o', 'u', 'g', 'k', 'o', 'j', 'g', 'n', 'a', 'e', 'i', 'o', 'r', 'u', 's', 't', 'h', 'a', 'e', 'o', 'y', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 'a', 'b', 'd', 'd', 'e', 'f', 'g', 'h', 'h', 'i', 'k', 'l', 'l', 'm', 'n', 'p', 'r', 'r', 's', 't', 'u', 'v', 'w', 'w', 'x', 'y', 'z', 'h', 't', 'w', 'y', 'a', 'e', 'i', 'o', 'u', 'y', }; unsigned int key = (((unsigned int)c)<<3) | 0x00000007; int iRes = 0; int iHi = sizeof(aDia)/sizeof(aDia[0]) - 1; int iLo = 0; while( iHi>=iLo ){ int iTest = (iHi + iLo) / 2; if( key >= aDia[iTest] ){ iRes = iTest; iLo = iTest+1; }else{ iHi = iTest-1; } } assert( key>=aDia[iRes] ); return ((c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : (int)aChar[iRes]); } /* ** Return true if the argument interpreted as a unicode codepoint ** is a diacritical modifier character. */ int sqlite3FtsUnicodeIsdiacritic(int c){ unsigned int mask0 = 0x08029FDF; unsigned int mask1 = 0x000361F8; if( c<768 || c>817 ) return 0; return (c < 768+32) ? (mask0 & (1 << (c-768))) : (mask1 & (1 << (c-768-32))); } /* ** Interpret the argument as a unicode codepoint. If the codepoint ** is an upper case character that has a lower case equivalent, ** return the codepoint corresponding to the lower case version. ** Otherwise, return a copy of the argument. ** ** The results are undefined if the value passed to this function ** is less than zero. */ int sqlite3FtsUnicodeFold(int c, int bRemoveDiacritic){ /* Each entry in the following array defines a rule for folding a range ** of codepoints to lower case. The rule applies to a range of nRange ** codepoints starting at codepoint iCode. ** ** If the least significant bit in flags is clear, then the rule applies ** to all nRange codepoints (i.e. all nRange codepoints are upper case and ** need to be folded). Or, if it is set, then the rule only applies to ** every second codepoint in the range, starting with codepoint C. ** ** The 7 most significant bits in flags are an index into the aiOff[] ** array. If a specific codepoint C does require folding, then its lower ** case equivalent is ((C + aiOff[flags>>1]) & 0xFFFF). ** ** The contents of this array are generated by parsing the CaseFolding.txt ** file distributed as part of the "Unicode Character Database". See ** http://www.unicode.org for details. */ static const struct TableEntry { unsigned short iCode; unsigned char flags; unsigned char nRange; } aEntry[] = { {65, 14, 26}, {181, 64, 1}, {192, 14, 23}, {216, 14, 7}, {256, 1, 48}, {306, 1, 6}, {313, 1, 16}, {330, 1, 46}, {376, 116, 1}, {377, 1, 6}, {383, 104, 1}, {385, 50, 1}, {386, 1, 4}, {390, 44, 1}, {391, 0, 1}, {393, 42, 2}, {395, 0, 1}, {398, 32, 1}, {399, 38, 1}, {400, 40, 1}, {401, 0, 1}, {403, 42, 1}, {404, 46, 1}, {406, 52, 1}, {407, 48, 1}, {408, 0, 1}, {412, 52, 1}, {413, 54, 1}, {415, 56, 1}, {416, 1, 6}, {422, 60, 1}, {423, 0, 1}, {425, 60, 1}, {428, 0, 1}, {430, 60, 1}, {431, 0, 1}, {433, 58, 2}, {435, 1, 4}, {439, 62, 1}, {440, 0, 1}, {444, 0, 1}, {452, 2, 1}, {453, 0, 1}, {455, 2, 1}, {456, 0, 1}, {458, 2, 1}, {459, 1, 18}, {478, 1, 18}, {497, 2, 1}, {498, 1, 4}, {502, 122, 1}, {503, 134, 1}, {504, 1, 40}, {544, 110, 1}, {546, 1, 18}, {570, 70, 1}, {571, 0, 1}, {573, 108, 1}, {574, 68, 1}, {577, 0, 1}, {579, 106, 1}, {580, 28, 1}, {581, 30, 1}, {582, 1, 10}, {837, 36, 1}, {880, 1, 4}, {886, 0, 1}, {902, 18, 1}, {904, 16, 3}, {908, 26, 1}, {910, 24, 2}, {913, 14, 17}, {931, 14, 9}, {962, 0, 1}, {975, 4, 1}, {976, 140, 1}, {977, 142, 1}, {981, 146, 1}, {982, 144, 1}, {984, 1, 24}, {1008, 136, 1}, {1009, 138, 1}, {1012, 130, 1}, {1013, 128, 1}, {1015, 0, 1}, {1017, 152, 1}, {1018, 0, 1}, {1021, 110, 3}, {1024, 34, 16}, {1040, 14, 32}, {1120, 1, 34}, {1162, 1, 54}, {1216, 6, 1}, {1217, 1, 14}, {1232, 1, 88}, {1329, 22, 38}, {4256, 66, 38}, {4295, 66, 1}, {4301, 66, 1}, {7680, 1, 150}, {7835, 132, 1}, {7838, 96, 1}, {7840, 1, 96}, {7944, 150, 8}, {7960, 150, 6}, {7976, 150, 8}, {7992, 150, 8}, {8008, 150, 6}, {8025, 151, 8}, {8040, 150, 8}, {8072, 150, 8}, {8088, 150, 8}, {8104, 150, 8}, {8120, 150, 2}, {8122, 126, 2}, {8124, 148, 1}, {8126, 100, 1}, {8136, 124, 4}, {8140, 148, 1}, {8152, 150, 2}, {8154, 120, 2}, {8168, 150, 2}, {8170, 118, 2}, {8172, 152, 1}, {8184, 112, 2}, {8186, 114, 2}, {8188, 148, 1}, {8486, 98, 1}, {8490, 92, 1}, {8491, 94, 1}, {8498, 12, 1}, {8544, 8, 16}, {8579, 0, 1}, {9398, 10, 26}, {11264, 22, 47}, {11360, 0, 1}, {11362, 88, 1}, {11363, 102, 1}, {11364, 90, 1}, {11367, 1, 6}, {11373, 84, 1}, {11374, 86, 1}, {11375, 80, 1}, {11376, 82, 1}, {11378, 0, 1}, {11381, 0, 1}, {11390, 78, 2}, {11392, 1, 100}, {11499, 1, 4}, {11506, 0, 1}, {42560, 1, 46}, {42624, 1, 24}, {42786, 1, 14}, {42802, 1, 62}, {42873, 1, 4}, {42877, 76, 1}, {42878, 1, 10}, {42891, 0, 1}, {42893, 74, 1}, {42896, 1, 4}, {42912, 1, 10}, {42922, 72, 1}, {65313, 14, 26}, }; static const unsigned short aiOff[] = { 1, 2, 8, 15, 16, 26, 28, 32, 37, 38, 40, 48, 63, 64, 69, 71, 79, 80, 116, 202, 203, 205, 206, 207, 209, 210, 211, 213, 214, 217, 218, 219, 775, 7264, 10792, 10795, 23228, 23256, 30204, 54721, 54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274, 57921, 58019, 58363, 61722, 65268, 65341, 65373, 65406, 65408, 65410, 65415, 65424, 65436, 65439, 65450, 65462, 65472, 65476, 65478, 65480, 65482, 65488, 65506, 65511, 65514, 65521, 65527, 65528, 65529, }; int ret = c; assert( c>=0 ); assert( sizeof(unsigned short)==2 && sizeof(unsigned char)==1 ); if( c<128 ){ if( c>='A' && c<='Z' ) ret = c + ('a' - 'A'); }else if( c<65536 ){ int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1; int iLo = 0; int iRes = -1; while( iHi>=iLo ){ int iTest = (iHi + iLo) / 2; int cmp = (c - aEntry[iTest].iCode); if( cmp>=0 ){ iRes = iTest; iLo = iTest+1; }else{ iHi = iTest-1; } } assert( iRes<0 || c>=aEntry[iRes].iCode ); if( iRes>=0 ){ const struct TableEntry *p = &aEntry[iRes]; if( c<(p->iCode + p->nRange) && 0==(0x01 & p->flags & (p->iCode ^ c)) ){ ret = (c + (aiOff[p->flags>>1])) & 0x0000FFFF; assert( ret>0 ); } } if( bRemoveDiacritic ) ret = remove_diacritic(ret); } else if( c>=66560 && c<66600 ){ ret = c + 40; } return ret; } #endif /* defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) */ #endif /* !defined(SQLITE_DISABLE_FTS3_UNICODE) */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/fts3/fts3_write.c ================================================ /* ** 2009 Oct 23 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This file is part of the SQLite FTS3 extension module. Specifically, ** this file contains code to insert, update and delete rows from FTS3 ** tables. It also contains code to merge FTS3 b-tree segments. Some ** of the sub-routines used to merge segments are also used by the query ** code in fts3.c. */ #include "fts3Int.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #include #include #include #define FTS_MAX_APPENDABLE_HEIGHT 16 /* ** When full-text index nodes are loaded from disk, the buffer that they ** are loaded into has the following number of bytes of padding at the end ** of it. i.e. if a full-text index node is 900 bytes in size, then a buffer ** of 920 bytes is allocated for it. ** ** This means that if we have a pointer into a buffer containing node data, ** it is always safe to read up to two varints from it without risking an ** overread, even if the node data is corrupted. */ #define FTS3_NODE_PADDING (FTS3_VARINT_MAX*2) /* ** Under certain circumstances, b-tree nodes (doclists) can be loaded into ** memory incrementally instead of all at once. This can be a big performance ** win (reduced IO and CPU) if SQLite stops calling the virtual table xNext() ** method before retrieving all query results (as may happen, for example, ** if a query has a LIMIT clause). ** ** Incremental loading is used for b-tree nodes FTS3_NODE_CHUNK_THRESHOLD ** bytes and larger. Nodes are loaded in chunks of FTS3_NODE_CHUNKSIZE bytes. ** The code is written so that the hard lower-limit for each of these values ** is 1. Clearly such small values would be inefficient, but can be useful ** for testing purposes. ** ** If this module is built with SQLITE_TEST defined, these constants may ** be overridden at runtime for testing purposes. File fts3_test.c contains ** a Tcl interface to read and write the values. */ #ifdef SQLITE_TEST int test_fts3_node_chunksize = (4*1024); int test_fts3_node_chunk_threshold = (4*1024)*4; # define FTS3_NODE_CHUNKSIZE test_fts3_node_chunksize # define FTS3_NODE_CHUNK_THRESHOLD test_fts3_node_chunk_threshold #else # define FTS3_NODE_CHUNKSIZE (4*1024) # define FTS3_NODE_CHUNK_THRESHOLD (FTS3_NODE_CHUNKSIZE*4) #endif /* ** The two values that may be meaningfully bound to the :1 parameter in ** statements SQL_REPLACE_STAT and SQL_SELECT_STAT. */ #define FTS_STAT_DOCTOTAL 0 #define FTS_STAT_INCRMERGEHINT 1 #define FTS_STAT_AUTOINCRMERGE 2 /* ** If FTS_LOG_MERGES is defined, call sqlite3_log() to report each automatic ** and incremental merge operation that takes place. This is used for ** debugging FTS only, it should not usually be turned on in production ** systems. */ #ifdef FTS3_LOG_MERGES static void fts3LogMerge(int nMerge, sqlite3_int64 iAbsLevel){ sqlite3_log(SQLITE_OK, "%d-way merge from level %d", nMerge, (int)iAbsLevel); } #else #define fts3LogMerge(x, y) #endif typedef struct PendingList PendingList; typedef struct SegmentNode SegmentNode; typedef struct SegmentWriter SegmentWriter; /* ** An instance of the following data structure is used to build doclists ** incrementally. See function fts3PendingListAppend() for details. */ struct PendingList { int nData; char *aData; int nSpace; sqlite3_int64 iLastDocid; sqlite3_int64 iLastCol; sqlite3_int64 iLastPos; }; /* ** Each cursor has a (possibly empty) linked list of the following objects. */ struct Fts3DeferredToken { Fts3PhraseToken *pToken; /* Pointer to corresponding expr token */ int iCol; /* Column token must occur in */ Fts3DeferredToken *pNext; /* Next in list of deferred tokens */ PendingList *pList; /* Doclist is assembled here */ }; /* ** An instance of this structure is used to iterate through the terms on ** a contiguous set of segment b-tree leaf nodes. Although the details of ** this structure are only manipulated by code in this file, opaque handles ** of type Fts3SegReader* are also used by code in fts3.c to iterate through ** terms when querying the full-text index. See functions: ** ** sqlite3Fts3SegReaderNew() ** sqlite3Fts3SegReaderFree() ** sqlite3Fts3SegReaderIterate() ** ** Methods used to manipulate Fts3SegReader structures: ** ** fts3SegReaderNext() ** fts3SegReaderFirstDocid() ** fts3SegReaderNextDocid() */ struct Fts3SegReader { int iIdx; /* Index within level, or 0x7FFFFFFF for PT */ u8 bLookup; /* True for a lookup only */ u8 rootOnly; /* True for a root-only reader */ sqlite3_int64 iStartBlock; /* Rowid of first leaf block to traverse */ sqlite3_int64 iLeafEndBlock; /* Rowid of final leaf block to traverse */ sqlite3_int64 iEndBlock; /* Rowid of final block in segment (or 0) */ sqlite3_int64 iCurrentBlock; /* Current leaf block (or 0) */ char *aNode; /* Pointer to node data (or NULL) */ int nNode; /* Size of buffer at aNode (or 0) */ int nPopulate; /* If >0, bytes of buffer aNode[] loaded */ sqlite3_blob *pBlob; /* If not NULL, blob handle to read node */ Fts3HashElem **ppNextElem; /* Variables set by fts3SegReaderNext(). These may be read directly ** by the caller. They are valid from the time SegmentReaderNew() returns ** until SegmentReaderNext() returns something other than SQLITE_OK ** (i.e. SQLITE_DONE). */ int nTerm; /* Number of bytes in current term */ char *zTerm; /* Pointer to current term */ int nTermAlloc; /* Allocated size of zTerm buffer */ char *aDoclist; /* Pointer to doclist of current entry */ int nDoclist; /* Size of doclist in current entry */ /* The following variables are used by fts3SegReaderNextDocid() to iterate ** through the current doclist (aDoclist/nDoclist). */ char *pOffsetList; int nOffsetList; /* For descending pending seg-readers only */ sqlite3_int64 iDocid; }; #define fts3SegReaderIsPending(p) ((p)->ppNextElem!=0) #define fts3SegReaderIsRootOnly(p) ((p)->rootOnly!=0) /* ** An instance of this structure is used to create a segment b-tree in the ** database. The internal details of this type are only accessed by the ** following functions: ** ** fts3SegWriterAdd() ** fts3SegWriterFlush() ** fts3SegWriterFree() */ struct SegmentWriter { SegmentNode *pTree; /* Pointer to interior tree structure */ sqlite3_int64 iFirst; /* First slot in %_segments written */ sqlite3_int64 iFree; /* Next free slot in %_segments */ char *zTerm; /* Pointer to previous term buffer */ int nTerm; /* Number of bytes in zTerm */ int nMalloc; /* Size of malloc'd buffer at zMalloc */ char *zMalloc; /* Malloc'd space (possibly) used for zTerm */ int nSize; /* Size of allocation at aData */ int nData; /* Bytes of data in aData */ char *aData; /* Pointer to block from malloc() */ i64 nLeafData; /* Number of bytes of leaf data written */ }; /* ** Type SegmentNode is used by the following three functions to create ** the interior part of the segment b+-tree structures (everything except ** the leaf nodes). These functions and type are only ever used by code ** within the fts3SegWriterXXX() family of functions described above. ** ** fts3NodeAddTerm() ** fts3NodeWrite() ** fts3NodeFree() ** ** When a b+tree is written to the database (either as a result of a merge ** or the pending-terms table being flushed), leaves are written into the ** database file as soon as they are completely populated. The interior of ** the tree is assembled in memory and written out only once all leaves have ** been populated and stored. This is Ok, as the b+-tree fanout is usually ** very large, meaning that the interior of the tree consumes relatively ** little memory. */ struct SegmentNode { SegmentNode *pParent; /* Parent node (or NULL for root node) */ SegmentNode *pRight; /* Pointer to right-sibling */ SegmentNode *pLeftmost; /* Pointer to left-most node of this depth */ int nEntry; /* Number of terms written to node so far */ char *zTerm; /* Pointer to previous term buffer */ int nTerm; /* Number of bytes in zTerm */ int nMalloc; /* Size of malloc'd buffer at zMalloc */ char *zMalloc; /* Malloc'd space (possibly) used for zTerm */ int nData; /* Bytes of valid data so far */ char *aData; /* Node data */ }; /* ** Valid values for the second argument to fts3SqlStmt(). */ #define SQL_DELETE_CONTENT 0 #define SQL_IS_EMPTY 1 #define SQL_DELETE_ALL_CONTENT 2 #define SQL_DELETE_ALL_SEGMENTS 3 #define SQL_DELETE_ALL_SEGDIR 4 #define SQL_DELETE_ALL_DOCSIZE 5 #define SQL_DELETE_ALL_STAT 6 #define SQL_SELECT_CONTENT_BY_ROWID 7 #define SQL_NEXT_SEGMENT_INDEX 8 #define SQL_INSERT_SEGMENTS 9 #define SQL_NEXT_SEGMENTS_ID 10 #define SQL_INSERT_SEGDIR 11 #define SQL_SELECT_LEVEL 12 #define SQL_SELECT_LEVEL_RANGE 13 #define SQL_SELECT_LEVEL_COUNT 14 #define SQL_SELECT_SEGDIR_MAX_LEVEL 15 #define SQL_DELETE_SEGDIR_LEVEL 16 #define SQL_DELETE_SEGMENTS_RANGE 17 #define SQL_CONTENT_INSERT 18 #define SQL_DELETE_DOCSIZE 19 #define SQL_REPLACE_DOCSIZE 20 #define SQL_SELECT_DOCSIZE 21 #define SQL_SELECT_STAT 22 #define SQL_REPLACE_STAT 23 #define SQL_SELECT_ALL_PREFIX_LEVEL 24 #define SQL_DELETE_ALL_TERMS_SEGDIR 25 #define SQL_DELETE_SEGDIR_RANGE 26 #define SQL_SELECT_ALL_LANGID 27 #define SQL_FIND_MERGE_LEVEL 28 #define SQL_MAX_LEAF_NODE_ESTIMATE 29 #define SQL_DELETE_SEGDIR_ENTRY 30 #define SQL_SHIFT_SEGDIR_ENTRY 31 #define SQL_SELECT_SEGDIR 32 #define SQL_CHOMP_SEGDIR 33 #define SQL_SEGMENT_IS_APPENDABLE 34 #define SQL_SELECT_INDEXES 35 #define SQL_SELECT_MXLEVEL 36 #define SQL_SELECT_LEVEL_RANGE2 37 #define SQL_UPDATE_LEVEL_IDX 38 #define SQL_UPDATE_LEVEL 39 /* ** This function is used to obtain an SQLite prepared statement handle ** for the statement identified by the second argument. If successful, ** *pp is set to the requested statement handle and SQLITE_OK returned. ** Otherwise, an SQLite error code is returned and *pp is set to 0. ** ** If argument apVal is not NULL, then it must point to an array with ** at least as many entries as the requested statement has bound ** parameters. The values are bound to the statements parameters before ** returning. */ static int fts3SqlStmt( Fts3Table *p, /* Virtual table handle */ int eStmt, /* One of the SQL_XXX constants above */ sqlite3_stmt **pp, /* OUT: Statement handle */ sqlite3_value **apVal /* Values to bind to statement */ ){ const char *azSql[] = { /* 0 */ "DELETE FROM %Q.'%q_content' WHERE rowid = ?", /* 1 */ "SELECT NOT EXISTS(SELECT docid FROM %Q.'%q_content' WHERE rowid!=?)", /* 2 */ "DELETE FROM %Q.'%q_content'", /* 3 */ "DELETE FROM %Q.'%q_segments'", /* 4 */ "DELETE FROM %Q.'%q_segdir'", /* 5 */ "DELETE FROM %Q.'%q_docsize'", /* 6 */ "DELETE FROM %Q.'%q_stat'", /* 7 */ "SELECT %s WHERE rowid=?", /* 8 */ "SELECT (SELECT max(idx) FROM %Q.'%q_segdir' WHERE level = ?) + 1", /* 9 */ "REPLACE INTO %Q.'%q_segments'(blockid, block) VALUES(?, ?)", /* 10 */ "SELECT coalesce((SELECT max(blockid) FROM %Q.'%q_segments') + 1, 1)", /* 11 */ "REPLACE INTO %Q.'%q_segdir' VALUES(?,?,?,?,?,?)", /* Return segments in order from oldest to newest.*/ /* 12 */ "SELECT idx, start_block, leaves_end_block, end_block, root " "FROM %Q.'%q_segdir' WHERE level = ? ORDER BY idx ASC", /* 13 */ "SELECT idx, start_block, leaves_end_block, end_block, root " "FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ?" "ORDER BY level DESC, idx ASC", /* 14 */ "SELECT count(*) FROM %Q.'%q_segdir' WHERE level = ?", /* 15 */ "SELECT max(level) FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ?", /* 16 */ "DELETE FROM %Q.'%q_segdir' WHERE level = ?", /* 17 */ "DELETE FROM %Q.'%q_segments' WHERE blockid BETWEEN ? AND ?", /* 18 */ "INSERT INTO %Q.'%q_content' VALUES(%s)", /* 19 */ "DELETE FROM %Q.'%q_docsize' WHERE docid = ?", /* 20 */ "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* 21 */ "SELECT size FROM %Q.'%q_docsize' WHERE docid=?", /* 22 */ "SELECT value FROM %Q.'%q_stat' WHERE id=?", /* 23 */ "REPLACE INTO %Q.'%q_stat' VALUES(?,?)", /* 24 */ "", /* 25 */ "", /* 26 */ "DELETE FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ?", /* 27 */ "SELECT ? UNION SELECT level / (1024 * ?) FROM %Q.'%q_segdir'", /* This statement is used to determine which level to read the input from ** when performing an incremental merge. It returns the absolute level number ** of the oldest level in the db that contains at least ? segments. Or, ** if no level in the FTS index contains more than ? segments, the statement ** returns zero rows. */ /* 28 */ "SELECT level, count(*) AS cnt FROM %Q.'%q_segdir' " " GROUP BY level HAVING cnt>=?" " ORDER BY (level %% 1024) ASC LIMIT 1", /* Estimate the upper limit on the number of leaf nodes in a new segment ** created by merging the oldest :2 segments from absolute level :1. See ** function sqlite3Fts3Incrmerge() for details. */ /* 29 */ "SELECT 2 * total(1 + leaves_end_block - start_block) " " FROM %Q.'%q_segdir' WHERE level = ? AND idx < ?", /* SQL_DELETE_SEGDIR_ENTRY ** Delete the %_segdir entry on absolute level :1 with index :2. */ /* 30 */ "DELETE FROM %Q.'%q_segdir' WHERE level = ? AND idx = ?", /* SQL_SHIFT_SEGDIR_ENTRY ** Modify the idx value for the segment with idx=:3 on absolute level :2 ** to :1. */ /* 31 */ "UPDATE %Q.'%q_segdir' SET idx = ? WHERE level=? AND idx=?", /* SQL_SELECT_SEGDIR ** Read a single entry from the %_segdir table. The entry from absolute ** level :1 with index value :2. */ /* 32 */ "SELECT idx, start_block, leaves_end_block, end_block, root " "FROM %Q.'%q_segdir' WHERE level = ? AND idx = ?", /* SQL_CHOMP_SEGDIR ** Update the start_block (:1) and root (:2) fields of the %_segdir ** entry located on absolute level :3 with index :4. */ /* 33 */ "UPDATE %Q.'%q_segdir' SET start_block = ?, root = ?" "WHERE level = ? AND idx = ?", /* SQL_SEGMENT_IS_APPENDABLE ** Return a single row if the segment with end_block=? is appendable. Or ** no rows otherwise. */ /* 34 */ "SELECT 1 FROM %Q.'%q_segments' WHERE blockid=? AND block IS NULL", /* SQL_SELECT_INDEXES ** Return the list of valid segment indexes for absolute level ? */ /* 35 */ "SELECT idx FROM %Q.'%q_segdir' WHERE level=? ORDER BY 1 ASC", /* SQL_SELECT_MXLEVEL ** Return the largest relative level in the FTS index or indexes. */ /* 36 */ "SELECT max( level %% 1024 ) FROM %Q.'%q_segdir'", /* Return segments in order from oldest to newest.*/ /* 37 */ "SELECT level, idx, end_block " "FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ? " "ORDER BY level DESC, idx ASC", /* Update statements used while promoting segments */ /* 38 */ "UPDATE OR FAIL %Q.'%q_segdir' SET level=-1,idx=? " "WHERE level=? AND idx=?", /* 39 */ "UPDATE OR FAIL %Q.'%q_segdir' SET level=? WHERE level=-1" }; int rc = SQLITE_OK; sqlite3_stmt *pStmt; assert( SizeofArray(azSql)==SizeofArray(p->aStmt) ); assert( eStmt=0 ); pStmt = p->aStmt[eStmt]; if( !pStmt ){ char *zSql; if( eStmt==SQL_CONTENT_INSERT ){ zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist); }else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){ zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist); }else{ zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName); } if( !zSql ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, NULL); sqlite3_free(zSql); assert( rc==SQLITE_OK || pStmt==0 ); p->aStmt[eStmt] = pStmt; } } if( apVal ){ int i; int nParam = sqlite3_bind_parameter_count(pStmt); for(i=0; rc==SQLITE_OK && inPendingData==0 ){ sqlite3_stmt *pStmt; rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_LEVEL, &pStmt, 0); if( rc==SQLITE_OK ){ sqlite3_bind_null(pStmt, 1); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); } } return rc; } /* ** FTS maintains a separate indexes for each language-id (a 32-bit integer). ** Within each language id, a separate index is maintained to store the ** document terms, and each configured prefix size (configured the FTS ** "prefix=" option). And each index consists of multiple levels ("relative ** levels"). ** ** All three of these values (the language id, the specific index and the ** level within the index) are encoded in 64-bit integer values stored ** in the %_segdir table on disk. This function is used to convert three ** separate component values into the single 64-bit integer value that ** can be used to query the %_segdir table. ** ** Specifically, each language-id/index combination is allocated 1024 ** 64-bit integer level values ("absolute levels"). The main terms index ** for language-id 0 is allocate values 0-1023. The first prefix index ** (if any) for language-id 0 is allocated values 1024-2047. And so on. ** Language 1 indexes are allocated immediately following language 0. ** ** So, for a system with nPrefix prefix indexes configured, the block of ** absolute levels that corresponds to language-id iLangid and index ** iIndex starts at absolute level ((iLangid * (nPrefix+1) + iIndex) * 1024). */ static sqlite3_int64 getAbsoluteLevel( Fts3Table *p, /* FTS3 table handle */ int iLangid, /* Language id */ int iIndex, /* Index in p->aIndex[] */ int iLevel /* Level of segments */ ){ sqlite3_int64 iBase; /* First absolute level for iLangid/iIndex */ assert( iLangid>=0 ); assert( p->nIndex>0 ); assert( iIndex>=0 && iIndexnIndex ); iBase = ((sqlite3_int64)iLangid * p->nIndex + iIndex) * FTS3_SEGDIR_MAXLEVEL; return iBase + iLevel; } /* ** Set *ppStmt to a statement handle that may be used to iterate through ** all rows in the %_segdir table, from oldest to newest. If successful, ** return SQLITE_OK. If an error occurs while preparing the statement, ** return an SQLite error code. ** ** There is only ever one instance of this SQL statement compiled for ** each FTS3 table. ** ** The statement returns the following columns from the %_segdir table: ** ** 0: idx ** 1: start_block ** 2: leaves_end_block ** 3: end_block ** 4: root */ int sqlite3Fts3AllSegdirs( Fts3Table *p, /* FTS3 table */ int iLangid, /* Language being queried */ int iIndex, /* Index for p->aIndex[] */ int iLevel, /* Level to select (relative level) */ sqlite3_stmt **ppStmt /* OUT: Compiled statement */ ){ int rc; sqlite3_stmt *pStmt = 0; assert( iLevel==FTS3_SEGCURSOR_ALL || iLevel>=0 ); assert( iLevel=0 && iIndexnIndex ); if( iLevel<0 ){ /* "SELECT * FROM %_segdir WHERE level BETWEEN ? AND ? ORDER BY ..." */ rc = fts3SqlStmt(p, SQL_SELECT_LEVEL_RANGE, &pStmt, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex, 0)); sqlite3_bind_int64(pStmt, 2, getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1) ); } }else{ /* "SELECT * FROM %_segdir WHERE level = ? ORDER BY ..." */ rc = fts3SqlStmt(p, SQL_SELECT_LEVEL, &pStmt, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex,iLevel)); } } *ppStmt = pStmt; return rc; } /* ** Append a single varint to a PendingList buffer. SQLITE_OK is returned ** if successful, or an SQLite error code otherwise. ** ** This function also serves to allocate the PendingList structure itself. ** For example, to create a new PendingList structure containing two ** varints: ** ** PendingList *p = 0; ** fts3PendingListAppendVarint(&p, 1); ** fts3PendingListAppendVarint(&p, 2); */ static int fts3PendingListAppendVarint( PendingList **pp, /* IN/OUT: Pointer to PendingList struct */ sqlite3_int64 i /* Value to append to data */ ){ PendingList *p = *pp; /* Allocate or grow the PendingList as required. */ if( !p ){ p = sqlite3_malloc(sizeof(*p) + 100); if( !p ){ return SQLITE_NOMEM; } p->nSpace = 100; p->aData = (char *)&p[1]; p->nData = 0; } else if( p->nData+FTS3_VARINT_MAX+1>p->nSpace ){ int nNew = p->nSpace * 2; p = sqlite3_realloc(p, sizeof(*p) + nNew); if( !p ){ sqlite3_free(*pp); *pp = 0; return SQLITE_NOMEM; } p->nSpace = nNew; p->aData = (char *)&p[1]; } /* Append the new serialized varint to the end of the list. */ p->nData += sqlite3Fts3PutVarint(&p->aData[p->nData], i); p->aData[p->nData] = '\0'; *pp = p; return SQLITE_OK; } /* ** Add a docid/column/position entry to a PendingList structure. Non-zero ** is returned if the structure is sqlite3_realloced as part of adding ** the entry. Otherwise, zero. ** ** If an OOM error occurs, *pRc is set to SQLITE_NOMEM before returning. ** Zero is always returned in this case. Otherwise, if no OOM error occurs, ** it is set to SQLITE_OK. */ static int fts3PendingListAppend( PendingList **pp, /* IN/OUT: PendingList structure */ sqlite3_int64 iDocid, /* Docid for entry to add */ sqlite3_int64 iCol, /* Column for entry to add */ sqlite3_int64 iPos, /* Position of term for entry to add */ int *pRc /* OUT: Return code */ ){ PendingList *p = *pp; int rc = SQLITE_OK; assert( !p || p->iLastDocid<=iDocid ); if( !p || p->iLastDocid!=iDocid ){ sqlite3_int64 iDelta = iDocid - (p ? p->iLastDocid : 0); if( p ){ assert( p->nDatanSpace ); assert( p->aData[p->nData]==0 ); p->nData++; } if( SQLITE_OK!=(rc = fts3PendingListAppendVarint(&p, iDelta)) ){ goto pendinglistappend_out; } p->iLastCol = -1; p->iLastPos = 0; p->iLastDocid = iDocid; } if( iCol>0 && p->iLastCol!=iCol ){ if( SQLITE_OK!=(rc = fts3PendingListAppendVarint(&p, 1)) || SQLITE_OK!=(rc = fts3PendingListAppendVarint(&p, iCol)) ){ goto pendinglistappend_out; } p->iLastCol = iCol; p->iLastPos = 0; } if( iCol>=0 ){ assert( iPos>p->iLastPos || (iPos==0 && p->iLastPos==0) ); rc = fts3PendingListAppendVarint(&p, 2+iPos-p->iLastPos); if( rc==SQLITE_OK ){ p->iLastPos = iPos; } } pendinglistappend_out: *pRc = rc; if( p!=*pp ){ *pp = p; return 1; } return 0; } /* ** Free a PendingList object allocated by fts3PendingListAppend(). */ static void fts3PendingListDelete(PendingList *pList){ sqlite3_free(pList); } /* ** Add an entry to one of the pending-terms hash tables. */ static int fts3PendingTermsAddOne( Fts3Table *p, int iCol, int iPos, Fts3Hash *pHash, /* Pending terms hash table to add entry to */ const char *zToken, int nToken ){ PendingList *pList; int rc = SQLITE_OK; pList = (PendingList *)fts3HashFind(pHash, zToken, nToken); if( pList ){ p->nPendingData -= (pList->nData + nToken + sizeof(Fts3HashElem)); } if( fts3PendingListAppend(&pList, p->iPrevDocid, iCol, iPos, &rc) ){ if( pList==fts3HashInsert(pHash, zToken, nToken, pList) ){ /* Malloc failed while inserting the new entry. This can only ** happen if there was no previous entry for this token. */ assert( 0==fts3HashFind(pHash, zToken, nToken) ); sqlite3_free(pList); rc = SQLITE_NOMEM; } } if( rc==SQLITE_OK ){ p->nPendingData += (pList->nData + nToken + sizeof(Fts3HashElem)); } return rc; } /* ** Tokenize the nul-terminated string zText and add all tokens to the ** pending-terms hash-table. The docid used is that currently stored in ** p->iPrevDocid, and the column is specified by argument iCol. ** ** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. */ static int fts3PendingTermsAdd( Fts3Table *p, /* Table into which text will be inserted */ int iLangid, /* Language id to use */ const char *zText, /* Text of document to be inserted */ int iCol, /* Column into which text is being inserted */ u32 *pnWord /* IN/OUT: Incr. by number tokens inserted */ ){ int rc; int iStart = 0; int iEnd = 0; int iPos = 0; int nWord = 0; char const *zToken; int nToken = 0; sqlite3_tokenizer *pTokenizer = p->pTokenizer; sqlite3_tokenizer_module const *pModule = pTokenizer->pModule; sqlite3_tokenizer_cursor *pCsr; int (*xNext)(sqlite3_tokenizer_cursor *pCursor, const char**,int*,int*,int*,int*); assert( pTokenizer && pModule ); /* If the user has inserted a NULL value, this function may be called with ** zText==0. In this case, add zero token entries to the hash table and ** return early. */ if( zText==0 ){ *pnWord = 0; return SQLITE_OK; } rc = sqlite3Fts3OpenTokenizer(pTokenizer, iLangid, zText, -1, &pCsr); if( rc!=SQLITE_OK ){ return rc; } xNext = pModule->xNext; while( SQLITE_OK==rc && SQLITE_OK==(rc = xNext(pCsr, &zToken, &nToken, &iStart, &iEnd, &iPos)) ){ int i; if( iPos>=nWord ) nWord = iPos+1; /* Positions cannot be negative; we use -1 as a terminator internally. ** Tokens must have a non-zero length. */ if( iPos<0 || !zToken || nToken<=0 ){ rc = SQLITE_ERROR; break; } /* Add the term to the terms index */ rc = fts3PendingTermsAddOne( p, iCol, iPos, &p->aIndex[0].hPending, zToken, nToken ); /* Add the term to each of the prefix indexes that it is not too ** short for. */ for(i=1; rc==SQLITE_OK && inIndex; i++){ struct Fts3Index *pIndex = &p->aIndex[i]; if( nTokennPrefix ) continue; rc = fts3PendingTermsAddOne( p, iCol, iPos, &pIndex->hPending, zToken, pIndex->nPrefix ); } } pModule->xClose(pCsr); *pnWord += nWord; return (rc==SQLITE_DONE ? SQLITE_OK : rc); } /* ** Calling this function indicates that subsequent calls to ** fts3PendingTermsAdd() are to add term/position-list pairs for the ** contents of the document with docid iDocid. */ static int fts3PendingTermsDocid( Fts3Table *p, /* Full-text table handle */ int bDelete, /* True if this op is a delete */ int iLangid, /* Language id of row being written */ sqlite_int64 iDocid /* Docid of row being written */ ){ assert( iLangid>=0 ); assert( bDelete==1 || bDelete==0 ); /* TODO(shess) Explore whether partially flushing the buffer on ** forced-flush would provide better performance. I suspect that if ** we ordered the doclists by size and flushed the largest until the ** buffer was half empty, that would let the less frequent terms ** generate longer doclists. */ if( iDocidiPrevDocid || (iDocid==p->iPrevDocid && p->bPrevDelete==0) || p->iPrevLangid!=iLangid || p->nPendingData>p->nMaxPendingData ){ int rc = sqlite3Fts3PendingTermsFlush(p); if( rc!=SQLITE_OK ) return rc; } p->iPrevDocid = iDocid; p->iPrevLangid = iLangid; p->bPrevDelete = bDelete; return SQLITE_OK; } /* ** Discard the contents of the pending-terms hash tables. */ void sqlite3Fts3PendingTermsClear(Fts3Table *p){ int i; for(i=0; inIndex; i++){ Fts3HashElem *pElem; Fts3Hash *pHash = &p->aIndex[i].hPending; for(pElem=fts3HashFirst(pHash); pElem; pElem=fts3HashNext(pElem)){ PendingList *pList = (PendingList *)fts3HashData(pElem); fts3PendingListDelete(pList); } fts3HashClear(pHash); } p->nPendingData = 0; } /* ** This function is called by the xUpdate() method as part of an INSERT ** operation. It adds entries for each term in the new record to the ** pendingTerms hash table. ** ** Argument apVal is the same as the similarly named argument passed to ** fts3InsertData(). Parameter iDocid is the docid of the new row. */ static int fts3InsertTerms( Fts3Table *p, int iLangid, sqlite3_value **apVal, u32 *aSz ){ int i; /* Iterator variable */ for(i=2; inColumn+2; i++){ int iCol = i-2; if( p->abNotindexed[iCol]==0 ){ const char *zText = (const char *)sqlite3_value_text(apVal[i]); int rc = fts3PendingTermsAdd(p, iLangid, zText, iCol, &aSz[iCol]); if( rc!=SQLITE_OK ){ return rc; } aSz[p->nColumn] += sqlite3_value_bytes(apVal[i]); } } return SQLITE_OK; } /* ** This function is called by the xUpdate() method for an INSERT operation. ** The apVal parameter is passed a copy of the apVal argument passed by ** SQLite to the xUpdate() method. i.e: ** ** apVal[0] Not used for INSERT. ** apVal[1] rowid ** apVal[2] Left-most user-defined column ** ... ** apVal[p->nColumn+1] Right-most user-defined column ** apVal[p->nColumn+2] Hidden column with same name as table ** apVal[p->nColumn+3] Hidden "docid" column (alias for rowid) ** apVal[p->nColumn+4] Hidden languageid column */ static int fts3InsertData( Fts3Table *p, /* Full-text table */ sqlite3_value **apVal, /* Array of values to insert */ sqlite3_int64 *piDocid /* OUT: Docid for row just inserted */ ){ int rc; /* Return code */ sqlite3_stmt *pContentInsert; /* INSERT INTO %_content VALUES(...) */ if( p->zContentTbl ){ sqlite3_value *pRowid = apVal[p->nColumn+3]; if( sqlite3_value_type(pRowid)==SQLITE_NULL ){ pRowid = apVal[1]; } if( sqlite3_value_type(pRowid)!=SQLITE_INTEGER ){ return SQLITE_CONSTRAINT; } *piDocid = sqlite3_value_int64(pRowid); return SQLITE_OK; } /* Locate the statement handle used to insert data into the %_content ** table. The SQL for this statement is: ** ** INSERT INTO %_content VALUES(?, ?, ?, ...) ** ** The statement features N '?' variables, where N is the number of user ** defined columns in the FTS3 table, plus one for the docid field. */ rc = fts3SqlStmt(p, SQL_CONTENT_INSERT, &pContentInsert, &apVal[1]); if( rc==SQLITE_OK && p->zLanguageid ){ rc = sqlite3_bind_int( pContentInsert, p->nColumn+2, sqlite3_value_int(apVal[p->nColumn+4]) ); } if( rc!=SQLITE_OK ) return rc; /* There is a quirk here. The users INSERT statement may have specified ** a value for the "rowid" field, for the "docid" field, or for both. ** Which is a problem, since "rowid" and "docid" are aliases for the ** same value. For example: ** ** INSERT INTO fts3tbl(rowid, docid) VALUES(1, 2); ** ** In FTS3, this is an error. It is an error to specify non-NULL values ** for both docid and some other rowid alias. */ if( SQLITE_NULL!=sqlite3_value_type(apVal[3+p->nColumn]) ){ if( SQLITE_NULL==sqlite3_value_type(apVal[0]) && SQLITE_NULL!=sqlite3_value_type(apVal[1]) ){ /* A rowid/docid conflict. */ return SQLITE_ERROR; } rc = sqlite3_bind_value(pContentInsert, 1, apVal[3+p->nColumn]); if( rc!=SQLITE_OK ) return rc; } /* Execute the statement to insert the record. Set *piDocid to the ** new docid value. */ sqlite3_step(pContentInsert); rc = sqlite3_reset(pContentInsert); *piDocid = sqlite3_last_insert_rowid(p->db); return rc; } /* ** Remove all data from the FTS3 table. Clear the hash table containing ** pending terms. */ static int fts3DeleteAll(Fts3Table *p, int bContent){ int rc = SQLITE_OK; /* Return code */ /* Discard the contents of the pending-terms hash table. */ sqlite3Fts3PendingTermsClear(p); /* Delete everything from the shadow tables. Except, leave %_content as ** is if bContent is false. */ assert( p->zContentTbl==0 || bContent==0 ); if( bContent ) fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0); fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGMENTS, 0); fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0); if( p->bHasDocsize ){ fts3SqlExec(&rc, p, SQL_DELETE_ALL_DOCSIZE, 0); } if( p->bHasStat ){ fts3SqlExec(&rc, p, SQL_DELETE_ALL_STAT, 0); } return rc; } /* ** */ static int langidFromSelect(Fts3Table *p, sqlite3_stmt *pSelect){ int iLangid = 0; if( p->zLanguageid ) iLangid = sqlite3_column_int(pSelect, p->nColumn+1); return iLangid; } /* ** The first element in the apVal[] array is assumed to contain the docid ** (an integer) of a row about to be deleted. Remove all terms from the ** full-text index. */ static void fts3DeleteTerms( int *pRC, /* Result code */ Fts3Table *p, /* The FTS table to delete from */ sqlite3_value *pRowid, /* The docid to be deleted */ u32 *aSz, /* Sizes of deleted document written here */ int *pbFound /* OUT: Set to true if row really does exist */ ){ int rc; sqlite3_stmt *pSelect; assert( *pbFound==0 ); if( *pRC ) return; rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pSelect, &pRowid); if( rc==SQLITE_OK ){ if( SQLITE_ROW==sqlite3_step(pSelect) ){ int i; int iLangid = langidFromSelect(p, pSelect); i64 iDocid = sqlite3_column_int64(pSelect, 0); rc = fts3PendingTermsDocid(p, 1, iLangid, iDocid); for(i=1; rc==SQLITE_OK && i<=p->nColumn; i++){ int iCol = i-1; if( p->abNotindexed[iCol]==0 ){ const char *zText = (const char *)sqlite3_column_text(pSelect, i); rc = fts3PendingTermsAdd(p, iLangid, zText, -1, &aSz[iCol]); aSz[p->nColumn] += sqlite3_column_bytes(pSelect, i); } } if( rc!=SQLITE_OK ){ sqlite3_reset(pSelect); *pRC = rc; return; } *pbFound = 1; } rc = sqlite3_reset(pSelect); }else{ sqlite3_reset(pSelect); } *pRC = rc; } /* ** Forward declaration to account for the circular dependency between ** functions fts3SegmentMerge() and fts3AllocateSegdirIdx(). */ static int fts3SegmentMerge(Fts3Table *, int, int, int); /* ** This function allocates a new level iLevel index in the segdir table. ** Usually, indexes are allocated within a level sequentially starting ** with 0, so the allocated index is one greater than the value returned ** by: ** ** SELECT max(idx) FROM %_segdir WHERE level = :iLevel ** ** However, if there are already FTS3_MERGE_COUNT indexes at the requested ** level, they are merged into a single level (iLevel+1) segment and the ** allocated index is 0. ** ** If successful, *piIdx is set to the allocated index slot and SQLITE_OK ** returned. Otherwise, an SQLite error code is returned. */ static int fts3AllocateSegdirIdx( Fts3Table *p, int iLangid, /* Language id */ int iIndex, /* Index for p->aIndex */ int iLevel, int *piIdx ){ int rc; /* Return Code */ sqlite3_stmt *pNextIdx; /* Query for next idx at level iLevel */ int iNext = 0; /* Result of query pNextIdx */ assert( iLangid>=0 ); assert( p->nIndex>=1 ); /* Set variable iNext to the next available segdir index at level iLevel. */ rc = fts3SqlStmt(p, SQL_NEXT_SEGMENT_INDEX, &pNextIdx, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64( pNextIdx, 1, getAbsoluteLevel(p, iLangid, iIndex, iLevel) ); if( SQLITE_ROW==sqlite3_step(pNextIdx) ){ iNext = sqlite3_column_int(pNextIdx, 0); } rc = sqlite3_reset(pNextIdx); } if( rc==SQLITE_OK ){ /* If iNext is FTS3_MERGE_COUNT, indicating that level iLevel is already ** full, merge all segments in level iLevel into a single iLevel+1 ** segment and allocate (newly freed) index 0 at level iLevel. Otherwise, ** if iNext is less than FTS3_MERGE_COUNT, allocate index iNext. */ if( iNext>=FTS3_MERGE_COUNT ){ fts3LogMerge(16, getAbsoluteLevel(p, iLangid, iIndex, iLevel)); rc = fts3SegmentMerge(p, iLangid, iIndex, iLevel); *piIdx = 0; }else{ *piIdx = iNext; } } return rc; } /* ** The %_segments table is declared as follows: ** ** CREATE TABLE %_segments(blockid INTEGER PRIMARY KEY, block BLOB) ** ** This function reads data from a single row of the %_segments table. The ** specific row is identified by the iBlockid parameter. If paBlob is not ** NULL, then a buffer is allocated using sqlite3_malloc() and populated ** with the contents of the blob stored in the "block" column of the ** identified table row is. Whether or not paBlob is NULL, *pnBlob is set ** to the size of the blob in bytes before returning. ** ** If an error occurs, or the table does not contain the specified row, ** an SQLite error code is returned. Otherwise, SQLITE_OK is returned. If ** paBlob is non-NULL, then it is the responsibility of the caller to ** eventually free the returned buffer. ** ** This function may leave an open sqlite3_blob* handle in the ** Fts3Table.pSegments variable. This handle is reused by subsequent calls ** to this function. The handle may be closed by calling the ** sqlite3Fts3SegmentsClose() function. Reusing a blob handle is a handy ** performance improvement, but the blob handle should always be closed ** before control is returned to the user (to prevent a lock being held ** on the database file for longer than necessary). Thus, any virtual table ** method (xFilter etc.) that may directly or indirectly call this function ** must call sqlite3Fts3SegmentsClose() before returning. */ int sqlite3Fts3ReadBlock( Fts3Table *p, /* FTS3 table handle */ sqlite3_int64 iBlockid, /* Access the row with blockid=$iBlockid */ char **paBlob, /* OUT: Blob data in malloc'd buffer */ int *pnBlob, /* OUT: Size of blob data */ int *pnLoad /* OUT: Bytes actually loaded */ ){ int rc; /* Return code */ /* pnBlob must be non-NULL. paBlob may be NULL or non-NULL. */ assert( pnBlob ); if( p->pSegments ){ rc = sqlite3_blob_reopen(p->pSegments, iBlockid); }else{ if( 0==p->zSegmentsTbl ){ p->zSegmentsTbl = sqlite3_mprintf("%s_segments", p->zName); if( 0==p->zSegmentsTbl ) return SQLITE_NOMEM; } rc = sqlite3_blob_open( p->db, p->zDb, p->zSegmentsTbl, "block", iBlockid, 0, &p->pSegments ); } if( rc==SQLITE_OK ){ int nByte = sqlite3_blob_bytes(p->pSegments); *pnBlob = nByte; if( paBlob ){ char *aByte = sqlite3_malloc(nByte + FTS3_NODE_PADDING); if( !aByte ){ rc = SQLITE_NOMEM; }else{ if( pnLoad && nByte>(FTS3_NODE_CHUNK_THRESHOLD) ){ nByte = FTS3_NODE_CHUNKSIZE; *pnLoad = nByte; } rc = sqlite3_blob_read(p->pSegments, aByte, nByte, 0); memset(&aByte[nByte], 0, FTS3_NODE_PADDING); if( rc!=SQLITE_OK ){ sqlite3_free(aByte); aByte = 0; } } *paBlob = aByte; } } return rc; } /* ** Close the blob handle at p->pSegments, if it is open. See comments above ** the sqlite3Fts3ReadBlock() function for details. */ void sqlite3Fts3SegmentsClose(Fts3Table *p){ sqlite3_blob_close(p->pSegments); p->pSegments = 0; } static int fts3SegReaderIncrRead(Fts3SegReader *pReader){ int nRead; /* Number of bytes to read */ int rc; /* Return code */ nRead = MIN(pReader->nNode - pReader->nPopulate, FTS3_NODE_CHUNKSIZE); rc = sqlite3_blob_read( pReader->pBlob, &pReader->aNode[pReader->nPopulate], nRead, pReader->nPopulate ); if( rc==SQLITE_OK ){ pReader->nPopulate += nRead; memset(&pReader->aNode[pReader->nPopulate], 0, FTS3_NODE_PADDING); if( pReader->nPopulate==pReader->nNode ){ sqlite3_blob_close(pReader->pBlob); pReader->pBlob = 0; pReader->nPopulate = 0; } } return rc; } static int fts3SegReaderRequire(Fts3SegReader *pReader, char *pFrom, int nByte){ int rc = SQLITE_OK; assert( !pReader->pBlob || (pFrom>=pReader->aNode && pFrom<&pReader->aNode[pReader->nNode]) ); while( pReader->pBlob && rc==SQLITE_OK && (pFrom - pReader->aNode + nByte)>pReader->nPopulate ){ rc = fts3SegReaderIncrRead(pReader); } return rc; } /* ** Set an Fts3SegReader cursor to point at EOF. */ static void fts3SegReaderSetEof(Fts3SegReader *pSeg){ if( !fts3SegReaderIsRootOnly(pSeg) ){ sqlite3_free(pSeg->aNode); sqlite3_blob_close(pSeg->pBlob); pSeg->pBlob = 0; } pSeg->aNode = 0; } /* ** Move the iterator passed as the first argument to the next term in the ** segment. If successful, SQLITE_OK is returned. If there is no next term, ** SQLITE_DONE. Otherwise, an SQLite error code. */ static int fts3SegReaderNext( Fts3Table *p, Fts3SegReader *pReader, int bIncr ){ int rc; /* Return code of various sub-routines */ char *pNext; /* Cursor variable */ int nPrefix; /* Number of bytes in term prefix */ int nSuffix; /* Number of bytes in term suffix */ if( !pReader->aDoclist ){ pNext = pReader->aNode; }else{ pNext = &pReader->aDoclist[pReader->nDoclist]; } if( !pNext || pNext>=&pReader->aNode[pReader->nNode] ){ if( fts3SegReaderIsPending(pReader) ){ Fts3HashElem *pElem = *(pReader->ppNextElem); sqlite3_free(pReader->aNode); pReader->aNode = 0; if( pElem ){ char *aCopy; PendingList *pList = (PendingList *)fts3HashData(pElem); int nCopy = pList->nData+1; pReader->zTerm = (char *)fts3HashKey(pElem); pReader->nTerm = fts3HashKeysize(pElem); aCopy = (char*)sqlite3_malloc(nCopy); if( !aCopy ) return SQLITE_NOMEM; memcpy(aCopy, pList->aData, nCopy); pReader->nNode = pReader->nDoclist = nCopy; pReader->aNode = pReader->aDoclist = aCopy; pReader->ppNextElem++; assert( pReader->aNode ); } return SQLITE_OK; } fts3SegReaderSetEof(pReader); /* If iCurrentBlock>=iLeafEndBlock, this is an EOF condition. All leaf ** blocks have already been traversed. */ assert( pReader->iCurrentBlock<=pReader->iLeafEndBlock ); if( pReader->iCurrentBlock>=pReader->iLeafEndBlock ){ return SQLITE_OK; } rc = sqlite3Fts3ReadBlock( p, ++pReader->iCurrentBlock, &pReader->aNode, &pReader->nNode, (bIncr ? &pReader->nPopulate : 0) ); if( rc!=SQLITE_OK ) return rc; assert( pReader->pBlob==0 ); if( bIncr && pReader->nPopulatenNode ){ pReader->pBlob = p->pSegments; p->pSegments = 0; } pNext = pReader->aNode; } assert( !fts3SegReaderIsPending(pReader) ); rc = fts3SegReaderRequire(pReader, pNext, FTS3_VARINT_MAX*2); if( rc!=SQLITE_OK ) return rc; /* Because of the FTS3_NODE_PADDING bytes of padding, the following is ** safe (no risk of overread) even if the node data is corrupted. */ pNext += fts3GetVarint32(pNext, &nPrefix); pNext += fts3GetVarint32(pNext, &nSuffix); if( nPrefix<0 || nSuffix<=0 || &pNext[nSuffix]>&pReader->aNode[pReader->nNode] ){ return FTS_CORRUPT_VTAB; } if( nPrefix+nSuffix>pReader->nTermAlloc ){ int nNew = (nPrefix+nSuffix)*2; char *zNew = sqlite3_realloc(pReader->zTerm, nNew); if( !zNew ){ return SQLITE_NOMEM; } pReader->zTerm = zNew; pReader->nTermAlloc = nNew; } rc = fts3SegReaderRequire(pReader, pNext, nSuffix+FTS3_VARINT_MAX); if( rc!=SQLITE_OK ) return rc; memcpy(&pReader->zTerm[nPrefix], pNext, nSuffix); pReader->nTerm = nPrefix+nSuffix; pNext += nSuffix; pNext += fts3GetVarint32(pNext, &pReader->nDoclist); pReader->aDoclist = pNext; pReader->pOffsetList = 0; /* Check that the doclist does not appear to extend past the end of the ** b-tree node. And that the final byte of the doclist is 0x00. If either ** of these statements is untrue, then the data structure is corrupt. */ if( &pReader->aDoclist[pReader->nDoclist]>&pReader->aNode[pReader->nNode] || (pReader->nPopulate==0 && pReader->aDoclist[pReader->nDoclist-1]) ){ return FTS_CORRUPT_VTAB; } return SQLITE_OK; } /* ** Set the SegReader to point to the first docid in the doclist associated ** with the current term. */ static int fts3SegReaderFirstDocid(Fts3Table *pTab, Fts3SegReader *pReader){ int rc = SQLITE_OK; assert( pReader->aDoclist ); assert( !pReader->pOffsetList ); if( pTab->bDescIdx && fts3SegReaderIsPending(pReader) ){ u8 bEof = 0; pReader->iDocid = 0; pReader->nOffsetList = 0; sqlite3Fts3DoclistPrev(0, pReader->aDoclist, pReader->nDoclist, &pReader->pOffsetList, &pReader->iDocid, &pReader->nOffsetList, &bEof ); }else{ rc = fts3SegReaderRequire(pReader, pReader->aDoclist, FTS3_VARINT_MAX); if( rc==SQLITE_OK ){ int n = sqlite3Fts3GetVarint(pReader->aDoclist, &pReader->iDocid); pReader->pOffsetList = &pReader->aDoclist[n]; } } return rc; } /* ** Advance the SegReader to point to the next docid in the doclist ** associated with the current term. ** ** If arguments ppOffsetList and pnOffsetList are not NULL, then ** *ppOffsetList is set to point to the first column-offset list ** in the doclist entry (i.e. immediately past the docid varint). ** *pnOffsetList is set to the length of the set of column-offset ** lists, not including the nul-terminator byte. For example: */ static int fts3SegReaderNextDocid( Fts3Table *pTab, Fts3SegReader *pReader, /* Reader to advance to next docid */ char **ppOffsetList, /* OUT: Pointer to current position-list */ int *pnOffsetList /* OUT: Length of *ppOffsetList in bytes */ ){ int rc = SQLITE_OK; char *p = pReader->pOffsetList; char c = 0; assert( p ); if( pTab->bDescIdx && fts3SegReaderIsPending(pReader) ){ /* A pending-terms seg-reader for an FTS4 table that uses order=desc. ** Pending-terms doclists are always built up in ascending order, so ** we have to iterate through them backwards here. */ u8 bEof = 0; if( ppOffsetList ){ *ppOffsetList = pReader->pOffsetList; *pnOffsetList = pReader->nOffsetList - 1; } sqlite3Fts3DoclistPrev(0, pReader->aDoclist, pReader->nDoclist, &p, &pReader->iDocid, &pReader->nOffsetList, &bEof ); if( bEof ){ pReader->pOffsetList = 0; }else{ pReader->pOffsetList = p; } }else{ char *pEnd = &pReader->aDoclist[pReader->nDoclist]; /* Pointer p currently points at the first byte of an offset list. The ** following block advances it to point one byte past the end of ** the same offset list. */ while( 1 ){ /* The following line of code (and the "p++" below the while() loop) is ** normally all that is required to move pointer p to the desired ** position. The exception is if this node is being loaded from disk ** incrementally and pointer "p" now points to the first byte past ** the populated part of pReader->aNode[]. */ while( *p | c ) c = *p++ & 0x80; assert( *p==0 ); if( pReader->pBlob==0 || p<&pReader->aNode[pReader->nPopulate] ) break; rc = fts3SegReaderIncrRead(pReader); if( rc!=SQLITE_OK ) return rc; } p++; /* If required, populate the output variables with a pointer to and the ** size of the previous offset-list. */ if( ppOffsetList ){ *ppOffsetList = pReader->pOffsetList; *pnOffsetList = (int)(p - pReader->pOffsetList - 1); } /* List may have been edited in place by fts3EvalNearTrim() */ while( p=pEnd ){ pReader->pOffsetList = 0; }else{ rc = fts3SegReaderRequire(pReader, p, FTS3_VARINT_MAX); if( rc==SQLITE_OK ){ sqlite3_int64 iDelta; pReader->pOffsetList = p + sqlite3Fts3GetVarint(p, &iDelta); if( pTab->bDescIdx ){ pReader->iDocid -= iDelta; }else{ pReader->iDocid += iDelta; } } } } return SQLITE_OK; } int sqlite3Fts3MsrOvfl( Fts3Cursor *pCsr, Fts3MultiSegReader *pMsr, int *pnOvfl ){ Fts3Table *p = (Fts3Table*)pCsr->base.pVtab; int nOvfl = 0; int ii; int rc = SQLITE_OK; int pgsz = p->nPgsz; assert( p->bFts4 ); assert( pgsz>0 ); for(ii=0; rc==SQLITE_OK && iinSegment; ii++){ Fts3SegReader *pReader = pMsr->apSegment[ii]; if( !fts3SegReaderIsPending(pReader) && !fts3SegReaderIsRootOnly(pReader) ){ sqlite3_int64 jj; for(jj=pReader->iStartBlock; jj<=pReader->iLeafEndBlock; jj++){ int nBlob; rc = sqlite3Fts3ReadBlock(p, jj, 0, &nBlob, 0); if( rc!=SQLITE_OK ) break; if( (nBlob+35)>pgsz ){ nOvfl += (nBlob + 34)/pgsz; } } } } *pnOvfl = nOvfl; return rc; } /* ** Free all allocations associated with the iterator passed as the ** second argument. */ void sqlite3Fts3SegReaderFree(Fts3SegReader *pReader){ if( pReader ){ if( !fts3SegReaderIsPending(pReader) ){ sqlite3_free(pReader->zTerm); } if( !fts3SegReaderIsRootOnly(pReader) ){ sqlite3_free(pReader->aNode); } sqlite3_blob_close(pReader->pBlob); } sqlite3_free(pReader); } /* ** Allocate a new SegReader object. */ int sqlite3Fts3SegReaderNew( int iAge, /* Segment "age". */ int bLookup, /* True for a lookup only */ sqlite3_int64 iStartLeaf, /* First leaf to traverse */ sqlite3_int64 iEndLeaf, /* Final leaf to traverse */ sqlite3_int64 iEndBlock, /* Final block of segment */ const char *zRoot, /* Buffer containing root node */ int nRoot, /* Size of buffer containing root node */ Fts3SegReader **ppReader /* OUT: Allocated Fts3SegReader */ ){ Fts3SegReader *pReader; /* Newly allocated SegReader object */ int nExtra = 0; /* Bytes to allocate segment root node */ assert( iStartLeaf<=iEndLeaf ); if( iStartLeaf==0 ){ nExtra = nRoot + FTS3_NODE_PADDING; } pReader = (Fts3SegReader *)sqlite3_malloc(sizeof(Fts3SegReader) + nExtra); if( !pReader ){ return SQLITE_NOMEM; } memset(pReader, 0, sizeof(Fts3SegReader)); pReader->iIdx = iAge; pReader->bLookup = bLookup!=0; pReader->iStartBlock = iStartLeaf; pReader->iLeafEndBlock = iEndLeaf; pReader->iEndBlock = iEndBlock; if( nExtra ){ /* The entire segment is stored in the root node. */ pReader->aNode = (char *)&pReader[1]; pReader->rootOnly = 1; pReader->nNode = nRoot; memcpy(pReader->aNode, zRoot, nRoot); memset(&pReader->aNode[nRoot], 0, FTS3_NODE_PADDING); }else{ pReader->iCurrentBlock = iStartLeaf-1; } *ppReader = pReader; return SQLITE_OK; } /* ** This is a comparison function used as a qsort() callback when sorting ** an array of pending terms by term. This occurs as part of flushing ** the contents of the pending-terms hash table to the database. */ static int SQLITE_CDECL fts3CompareElemByTerm( const void *lhs, const void *rhs ){ char *z1 = fts3HashKey(*(Fts3HashElem **)lhs); char *z2 = fts3HashKey(*(Fts3HashElem **)rhs); int n1 = fts3HashKeysize(*(Fts3HashElem **)lhs); int n2 = fts3HashKeysize(*(Fts3HashElem **)rhs); int n = (n1aIndex */ const char *zTerm, /* Term to search for */ int nTerm, /* Size of buffer zTerm */ int bPrefix, /* True for a prefix iterator */ Fts3SegReader **ppReader /* OUT: SegReader for pending-terms */ ){ Fts3SegReader *pReader = 0; /* Fts3SegReader object to return */ Fts3HashElem *pE; /* Iterator variable */ Fts3HashElem **aElem = 0; /* Array of term hash entries to scan */ int nElem = 0; /* Size of array at aElem */ int rc = SQLITE_OK; /* Return Code */ Fts3Hash *pHash; pHash = &p->aIndex[iIndex].hPending; if( bPrefix ){ int nAlloc = 0; /* Size of allocated array at aElem */ for(pE=fts3HashFirst(pHash); pE; pE=fts3HashNext(pE)){ char *zKey = (char *)fts3HashKey(pE); int nKey = fts3HashKeysize(pE); if( nTerm==0 || (nKey>=nTerm && 0==memcmp(zKey, zTerm, nTerm)) ){ if( nElem==nAlloc ){ Fts3HashElem **aElem2; nAlloc += 16; aElem2 = (Fts3HashElem **)sqlite3_realloc( aElem, nAlloc*sizeof(Fts3HashElem *) ); if( !aElem2 ){ rc = SQLITE_NOMEM; nElem = 0; break; } aElem = aElem2; } aElem[nElem++] = pE; } } /* If more than one term matches the prefix, sort the Fts3HashElem ** objects in term order using qsort(). This uses the same comparison ** callback as is used when flushing terms to disk. */ if( nElem>1 ){ qsort(aElem, nElem, sizeof(Fts3HashElem *), fts3CompareElemByTerm); } }else{ /* The query is a simple term lookup that matches at most one term in ** the index. All that is required is a straight hash-lookup. ** ** Because the stack address of pE may be accessed via the aElem pointer ** below, the "Fts3HashElem *pE" must be declared so that it is valid ** within this entire function, not just this "else{...}" block. */ pE = fts3HashFindElem(pHash, zTerm, nTerm); if( pE ){ aElem = &pE; nElem = 1; } } if( nElem>0 ){ int nByte = sizeof(Fts3SegReader) + (nElem+1)*sizeof(Fts3HashElem *); pReader = (Fts3SegReader *)sqlite3_malloc(nByte); if( !pReader ){ rc = SQLITE_NOMEM; }else{ memset(pReader, 0, nByte); pReader->iIdx = 0x7FFFFFFF; pReader->ppNextElem = (Fts3HashElem **)&pReader[1]; memcpy(pReader->ppNextElem, aElem, nElem*sizeof(Fts3HashElem *)); } } if( bPrefix ){ sqlite3_free(aElem); } *ppReader = pReader; return rc; } /* ** Compare the entries pointed to by two Fts3SegReader structures. ** Comparison is as follows: ** ** 1) EOF is greater than not EOF. ** ** 2) The current terms (if any) are compared using memcmp(). If one ** term is a prefix of another, the longer term is considered the ** larger. ** ** 3) By segment age. An older segment is considered larger. */ static int fts3SegReaderCmp(Fts3SegReader *pLhs, Fts3SegReader *pRhs){ int rc; if( pLhs->aNode && pRhs->aNode ){ int rc2 = pLhs->nTerm - pRhs->nTerm; if( rc2<0 ){ rc = memcmp(pLhs->zTerm, pRhs->zTerm, pLhs->nTerm); }else{ rc = memcmp(pLhs->zTerm, pRhs->zTerm, pRhs->nTerm); } if( rc==0 ){ rc = rc2; } }else{ rc = (pLhs->aNode==0) - (pRhs->aNode==0); } if( rc==0 ){ rc = pRhs->iIdx - pLhs->iIdx; } assert( rc!=0 ); return rc; } /* ** A different comparison function for SegReader structures. In this ** version, it is assumed that each SegReader points to an entry in ** a doclist for identical terms. Comparison is made as follows: ** ** 1) EOF (end of doclist in this case) is greater than not EOF. ** ** 2) By current docid. ** ** 3) By segment age. An older segment is considered larger. */ static int fts3SegReaderDoclistCmp(Fts3SegReader *pLhs, Fts3SegReader *pRhs){ int rc = (pLhs->pOffsetList==0)-(pRhs->pOffsetList==0); if( rc==0 ){ if( pLhs->iDocid==pRhs->iDocid ){ rc = pRhs->iIdx - pLhs->iIdx; }else{ rc = (pLhs->iDocid > pRhs->iDocid) ? 1 : -1; } } assert( pLhs->aNode && pRhs->aNode ); return rc; } static int fts3SegReaderDoclistCmpRev(Fts3SegReader *pLhs, Fts3SegReader *pRhs){ int rc = (pLhs->pOffsetList==0)-(pRhs->pOffsetList==0); if( rc==0 ){ if( pLhs->iDocid==pRhs->iDocid ){ rc = pRhs->iIdx - pLhs->iIdx; }else{ rc = (pLhs->iDocid < pRhs->iDocid) ? 1 : -1; } } assert( pLhs->aNode && pRhs->aNode ); return rc; } /* ** Compare the term that the Fts3SegReader object passed as the first argument ** points to with the term specified by arguments zTerm and nTerm. ** ** If the pSeg iterator is already at EOF, return 0. Otherwise, return ** -ve if the pSeg term is less than zTerm/nTerm, 0 if the two terms are ** equal, or +ve if the pSeg term is greater than zTerm/nTerm. */ static int fts3SegReaderTermCmp( Fts3SegReader *pSeg, /* Segment reader object */ const char *zTerm, /* Term to compare to */ int nTerm /* Size of term zTerm in bytes */ ){ int res = 0; if( pSeg->aNode ){ if( pSeg->nTerm>nTerm ){ res = memcmp(pSeg->zTerm, zTerm, nTerm); }else{ res = memcmp(pSeg->zTerm, zTerm, pSeg->nTerm); } if( res==0 ){ res = pSeg->nTerm-nTerm; } } return res; } /* ** Argument apSegment is an array of nSegment elements. It is known that ** the final (nSegment-nSuspect) members are already in sorted order ** (according to the comparison function provided). This function shuffles ** the array around until all entries are in sorted order. */ static void fts3SegReaderSort( Fts3SegReader **apSegment, /* Array to sort entries of */ int nSegment, /* Size of apSegment array */ int nSuspect, /* Unsorted entry count */ int (*xCmp)(Fts3SegReader *, Fts3SegReader *) /* Comparison function */ ){ int i; /* Iterator variable */ assert( nSuspect<=nSegment ); if( nSuspect==nSegment ) nSuspect--; for(i=nSuspect-1; i>=0; i--){ int j; for(j=i; j<(nSegment-1); j++){ Fts3SegReader *pTmp; if( xCmp(apSegment[j], apSegment[j+1])<0 ) break; pTmp = apSegment[j+1]; apSegment[j+1] = apSegment[j]; apSegment[j] = pTmp; } } #ifndef NDEBUG /* Check that the list really is sorted now. */ for(i=0; i<(nSuspect-1); i++){ assert( xCmp(apSegment[i], apSegment[i+1])<0 ); } #endif } /* ** Insert a record into the %_segments table. */ static int fts3WriteSegment( Fts3Table *p, /* Virtual table handle */ sqlite3_int64 iBlock, /* Block id for new block */ char *z, /* Pointer to buffer containing block data */ int n /* Size of buffer z in bytes */ ){ sqlite3_stmt *pStmt; int rc = fts3SqlStmt(p, SQL_INSERT_SEGMENTS, &pStmt, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pStmt, 1, iBlock); sqlite3_bind_blob(pStmt, 2, z, n, SQLITE_STATIC); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); } return rc; } /* ** Find the largest relative level number in the table. If successful, set ** *pnMax to this value and return SQLITE_OK. Otherwise, if an error occurs, ** set *pnMax to zero and return an SQLite error code. */ int sqlite3Fts3MaxLevel(Fts3Table *p, int *pnMax){ int rc; int mxLevel = 0; sqlite3_stmt *pStmt = 0; rc = fts3SqlStmt(p, SQL_SELECT_MXLEVEL, &pStmt, 0); if( rc==SQLITE_OK ){ if( SQLITE_ROW==sqlite3_step(pStmt) ){ mxLevel = sqlite3_column_int(pStmt, 0); } rc = sqlite3_reset(pStmt); } *pnMax = mxLevel; return rc; } /* ** Insert a record into the %_segdir table. */ static int fts3WriteSegdir( Fts3Table *p, /* Virtual table handle */ sqlite3_int64 iLevel, /* Value for "level" field (absolute level) */ int iIdx, /* Value for "idx" field */ sqlite3_int64 iStartBlock, /* Value for "start_block" field */ sqlite3_int64 iLeafEndBlock, /* Value for "leaves_end_block" field */ sqlite3_int64 iEndBlock, /* Value for "end_block" field */ sqlite3_int64 nLeafData, /* Bytes of leaf data in segment */ char *zRoot, /* Blob value for "root" field */ int nRoot /* Number of bytes in buffer zRoot */ ){ sqlite3_stmt *pStmt; int rc = fts3SqlStmt(p, SQL_INSERT_SEGDIR, &pStmt, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pStmt, 1, iLevel); sqlite3_bind_int(pStmt, 2, iIdx); sqlite3_bind_int64(pStmt, 3, iStartBlock); sqlite3_bind_int64(pStmt, 4, iLeafEndBlock); if( nLeafData==0 ){ sqlite3_bind_int64(pStmt, 5, iEndBlock); }else{ char *zEnd = sqlite3_mprintf("%lld %lld", iEndBlock, nLeafData); if( !zEnd ) return SQLITE_NOMEM; sqlite3_bind_text(pStmt, 5, zEnd, -1, sqlite3_free); } sqlite3_bind_blob(pStmt, 6, zRoot, nRoot, SQLITE_STATIC); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); } return rc; } /* ** Return the size of the common prefix (if any) shared by zPrev and ** zNext, in bytes. For example, ** ** fts3PrefixCompress("abc", 3, "abcdef", 6) // returns 3 ** fts3PrefixCompress("abX", 3, "abcdef", 6) // returns 2 ** fts3PrefixCompress("abX", 3, "Xbcdef", 6) // returns 0 */ static int fts3PrefixCompress( const char *zPrev, /* Buffer containing previous term */ int nPrev, /* Size of buffer zPrev in bytes */ const char *zNext, /* Buffer containing next term */ int nNext /* Size of buffer zNext in bytes */ ){ int n; UNUSED_PARAMETER(nNext); for(n=0; nnData; /* Current size of node in bytes */ int nReq = nData; /* Required space after adding zTerm */ int nPrefix; /* Number of bytes of prefix compression */ int nSuffix; /* Suffix length */ nPrefix = fts3PrefixCompress(pTree->zTerm, pTree->nTerm, zTerm, nTerm); nSuffix = nTerm-nPrefix; nReq += sqlite3Fts3VarintLen(nPrefix)+sqlite3Fts3VarintLen(nSuffix)+nSuffix; if( nReq<=p->nNodeSize || !pTree->zTerm ){ if( nReq>p->nNodeSize ){ /* An unusual case: this is the first term to be added to the node ** and the static node buffer (p->nNodeSize bytes) is not large ** enough. Use a separately malloced buffer instead This wastes ** p->nNodeSize bytes, but since this scenario only comes about when ** the database contain two terms that share a prefix of almost 2KB, ** this is not expected to be a serious problem. */ assert( pTree->aData==(char *)&pTree[1] ); pTree->aData = (char *)sqlite3_malloc(nReq); if( !pTree->aData ){ return SQLITE_NOMEM; } } if( pTree->zTerm ){ /* There is no prefix-length field for first term in a node */ nData += sqlite3Fts3PutVarint(&pTree->aData[nData], nPrefix); } nData += sqlite3Fts3PutVarint(&pTree->aData[nData], nSuffix); memcpy(&pTree->aData[nData], &zTerm[nPrefix], nSuffix); pTree->nData = nData + nSuffix; pTree->nEntry++; if( isCopyTerm ){ if( pTree->nMalloczMalloc, nTerm*2); if( !zNew ){ return SQLITE_NOMEM; } pTree->nMalloc = nTerm*2; pTree->zMalloc = zNew; } pTree->zTerm = pTree->zMalloc; memcpy(pTree->zTerm, zTerm, nTerm); pTree->nTerm = nTerm; }else{ pTree->zTerm = (char *)zTerm; pTree->nTerm = nTerm; } return SQLITE_OK; } } /* If control flows to here, it was not possible to append zTerm to the ** current node. Create a new node (a right-sibling of the current node). ** If this is the first node in the tree, the term is added to it. ** ** Otherwise, the term is not added to the new node, it is left empty for ** now. Instead, the term is inserted into the parent of pTree. If pTree ** has no parent, one is created here. */ pNew = (SegmentNode *)sqlite3_malloc(sizeof(SegmentNode) + p->nNodeSize); if( !pNew ){ return SQLITE_NOMEM; } memset(pNew, 0, sizeof(SegmentNode)); pNew->nData = 1 + FTS3_VARINT_MAX; pNew->aData = (char *)&pNew[1]; if( pTree ){ SegmentNode *pParent = pTree->pParent; rc = fts3NodeAddTerm(p, &pParent, isCopyTerm, zTerm, nTerm); if( pTree->pParent==0 ){ pTree->pParent = pParent; } pTree->pRight = pNew; pNew->pLeftmost = pTree->pLeftmost; pNew->pParent = pParent; pNew->zMalloc = pTree->zMalloc; pNew->nMalloc = pTree->nMalloc; pTree->zMalloc = 0; }else{ pNew->pLeftmost = pNew; rc = fts3NodeAddTerm(p, &pNew, isCopyTerm, zTerm, nTerm); } *ppTree = pNew; return rc; } /* ** Helper function for fts3NodeWrite(). */ static int fts3TreeFinishNode( SegmentNode *pTree, int iHeight, sqlite3_int64 iLeftChild ){ int nStart; assert( iHeight>=1 && iHeight<128 ); nStart = FTS3_VARINT_MAX - sqlite3Fts3VarintLen(iLeftChild); pTree->aData[nStart] = (char)iHeight; sqlite3Fts3PutVarint(&pTree->aData[nStart+1], iLeftChild); return nStart; } /* ** Write the buffer for the segment node pTree and all of its peers to the ** database. Then call this function recursively to write the parent of ** pTree and its peers to the database. ** ** Except, if pTree is a root node, do not write it to the database. Instead, ** set output variables *paRoot and *pnRoot to contain the root node. ** ** If successful, SQLITE_OK is returned and output variable *piLast is ** set to the largest blockid written to the database (or zero if no ** blocks were written to the db). Otherwise, an SQLite error code is ** returned. */ static int fts3NodeWrite( Fts3Table *p, /* Virtual table handle */ SegmentNode *pTree, /* SegmentNode handle */ int iHeight, /* Height of this node in tree */ sqlite3_int64 iLeaf, /* Block id of first leaf node */ sqlite3_int64 iFree, /* Block id of next free slot in %_segments */ sqlite3_int64 *piLast, /* OUT: Block id of last entry written */ char **paRoot, /* OUT: Data for root node */ int *pnRoot /* OUT: Size of root node in bytes */ ){ int rc = SQLITE_OK; if( !pTree->pParent ){ /* Root node of the tree. */ int nStart = fts3TreeFinishNode(pTree, iHeight, iLeaf); *piLast = iFree-1; *pnRoot = pTree->nData - nStart; *paRoot = &pTree->aData[nStart]; }else{ SegmentNode *pIter; sqlite3_int64 iNextFree = iFree; sqlite3_int64 iNextLeaf = iLeaf; for(pIter=pTree->pLeftmost; pIter && rc==SQLITE_OK; pIter=pIter->pRight){ int nStart = fts3TreeFinishNode(pIter, iHeight, iNextLeaf); int nWrite = pIter->nData - nStart; rc = fts3WriteSegment(p, iNextFree, &pIter->aData[nStart], nWrite); iNextFree++; iNextLeaf += (pIter->nEntry+1); } if( rc==SQLITE_OK ){ assert( iNextLeaf==iFree ); rc = fts3NodeWrite( p, pTree->pParent, iHeight+1, iFree, iNextFree, piLast, paRoot, pnRoot ); } } return rc; } /* ** Free all memory allocations associated with the tree pTree. */ static void fts3NodeFree(SegmentNode *pTree){ if( pTree ){ SegmentNode *p = pTree->pLeftmost; fts3NodeFree(p->pParent); while( p ){ SegmentNode *pRight = p->pRight; if( p->aData!=(char *)&p[1] ){ sqlite3_free(p->aData); } assert( pRight==0 || p->zMalloc==0 ); sqlite3_free(p->zMalloc); sqlite3_free(p); p = pRight; } } } /* ** Add a term to the segment being constructed by the SegmentWriter object ** *ppWriter. When adding the first term to a segment, *ppWriter should ** be passed NULL. This function will allocate a new SegmentWriter object ** and return it via the input/output variable *ppWriter in this case. ** ** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. */ static int fts3SegWriterAdd( Fts3Table *p, /* Virtual table handle */ SegmentWriter **ppWriter, /* IN/OUT: SegmentWriter handle */ int isCopyTerm, /* True if buffer zTerm must be copied */ const char *zTerm, /* Pointer to buffer containing term */ int nTerm, /* Size of term in bytes */ const char *aDoclist, /* Pointer to buffer containing doclist */ int nDoclist /* Size of doclist in bytes */ ){ int nPrefix; /* Size of term prefix in bytes */ int nSuffix; /* Size of term suffix in bytes */ int nReq; /* Number of bytes required on leaf page */ int nData; SegmentWriter *pWriter = *ppWriter; if( !pWriter ){ int rc; sqlite3_stmt *pStmt; /* Allocate the SegmentWriter structure */ pWriter = (SegmentWriter *)sqlite3_malloc(sizeof(SegmentWriter)); if( !pWriter ) return SQLITE_NOMEM; memset(pWriter, 0, sizeof(SegmentWriter)); *ppWriter = pWriter; /* Allocate a buffer in which to accumulate data */ pWriter->aData = (char *)sqlite3_malloc(p->nNodeSize); if( !pWriter->aData ) return SQLITE_NOMEM; pWriter->nSize = p->nNodeSize; /* Find the next free blockid in the %_segments table */ rc = fts3SqlStmt(p, SQL_NEXT_SEGMENTS_ID, &pStmt, 0); if( rc!=SQLITE_OK ) return rc; if( SQLITE_ROW==sqlite3_step(pStmt) ){ pWriter->iFree = sqlite3_column_int64(pStmt, 0); pWriter->iFirst = pWriter->iFree; } rc = sqlite3_reset(pStmt); if( rc!=SQLITE_OK ) return rc; } nData = pWriter->nData; nPrefix = fts3PrefixCompress(pWriter->zTerm, pWriter->nTerm, zTerm, nTerm); nSuffix = nTerm-nPrefix; /* Figure out how many bytes are required by this new entry */ nReq = sqlite3Fts3VarintLen(nPrefix) + /* varint containing prefix size */ sqlite3Fts3VarintLen(nSuffix) + /* varint containing suffix size */ nSuffix + /* Term suffix */ sqlite3Fts3VarintLen(nDoclist) + /* Size of doclist */ nDoclist; /* Doclist data */ if( nData>0 && nData+nReq>p->nNodeSize ){ int rc; /* The current leaf node is full. Write it out to the database. */ rc = fts3WriteSegment(p, pWriter->iFree++, pWriter->aData, nData); if( rc!=SQLITE_OK ) return rc; p->nLeafAdd++; /* Add the current term to the interior node tree. The term added to ** the interior tree must: ** ** a) be greater than the largest term on the leaf node just written ** to the database (still available in pWriter->zTerm), and ** ** b) be less than or equal to the term about to be added to the new ** leaf node (zTerm/nTerm). ** ** In other words, it must be the prefix of zTerm 1 byte longer than ** the common prefix (if any) of zTerm and pWriter->zTerm. */ assert( nPrefixpTree, isCopyTerm, zTerm, nPrefix+1); if( rc!=SQLITE_OK ) return rc; nData = 0; pWriter->nTerm = 0; nPrefix = 0; nSuffix = nTerm; nReq = 1 + /* varint containing prefix size */ sqlite3Fts3VarintLen(nTerm) + /* varint containing suffix size */ nTerm + /* Term suffix */ sqlite3Fts3VarintLen(nDoclist) + /* Size of doclist */ nDoclist; /* Doclist data */ } /* Increase the total number of bytes written to account for the new entry. */ pWriter->nLeafData += nReq; /* If the buffer currently allocated is too small for this entry, realloc ** the buffer to make it large enough. */ if( nReq>pWriter->nSize ){ char *aNew = sqlite3_realloc(pWriter->aData, nReq); if( !aNew ) return SQLITE_NOMEM; pWriter->aData = aNew; pWriter->nSize = nReq; } assert( nData+nReq<=pWriter->nSize ); /* Append the prefix-compressed term and doclist to the buffer. */ nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nPrefix); nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nSuffix); memcpy(&pWriter->aData[nData], &zTerm[nPrefix], nSuffix); nData += nSuffix; nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nDoclist); memcpy(&pWriter->aData[nData], aDoclist, nDoclist); pWriter->nData = nData + nDoclist; /* Save the current term so that it can be used to prefix-compress the next. ** If the isCopyTerm parameter is true, then the buffer pointed to by ** zTerm is transient, so take a copy of the term data. Otherwise, just ** store a copy of the pointer. */ if( isCopyTerm ){ if( nTerm>pWriter->nMalloc ){ char *zNew = sqlite3_realloc(pWriter->zMalloc, nTerm*2); if( !zNew ){ return SQLITE_NOMEM; } pWriter->nMalloc = nTerm*2; pWriter->zMalloc = zNew; pWriter->zTerm = zNew; } assert( pWriter->zTerm==pWriter->zMalloc ); memcpy(pWriter->zTerm, zTerm, nTerm); }else{ pWriter->zTerm = (char *)zTerm; } pWriter->nTerm = nTerm; return SQLITE_OK; } /* ** Flush all data associated with the SegmentWriter object pWriter to the ** database. This function must be called after all terms have been added ** to the segment using fts3SegWriterAdd(). If successful, SQLITE_OK is ** returned. Otherwise, an SQLite error code. */ static int fts3SegWriterFlush( Fts3Table *p, /* Virtual table handle */ SegmentWriter *pWriter, /* SegmentWriter to flush to the db */ sqlite3_int64 iLevel, /* Value for 'level' column of %_segdir */ int iIdx /* Value for 'idx' column of %_segdir */ ){ int rc; /* Return code */ if( pWriter->pTree ){ sqlite3_int64 iLast = 0; /* Largest block id written to database */ sqlite3_int64 iLastLeaf; /* Largest leaf block id written to db */ char *zRoot = NULL; /* Pointer to buffer containing root node */ int nRoot = 0; /* Size of buffer zRoot */ iLastLeaf = pWriter->iFree; rc = fts3WriteSegment(p, pWriter->iFree++, pWriter->aData, pWriter->nData); if( rc==SQLITE_OK ){ rc = fts3NodeWrite(p, pWriter->pTree, 1, pWriter->iFirst, pWriter->iFree, &iLast, &zRoot, &nRoot); } if( rc==SQLITE_OK ){ rc = fts3WriteSegdir(p, iLevel, iIdx, pWriter->iFirst, iLastLeaf, iLast, pWriter->nLeafData, zRoot, nRoot); } }else{ /* The entire tree fits on the root node. Write it to the segdir table. */ rc = fts3WriteSegdir(p, iLevel, iIdx, 0, 0, 0, pWriter->nLeafData, pWriter->aData, pWriter->nData); } p->nLeafAdd++; return rc; } /* ** Release all memory held by the SegmentWriter object passed as the ** first argument. */ static void fts3SegWriterFree(SegmentWriter *pWriter){ if( pWriter ){ sqlite3_free(pWriter->aData); sqlite3_free(pWriter->zMalloc); fts3NodeFree(pWriter->pTree); sqlite3_free(pWriter); } } /* ** The first value in the apVal[] array is assumed to contain an integer. ** This function tests if there exist any documents with docid values that ** are different from that integer. i.e. if deleting the document with docid ** pRowid would mean the FTS3 table were empty. ** ** If successful, *pisEmpty is set to true if the table is empty except for ** document pRowid, or false otherwise, and SQLITE_OK is returned. If an ** error occurs, an SQLite error code is returned. */ static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){ sqlite3_stmt *pStmt; int rc; if( p->zContentTbl ){ /* If using the content=xxx option, assume the table is never empty */ *pisEmpty = 0; rc = SQLITE_OK; }else{ rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid); if( rc==SQLITE_OK ){ if( SQLITE_ROW==sqlite3_step(pStmt) ){ *pisEmpty = sqlite3_column_int(pStmt, 0); } rc = sqlite3_reset(pStmt); } } return rc; } /* ** Set *pnMax to the largest segment level in the database for the index ** iIndex. ** ** Segment levels are stored in the 'level' column of the %_segdir table. ** ** Return SQLITE_OK if successful, or an SQLite error code if not. */ static int fts3SegmentMaxLevel( Fts3Table *p, int iLangid, int iIndex, sqlite3_int64 *pnMax ){ sqlite3_stmt *pStmt; int rc; assert( iIndex>=0 && iIndexnIndex ); /* Set pStmt to the compiled version of: ** ** SELECT max(level) FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ? ** ** (1024 is actually the value of macro FTS3_SEGDIR_PREFIXLEVEL_STR). */ rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_MAX_LEVEL, &pStmt, 0); if( rc!=SQLITE_OK ) return rc; sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex, 0)); sqlite3_bind_int64(pStmt, 2, getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1) ); if( SQLITE_ROW==sqlite3_step(pStmt) ){ *pnMax = sqlite3_column_int64(pStmt, 0); } return sqlite3_reset(pStmt); } /* ** iAbsLevel is an absolute level that may be assumed to exist within ** the database. This function checks if it is the largest level number ** within its index. Assuming no error occurs, *pbMax is set to 1 if ** iAbsLevel is indeed the largest level, or 0 otherwise, and SQLITE_OK ** is returned. If an error occurs, an error code is returned and the ** final value of *pbMax is undefined. */ static int fts3SegmentIsMaxLevel(Fts3Table *p, i64 iAbsLevel, int *pbMax){ /* Set pStmt to the compiled version of: ** ** SELECT max(level) FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ? ** ** (1024 is actually the value of macro FTS3_SEGDIR_PREFIXLEVEL_STR). */ sqlite3_stmt *pStmt; int rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_MAX_LEVEL, &pStmt, 0); if( rc!=SQLITE_OK ) return rc; sqlite3_bind_int64(pStmt, 1, iAbsLevel+1); sqlite3_bind_int64(pStmt, 2, ((iAbsLevel/FTS3_SEGDIR_MAXLEVEL)+1) * FTS3_SEGDIR_MAXLEVEL ); *pbMax = 0; if( SQLITE_ROW==sqlite3_step(pStmt) ){ *pbMax = sqlite3_column_type(pStmt, 0)==SQLITE_NULL; } return sqlite3_reset(pStmt); } /* ** Delete all entries in the %_segments table associated with the segment ** opened with seg-reader pSeg. This function does not affect the contents ** of the %_segdir table. */ static int fts3DeleteSegment( Fts3Table *p, /* FTS table handle */ Fts3SegReader *pSeg /* Segment to delete */ ){ int rc = SQLITE_OK; /* Return code */ if( pSeg->iStartBlock ){ sqlite3_stmt *pDelete; /* SQL statement to delete rows */ rc = fts3SqlStmt(p, SQL_DELETE_SEGMENTS_RANGE, &pDelete, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pDelete, 1, pSeg->iStartBlock); sqlite3_bind_int64(pDelete, 2, pSeg->iEndBlock); sqlite3_step(pDelete); rc = sqlite3_reset(pDelete); } } return rc; } /* ** This function is used after merging multiple segments into a single large ** segment to delete the old, now redundant, segment b-trees. Specifically, ** it: ** ** 1) Deletes all %_segments entries for the segments associated with ** each of the SegReader objects in the array passed as the third ** argument, and ** ** 2) deletes all %_segdir entries with level iLevel, or all %_segdir ** entries regardless of level if (iLevel<0). ** ** SQLITE_OK is returned if successful, otherwise an SQLite error code. */ static int fts3DeleteSegdir( Fts3Table *p, /* Virtual table handle */ int iLangid, /* Language id */ int iIndex, /* Index for p->aIndex */ int iLevel, /* Level of %_segdir entries to delete */ Fts3SegReader **apSegment, /* Array of SegReader objects */ int nReader /* Size of array apSegment */ ){ int rc = SQLITE_OK; /* Return Code */ int i; /* Iterator variable */ sqlite3_stmt *pDelete = 0; /* SQL statement to delete rows */ for(i=0; rc==SQLITE_OK && i=0 || iLevel==FTS3_SEGCURSOR_ALL ); if( iLevel==FTS3_SEGCURSOR_ALL ){ rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_RANGE, &pDelete, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pDelete, 1, getAbsoluteLevel(p, iLangid, iIndex, 0)); sqlite3_bind_int64(pDelete, 2, getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1) ); } }else{ rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_LEVEL, &pDelete, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64( pDelete, 1, getAbsoluteLevel(p, iLangid, iIndex, iLevel) ); } } if( rc==SQLITE_OK ){ sqlite3_step(pDelete); rc = sqlite3_reset(pDelete); } return rc; } /* ** When this function is called, buffer *ppList (size *pnList bytes) contains ** a position list that may (or may not) feature multiple columns. This ** function adjusts the pointer *ppList and the length *pnList so that they ** identify the subset of the position list that corresponds to column iCol. ** ** If there are no entries in the input position list for column iCol, then ** *pnList is set to zero before returning. ** ** If parameter bZero is non-zero, then any part of the input list following ** the end of the output list is zeroed before returning. */ static void fts3ColumnFilter( int iCol, /* Column to filter on */ int bZero, /* Zero out anything following *ppList */ char **ppList, /* IN/OUT: Pointer to position list */ int *pnList /* IN/OUT: Size of buffer *ppList in bytes */ ){ char *pList = *ppList; int nList = *pnList; char *pEnd = &pList[nList]; int iCurrent = 0; char *p = pList; assert( iCol>=0 ); while( 1 ){ char c = 0; while( ppMsr->nBuffer ){ char *pNew; pMsr->nBuffer = nList*2; pNew = (char *)sqlite3_realloc(pMsr->aBuffer, pMsr->nBuffer); if( !pNew ) return SQLITE_NOMEM; pMsr->aBuffer = pNew; } memcpy(pMsr->aBuffer, pList, nList); return SQLITE_OK; } int sqlite3Fts3MsrIncrNext( Fts3Table *p, /* Virtual table handle */ Fts3MultiSegReader *pMsr, /* Multi-segment-reader handle */ sqlite3_int64 *piDocid, /* OUT: Docid value */ char **paPoslist, /* OUT: Pointer to position list */ int *pnPoslist /* OUT: Size of position list in bytes */ ){ int nMerge = pMsr->nAdvance; Fts3SegReader **apSegment = pMsr->apSegment; int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = ( p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp ); if( nMerge==0 ){ *paPoslist = 0; return SQLITE_OK; } while( 1 ){ Fts3SegReader *pSeg; pSeg = pMsr->apSegment[0]; if( pSeg->pOffsetList==0 ){ *paPoslist = 0; break; }else{ int rc; char *pList; int nList; int j; sqlite3_int64 iDocid = apSegment[0]->iDocid; rc = fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList); j = 1; while( rc==SQLITE_OK && jpOffsetList && apSegment[j]->iDocid==iDocid ){ rc = fts3SegReaderNextDocid(p, apSegment[j], 0, 0); j++; } if( rc!=SQLITE_OK ) return rc; fts3SegReaderSort(pMsr->apSegment, nMerge, j, xCmp); if( nList>0 && fts3SegReaderIsPending(apSegment[0]) ){ rc = fts3MsrBufferData(pMsr, pList, nList+1); if( rc!=SQLITE_OK ) return rc; assert( (pMsr->aBuffer[nList] & 0xFE)==0x00 ); pList = pMsr->aBuffer; } if( pMsr->iColFilter>=0 ){ fts3ColumnFilter(pMsr->iColFilter, 1, &pList, &nList); } if( nList>0 ){ *paPoslist = pList; *piDocid = iDocid; *pnPoslist = nList; break; } } } return SQLITE_OK; } static int fts3SegReaderStart( Fts3Table *p, /* Virtual table handle */ Fts3MultiSegReader *pCsr, /* Cursor object */ const char *zTerm, /* Term searched for (or NULL) */ int nTerm /* Length of zTerm in bytes */ ){ int i; int nSeg = pCsr->nSegment; /* If the Fts3SegFilter defines a specific term (or term prefix) to search ** for, then advance each segment iterator until it points to a term of ** equal or greater value than the specified term. This prevents many ** unnecessary merge/sort operations for the case where single segment ** b-tree leaf nodes contain more than one term. */ for(i=0; pCsr->bRestart==0 && inSegment; i++){ int res = 0; Fts3SegReader *pSeg = pCsr->apSegment[i]; do { int rc = fts3SegReaderNext(p, pSeg, 0); if( rc!=SQLITE_OK ) return rc; }while( zTerm && (res = fts3SegReaderTermCmp(pSeg, zTerm, nTerm))<0 ); if( pSeg->bLookup && res!=0 ){ fts3SegReaderSetEof(pSeg); } } fts3SegReaderSort(pCsr->apSegment, nSeg, nSeg, fts3SegReaderCmp); return SQLITE_OK; } int sqlite3Fts3SegReaderStart( Fts3Table *p, /* Virtual table handle */ Fts3MultiSegReader *pCsr, /* Cursor object */ Fts3SegFilter *pFilter /* Restrictions on range of iteration */ ){ pCsr->pFilter = pFilter; return fts3SegReaderStart(p, pCsr, pFilter->zTerm, pFilter->nTerm); } int sqlite3Fts3MsrIncrStart( Fts3Table *p, /* Virtual table handle */ Fts3MultiSegReader *pCsr, /* Cursor object */ int iCol, /* Column to match on. */ const char *zTerm, /* Term to iterate through a doclist for */ int nTerm /* Number of bytes in zTerm */ ){ int i; int rc; int nSegment = pCsr->nSegment; int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = ( p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp ); assert( pCsr->pFilter==0 ); assert( zTerm && nTerm>0 ); /* Advance each segment iterator until it points to the term zTerm/nTerm. */ rc = fts3SegReaderStart(p, pCsr, zTerm, nTerm); if( rc!=SQLITE_OK ) return rc; /* Determine how many of the segments actually point to zTerm/nTerm. */ for(i=0; iapSegment[i]; if( !pSeg->aNode || fts3SegReaderTermCmp(pSeg, zTerm, nTerm) ){ break; } } pCsr->nAdvance = i; /* Advance each of the segments to point to the first docid. */ for(i=0; inAdvance; i++){ rc = fts3SegReaderFirstDocid(p, pCsr->apSegment[i]); if( rc!=SQLITE_OK ) return rc; } fts3SegReaderSort(pCsr->apSegment, i, i, xCmp); assert( iCol<0 || iColnColumn ); pCsr->iColFilter = iCol; return SQLITE_OK; } /* ** This function is called on a MultiSegReader that has been started using ** sqlite3Fts3MsrIncrStart(). One or more calls to MsrIncrNext() may also ** have been made. Calling this function puts the MultiSegReader in such ** a state that if the next two calls are: ** ** sqlite3Fts3SegReaderStart() ** sqlite3Fts3SegReaderStep() ** ** then the entire doclist for the term is available in ** MultiSegReader.aDoclist/nDoclist. */ int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr){ int i; /* Used to iterate through segment-readers */ assert( pCsr->zTerm==0 ); assert( pCsr->nTerm==0 ); assert( pCsr->aDoclist==0 ); assert( pCsr->nDoclist==0 ); pCsr->nAdvance = 0; pCsr->bRestart = 1; for(i=0; inSegment; i++){ pCsr->apSegment[i]->pOffsetList = 0; pCsr->apSegment[i]->nOffsetList = 0; pCsr->apSegment[i]->iDocid = 0; } return SQLITE_OK; } int sqlite3Fts3SegReaderStep( Fts3Table *p, /* Virtual table handle */ Fts3MultiSegReader *pCsr /* Cursor object */ ){ int rc = SQLITE_OK; int isIgnoreEmpty = (pCsr->pFilter->flags & FTS3_SEGMENT_IGNORE_EMPTY); int isRequirePos = (pCsr->pFilter->flags & FTS3_SEGMENT_REQUIRE_POS); int isColFilter = (pCsr->pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER); int isPrefix = (pCsr->pFilter->flags & FTS3_SEGMENT_PREFIX); int isScan = (pCsr->pFilter->flags & FTS3_SEGMENT_SCAN); int isFirst = (pCsr->pFilter->flags & FTS3_SEGMENT_FIRST); Fts3SegReader **apSegment = pCsr->apSegment; int nSegment = pCsr->nSegment; Fts3SegFilter *pFilter = pCsr->pFilter; int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = ( p->bDescIdx ? fts3SegReaderDoclistCmpRev : fts3SegReaderDoclistCmp ); if( pCsr->nSegment==0 ) return SQLITE_OK; do { int nMerge; int i; /* Advance the first pCsr->nAdvance entries in the apSegment[] array ** forward. Then sort the list in order of current term again. */ for(i=0; inAdvance; i++){ Fts3SegReader *pSeg = apSegment[i]; if( pSeg->bLookup ){ fts3SegReaderSetEof(pSeg); }else{ rc = fts3SegReaderNext(p, pSeg, 0); } if( rc!=SQLITE_OK ) return rc; } fts3SegReaderSort(apSegment, nSegment, pCsr->nAdvance, fts3SegReaderCmp); pCsr->nAdvance = 0; /* If all the seg-readers are at EOF, we're finished. return SQLITE_OK. */ assert( rc==SQLITE_OK ); if( apSegment[0]->aNode==0 ) break; pCsr->nTerm = apSegment[0]->nTerm; pCsr->zTerm = apSegment[0]->zTerm; /* If this is a prefix-search, and if the term that apSegment[0] points ** to does not share a suffix with pFilter->zTerm/nTerm, then all ** required callbacks have been made. In this case exit early. ** ** Similarly, if this is a search for an exact match, and the first term ** of segment apSegment[0] is not a match, exit early. */ if( pFilter->zTerm && !isScan ){ if( pCsr->nTermnTerm || (!isPrefix && pCsr->nTerm>pFilter->nTerm) || memcmp(pCsr->zTerm, pFilter->zTerm, pFilter->nTerm) ){ break; } } nMerge = 1; while( nMergeaNode && apSegment[nMerge]->nTerm==pCsr->nTerm && 0==memcmp(pCsr->zTerm, apSegment[nMerge]->zTerm, pCsr->nTerm) ){ nMerge++; } assert( isIgnoreEmpty || (isRequirePos && !isColFilter) ); if( nMerge==1 && !isIgnoreEmpty && !isFirst && (p->bDescIdx==0 || fts3SegReaderIsPending(apSegment[0])==0) ){ pCsr->nDoclist = apSegment[0]->nDoclist; if( fts3SegReaderIsPending(apSegment[0]) ){ rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, pCsr->nDoclist); pCsr->aDoclist = pCsr->aBuffer; }else{ pCsr->aDoclist = apSegment[0]->aDoclist; } if( rc==SQLITE_OK ) rc = SQLITE_ROW; }else{ int nDoclist = 0; /* Size of doclist */ sqlite3_int64 iPrev = 0; /* Previous docid stored in doclist */ /* The current term of the first nMerge entries in the array ** of Fts3SegReader objects is the same. The doclists must be merged ** and a single term returned with the merged doclist. */ for(i=0; ipOffsetList ){ int j; /* Number of segments that share a docid */ char *pList = 0; int nList = 0; int nByte; sqlite3_int64 iDocid = apSegment[0]->iDocid; fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList); j = 1; while( jpOffsetList && apSegment[j]->iDocid==iDocid ){ fts3SegReaderNextDocid(p, apSegment[j], 0, 0); j++; } if( isColFilter ){ fts3ColumnFilter(pFilter->iCol, 0, &pList, &nList); } if( !isIgnoreEmpty || nList>0 ){ /* Calculate the 'docid' delta value to write into the merged ** doclist. */ sqlite3_int64 iDelta; if( p->bDescIdx && nDoclist>0 ){ iDelta = iPrev - iDocid; }else{ iDelta = iDocid - iPrev; } assert( iDelta>0 || (nDoclist==0 && iDelta==iDocid) ); assert( nDoclist>0 || iDelta==iDocid ); nByte = sqlite3Fts3VarintLen(iDelta) + (isRequirePos?nList+1:0); if( nDoclist+nByte>pCsr->nBuffer ){ char *aNew; pCsr->nBuffer = (nDoclist+nByte)*2; aNew = sqlite3_realloc(pCsr->aBuffer, pCsr->nBuffer); if( !aNew ){ return SQLITE_NOMEM; } pCsr->aBuffer = aNew; } if( isFirst ){ char *a = &pCsr->aBuffer[nDoclist]; int nWrite; nWrite = sqlite3Fts3FirstFilter(iDelta, pList, nList, a); if( nWrite ){ iPrev = iDocid; nDoclist += nWrite; } }else{ nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta); iPrev = iDocid; if( isRequirePos ){ memcpy(&pCsr->aBuffer[nDoclist], pList, nList); nDoclist += nList; pCsr->aBuffer[nDoclist++] = '\0'; } } } fts3SegReaderSort(apSegment, nMerge, j, xCmp); } if( nDoclist>0 ){ pCsr->aDoclist = pCsr->aBuffer; pCsr->nDoclist = nDoclist; rc = SQLITE_ROW; } } pCsr->nAdvance = nMerge; }while( rc==SQLITE_OK ); return rc; } void sqlite3Fts3SegReaderFinish( Fts3MultiSegReader *pCsr /* Cursor object */ ){ if( pCsr ){ int i; for(i=0; inSegment; i++){ sqlite3Fts3SegReaderFree(pCsr->apSegment[i]); } sqlite3_free(pCsr->apSegment); sqlite3_free(pCsr->aBuffer); pCsr->nSegment = 0; pCsr->apSegment = 0; pCsr->aBuffer = 0; } } /* ** Decode the "end_block" field, selected by column iCol of the SELECT ** statement passed as the first argument. ** ** The "end_block" field may contain either an integer, or a text field ** containing the text representation of two non-negative integers separated ** by one or more space (0x20) characters. In the first case, set *piEndBlock ** to the integer value and *pnByte to zero before returning. In the second, ** set *piEndBlock to the first value and *pnByte to the second. */ static void fts3ReadEndBlockField( sqlite3_stmt *pStmt, int iCol, i64 *piEndBlock, i64 *pnByte ){ const unsigned char *zText = sqlite3_column_text(pStmt, iCol); if( zText ){ int i; int iMul = 1; i64 iVal = 0; for(i=0; zText[i]>='0' && zText[i]<='9'; i++){ iVal = iVal*10 + (zText[i] - '0'); } *piEndBlock = iVal; while( zText[i]==' ' ) i++; iVal = 0; if( zText[i]=='-' ){ i++; iMul = -1; } for(/* no-op */; zText[i]>='0' && zText[i]<='9'; i++){ iVal = iVal*10 + (zText[i] - '0'); } *pnByte = (iVal * (i64)iMul); } } /* ** A segment of size nByte bytes has just been written to absolute level ** iAbsLevel. Promote any segments that should be promoted as a result. */ static int fts3PromoteSegments( Fts3Table *p, /* FTS table handle */ sqlite3_int64 iAbsLevel, /* Absolute level just updated */ sqlite3_int64 nByte /* Size of new segment at iAbsLevel */ ){ int rc = SQLITE_OK; sqlite3_stmt *pRange; rc = fts3SqlStmt(p, SQL_SELECT_LEVEL_RANGE2, &pRange, 0); if( rc==SQLITE_OK ){ int bOk = 0; i64 iLast = (iAbsLevel/FTS3_SEGDIR_MAXLEVEL + 1) * FTS3_SEGDIR_MAXLEVEL - 1; i64 nLimit = (nByte*3)/2; /* Loop through all entries in the %_segdir table corresponding to ** segments in this index on levels greater than iAbsLevel. If there is ** at least one such segment, and it is possible to determine that all ** such segments are smaller than nLimit bytes in size, they will be ** promoted to level iAbsLevel. */ sqlite3_bind_int64(pRange, 1, iAbsLevel+1); sqlite3_bind_int64(pRange, 2, iLast); while( SQLITE_ROW==sqlite3_step(pRange) ){ i64 nSize = 0, dummy; fts3ReadEndBlockField(pRange, 2, &dummy, &nSize); if( nSize<=0 || nSize>nLimit ){ /* If nSize==0, then the %_segdir.end_block field does not not ** contain a size value. This happens if it was written by an ** old version of FTS. In this case it is not possible to determine ** the size of the segment, and so segment promotion does not ** take place. */ bOk = 0; break; } bOk = 1; } rc = sqlite3_reset(pRange); if( bOk ){ int iIdx = 0; sqlite3_stmt *pUpdate1 = 0; sqlite3_stmt *pUpdate2 = 0; if( rc==SQLITE_OK ){ rc = fts3SqlStmt(p, SQL_UPDATE_LEVEL_IDX, &pUpdate1, 0); } if( rc==SQLITE_OK ){ rc = fts3SqlStmt(p, SQL_UPDATE_LEVEL, &pUpdate2, 0); } if( rc==SQLITE_OK ){ /* Loop through all %_segdir entries for segments in this index with ** levels equal to or greater than iAbsLevel. As each entry is visited, ** updated it to set (level = -1) and (idx = N), where N is 0 for the ** oldest segment in the range, 1 for the next oldest, and so on. ** ** In other words, move all segments being promoted to level -1, ** setting the "idx" fields as appropriate to keep them in the same ** order. The contents of level -1 (which is never used, except ** transiently here), will be moved back to level iAbsLevel below. */ sqlite3_bind_int64(pRange, 1, iAbsLevel); while( SQLITE_ROW==sqlite3_step(pRange) ){ sqlite3_bind_int(pUpdate1, 1, iIdx++); sqlite3_bind_int(pUpdate1, 2, sqlite3_column_int(pRange, 0)); sqlite3_bind_int(pUpdate1, 3, sqlite3_column_int(pRange, 1)); sqlite3_step(pUpdate1); rc = sqlite3_reset(pUpdate1); if( rc!=SQLITE_OK ){ sqlite3_reset(pRange); break; } } } if( rc==SQLITE_OK ){ rc = sqlite3_reset(pRange); } /* Move level -1 to level iAbsLevel */ if( rc==SQLITE_OK ){ sqlite3_bind_int64(pUpdate2, 1, iAbsLevel); sqlite3_step(pUpdate2); rc = sqlite3_reset(pUpdate2); } } } return rc; } /* ** Merge all level iLevel segments in the database into a single ** iLevel+1 segment. Or, if iLevel<0, merge all segments into a ** single segment with a level equal to the numerically largest level ** currently present in the database. ** ** If this function is called with iLevel<0, but there is only one ** segment in the database, SQLITE_DONE is returned immediately. ** Otherwise, if successful, SQLITE_OK is returned. If an error occurs, ** an SQLite error code is returned. */ static int fts3SegmentMerge( Fts3Table *p, int iLangid, /* Language id to merge */ int iIndex, /* Index in p->aIndex[] to merge */ int iLevel /* Level to merge */ ){ int rc; /* Return code */ int iIdx = 0; /* Index of new segment */ sqlite3_int64 iNewLevel = 0; /* Level/index to create new segment at */ SegmentWriter *pWriter = 0; /* Used to write the new, merged, segment */ Fts3SegFilter filter; /* Segment term filter condition */ Fts3MultiSegReader csr; /* Cursor to iterate through level(s) */ int bIgnoreEmpty = 0; /* True to ignore empty segments */ i64 iMaxLevel = 0; /* Max level number for this index/langid */ assert( iLevel==FTS3_SEGCURSOR_ALL || iLevel==FTS3_SEGCURSOR_PENDING || iLevel>=0 ); assert( iLevel=0 && iIndexnIndex ); rc = sqlite3Fts3SegReaderCursor(p, iLangid, iIndex, iLevel, 0, 0, 1, 0, &csr); if( rc!=SQLITE_OK || csr.nSegment==0 ) goto finished; if( iLevel!=FTS3_SEGCURSOR_PENDING ){ rc = fts3SegmentMaxLevel(p, iLangid, iIndex, &iMaxLevel); if( rc!=SQLITE_OK ) goto finished; } if( iLevel==FTS3_SEGCURSOR_ALL ){ /* This call is to merge all segments in the database to a single ** segment. The level of the new segment is equal to the numerically ** greatest segment level currently present in the database for this ** index. The idx of the new segment is always 0. */ if( csr.nSegment==1 && 0==fts3SegReaderIsPending(csr.apSegment[0]) ){ rc = SQLITE_DONE; goto finished; } iNewLevel = iMaxLevel; bIgnoreEmpty = 1; }else{ /* This call is to merge all segments at level iLevel. find the next ** available segment index at level iLevel+1. The call to ** fts3AllocateSegdirIdx() will merge the segments at level iLevel+1 to ** a single iLevel+2 segment if necessary. */ assert( FTS3_SEGCURSOR_PENDING==-1 ); iNewLevel = getAbsoluteLevel(p, iLangid, iIndex, iLevel+1); rc = fts3AllocateSegdirIdx(p, iLangid, iIndex, iLevel+1, &iIdx); bIgnoreEmpty = (iLevel!=FTS3_SEGCURSOR_PENDING) && (iNewLevel>iMaxLevel); } if( rc!=SQLITE_OK ) goto finished; assert( csr.nSegment>0 ); assert( iNewLevel>=getAbsoluteLevel(p, iLangid, iIndex, 0) ); assert( iNewLevelnLeafData); } } } finished: fts3SegWriterFree(pWriter); sqlite3Fts3SegReaderFinish(&csr); return rc; } /* ** Flush the contents of pendingTerms to level 0 segments. */ int sqlite3Fts3PendingTermsFlush(Fts3Table *p){ int rc = SQLITE_OK; int i; for(i=0; rc==SQLITE_OK && inIndex; i++){ rc = fts3SegmentMerge(p, p->iPrevLangid, i, FTS3_SEGCURSOR_PENDING); if( rc==SQLITE_DONE ) rc = SQLITE_OK; } sqlite3Fts3PendingTermsClear(p); /* Determine the auto-incr-merge setting if unknown. If enabled, ** estimate the number of leaf blocks of content to be written */ if( rc==SQLITE_OK && p->bHasStat && p->nAutoincrmerge==0xff && p->nLeafAdd>0 ){ sqlite3_stmt *pStmt = 0; rc = fts3SqlStmt(p, SQL_SELECT_STAT, &pStmt, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int(pStmt, 1, FTS_STAT_AUTOINCRMERGE); rc = sqlite3_step(pStmt); if( rc==SQLITE_ROW ){ p->nAutoincrmerge = sqlite3_column_int(pStmt, 0); if( p->nAutoincrmerge==1 ) p->nAutoincrmerge = 8; }else if( rc==SQLITE_DONE ){ p->nAutoincrmerge = 0; } rc = sqlite3_reset(pStmt); } } return rc; } /* ** Encode N integers as varints into a blob. */ static void fts3EncodeIntArray( int N, /* The number of integers to encode */ u32 *a, /* The integer values */ char *zBuf, /* Write the BLOB here */ int *pNBuf /* Write number of bytes if zBuf[] used here */ ){ int i, j; for(i=j=0; iiPrevDocid. The sizes are encoded as ** a blob of varints. */ static void fts3InsertDocsize( int *pRC, /* Result code */ Fts3Table *p, /* Table into which to insert */ u32 *aSz /* Sizes of each column, in tokens */ ){ char *pBlob; /* The BLOB encoding of the document size */ int nBlob; /* Number of bytes in the BLOB */ sqlite3_stmt *pStmt; /* Statement used to insert the encoding */ int rc; /* Result code from subfunctions */ if( *pRC ) return; pBlob = sqlite3_malloc( 10*p->nColumn ); if( pBlob==0 ){ *pRC = SQLITE_NOMEM; return; } fts3EncodeIntArray(p->nColumn, aSz, pBlob, &nBlob); rc = fts3SqlStmt(p, SQL_REPLACE_DOCSIZE, &pStmt, 0); if( rc ){ sqlite3_free(pBlob); *pRC = rc; return; } sqlite3_bind_int64(pStmt, 1, p->iPrevDocid); sqlite3_bind_blob(pStmt, 2, pBlob, nBlob, sqlite3_free); sqlite3_step(pStmt); *pRC = sqlite3_reset(pStmt); } /* ** Record 0 of the %_stat table contains a blob consisting of N varints, ** where N is the number of user defined columns in the fts3 table plus ** two. If nCol is the number of user defined columns, then values of the ** varints are set as follows: ** ** Varint 0: Total number of rows in the table. ** ** Varint 1..nCol: For each column, the total number of tokens stored in ** the column for all rows of the table. ** ** Varint 1+nCol: The total size, in bytes, of all text values in all ** columns of all rows of the table. ** */ static void fts3UpdateDocTotals( int *pRC, /* The result code */ Fts3Table *p, /* Table being updated */ u32 *aSzIns, /* Size increases */ u32 *aSzDel, /* Size decreases */ int nChng /* Change in the number of documents */ ){ char *pBlob; /* Storage for BLOB written into %_stat */ int nBlob; /* Size of BLOB written into %_stat */ u32 *a; /* Array of integers that becomes the BLOB */ sqlite3_stmt *pStmt; /* Statement for reading and writing */ int i; /* Loop counter */ int rc; /* Result code from subfunctions */ const int nStat = p->nColumn+2; if( *pRC ) return; a = sqlite3_malloc( (sizeof(u32)+10)*nStat ); if( a==0 ){ *pRC = SQLITE_NOMEM; return; } pBlob = (char*)&a[nStat]; rc = fts3SqlStmt(p, SQL_SELECT_STAT, &pStmt, 0); if( rc ){ sqlite3_free(a); *pRC = rc; return; } sqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL); if( sqlite3_step(pStmt)==SQLITE_ROW ){ fts3DecodeIntArray(nStat, a, sqlite3_column_blob(pStmt, 0), sqlite3_column_bytes(pStmt, 0)); }else{ memset(a, 0, sizeof(u32)*(nStat) ); } rc = sqlite3_reset(pStmt); if( rc!=SQLITE_OK ){ sqlite3_free(a); *pRC = rc; return; } if( nChng<0 && a[0]<(u32)(-nChng) ){ a[0] = 0; }else{ a[0] += nChng; } for(i=0; inColumn+1; i++){ u32 x = a[i+1]; if( x+aSzIns[i] < aSzDel[i] ){ x = 0; }else{ x = x + aSzIns[i] - aSzDel[i]; } a[i+1] = x; } fts3EncodeIntArray(nStat, a, pBlob, &nBlob); rc = fts3SqlStmt(p, SQL_REPLACE_STAT, &pStmt, 0); if( rc ){ sqlite3_free(a); *pRC = rc; return; } sqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL); sqlite3_bind_blob(pStmt, 2, pBlob, nBlob, SQLITE_STATIC); sqlite3_step(pStmt); *pRC = sqlite3_reset(pStmt); sqlite3_free(a); } /* ** Merge the entire database so that there is one segment for each ** iIndex/iLangid combination. */ static int fts3DoOptimize(Fts3Table *p, int bReturnDone){ int bSeenDone = 0; int rc; sqlite3_stmt *pAllLangid = 0; rc = fts3SqlStmt(p, SQL_SELECT_ALL_LANGID, &pAllLangid, 0); if( rc==SQLITE_OK ){ int rc2; sqlite3_bind_int(pAllLangid, 1, p->iPrevLangid); sqlite3_bind_int(pAllLangid, 2, p->nIndex); while( sqlite3_step(pAllLangid)==SQLITE_ROW ){ int i; int iLangid = sqlite3_column_int(pAllLangid, 0); for(i=0; rc==SQLITE_OK && inIndex; i++){ rc = fts3SegmentMerge(p, iLangid, i, FTS3_SEGCURSOR_ALL); if( rc==SQLITE_DONE ){ bSeenDone = 1; rc = SQLITE_OK; } } } rc2 = sqlite3_reset(pAllLangid); if( rc==SQLITE_OK ) rc = rc2; } sqlite3Fts3SegmentsClose(p); sqlite3Fts3PendingTermsClear(p); return (rc==SQLITE_OK && bReturnDone && bSeenDone) ? SQLITE_DONE : rc; } /* ** This function is called when the user executes the following statement: ** ** INSERT INTO () VALUES('rebuild'); ** ** The entire FTS index is discarded and rebuilt. If the table is one ** created using the content=xxx option, then the new index is based on ** the current contents of the xxx table. Otherwise, it is rebuilt based ** on the contents of the %_content table. */ static int fts3DoRebuild(Fts3Table *p){ int rc; /* Return Code */ rc = fts3DeleteAll(p, 0); if( rc==SQLITE_OK ){ u32 *aSz = 0; u32 *aSzIns = 0; u32 *aSzDel = 0; sqlite3_stmt *pStmt = 0; int nEntry = 0; /* Compose and prepare an SQL statement to loop through the content table */ char *zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist); if( !zSql ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); } if( rc==SQLITE_OK ){ int nByte = sizeof(u32) * (p->nColumn+1)*3; aSz = (u32 *)sqlite3_malloc(nByte); if( aSz==0 ){ rc = SQLITE_NOMEM; }else{ memset(aSz, 0, nByte); aSzIns = &aSz[p->nColumn+1]; aSzDel = &aSzIns[p->nColumn+1]; } } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ int iCol; int iLangid = langidFromSelect(p, pStmt); rc = fts3PendingTermsDocid(p, 0, iLangid, sqlite3_column_int64(pStmt, 0)); memset(aSz, 0, sizeof(aSz[0]) * (p->nColumn+1)); for(iCol=0; rc==SQLITE_OK && iColnColumn; iCol++){ if( p->abNotindexed[iCol]==0 ){ const char *z = (const char *) sqlite3_column_text(pStmt, iCol+1); rc = fts3PendingTermsAdd(p, iLangid, z, iCol, &aSz[iCol]); aSz[p->nColumn] += sqlite3_column_bytes(pStmt, iCol+1); } } if( p->bHasDocsize ){ fts3InsertDocsize(&rc, p, aSz); } if( rc!=SQLITE_OK ){ sqlite3_finalize(pStmt); pStmt = 0; }else{ nEntry++; for(iCol=0; iCol<=p->nColumn; iCol++){ aSzIns[iCol] += aSz[iCol]; } } } if( p->bFts4 ){ fts3UpdateDocTotals(&rc, p, aSzIns, aSzDel, nEntry); } sqlite3_free(aSz); if( pStmt ){ int rc2 = sqlite3_finalize(pStmt); if( rc==SQLITE_OK ){ rc = rc2; } } } return rc; } /* ** This function opens a cursor used to read the input data for an ** incremental merge operation. Specifically, it opens a cursor to scan ** the oldest nSeg segments (idx=0 through idx=(nSeg-1)) in absolute ** level iAbsLevel. */ static int fts3IncrmergeCsr( Fts3Table *p, /* FTS3 table handle */ sqlite3_int64 iAbsLevel, /* Absolute level to open */ int nSeg, /* Number of segments to merge */ Fts3MultiSegReader *pCsr /* Cursor object to populate */ ){ int rc; /* Return Code */ sqlite3_stmt *pStmt = 0; /* Statement used to read %_segdir entry */ int nByte; /* Bytes allocated at pCsr->apSegment[] */ /* Allocate space for the Fts3MultiSegReader.aCsr[] array */ memset(pCsr, 0, sizeof(*pCsr)); nByte = sizeof(Fts3SegReader *) * nSeg; pCsr->apSegment = (Fts3SegReader **)sqlite3_malloc(nByte); if( pCsr->apSegment==0 ){ rc = SQLITE_NOMEM; }else{ memset(pCsr->apSegment, 0, nByte); rc = fts3SqlStmt(p, SQL_SELECT_LEVEL, &pStmt, 0); } if( rc==SQLITE_OK ){ int i; int rc2; sqlite3_bind_int64(pStmt, 1, iAbsLevel); assert( pCsr->nSegment==0 ); for(i=0; rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW && iapSegment[i] ); pCsr->nSegment++; } rc2 = sqlite3_reset(pStmt); if( rc==SQLITE_OK ) rc = rc2; } return rc; } typedef struct IncrmergeWriter IncrmergeWriter; typedef struct NodeWriter NodeWriter; typedef struct Blob Blob; typedef struct NodeReader NodeReader; /* ** An instance of the following structure is used as a dynamic buffer ** to build up nodes or other blobs of data in. ** ** The function blobGrowBuffer() is used to extend the allocation. */ struct Blob { char *a; /* Pointer to allocation */ int n; /* Number of valid bytes of data in a[] */ int nAlloc; /* Allocated size of a[] (nAlloc>=n) */ }; /* ** This structure is used to build up buffers containing segment b-tree ** nodes (blocks). */ struct NodeWriter { sqlite3_int64 iBlock; /* Current block id */ Blob key; /* Last key written to the current block */ Blob block; /* Current block image */ }; /* ** An object of this type contains the state required to create or append ** to an appendable b-tree segment. */ struct IncrmergeWriter { int nLeafEst; /* Space allocated for leaf blocks */ int nWork; /* Number of leaf pages flushed */ sqlite3_int64 iAbsLevel; /* Absolute level of input segments */ int iIdx; /* Index of *output* segment in iAbsLevel+1 */ sqlite3_int64 iStart; /* Block number of first allocated block */ sqlite3_int64 iEnd; /* Block number of last allocated block */ sqlite3_int64 nLeafData; /* Bytes of leaf page data so far */ u8 bNoLeafData; /* If true, store 0 for segment size */ NodeWriter aNodeWriter[FTS_MAX_APPENDABLE_HEIGHT]; }; /* ** An object of the following type is used to read data from a single ** FTS segment node. See the following functions: ** ** nodeReaderInit() ** nodeReaderNext() ** nodeReaderRelease() */ struct NodeReader { const char *aNode; int nNode; int iOff; /* Current offset within aNode[] */ /* Output variables. Containing the current node entry. */ sqlite3_int64 iChild; /* Pointer to child node */ Blob term; /* Current term */ const char *aDoclist; /* Pointer to doclist */ int nDoclist; /* Size of doclist in bytes */ }; /* ** If *pRc is not SQLITE_OK when this function is called, it is a no-op. ** Otherwise, if the allocation at pBlob->a is not already at least nMin ** bytes in size, extend (realloc) it to be so. ** ** If an OOM error occurs, set *pRc to SQLITE_NOMEM and leave pBlob->a ** unmodified. Otherwise, if the allocation succeeds, update pBlob->nAlloc ** to reflect the new size of the pBlob->a[] buffer. */ static void blobGrowBuffer(Blob *pBlob, int nMin, int *pRc){ if( *pRc==SQLITE_OK && nMin>pBlob->nAlloc ){ int nAlloc = nMin; char *a = (char *)sqlite3_realloc(pBlob->a, nAlloc); if( a ){ pBlob->nAlloc = nAlloc; pBlob->a = a; }else{ *pRc = SQLITE_NOMEM; } } } /* ** Attempt to advance the node-reader object passed as the first argument to ** the next entry on the node. ** ** Return an error code if an error occurs (SQLITE_NOMEM is possible). ** Otherwise return SQLITE_OK. If there is no next entry on the node ** (e.g. because the current entry is the last) set NodeReader->aNode to ** NULL to indicate EOF. Otherwise, populate the NodeReader structure output ** variables for the new entry. */ static int nodeReaderNext(NodeReader *p){ int bFirst = (p->term.n==0); /* True for first term on the node */ int nPrefix = 0; /* Bytes to copy from previous term */ int nSuffix = 0; /* Bytes to append to the prefix */ int rc = SQLITE_OK; /* Return code */ assert( p->aNode ); if( p->iChild && bFirst==0 ) p->iChild++; if( p->iOff>=p->nNode ){ /* EOF */ p->aNode = 0; }else{ if( bFirst==0 ){ p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &nPrefix); } p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &nSuffix); blobGrowBuffer(&p->term, nPrefix+nSuffix, &rc); if( rc==SQLITE_OK ){ memcpy(&p->term.a[nPrefix], &p->aNode[p->iOff], nSuffix); p->term.n = nPrefix+nSuffix; p->iOff += nSuffix; if( p->iChild==0 ){ p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &p->nDoclist); p->aDoclist = &p->aNode[p->iOff]; p->iOff += p->nDoclist; } } } assert( p->iOff<=p->nNode ); return rc; } /* ** Release all dynamic resources held by node-reader object *p. */ static void nodeReaderRelease(NodeReader *p){ sqlite3_free(p->term.a); } /* ** Initialize a node-reader object to read the node in buffer aNode/nNode. ** ** If successful, SQLITE_OK is returned and the NodeReader object set to ** point to the first entry on the node (if any). Otherwise, an SQLite ** error code is returned. */ static int nodeReaderInit(NodeReader *p, const char *aNode, int nNode){ memset(p, 0, sizeof(NodeReader)); p->aNode = aNode; p->nNode = nNode; /* Figure out if this is a leaf or an internal node. */ if( p->aNode[0] ){ /* An internal node. */ p->iOff = 1 + sqlite3Fts3GetVarint(&p->aNode[1], &p->iChild); }else{ p->iOff = 1; } return nodeReaderNext(p); } /* ** This function is called while writing an FTS segment each time a leaf o ** node is finished and written to disk. The key (zTerm/nTerm) is guaranteed ** to be greater than the largest key on the node just written, but smaller ** than or equal to the first key that will be written to the next leaf ** node. ** ** The block id of the leaf node just written to disk may be found in ** (pWriter->aNodeWriter[0].iBlock) when this function is called. */ static int fts3IncrmergePush( Fts3Table *p, /* Fts3 table handle */ IncrmergeWriter *pWriter, /* Writer object */ const char *zTerm, /* Term to write to internal node */ int nTerm /* Bytes at zTerm */ ){ sqlite3_int64 iPtr = pWriter->aNodeWriter[0].iBlock; int iLayer; assert( nTerm>0 ); for(iLayer=1; ALWAYS(iLayeraNodeWriter[iLayer]; int rc = SQLITE_OK; int nPrefix; int nSuffix; int nSpace; /* Figure out how much space the key will consume if it is written to ** the current node of layer iLayer. Due to the prefix compression, ** the space required changes depending on which node the key is to ** be added to. */ nPrefix = fts3PrefixCompress(pNode->key.a, pNode->key.n, zTerm, nTerm); nSuffix = nTerm - nPrefix; nSpace = sqlite3Fts3VarintLen(nPrefix); nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; if( pNode->key.n==0 || (pNode->block.n + nSpace)<=p->nNodeSize ){ /* If the current node of layer iLayer contains zero keys, or if adding ** the key to it will not cause it to grow to larger than nNodeSize ** bytes in size, write the key here. */ Blob *pBlk = &pNode->block; if( pBlk->n==0 ){ blobGrowBuffer(pBlk, p->nNodeSize, &rc); if( rc==SQLITE_OK ){ pBlk->a[0] = (char)iLayer; pBlk->n = 1 + sqlite3Fts3PutVarint(&pBlk->a[1], iPtr); } } blobGrowBuffer(pBlk, pBlk->n + nSpace, &rc); blobGrowBuffer(&pNode->key, nTerm, &rc); if( rc==SQLITE_OK ){ if( pNode->key.n ){ pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nPrefix); } pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nSuffix); memcpy(&pBlk->a[pBlk->n], &zTerm[nPrefix], nSuffix); pBlk->n += nSuffix; memcpy(pNode->key.a, zTerm, nTerm); pNode->key.n = nTerm; } }else{ /* Otherwise, flush the current node of layer iLayer to disk. ** Then allocate a new, empty sibling node. The key will be written ** into the parent of this node. */ rc = fts3WriteSegment(p, pNode->iBlock, pNode->block.a, pNode->block.n); assert( pNode->block.nAlloc>=p->nNodeSize ); pNode->block.a[0] = (char)iLayer; pNode->block.n = 1 + sqlite3Fts3PutVarint(&pNode->block.a[1], iPtr+1); iNextPtr = pNode->iBlock; pNode->iBlock++; pNode->key.n = 0; } if( rc!=SQLITE_OK || iNextPtr==0 ) return rc; iPtr = iNextPtr; } assert( 0 ); return 0; } /* ** Append a term and (optionally) doclist to the FTS segment node currently ** stored in blob *pNode. The node need not contain any terms, but the ** header must be written before this function is called. ** ** A node header is a single 0x00 byte for a leaf node, or a height varint ** followed by the left-hand-child varint for an internal node. ** ** The term to be appended is passed via arguments zTerm/nTerm. For a ** leaf node, the doclist is passed as aDoclist/nDoclist. For an internal ** node, both aDoclist and nDoclist must be passed 0. ** ** If the size of the value in blob pPrev is zero, then this is the first ** term written to the node. Otherwise, pPrev contains a copy of the ** previous term. Before this function returns, it is updated to contain a ** copy of zTerm/nTerm. ** ** It is assumed that the buffer associated with pNode is already large ** enough to accommodate the new entry. The buffer associated with pPrev ** is extended by this function if requrired. ** ** If an error (i.e. OOM condition) occurs, an SQLite error code is ** returned. Otherwise, SQLITE_OK. */ static int fts3AppendToNode( Blob *pNode, /* Current node image to append to */ Blob *pPrev, /* Buffer containing previous term written */ const char *zTerm, /* New term to write */ int nTerm, /* Size of zTerm in bytes */ const char *aDoclist, /* Doclist (or NULL) to write */ int nDoclist /* Size of aDoclist in bytes */ ){ int rc = SQLITE_OK; /* Return code */ int bFirst = (pPrev->n==0); /* True if this is the first term written */ int nPrefix; /* Size of term prefix in bytes */ int nSuffix; /* Size of term suffix in bytes */ /* Node must have already been started. There must be a doclist for a ** leaf node, and there must not be a doclist for an internal node. */ assert( pNode->n>0 ); assert( (pNode->a[0]=='\0')==(aDoclist!=0) ); blobGrowBuffer(pPrev, nTerm, &rc); if( rc!=SQLITE_OK ) return rc; nPrefix = fts3PrefixCompress(pPrev->a, pPrev->n, zTerm, nTerm); nSuffix = nTerm - nPrefix; memcpy(pPrev->a, zTerm, nTerm); pPrev->n = nTerm; if( bFirst==0 ){ pNode->n += sqlite3Fts3PutVarint(&pNode->a[pNode->n], nPrefix); } pNode->n += sqlite3Fts3PutVarint(&pNode->a[pNode->n], nSuffix); memcpy(&pNode->a[pNode->n], &zTerm[nPrefix], nSuffix); pNode->n += nSuffix; if( aDoclist ){ pNode->n += sqlite3Fts3PutVarint(&pNode->a[pNode->n], nDoclist); memcpy(&pNode->a[pNode->n], aDoclist, nDoclist); pNode->n += nDoclist; } assert( pNode->n<=pNode->nAlloc ); return SQLITE_OK; } /* ** Append the current term and doclist pointed to by cursor pCsr to the ** appendable b-tree segment opened for writing by pWriter. ** ** Return SQLITE_OK if successful, or an SQLite error code otherwise. */ static int fts3IncrmergeAppend( Fts3Table *p, /* Fts3 table handle */ IncrmergeWriter *pWriter, /* Writer object */ Fts3MultiSegReader *pCsr /* Cursor containing term and doclist */ ){ const char *zTerm = pCsr->zTerm; int nTerm = pCsr->nTerm; const char *aDoclist = pCsr->aDoclist; int nDoclist = pCsr->nDoclist; int rc = SQLITE_OK; /* Return code */ int nSpace; /* Total space in bytes required on leaf */ int nPrefix; /* Size of prefix shared with previous term */ int nSuffix; /* Size of suffix (nTerm - nPrefix) */ NodeWriter *pLeaf; /* Object used to write leaf nodes */ pLeaf = &pWriter->aNodeWriter[0]; nPrefix = fts3PrefixCompress(pLeaf->key.a, pLeaf->key.n, zTerm, nTerm); nSuffix = nTerm - nPrefix; nSpace = sqlite3Fts3VarintLen(nPrefix); nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist; /* If the current block is not empty, and if adding this term/doclist ** to the current block would make it larger than Fts3Table.nNodeSize ** bytes, write this block out to the database. */ if( pLeaf->block.n>0 && (pLeaf->block.n + nSpace)>p->nNodeSize ){ rc = fts3WriteSegment(p, pLeaf->iBlock, pLeaf->block.a, pLeaf->block.n); pWriter->nWork++; /* Add the current term to the parent node. The term added to the ** parent must: ** ** a) be greater than the largest term on the leaf node just written ** to the database (still available in pLeaf->key), and ** ** b) be less than or equal to the term about to be added to the new ** leaf node (zTerm/nTerm). ** ** In other words, it must be the prefix of zTerm 1 byte longer than ** the common prefix (if any) of zTerm and pWriter->zTerm. */ if( rc==SQLITE_OK ){ rc = fts3IncrmergePush(p, pWriter, zTerm, nPrefix+1); } /* Advance to the next output block */ pLeaf->iBlock++; pLeaf->key.n = 0; pLeaf->block.n = 0; nSuffix = nTerm; nSpace = 1; nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist; } pWriter->nLeafData += nSpace; blobGrowBuffer(&pLeaf->block, pLeaf->block.n + nSpace, &rc); if( rc==SQLITE_OK ){ if( pLeaf->block.n==0 ){ pLeaf->block.n = 1; pLeaf->block.a[0] = '\0'; } rc = fts3AppendToNode( &pLeaf->block, &pLeaf->key, zTerm, nTerm, aDoclist, nDoclist ); } return rc; } /* ** This function is called to release all dynamic resources held by the ** merge-writer object pWriter, and if no error has occurred, to flush ** all outstanding node buffers held by pWriter to disk. ** ** If *pRc is not SQLITE_OK when this function is called, then no attempt ** is made to write any data to disk. Instead, this function serves only ** to release outstanding resources. ** ** Otherwise, if *pRc is initially SQLITE_OK and an error occurs while ** flushing buffers to disk, *pRc is set to an SQLite error code before ** returning. */ static void fts3IncrmergeRelease( Fts3Table *p, /* FTS3 table handle */ IncrmergeWriter *pWriter, /* Merge-writer object */ int *pRc /* IN/OUT: Error code */ ){ int i; /* Used to iterate through non-root layers */ int iRoot; /* Index of root in pWriter->aNodeWriter */ NodeWriter *pRoot; /* NodeWriter for root node */ int rc = *pRc; /* Error code */ /* Set iRoot to the index in pWriter->aNodeWriter[] of the output segment ** root node. If the segment fits entirely on a single leaf node, iRoot ** will be set to 0. If the root node is the parent of the leaves, iRoot ** will be 1. And so on. */ for(iRoot=FTS_MAX_APPENDABLE_HEIGHT-1; iRoot>=0; iRoot--){ NodeWriter *pNode = &pWriter->aNodeWriter[iRoot]; if( pNode->block.n>0 ) break; assert( *pRc || pNode->block.nAlloc==0 ); assert( *pRc || pNode->key.nAlloc==0 ); sqlite3_free(pNode->block.a); sqlite3_free(pNode->key.a); } /* Empty output segment. This is a no-op. */ if( iRoot<0 ) return; /* The entire output segment fits on a single node. Normally, this means ** the node would be stored as a blob in the "root" column of the %_segdir ** table. However, this is not permitted in this case. The problem is that ** space has already been reserved in the %_segments table, and so the ** start_block and end_block fields of the %_segdir table must be populated. ** And, by design or by accident, released versions of FTS cannot handle ** segments that fit entirely on the root node with start_block!=0. ** ** Instead, create a synthetic root node that contains nothing but a ** pointer to the single content node. So that the segment consists of a ** single leaf and a single interior (root) node. ** ** Todo: Better might be to defer allocating space in the %_segments ** table until we are sure it is needed. */ if( iRoot==0 ){ Blob *pBlock = &pWriter->aNodeWriter[1].block; blobGrowBuffer(pBlock, 1 + FTS3_VARINT_MAX, &rc); if( rc==SQLITE_OK ){ pBlock->a[0] = 0x01; pBlock->n = 1 + sqlite3Fts3PutVarint( &pBlock->a[1], pWriter->aNodeWriter[0].iBlock ); } iRoot = 1; } pRoot = &pWriter->aNodeWriter[iRoot]; /* Flush all currently outstanding nodes to disk. */ for(i=0; iaNodeWriter[i]; if( pNode->block.n>0 && rc==SQLITE_OK ){ rc = fts3WriteSegment(p, pNode->iBlock, pNode->block.a, pNode->block.n); } sqlite3_free(pNode->block.a); sqlite3_free(pNode->key.a); } /* Write the %_segdir record. */ if( rc==SQLITE_OK ){ rc = fts3WriteSegdir(p, pWriter->iAbsLevel+1, /* level */ pWriter->iIdx, /* idx */ pWriter->iStart, /* start_block */ pWriter->aNodeWriter[0].iBlock, /* leaves_end_block */ pWriter->iEnd, /* end_block */ (pWriter->bNoLeafData==0 ? pWriter->nLeafData : 0), /* end_block */ pRoot->block.a, pRoot->block.n /* root */ ); } sqlite3_free(pRoot->block.a); sqlite3_free(pRoot->key.a); *pRc = rc; } /* ** Compare the term in buffer zLhs (size in bytes nLhs) with that in ** zRhs (size in bytes nRhs) using memcmp. If one term is a prefix of ** the other, it is considered to be smaller than the other. ** ** Return -ve if zLhs is smaller than zRhs, 0 if it is equal, or +ve ** if it is greater. */ static int fts3TermCmp( const char *zLhs, int nLhs, /* LHS of comparison */ const char *zRhs, int nRhs /* RHS of comparison */ ){ int nCmp = MIN(nLhs, nRhs); int res; res = memcmp(zLhs, zRhs, nCmp); if( res==0 ) res = nLhs - nRhs; return res; } /* ** Query to see if the entry in the %_segments table with blockid iEnd is ** NULL. If no error occurs and the entry is NULL, set *pbRes 1 before ** returning. Otherwise, set *pbRes to 0. ** ** Or, if an error occurs while querying the database, return an SQLite ** error code. The final value of *pbRes is undefined in this case. ** ** This is used to test if a segment is an "appendable" segment. If it ** is, then a NULL entry has been inserted into the %_segments table ** with blockid %_segdir.end_block. */ static int fts3IsAppendable(Fts3Table *p, sqlite3_int64 iEnd, int *pbRes){ int bRes = 0; /* Result to set *pbRes to */ sqlite3_stmt *pCheck = 0; /* Statement to query database with */ int rc; /* Return code */ rc = fts3SqlStmt(p, SQL_SEGMENT_IS_APPENDABLE, &pCheck, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pCheck, 1, iEnd); if( SQLITE_ROW==sqlite3_step(pCheck) ) bRes = 1; rc = sqlite3_reset(pCheck); } *pbRes = bRes; return rc; } /* ** This function is called when initializing an incremental-merge operation. ** It checks if the existing segment with index value iIdx at absolute level ** (iAbsLevel+1) can be appended to by the incremental merge. If it can, the ** merge-writer object *pWriter is initialized to write to it. ** ** An existing segment can be appended to by an incremental merge if: ** ** * It was initially created as an appendable segment (with all required ** space pre-allocated), and ** ** * The first key read from the input (arguments zKey and nKey) is ** greater than the largest key currently stored in the potential ** output segment. */ static int fts3IncrmergeLoad( Fts3Table *p, /* Fts3 table handle */ sqlite3_int64 iAbsLevel, /* Absolute level of input segments */ int iIdx, /* Index of candidate output segment */ const char *zKey, /* First key to write */ int nKey, /* Number of bytes in nKey */ IncrmergeWriter *pWriter /* Populate this object */ ){ int rc; /* Return code */ sqlite3_stmt *pSelect = 0; /* SELECT to read %_segdir entry */ rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR, &pSelect, 0); if( rc==SQLITE_OK ){ sqlite3_int64 iStart = 0; /* Value of %_segdir.start_block */ sqlite3_int64 iLeafEnd = 0; /* Value of %_segdir.leaves_end_block */ sqlite3_int64 iEnd = 0; /* Value of %_segdir.end_block */ const char *aRoot = 0; /* Pointer to %_segdir.root buffer */ int nRoot = 0; /* Size of aRoot[] in bytes */ int rc2; /* Return code from sqlite3_reset() */ int bAppendable = 0; /* Set to true if segment is appendable */ /* Read the %_segdir entry for index iIdx absolute level (iAbsLevel+1) */ sqlite3_bind_int64(pSelect, 1, iAbsLevel+1); sqlite3_bind_int(pSelect, 2, iIdx); if( sqlite3_step(pSelect)==SQLITE_ROW ){ iStart = sqlite3_column_int64(pSelect, 1); iLeafEnd = sqlite3_column_int64(pSelect, 2); fts3ReadEndBlockField(pSelect, 3, &iEnd, &pWriter->nLeafData); if( pWriter->nLeafData<0 ){ pWriter->nLeafData = pWriter->nLeafData * -1; } pWriter->bNoLeafData = (pWriter->nLeafData==0); nRoot = sqlite3_column_bytes(pSelect, 4); aRoot = sqlite3_column_blob(pSelect, 4); }else{ return sqlite3_reset(pSelect); } /* Check for the zero-length marker in the %_segments table */ rc = fts3IsAppendable(p, iEnd, &bAppendable); /* Check that zKey/nKey is larger than the largest key the candidate */ if( rc==SQLITE_OK && bAppendable ){ char *aLeaf = 0; int nLeaf = 0; rc = sqlite3Fts3ReadBlock(p, iLeafEnd, &aLeaf, &nLeaf, 0); if( rc==SQLITE_OK ){ NodeReader reader; for(rc = nodeReaderInit(&reader, aLeaf, nLeaf); rc==SQLITE_OK && reader.aNode; rc = nodeReaderNext(&reader) ){ assert( reader.aNode ); } if( fts3TermCmp(zKey, nKey, reader.term.a, reader.term.n)<=0 ){ bAppendable = 0; } nodeReaderRelease(&reader); } sqlite3_free(aLeaf); } if( rc==SQLITE_OK && bAppendable ){ /* It is possible to append to this segment. Set up the IncrmergeWriter ** object to do so. */ int i; int nHeight = (int)aRoot[0]; NodeWriter *pNode; pWriter->nLeafEst = (int)((iEnd - iStart) + 1)/FTS_MAX_APPENDABLE_HEIGHT; pWriter->iStart = iStart; pWriter->iEnd = iEnd; pWriter->iAbsLevel = iAbsLevel; pWriter->iIdx = iIdx; for(i=nHeight+1; iaNodeWriter[i].iBlock = pWriter->iStart + i*pWriter->nLeafEst; } pNode = &pWriter->aNodeWriter[nHeight]; pNode->iBlock = pWriter->iStart + pWriter->nLeafEst*nHeight; blobGrowBuffer(&pNode->block, MAX(nRoot, p->nNodeSize), &rc); if( rc==SQLITE_OK ){ memcpy(pNode->block.a, aRoot, nRoot); pNode->block.n = nRoot; } for(i=nHeight; i>=0 && rc==SQLITE_OK; i--){ NodeReader reader; pNode = &pWriter->aNodeWriter[i]; rc = nodeReaderInit(&reader, pNode->block.a, pNode->block.n); while( reader.aNode && rc==SQLITE_OK ) rc = nodeReaderNext(&reader); blobGrowBuffer(&pNode->key, reader.term.n, &rc); if( rc==SQLITE_OK ){ memcpy(pNode->key.a, reader.term.a, reader.term.n); pNode->key.n = reader.term.n; if( i>0 ){ char *aBlock = 0; int nBlock = 0; pNode = &pWriter->aNodeWriter[i-1]; pNode->iBlock = reader.iChild; rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock, 0); blobGrowBuffer(&pNode->block, MAX(nBlock, p->nNodeSize), &rc); if( rc==SQLITE_OK ){ memcpy(pNode->block.a, aBlock, nBlock); pNode->block.n = nBlock; } sqlite3_free(aBlock); } } nodeReaderRelease(&reader); } } rc2 = sqlite3_reset(pSelect); if( rc==SQLITE_OK ) rc = rc2; } return rc; } /* ** Determine the largest segment index value that exists within absolute ** level iAbsLevel+1. If no error occurs, set *piIdx to this value plus ** one before returning SQLITE_OK. Or, if there are no segments at all ** within level iAbsLevel, set *piIdx to zero. ** ** If an error occurs, return an SQLite error code. The final value of ** *piIdx is undefined in this case. */ static int fts3IncrmergeOutputIdx( Fts3Table *p, /* FTS Table handle */ sqlite3_int64 iAbsLevel, /* Absolute index of input segments */ int *piIdx /* OUT: Next free index at iAbsLevel+1 */ ){ int rc; sqlite3_stmt *pOutputIdx = 0; /* SQL used to find output index */ rc = fts3SqlStmt(p, SQL_NEXT_SEGMENT_INDEX, &pOutputIdx, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pOutputIdx, 1, iAbsLevel+1); sqlite3_step(pOutputIdx); *piIdx = sqlite3_column_int(pOutputIdx, 0); rc = sqlite3_reset(pOutputIdx); } return rc; } /* ** Allocate an appendable output segment on absolute level iAbsLevel+1 ** with idx value iIdx. ** ** In the %_segdir table, a segment is defined by the values in three ** columns: ** ** start_block ** leaves_end_block ** end_block ** ** When an appendable segment is allocated, it is estimated that the ** maximum number of leaf blocks that may be required is the sum of the ** number of leaf blocks consumed by the input segments, plus the number ** of input segments, multiplied by two. This value is stored in stack ** variable nLeafEst. ** ** A total of 16*nLeafEst blocks are allocated when an appendable segment ** is created ((1 + end_block - start_block)==16*nLeafEst). The contiguous ** array of leaf nodes starts at the first block allocated. The array ** of interior nodes that are parents of the leaf nodes start at block ** (start_block + (1 + end_block - start_block) / 16). And so on. ** ** In the actual code below, the value "16" is replaced with the ** pre-processor macro FTS_MAX_APPENDABLE_HEIGHT. */ static int fts3IncrmergeWriter( Fts3Table *p, /* Fts3 table handle */ sqlite3_int64 iAbsLevel, /* Absolute level of input segments */ int iIdx, /* Index of new output segment */ Fts3MultiSegReader *pCsr, /* Cursor that data will be read from */ IncrmergeWriter *pWriter /* Populate this object */ ){ int rc; /* Return Code */ int i; /* Iterator variable */ int nLeafEst = 0; /* Blocks allocated for leaf nodes */ sqlite3_stmt *pLeafEst = 0; /* SQL used to determine nLeafEst */ sqlite3_stmt *pFirstBlock = 0; /* SQL used to determine first block */ /* Calculate nLeafEst. */ rc = fts3SqlStmt(p, SQL_MAX_LEAF_NODE_ESTIMATE, &pLeafEst, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pLeafEst, 1, iAbsLevel); sqlite3_bind_int64(pLeafEst, 2, pCsr->nSegment); if( SQLITE_ROW==sqlite3_step(pLeafEst) ){ nLeafEst = sqlite3_column_int(pLeafEst, 0); } rc = sqlite3_reset(pLeafEst); } if( rc!=SQLITE_OK ) return rc; /* Calculate the first block to use in the output segment */ rc = fts3SqlStmt(p, SQL_NEXT_SEGMENTS_ID, &pFirstBlock, 0); if( rc==SQLITE_OK ){ if( SQLITE_ROW==sqlite3_step(pFirstBlock) ){ pWriter->iStart = sqlite3_column_int64(pFirstBlock, 0); pWriter->iEnd = pWriter->iStart - 1; pWriter->iEnd += nLeafEst * FTS_MAX_APPENDABLE_HEIGHT; } rc = sqlite3_reset(pFirstBlock); } if( rc!=SQLITE_OK ) return rc; /* Insert the marker in the %_segments table to make sure nobody tries ** to steal the space just allocated. This is also used to identify ** appendable segments. */ rc = fts3WriteSegment(p, pWriter->iEnd, 0, 0); if( rc!=SQLITE_OK ) return rc; pWriter->iAbsLevel = iAbsLevel; pWriter->nLeafEst = nLeafEst; pWriter->iIdx = iIdx; /* Set up the array of NodeWriter objects */ for(i=0; iaNodeWriter[i].iBlock = pWriter->iStart + i*pWriter->nLeafEst; } return SQLITE_OK; } /* ** Remove an entry from the %_segdir table. This involves running the ** following two statements: ** ** DELETE FROM %_segdir WHERE level = :iAbsLevel AND idx = :iIdx ** UPDATE %_segdir SET idx = idx - 1 WHERE level = :iAbsLevel AND idx > :iIdx ** ** The DELETE statement removes the specific %_segdir level. The UPDATE ** statement ensures that the remaining segments have contiguously allocated ** idx values. */ static int fts3RemoveSegdirEntry( Fts3Table *p, /* FTS3 table handle */ sqlite3_int64 iAbsLevel, /* Absolute level to delete from */ int iIdx /* Index of %_segdir entry to delete */ ){ int rc; /* Return code */ sqlite3_stmt *pDelete = 0; /* DELETE statement */ rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_ENTRY, &pDelete, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pDelete, 1, iAbsLevel); sqlite3_bind_int(pDelete, 2, iIdx); sqlite3_step(pDelete); rc = sqlite3_reset(pDelete); } return rc; } /* ** One or more segments have just been removed from absolute level iAbsLevel. ** Update the 'idx' values of the remaining segments in the level so that ** the idx values are a contiguous sequence starting from 0. */ static int fts3RepackSegdirLevel( Fts3Table *p, /* FTS3 table handle */ sqlite3_int64 iAbsLevel /* Absolute level to repack */ ){ int rc; /* Return code */ int *aIdx = 0; /* Array of remaining idx values */ int nIdx = 0; /* Valid entries in aIdx[] */ int nAlloc = 0; /* Allocated size of aIdx[] */ int i; /* Iterator variable */ sqlite3_stmt *pSelect = 0; /* Select statement to read idx values */ sqlite3_stmt *pUpdate = 0; /* Update statement to modify idx values */ rc = fts3SqlStmt(p, SQL_SELECT_INDEXES, &pSelect, 0); if( rc==SQLITE_OK ){ int rc2; sqlite3_bind_int64(pSelect, 1, iAbsLevel); while( SQLITE_ROW==sqlite3_step(pSelect) ){ if( nIdx>=nAlloc ){ int *aNew; nAlloc += 16; aNew = sqlite3_realloc(aIdx, nAlloc*sizeof(int)); if( !aNew ){ rc = SQLITE_NOMEM; break; } aIdx = aNew; } aIdx[nIdx++] = sqlite3_column_int(pSelect, 0); } rc2 = sqlite3_reset(pSelect); if( rc==SQLITE_OK ) rc = rc2; } if( rc==SQLITE_OK ){ rc = fts3SqlStmt(p, SQL_SHIFT_SEGDIR_ENTRY, &pUpdate, 0); } if( rc==SQLITE_OK ){ sqlite3_bind_int64(pUpdate, 2, iAbsLevel); } assert( p->bIgnoreSavepoint==0 ); p->bIgnoreSavepoint = 1; for(i=0; rc==SQLITE_OK && ibIgnoreSavepoint = 0; sqlite3_free(aIdx); return rc; } static void fts3StartNode(Blob *pNode, int iHeight, sqlite3_int64 iChild){ pNode->a[0] = (char)iHeight; if( iChild ){ assert( pNode->nAlloc>=1+sqlite3Fts3VarintLen(iChild) ); pNode->n = 1 + sqlite3Fts3PutVarint(&pNode->a[1], iChild); }else{ assert( pNode->nAlloc>=1 ); pNode->n = 1; } } /* ** The first two arguments are a pointer to and the size of a segment b-tree ** node. The node may be a leaf or an internal node. ** ** This function creates a new node image in blob object *pNew by copying ** all terms that are greater than or equal to zTerm/nTerm (for leaf nodes) ** or greater than zTerm/nTerm (for internal nodes) from aNode/nNode. */ static int fts3TruncateNode( const char *aNode, /* Current node image */ int nNode, /* Size of aNode in bytes */ Blob *pNew, /* OUT: Write new node image here */ const char *zTerm, /* Omit all terms smaller than this */ int nTerm, /* Size of zTerm in bytes */ sqlite3_int64 *piBlock /* OUT: Block number in next layer down */ ){ NodeReader reader; /* Reader object */ Blob prev = {0, 0, 0}; /* Previous term written to new node */ int rc = SQLITE_OK; /* Return code */ int bLeaf = aNode[0]=='\0'; /* True for a leaf node */ /* Allocate required output space */ blobGrowBuffer(pNew, nNode, &rc); if( rc!=SQLITE_OK ) return rc; pNew->n = 0; /* Populate new node buffer */ for(rc = nodeReaderInit(&reader, aNode, nNode); rc==SQLITE_OK && reader.aNode; rc = nodeReaderNext(&reader) ){ if( pNew->n==0 ){ int res = fts3TermCmp(reader.term.a, reader.term.n, zTerm, nTerm); if( res<0 || (bLeaf==0 && res==0) ) continue; fts3StartNode(pNew, (int)aNode[0], reader.iChild); *piBlock = reader.iChild; } rc = fts3AppendToNode( pNew, &prev, reader.term.a, reader.term.n, reader.aDoclist, reader.nDoclist ); if( rc!=SQLITE_OK ) break; } if( pNew->n==0 ){ fts3StartNode(pNew, (int)aNode[0], reader.iChild); *piBlock = reader.iChild; } assert( pNew->n<=pNew->nAlloc ); nodeReaderRelease(&reader); sqlite3_free(prev.a); return rc; } /* ** Remove all terms smaller than zTerm/nTerm from segment iIdx in absolute ** level iAbsLevel. This may involve deleting entries from the %_segments ** table, and modifying existing entries in both the %_segments and %_segdir ** tables. ** ** SQLITE_OK is returned if the segment is updated successfully. Or an ** SQLite error code otherwise. */ static int fts3TruncateSegment( Fts3Table *p, /* FTS3 table handle */ sqlite3_int64 iAbsLevel, /* Absolute level of segment to modify */ int iIdx, /* Index within level of segment to modify */ const char *zTerm, /* Remove terms smaller than this */ int nTerm /* Number of bytes in buffer zTerm */ ){ int rc = SQLITE_OK; /* Return code */ Blob root = {0,0,0}; /* New root page image */ Blob block = {0,0,0}; /* Buffer used for any other block */ sqlite3_int64 iBlock = 0; /* Block id */ sqlite3_int64 iNewStart = 0; /* New value for iStartBlock */ sqlite3_int64 iOldStart = 0; /* Old value for iStartBlock */ sqlite3_stmt *pFetch = 0; /* Statement used to fetch segdir */ rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR, &pFetch, 0); if( rc==SQLITE_OK ){ int rc2; /* sqlite3_reset() return code */ sqlite3_bind_int64(pFetch, 1, iAbsLevel); sqlite3_bind_int(pFetch, 2, iIdx); if( SQLITE_ROW==sqlite3_step(pFetch) ){ const char *aRoot = sqlite3_column_blob(pFetch, 4); int nRoot = sqlite3_column_bytes(pFetch, 4); iOldStart = sqlite3_column_int64(pFetch, 1); rc = fts3TruncateNode(aRoot, nRoot, &root, zTerm, nTerm, &iBlock); } rc2 = sqlite3_reset(pFetch); if( rc==SQLITE_OK ) rc = rc2; } while( rc==SQLITE_OK && iBlock ){ char *aBlock = 0; int nBlock = 0; iNewStart = iBlock; rc = sqlite3Fts3ReadBlock(p, iBlock, &aBlock, &nBlock, 0); if( rc==SQLITE_OK ){ rc = fts3TruncateNode(aBlock, nBlock, &block, zTerm, nTerm, &iBlock); } if( rc==SQLITE_OK ){ rc = fts3WriteSegment(p, iNewStart, block.a, block.n); } sqlite3_free(aBlock); } /* Variable iNewStart now contains the first valid leaf node. */ if( rc==SQLITE_OK && iNewStart ){ sqlite3_stmt *pDel = 0; rc = fts3SqlStmt(p, SQL_DELETE_SEGMENTS_RANGE, &pDel, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pDel, 1, iOldStart); sqlite3_bind_int64(pDel, 2, iNewStart-1); sqlite3_step(pDel); rc = sqlite3_reset(pDel); } } if( rc==SQLITE_OK ){ sqlite3_stmt *pChomp = 0; rc = fts3SqlStmt(p, SQL_CHOMP_SEGDIR, &pChomp, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pChomp, 1, iNewStart); sqlite3_bind_blob(pChomp, 2, root.a, root.n, SQLITE_STATIC); sqlite3_bind_int64(pChomp, 3, iAbsLevel); sqlite3_bind_int(pChomp, 4, iIdx); sqlite3_step(pChomp); rc = sqlite3_reset(pChomp); } } sqlite3_free(root.a); sqlite3_free(block.a); return rc; } /* ** This function is called after an incrmental-merge operation has run to ** merge (or partially merge) two or more segments from absolute level ** iAbsLevel. ** ** Each input segment is either removed from the db completely (if all of ** its data was copied to the output segment by the incrmerge operation) ** or modified in place so that it no longer contains those entries that ** have been duplicated in the output segment. */ static int fts3IncrmergeChomp( Fts3Table *p, /* FTS table handle */ sqlite3_int64 iAbsLevel, /* Absolute level containing segments */ Fts3MultiSegReader *pCsr, /* Chomp all segments opened by this cursor */ int *pnRem /* Number of segments not deleted */ ){ int i; int nRem = 0; int rc = SQLITE_OK; for(i=pCsr->nSegment-1; i>=0 && rc==SQLITE_OK; i--){ Fts3SegReader *pSeg = 0; int j; /* Find the Fts3SegReader object with Fts3SegReader.iIdx==i. It is hiding ** somewhere in the pCsr->apSegment[] array. */ for(j=0; ALWAYS(jnSegment); j++){ pSeg = pCsr->apSegment[j]; if( pSeg->iIdx==i ) break; } assert( jnSegment && pSeg->iIdx==i ); if( pSeg->aNode==0 ){ /* Seg-reader is at EOF. Remove the entire input segment. */ rc = fts3DeleteSegment(p, pSeg); if( rc==SQLITE_OK ){ rc = fts3RemoveSegdirEntry(p, iAbsLevel, pSeg->iIdx); } *pnRem = 0; }else{ /* The incremental merge did not copy all the data from this ** segment to the upper level. The segment is modified in place ** so that it contains no keys smaller than zTerm/nTerm. */ const char *zTerm = pSeg->zTerm; int nTerm = pSeg->nTerm; rc = fts3TruncateSegment(p, iAbsLevel, pSeg->iIdx, zTerm, nTerm); nRem++; } } if( rc==SQLITE_OK && nRem!=pCsr->nSegment ){ rc = fts3RepackSegdirLevel(p, iAbsLevel); } *pnRem = nRem; return rc; } /* ** Store an incr-merge hint in the database. */ static int fts3IncrmergeHintStore(Fts3Table *p, Blob *pHint){ sqlite3_stmt *pReplace = 0; int rc; /* Return code */ rc = fts3SqlStmt(p, SQL_REPLACE_STAT, &pReplace, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int(pReplace, 1, FTS_STAT_INCRMERGEHINT); sqlite3_bind_blob(pReplace, 2, pHint->a, pHint->n, SQLITE_STATIC); sqlite3_step(pReplace); rc = sqlite3_reset(pReplace); } return rc; } /* ** Load an incr-merge hint from the database. The incr-merge hint, if one ** exists, is stored in the rowid==1 row of the %_stat table. ** ** If successful, populate blob *pHint with the value read from the %_stat ** table and return SQLITE_OK. Otherwise, if an error occurs, return an ** SQLite error code. */ static int fts3IncrmergeHintLoad(Fts3Table *p, Blob *pHint){ sqlite3_stmt *pSelect = 0; int rc; pHint->n = 0; rc = fts3SqlStmt(p, SQL_SELECT_STAT, &pSelect, 0); if( rc==SQLITE_OK ){ int rc2; sqlite3_bind_int(pSelect, 1, FTS_STAT_INCRMERGEHINT); if( SQLITE_ROW==sqlite3_step(pSelect) ){ const char *aHint = sqlite3_column_blob(pSelect, 0); int nHint = sqlite3_column_bytes(pSelect, 0); if( aHint ){ blobGrowBuffer(pHint, nHint, &rc); if( rc==SQLITE_OK ){ memcpy(pHint->a, aHint, nHint); pHint->n = nHint; } } } rc2 = sqlite3_reset(pSelect); if( rc==SQLITE_OK ) rc = rc2; } return rc; } /* ** If *pRc is not SQLITE_OK when this function is called, it is a no-op. ** Otherwise, append an entry to the hint stored in blob *pHint. Each entry ** consists of two varints, the absolute level number of the input segments ** and the number of input segments. ** ** If successful, leave *pRc set to SQLITE_OK and return. If an error occurs, ** set *pRc to an SQLite error code before returning. */ static void fts3IncrmergeHintPush( Blob *pHint, /* Hint blob to append to */ i64 iAbsLevel, /* First varint to store in hint */ int nInput, /* Second varint to store in hint */ int *pRc /* IN/OUT: Error code */ ){ blobGrowBuffer(pHint, pHint->n + 2*FTS3_VARINT_MAX, pRc); if( *pRc==SQLITE_OK ){ pHint->n += sqlite3Fts3PutVarint(&pHint->a[pHint->n], iAbsLevel); pHint->n += sqlite3Fts3PutVarint(&pHint->a[pHint->n], (i64)nInput); } } /* ** Read the last entry (most recently pushed) from the hint blob *pHint ** and then remove the entry. Write the two values read to *piAbsLevel and ** *pnInput before returning. ** ** If no error occurs, return SQLITE_OK. If the hint blob in *pHint does ** not contain at least two valid varints, return SQLITE_CORRUPT_VTAB. */ static int fts3IncrmergeHintPop(Blob *pHint, i64 *piAbsLevel, int *pnInput){ const int nHint = pHint->n; int i; i = pHint->n-2; while( i>0 && (pHint->a[i-1] & 0x80) ) i--; while( i>0 && (pHint->a[i-1] & 0x80) ) i--; pHint->n = i; i += sqlite3Fts3GetVarint(&pHint->a[i], piAbsLevel); i += fts3GetVarint32(&pHint->a[i], pnInput); if( i!=nHint ) return FTS_CORRUPT_VTAB; return SQLITE_OK; } /* ** Attempt an incremental merge that writes nMerge leaf blocks. ** ** Incremental merges happen nMin segments at a time. The segments ** to be merged are the nMin oldest segments (the ones with the smallest ** values for the _segdir.idx field) in the highest level that contains ** at least nMin segments. Multiple merges might occur in an attempt to ** write the quota of nMerge leaf blocks. */ int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ int rc; /* Return code */ int nRem = nMerge; /* Number of leaf pages yet to be written */ Fts3MultiSegReader *pCsr; /* Cursor used to read input data */ Fts3SegFilter *pFilter; /* Filter used with cursor pCsr */ IncrmergeWriter *pWriter; /* Writer object */ int nSeg = 0; /* Number of input segments */ sqlite3_int64 iAbsLevel = 0; /* Absolute level number to work on */ Blob hint = {0, 0, 0}; /* Hint read from %_stat table */ int bDirtyHint = 0; /* True if blob 'hint' has been modified */ /* Allocate space for the cursor, filter and writer objects */ const int nAlloc = sizeof(*pCsr) + sizeof(*pFilter) + sizeof(*pWriter); pWriter = (IncrmergeWriter *)sqlite3_malloc(nAlloc); if( !pWriter ) return SQLITE_NOMEM; pFilter = (Fts3SegFilter *)&pWriter[1]; pCsr = (Fts3MultiSegReader *)&pFilter[1]; rc = fts3IncrmergeHintLoad(p, &hint); while( rc==SQLITE_OK && nRem>0 ){ const i64 nMod = FTS3_SEGDIR_MAXLEVEL * p->nIndex; sqlite3_stmt *pFindLevel = 0; /* SQL used to determine iAbsLevel */ int bUseHint = 0; /* True if attempting to append */ int iIdx = 0; /* Largest idx in level (iAbsLevel+1) */ /* Search the %_segdir table for the absolute level with the smallest ** relative level number that contains at least nMin segments, if any. ** If one is found, set iAbsLevel to the absolute level number and ** nSeg to nMin. If no level with at least nMin segments can be found, ** set nSeg to -1. */ rc = fts3SqlStmt(p, SQL_FIND_MERGE_LEVEL, &pFindLevel, 0); sqlite3_bind_int(pFindLevel, 1, MAX(2, nMin)); if( sqlite3_step(pFindLevel)==SQLITE_ROW ){ iAbsLevel = sqlite3_column_int64(pFindLevel, 0); nSeg = sqlite3_column_int(pFindLevel, 1); assert( nSeg>=2 ); }else{ nSeg = -1; } rc = sqlite3_reset(pFindLevel); /* If the hint read from the %_stat table is not empty, check if the ** last entry in it specifies a relative level smaller than or equal ** to the level identified by the block above (if any). If so, this ** iteration of the loop will work on merging at the hinted level. */ if( rc==SQLITE_OK && hint.n ){ int nHint = hint.n; sqlite3_int64 iHintAbsLevel = 0; /* Hint level */ int nHintSeg = 0; /* Hint number of segments */ rc = fts3IncrmergeHintPop(&hint, &iHintAbsLevel, &nHintSeg); if( nSeg<0 || (iAbsLevel % nMod) >= (iHintAbsLevel % nMod) ){ iAbsLevel = iHintAbsLevel; nSeg = nHintSeg; bUseHint = 1; bDirtyHint = 1; }else{ /* This undoes the effect of the HintPop() above - so that no entry ** is removed from the hint blob. */ hint.n = nHint; } } /* If nSeg is less that zero, then there is no level with at least ** nMin segments and no hint in the %_stat table. No work to do. ** Exit early in this case. */ if( nSeg<0 ) break; /* Open a cursor to iterate through the contents of the oldest nSeg ** indexes of absolute level iAbsLevel. If this cursor is opened using ** the 'hint' parameters, it is possible that there are less than nSeg ** segments available in level iAbsLevel. In this case, no work is ** done on iAbsLevel - fall through to the next iteration of the loop ** to start work on some other level. */ memset(pWriter, 0, nAlloc); pFilter->flags = FTS3_SEGMENT_REQUIRE_POS; if( rc==SQLITE_OK ){ rc = fts3IncrmergeOutputIdx(p, iAbsLevel, &iIdx); assert( bUseHint==1 || bUseHint==0 ); if( iIdx==0 || (bUseHint && iIdx==1) ){ int bIgnore = 0; rc = fts3SegmentIsMaxLevel(p, iAbsLevel+1, &bIgnore); if( bIgnore ){ pFilter->flags |= FTS3_SEGMENT_IGNORE_EMPTY; } } } if( rc==SQLITE_OK ){ rc = fts3IncrmergeCsr(p, iAbsLevel, nSeg, pCsr); } if( SQLITE_OK==rc && pCsr->nSegment==nSeg && SQLITE_OK==(rc = sqlite3Fts3SegReaderStart(p, pCsr, pFilter)) && SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, pCsr)) ){ if( bUseHint && iIdx>0 ){ const char *zKey = pCsr->zTerm; int nKey = pCsr->nTerm; rc = fts3IncrmergeLoad(p, iAbsLevel, iIdx-1, zKey, nKey, pWriter); }else{ rc = fts3IncrmergeWriter(p, iAbsLevel, iIdx, pCsr, pWriter); } if( rc==SQLITE_OK && pWriter->nLeafEst ){ fts3LogMerge(nSeg, iAbsLevel); do { rc = fts3IncrmergeAppend(p, pWriter, pCsr); if( rc==SQLITE_OK ) rc = sqlite3Fts3SegReaderStep(p, pCsr); if( pWriter->nWork>=nRem && rc==SQLITE_ROW ) rc = SQLITE_OK; }while( rc==SQLITE_ROW ); /* Update or delete the input segments */ if( rc==SQLITE_OK ){ nRem -= (1 + pWriter->nWork); rc = fts3IncrmergeChomp(p, iAbsLevel, pCsr, &nSeg); if( nSeg!=0 ){ bDirtyHint = 1; fts3IncrmergeHintPush(&hint, iAbsLevel, nSeg, &rc); } } } if( nSeg!=0 ){ pWriter->nLeafData = pWriter->nLeafData * -1; } fts3IncrmergeRelease(p, pWriter, &rc); if( nSeg==0 && pWriter->bNoLeafData==0 ){ fts3PromoteSegments(p, iAbsLevel+1, pWriter->nLeafData); } } sqlite3Fts3SegReaderFinish(pCsr); } /* Write the hint values into the %_stat table for the next incr-merger */ if( bDirtyHint && rc==SQLITE_OK ){ rc = fts3IncrmergeHintStore(p, &hint); } sqlite3_free(pWriter); sqlite3_free(hint.a); return rc; } /* ** Convert the text beginning at *pz into an integer and return ** its value. Advance *pz to point to the first character past ** the integer. */ static int fts3Getint(const char **pz){ const char *z = *pz; int i = 0; while( (*z)>='0' && (*z)<='9' ) i = 10*i + *(z++) - '0'; *pz = z; return i; } /* ** Process statements of the form: ** ** INSERT INTO table(table) VALUES('merge=A,B'); ** ** A and B are integers that decode to be the number of leaf pages ** written for the merge, and the minimum number of segments on a level ** before it will be selected for a merge, respectively. */ static int fts3DoIncrmerge( Fts3Table *p, /* FTS3 table handle */ const char *zParam /* Nul-terminated string containing "A,B" */ ){ int rc; int nMin = (FTS3_MERGE_COUNT / 2); int nMerge = 0; const char *z = zParam; /* Read the first integer value */ nMerge = fts3Getint(&z); /* If the first integer value is followed by a ',', read the second ** integer value. */ if( z[0]==',' && z[1]!='\0' ){ z++; nMin = fts3Getint(&z); } if( z[0]!='\0' || nMin<2 ){ rc = SQLITE_ERROR; }else{ rc = SQLITE_OK; if( !p->bHasStat ){ assert( p->bFts4==0 ); sqlite3Fts3CreateStatTable(&rc, p); } if( rc==SQLITE_OK ){ rc = sqlite3Fts3Incrmerge(p, nMerge, nMin); } sqlite3Fts3SegmentsClose(p); } return rc; } /* ** Process statements of the form: ** ** INSERT INTO table(table) VALUES('automerge=X'); ** ** where X is an integer. X==0 means to turn automerge off. X!=0 means ** turn it on. The setting is persistent. */ static int fts3DoAutoincrmerge( Fts3Table *p, /* FTS3 table handle */ const char *zParam /* Nul-terminated string containing boolean */ ){ int rc = SQLITE_OK; sqlite3_stmt *pStmt = 0; p->nAutoincrmerge = fts3Getint(&zParam); if( p->nAutoincrmerge==1 || p->nAutoincrmerge>FTS3_MERGE_COUNT ){ p->nAutoincrmerge = 8; } if( !p->bHasStat ){ assert( p->bFts4==0 ); sqlite3Fts3CreateStatTable(&rc, p); if( rc ) return rc; } rc = fts3SqlStmt(p, SQL_REPLACE_STAT, &pStmt, 0); if( rc ) return rc; sqlite3_bind_int(pStmt, 1, FTS_STAT_AUTOINCRMERGE); sqlite3_bind_int(pStmt, 2, p->nAutoincrmerge); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); return rc; } /* ** Return a 64-bit checksum for the FTS index entry specified by the ** arguments to this function. */ static u64 fts3ChecksumEntry( const char *zTerm, /* Pointer to buffer containing term */ int nTerm, /* Size of zTerm in bytes */ int iLangid, /* Language id for current row */ int iIndex, /* Index (0..Fts3Table.nIndex-1) */ i64 iDocid, /* Docid for current row. */ int iCol, /* Column number */ int iPos /* Position */ ){ int i; u64 ret = (u64)iDocid; ret += (ret<<3) + iLangid; ret += (ret<<3) + iIndex; ret += (ret<<3) + iCol; ret += (ret<<3) + iPos; for(i=0; inIndex-1) */ int *pRc /* OUT: Return code */ ){ Fts3SegFilter filter; Fts3MultiSegReader csr; int rc; u64 cksum = 0; assert( *pRc==SQLITE_OK ); memset(&filter, 0, sizeof(filter)); memset(&csr, 0, sizeof(csr)); filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY; filter.flags |= FTS3_SEGMENT_SCAN; rc = sqlite3Fts3SegReaderCursor( p, iLangid, iIndex, FTS3_SEGCURSOR_ALL, 0, 0, 0, 1,&csr ); if( rc==SQLITE_OK ){ rc = sqlite3Fts3SegReaderStart(p, &csr, &filter); } if( rc==SQLITE_OK ){ while( SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, &csr)) ){ char *pCsr = csr.aDoclist; char *pEnd = &pCsr[csr.nDoclist]; i64 iDocid = 0; i64 iCol = 0; i64 iPos = 0; pCsr += sqlite3Fts3GetVarint(pCsr, &iDocid); while( pCsriPrevLangid); sqlite3_bind_int(pAllLangid, 2, p->nIndex); while( rc==SQLITE_OK && sqlite3_step(pAllLangid)==SQLITE_ROW ){ int iLangid = sqlite3_column_int(pAllLangid, 0); int i; for(i=0; inIndex; i++){ cksum1 = cksum1 ^ fts3ChecksumIndex(p, iLangid, i, &rc); } } rc2 = sqlite3_reset(pAllLangid); if( rc==SQLITE_OK ) rc = rc2; } /* This block calculates the checksum according to the %_content table */ if( rc==SQLITE_OK ){ sqlite3_tokenizer_module const *pModule = p->pTokenizer->pModule; sqlite3_stmt *pStmt = 0; char *zSql; zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist); if( !zSql ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ i64 iDocid = sqlite3_column_int64(pStmt, 0); int iLang = langidFromSelect(p, pStmt); int iCol; for(iCol=0; rc==SQLITE_OK && iColnColumn; iCol++){ if( p->abNotindexed[iCol]==0 ){ const char *zText = (const char *)sqlite3_column_text(pStmt, iCol+1); int nText = sqlite3_column_bytes(pStmt, iCol+1); sqlite3_tokenizer_cursor *pT = 0; rc = sqlite3Fts3OpenTokenizer(p->pTokenizer, iLang, zText, nText,&pT); while( rc==SQLITE_OK ){ char const *zToken; /* Buffer containing token */ int nToken = 0; /* Number of bytes in token */ int iDum1 = 0, iDum2 = 0; /* Dummy variables */ int iPos = 0; /* Position of token in zText */ rc = pModule->xNext(pT, &zToken, &nToken, &iDum1, &iDum2, &iPos); if( rc==SQLITE_OK ){ int i; cksum2 = cksum2 ^ fts3ChecksumEntry( zToken, nToken, iLang, 0, iDocid, iCol, iPos ); for(i=1; inIndex; i++){ if( p->aIndex[i].nPrefix<=nToken ){ cksum2 = cksum2 ^ fts3ChecksumEntry( zToken, p->aIndex[i].nPrefix, iLang, i, iDocid, iCol, iPos ); } } } } if( pT ) pModule->xClose(pT); if( rc==SQLITE_DONE ) rc = SQLITE_OK; } } } sqlite3_finalize(pStmt); } *pbOk = (cksum1==cksum2); return rc; } /* ** Run the integrity-check. If no error occurs and the current contents of ** the FTS index are correct, return SQLITE_OK. Or, if the contents of the ** FTS index are incorrect, return SQLITE_CORRUPT_VTAB. ** ** Or, if an error (e.g. an OOM or IO error) occurs, return an SQLite ** error code. ** ** The integrity-check works as follows. For each token and indexed token ** prefix in the document set, a 64-bit checksum is calculated (by code ** in fts3ChecksumEntry()) based on the following: ** ** + The index number (0 for the main index, 1 for the first prefix ** index etc.), ** + The token (or token prefix) text itself, ** + The language-id of the row it appears in, ** + The docid of the row it appears in, ** + The column it appears in, and ** + The tokens position within that column. ** ** The checksums for all entries in the index are XORed together to create ** a single checksum for the entire index. ** ** The integrity-check code calculates the same checksum in two ways: ** ** 1. By scanning the contents of the FTS index, and ** 2. By scanning and tokenizing the content table. ** ** If the two checksums are identical, the integrity-check is deemed to have ** passed. */ static int fts3DoIntegrityCheck( Fts3Table *p /* FTS3 table handle */ ){ int rc; int bOk = 0; rc = fts3IntegrityCheck(p, &bOk); if( rc==SQLITE_OK && bOk==0 ) rc = FTS_CORRUPT_VTAB; return rc; } /* ** Handle a 'special' INSERT of the form: ** ** "INSERT INTO tbl(tbl) VALUES()" ** ** Argument pVal contains the result of . Currently the only ** meaningful value to insert is the text 'optimize'. */ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){ int rc; /* Return Code */ const char *zVal = (const char *)sqlite3_value_text(pVal); int nVal = sqlite3_value_bytes(pVal); if( !zVal ){ return SQLITE_NOMEM; }else if( nVal==8 && 0==sqlite3_strnicmp(zVal, "optimize", 8) ){ rc = fts3DoOptimize(p, 0); }else if( nVal==7 && 0==sqlite3_strnicmp(zVal, "rebuild", 7) ){ rc = fts3DoRebuild(p); }else if( nVal==15 && 0==sqlite3_strnicmp(zVal, "integrity-check", 15) ){ rc = fts3DoIntegrityCheck(p); }else if( nVal>6 && 0==sqlite3_strnicmp(zVal, "merge=", 6) ){ rc = fts3DoIncrmerge(p, &zVal[6]); }else if( nVal>10 && 0==sqlite3_strnicmp(zVal, "automerge=", 10) ){ rc = fts3DoAutoincrmerge(p, &zVal[10]); #ifdef SQLITE_TEST }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){ p->nNodeSize = atoi(&zVal[9]); rc = SQLITE_OK; }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){ p->nMaxPendingData = atoi(&zVal[11]); rc = SQLITE_OK; }else if( nVal>21 && 0==sqlite3_strnicmp(zVal, "test-no-incr-doclist=", 21) ){ p->bNoIncrDoclist = atoi(&zVal[21]); rc = SQLITE_OK; #endif }else{ rc = SQLITE_ERROR; } return rc; } #ifndef SQLITE_DISABLE_FTS4_DEFERRED /* ** Delete all cached deferred doclists. Deferred doclists are cached ** (allocated) by the sqlite3Fts3CacheDeferredDoclists() function. */ void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *pCsr){ Fts3DeferredToken *pDef; for(pDef=pCsr->pDeferred; pDef; pDef=pDef->pNext){ fts3PendingListDelete(pDef->pList); pDef->pList = 0; } } /* ** Free all entries in the pCsr->pDeffered list. Entries are added to ** this list using sqlite3Fts3DeferToken(). */ void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *pCsr){ Fts3DeferredToken *pDef; Fts3DeferredToken *pNext; for(pDef=pCsr->pDeferred; pDef; pDef=pNext){ pNext = pDef->pNext; fts3PendingListDelete(pDef->pList); sqlite3_free(pDef); } pCsr->pDeferred = 0; } /* ** Generate deferred-doclists for all tokens in the pCsr->pDeferred list ** based on the row that pCsr currently points to. ** ** A deferred-doclist is like any other doclist with position information ** included, except that it only contains entries for a single row of the ** table, not for all rows. */ int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *pCsr){ int rc = SQLITE_OK; /* Return code */ if( pCsr->pDeferred ){ int i; /* Used to iterate through table columns */ sqlite3_int64 iDocid; /* Docid of the row pCsr points to */ Fts3DeferredToken *pDef; /* Used to iterate through deferred tokens */ Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; sqlite3_tokenizer *pT = p->pTokenizer; sqlite3_tokenizer_module const *pModule = pT->pModule; assert( pCsr->isRequireSeek==0 ); iDocid = sqlite3_column_int64(pCsr->pStmt, 0); for(i=0; inColumn && rc==SQLITE_OK; i++){ if( p->abNotindexed[i]==0 ){ const char *zText = (const char *)sqlite3_column_text(pCsr->pStmt, i+1); sqlite3_tokenizer_cursor *pTC = 0; rc = sqlite3Fts3OpenTokenizer(pT, pCsr->iLangid, zText, -1, &pTC); while( rc==SQLITE_OK ){ char const *zToken; /* Buffer containing token */ int nToken = 0; /* Number of bytes in token */ int iDum1 = 0, iDum2 = 0; /* Dummy variables */ int iPos = 0; /* Position of token in zText */ rc = pModule->xNext(pTC, &zToken, &nToken, &iDum1, &iDum2, &iPos); for(pDef=pCsr->pDeferred; pDef && rc==SQLITE_OK; pDef=pDef->pNext){ Fts3PhraseToken *pPT = pDef->pToken; if( (pDef->iCol>=p->nColumn || pDef->iCol==i) && (pPT->bFirst==0 || iPos==0) && (pPT->n==nToken || (pPT->isPrefix && pPT->nz, pPT->n)) ){ fts3PendingListAppend(&pDef->pList, iDocid, i, iPos, &rc); } } } if( pTC ) pModule->xClose(pTC); if( rc==SQLITE_DONE ) rc = SQLITE_OK; } } for(pDef=pCsr->pDeferred; pDef && rc==SQLITE_OK; pDef=pDef->pNext){ if( pDef->pList ){ rc = fts3PendingListAppendVarint(&pDef->pList, 0); } } } return rc; } int sqlite3Fts3DeferredTokenList( Fts3DeferredToken *p, char **ppData, int *pnData ){ char *pRet; int nSkip; sqlite3_int64 dummy; *ppData = 0; *pnData = 0; if( p->pList==0 ){ return SQLITE_OK; } pRet = (char *)sqlite3_malloc(p->pList->nData); if( !pRet ) return SQLITE_NOMEM; nSkip = sqlite3Fts3GetVarint(p->pList->aData, &dummy); *pnData = p->pList->nData - nSkip; *ppData = pRet; memcpy(pRet, &p->pList->aData[nSkip], *pnData); return SQLITE_OK; } /* ** Add an entry for token pToken to the pCsr->pDeferred list. */ int sqlite3Fts3DeferToken( Fts3Cursor *pCsr, /* Fts3 table cursor */ Fts3PhraseToken *pToken, /* Token to defer */ int iCol /* Column that token must appear in (or -1) */ ){ Fts3DeferredToken *pDeferred; pDeferred = sqlite3_malloc(sizeof(*pDeferred)); if( !pDeferred ){ return SQLITE_NOMEM; } memset(pDeferred, 0, sizeof(*pDeferred)); pDeferred->pToken = pToken; pDeferred->pNext = pCsr->pDeferred; pDeferred->iCol = iCol; pCsr->pDeferred = pDeferred; assert( pToken->pDeferred==0 ); pToken->pDeferred = pDeferred; return SQLITE_OK; } #endif /* ** SQLite value pRowid contains the rowid of a row that may or may not be ** present in the FTS3 table. If it is, delete it and adjust the contents ** of subsiduary data structures accordingly. */ static int fts3DeleteByRowid( Fts3Table *p, sqlite3_value *pRowid, int *pnChng, /* IN/OUT: Decrement if row is deleted */ u32 *aSzDel ){ int rc = SQLITE_OK; /* Return code */ int bFound = 0; /* True if *pRowid really is in the table */ fts3DeleteTerms(&rc, p, pRowid, aSzDel, &bFound); if( bFound && rc==SQLITE_OK ){ int isEmpty = 0; /* Deleting *pRowid leaves the table empty */ rc = fts3IsEmpty(p, pRowid, &isEmpty); if( rc==SQLITE_OK ){ if( isEmpty ){ /* Deleting this row means the whole table is empty. In this case ** delete the contents of all three tables and throw away any ** data in the pendingTerms hash table. */ rc = fts3DeleteAll(p, 1); *pnChng = 0; memset(aSzDel, 0, sizeof(u32) * (p->nColumn+1) * 2); }else{ *pnChng = *pnChng - 1; if( p->zContentTbl==0 ){ fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid); } if( p->bHasDocsize ){ fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, &pRowid); } } } } return rc; } /* ** This function does the work for the xUpdate method of FTS3 virtual ** tables. The schema of the virtual table being: ** ** CREATE TABLE ( ** , **
HIDDEN, ** docid HIDDEN, ** HIDDEN ** ); ** ** */ int sqlite3Fts3UpdateMethod( sqlite3_vtab *pVtab, /* FTS3 vtab object */ int nArg, /* Size of argument array */ sqlite3_value **apVal, /* Array of arguments */ sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */ ){ Fts3Table *p = (Fts3Table *)pVtab; int rc = SQLITE_OK; /* Return Code */ int isRemove = 0; /* True for an UPDATE or DELETE */ u32 *aSzIns = 0; /* Sizes of inserted documents */ u32 *aSzDel = 0; /* Sizes of deleted documents */ int nChng = 0; /* Net change in number of documents */ int bInsertDone = 0; /* At this point it must be known if the %_stat table exists or not. ** So bHasStat may not be 2. */ assert( p->bHasStat==0 || p->bHasStat==1 ); assert( p->pSegments==0 ); assert( nArg==1 /* DELETE operations */ || nArg==(2 + p->nColumn + 3) /* INSERT or UPDATE operations */ ); /* Check for a "special" INSERT operation. One of the form: ** ** INSERT INTO xyz(xyz) VALUES('command'); */ if( nArg>1 && sqlite3_value_type(apVal[0])==SQLITE_NULL && sqlite3_value_type(apVal[p->nColumn+2])!=SQLITE_NULL ){ rc = fts3SpecialInsert(p, apVal[p->nColumn+2]); goto update_out; } if( nArg>1 && sqlite3_value_int(apVal[2 + p->nColumn + 2])<0 ){ rc = SQLITE_CONSTRAINT; goto update_out; } /* Allocate space to hold the change in document sizes */ aSzDel = sqlite3_malloc( sizeof(aSzDel[0])*(p->nColumn+1)*2 ); if( aSzDel==0 ){ rc = SQLITE_NOMEM; goto update_out; } aSzIns = &aSzDel[p->nColumn+1]; memset(aSzDel, 0, sizeof(aSzDel[0])*(p->nColumn+1)*2); rc = fts3Writelock(p); if( rc!=SQLITE_OK ) goto update_out; /* If this is an INSERT operation, or an UPDATE that modifies the rowid ** value, then this operation requires constraint handling. ** ** If the on-conflict mode is REPLACE, this means that the existing row ** should be deleted from the database before inserting the new row. Or, ** if the on-conflict mode is other than REPLACE, then this method must ** detect the conflict and return SQLITE_CONSTRAINT before beginning to ** modify the database file. */ if( nArg>1 && p->zContentTbl==0 ){ /* Find the value object that holds the new rowid value. */ sqlite3_value *pNewRowid = apVal[3+p->nColumn]; if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){ pNewRowid = apVal[1]; } if( sqlite3_value_type(pNewRowid)!=SQLITE_NULL && ( sqlite3_value_type(apVal[0])==SQLITE_NULL || sqlite3_value_int64(apVal[0])!=sqlite3_value_int64(pNewRowid) )){ /* The new rowid is not NULL (in this case the rowid will be ** automatically assigned and there is no chance of a conflict), and ** the statement is either an INSERT or an UPDATE that modifies the ** rowid column. So if the conflict mode is REPLACE, then delete any ** existing row with rowid=pNewRowid. ** ** Or, if the conflict mode is not REPLACE, insert the new record into ** the %_content table. If we hit the duplicate rowid constraint (or any ** other error) while doing so, return immediately. ** ** This branch may also run if pNewRowid contains a value that cannot ** be losslessly converted to an integer. In this case, the eventual ** call to fts3InsertData() (either just below or further on in this ** function) will return SQLITE_MISMATCH. If fts3DeleteByRowid is ** invoked, it will delete zero rows (since no row will have ** docid=$pNewRowid if $pNewRowid is not an integer value). */ if( sqlite3_vtab_on_conflict(p->db)==SQLITE_REPLACE ){ rc = fts3DeleteByRowid(p, pNewRowid, &nChng, aSzDel); }else{ rc = fts3InsertData(p, apVal, pRowid); bInsertDone = 1; } } } if( rc!=SQLITE_OK ){ goto update_out; } /* If this is a DELETE or UPDATE operation, remove the old record. */ if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER ); rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel); isRemove = 1; } /* If this is an INSERT or UPDATE operation, insert the new record. */ if( nArg>1 && rc==SQLITE_OK ){ int iLangid = sqlite3_value_int(apVal[2 + p->nColumn + 2]); if( bInsertDone==0 ){ rc = fts3InsertData(p, apVal, pRowid); if( rc==SQLITE_CONSTRAINT && p->zContentTbl==0 ){ rc = FTS_CORRUPT_VTAB; } } if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){ rc = fts3PendingTermsDocid(p, 0, iLangid, *pRowid); } if( rc==SQLITE_OK ){ assert( p->iPrevDocid==*pRowid ); rc = fts3InsertTerms(p, iLangid, apVal, aSzIns); } if( p->bHasDocsize ){ fts3InsertDocsize(&rc, p, aSzIns); } nChng++; } if( p->bFts4 ){ fts3UpdateDocTotals(&rc, p, aSzIns, aSzDel, nChng); } update_out: sqlite3_free(aSzDel); sqlite3Fts3SegmentsClose(p); return rc; } /* ** Flush any data in the pending-terms hash table to disk. If successful, ** merge all segments in the database (including the new segment, if ** there was any data to flush) into a single segment. */ int sqlite3Fts3Optimize(Fts3Table *p){ int rc; rc = sqlite3_exec(p->db, "SAVEPOINT fts3", 0, 0, 0); if( rc==SQLITE_OK ){ rc = fts3DoOptimize(p, 1); if( rc==SQLITE_OK || rc==SQLITE_DONE ){ int rc2 = sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0); if( rc2!=SQLITE_OK ) rc = rc2; }else{ sqlite3_exec(p->db, "ROLLBACK TO fts3", 0, 0, 0); sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0); } } sqlite3Fts3SegmentsClose(p); return rc; } #endif ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/icu/icu.c ================================================ /* ** 2007 May 6 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** $Id: icu.c,v 1.7 2007/12/13 21:54:11 drh Exp $ ** ** This file implements an integration between the ICU library ** ("International Components for Unicode", an open-source library ** for handling unicode data) and SQLite. The integration uses ** ICU to provide the following to SQLite: ** ** * An implementation of the SQL regexp() function (and hence REGEXP ** operator) using the ICU uregex_XX() APIs. ** ** * Implementations of the SQL scalar upper() and lower() functions ** for case mapping. ** ** * Integration of ICU and SQLite collation sequences. ** ** * An implementation of the LIKE operator that uses ICU to ** provide case-independent matching. */ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) /* Include ICU headers */ #include #include #include #include #include #ifndef SQLITE_CORE #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 #else #include "sqlite3.h" #endif /* ** Maximum length (in bytes) of the pattern in a LIKE or GLOB ** operator. */ #ifndef SQLITE_MAX_LIKE_PATTERN_LENGTH # define SQLITE_MAX_LIKE_PATTERN_LENGTH 50000 #endif /* ** Version of sqlite3_free() that is always a function, never a macro. */ static void xFree(void *p){ sqlite3_free(p); } /* ** This lookup table is used to help decode the first byte of ** a multi-byte UTF8 character. It is copied here from SQLite source ** code file utf8.c. */ static const unsigned char icuUtf8Trans1[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, }; #define SQLITE_ICU_READ_UTF8(zIn, c) \ c = *(zIn++); \ if( c>=0xc0 ){ \ c = icuUtf8Trans1[c-0xc0]; \ while( (*zIn & 0xc0)==0x80 ){ \ c = (c<<6) + (0x3f & *(zIn++)); \ } \ } #define SQLITE_ICU_SKIP_UTF8(zIn) \ assert( *zIn ); \ if( *(zIn++)>=0xc0 ){ \ while( (*zIn & 0xc0)==0x80 ){zIn++;} \ } /* ** Compare two UTF-8 strings for equality where the first string is ** a "LIKE" expression. Return true (1) if they are the same and ** false (0) if they are different. */ static int icuLikeCompare( const uint8_t *zPattern, /* LIKE pattern */ const uint8_t *zString, /* The UTF-8 string to compare against */ const UChar32 uEsc /* The escape character */ ){ static const int MATCH_ONE = (UChar32)'_'; static const int MATCH_ALL = (UChar32)'%'; int prevEscape = 0; /* True if the previous character was uEsc */ while( 1 ){ /* Read (and consume) the next character from the input pattern. */ UChar32 uPattern; SQLITE_ICU_READ_UTF8(zPattern, uPattern); if( uPattern==0 ) break; /* There are now 4 possibilities: ** ** 1. uPattern is an unescaped match-all character "%", ** 2. uPattern is an unescaped match-one character "_", ** 3. uPattern is an unescaped escape character, or ** 4. uPattern is to be handled as an ordinary character */ if( !prevEscape && uPattern==MATCH_ALL ){ /* Case 1. */ uint8_t c; /* Skip any MATCH_ALL or MATCH_ONE characters that follow a ** MATCH_ALL. For each MATCH_ONE, skip one character in the ** test string. */ while( (c=*zPattern) == MATCH_ALL || c == MATCH_ONE ){ if( c==MATCH_ONE ){ if( *zString==0 ) return 0; SQLITE_ICU_SKIP_UTF8(zString); } zPattern++; } if( *zPattern==0 ) return 1; while( *zString ){ if( icuLikeCompare(zPattern, zString, uEsc) ){ return 1; } SQLITE_ICU_SKIP_UTF8(zString); } return 0; }else if( !prevEscape && uPattern==MATCH_ONE ){ /* Case 2. */ if( *zString==0 ) return 0; SQLITE_ICU_SKIP_UTF8(zString); }else if( !prevEscape && uPattern==uEsc){ /* Case 3. */ prevEscape = 1; }else{ /* Case 4. */ UChar32 uString; SQLITE_ICU_READ_UTF8(zString, uString); uString = u_foldCase(uString, U_FOLD_CASE_DEFAULT); uPattern = u_foldCase(uPattern, U_FOLD_CASE_DEFAULT); if( uString!=uPattern ){ return 0; } prevEscape = 0; } } return *zString==0; } /* ** Implementation of the like() SQL function. This function implements ** the build-in LIKE operator. The first argument to the function is the ** pattern and the second argument is the string. So, the SQL statements: ** ** A LIKE B ** ** is implemented as like(B, A). If there is an escape character E, ** ** A LIKE B ESCAPE E ** ** is mapped to like(B, A, E). */ static void icuLikeFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ const unsigned char *zA = sqlite3_value_text(argv[0]); const unsigned char *zB = sqlite3_value_text(argv[1]); UChar32 uEsc = 0; /* Limit the length of the LIKE or GLOB pattern to avoid problems ** of deep recursion and N*N behavior in patternCompare(). */ if( sqlite3_value_bytes(argv[0])>SQLITE_MAX_LIKE_PATTERN_LENGTH ){ sqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1); return; } if( argc==3 ){ /* The escape character string must consist of a single UTF-8 character. ** Otherwise, return an error. */ int nE= sqlite3_value_bytes(argv[2]); const unsigned char *zE = sqlite3_value_text(argv[2]); int i = 0; if( zE==0 ) return; U8_NEXT(zE, i, nE, uEsc); if( i!=nE){ sqlite3_result_error(context, "ESCAPE expression must be a single character", -1); return; } } if( zA && zB ){ sqlite3_result_int(context, icuLikeCompare(zA, zB, uEsc)); } } /* ** This function is called when an ICU function called from within ** the implementation of an SQL scalar function returns an error. ** ** The scalar function context passed as the first argument is ** loaded with an error message based on the following two args. */ static void icuFunctionError( sqlite3_context *pCtx, /* SQLite scalar function context */ const char *zName, /* Name of ICU function that failed */ UErrorCode e /* Error code returned by ICU function */ ){ char zBuf[128]; sqlite3_snprintf(128, zBuf, "ICU error: %s(): %s", zName, u_errorName(e)); zBuf[127] = '\0'; sqlite3_result_error(pCtx, zBuf, -1); } /* ** Function to delete compiled regexp objects. Registered as ** a destructor function with sqlite3_set_auxdata(). */ static void icuRegexpDelete(void *p){ URegularExpression *pExpr = (URegularExpression *)p; uregex_close(pExpr); } /* ** Implementation of SQLite REGEXP operator. This scalar function takes ** two arguments. The first is a regular expression pattern to compile ** the second is a string to match against that pattern. If either ** argument is an SQL NULL, then NULL Is returned. Otherwise, the result ** is 1 if the string matches the pattern, or 0 otherwise. ** ** SQLite maps the regexp() function to the regexp() operator such ** that the following two are equivalent: ** ** zString REGEXP zPattern ** regexp(zPattern, zString) ** ** Uses the following ICU regexp APIs: ** ** uregex_open() ** uregex_matches() ** uregex_close() */ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){ UErrorCode status = U_ZERO_ERROR; URegularExpression *pExpr; UBool res; const UChar *zString = sqlite3_value_text16(apArg[1]); (void)nArg; /* Unused parameter */ /* If the left hand side of the regexp operator is NULL, ** then the result is also NULL. */ if( !zString ){ return; } pExpr = sqlite3_get_auxdata(p, 0); if( !pExpr ){ const UChar *zPattern = sqlite3_value_text16(apArg[0]); if( !zPattern ){ return; } pExpr = uregex_open(zPattern, -1, 0, 0, &status); if( U_SUCCESS(status) ){ sqlite3_set_auxdata(p, 0, pExpr, icuRegexpDelete); }else{ assert(!pExpr); icuFunctionError(p, "uregex_open", status); return; } } /* Configure the text that the regular expression operates on. */ uregex_setText(pExpr, zString, -1, &status); if( !U_SUCCESS(status) ){ icuFunctionError(p, "uregex_setText", status); return; } /* Attempt the match */ res = uregex_matches(pExpr, 0, &status); if( !U_SUCCESS(status) ){ icuFunctionError(p, "uregex_matches", status); return; } /* Set the text that the regular expression operates on to a NULL ** pointer. This is not really necessary, but it is tidier than ** leaving the regular expression object configured with an invalid ** pointer after this function returns. */ uregex_setText(pExpr, 0, 0, &status); /* Return 1 or 0. */ sqlite3_result_int(p, res ? 1 : 0); } /* ** Implementations of scalar functions for case mapping - upper() and ** lower(). Function upper() converts its input to upper-case (ABC). ** Function lower() converts to lower-case (abc). ** ** ICU provides two types of case mapping, "general" case mapping and ** "language specific". Refer to ICU documentation for the differences ** between the two. ** ** To utilise "general" case mapping, the upper() or lower() scalar ** functions are invoked with one argument: ** ** upper('ABC') -> 'abc' ** lower('abc') -> 'ABC' ** ** To access ICU "language specific" case mapping, upper() or lower() ** should be invoked with two arguments. The second argument is the name ** of the locale to use. Passing an empty string ("") or SQL NULL value ** as the second argument is the same as invoking the 1 argument version ** of upper() or lower(). ** ** lower('I', 'en_us') -> 'i' ** lower('I', 'tr_tr') -> '\u131' (small dotless i) ** ** http://www.icu-project.org/userguide/posix.html#case_mappings */ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ const UChar *zInput; /* Pointer to input string */ UChar *zOutput = 0; /* Pointer to output buffer */ int nInput; /* Size of utf-16 input string in bytes */ int nOut; /* Size of output buffer in bytes */ int cnt; int bToUpper; /* True for toupper(), false for tolower() */ UErrorCode status; const char *zLocale = 0; assert(nArg==1 || nArg==2); bToUpper = (sqlite3_user_data(p)!=0); if( nArg==2 ){ zLocale = (const char *)sqlite3_value_text(apArg[1]); } zInput = sqlite3_value_text16(apArg[0]); if( !zInput ){ return; } nOut = nInput = sqlite3_value_bytes16(apArg[0]); if( nOut==0 ){ sqlite3_result_text16(p, "", 0, SQLITE_STATIC); return; } for(cnt=0; cnt<2; cnt++){ UChar *zNew = sqlite3_realloc(zOutput, nOut); if( zNew==0 ){ sqlite3_free(zOutput); sqlite3_result_error_nomem(p); return; } zOutput = zNew; status = U_ZERO_ERROR; if( bToUpper ){ nOut = 2*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); }else{ nOut = 2*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); } if( U_SUCCESS(status) ){ sqlite3_result_text16(p, zOutput, nOut, xFree); }else if( status==U_BUFFER_OVERFLOW_ERROR ){ assert( cnt==0 ); continue; }else{ icuFunctionError(p, bToUpper ? "u_strToUpper" : "u_strToLower", status); } return; } assert( 0 ); /* Unreachable */ } /* ** Collation sequence destructor function. The pCtx argument points to ** a UCollator structure previously allocated using ucol_open(). */ static void icuCollationDel(void *pCtx){ UCollator *p = (UCollator *)pCtx; ucol_close(p); } /* ** Collation sequence comparison function. The pCtx argument points to ** a UCollator structure previously allocated using ucol_open(). */ static int icuCollationColl( void *pCtx, int nLeft, const void *zLeft, int nRight, const void *zRight ){ UCollationResult res; UCollator *p = (UCollator *)pCtx; res = ucol_strcoll(p, (UChar *)zLeft, nLeft/2, (UChar *)zRight, nRight/2); switch( res ){ case UCOL_LESS: return -1; case UCOL_GREATER: return +1; case UCOL_EQUAL: return 0; } assert(!"Unexpected return value from ucol_strcoll()"); return 0; } /* ** Implementation of the scalar function icu_load_collation(). ** ** This scalar function is used to add ICU collation based collation ** types to an SQLite database connection. It is intended to be called ** as follows: ** ** SELECT icu_load_collation(, ); ** ** Where is a string containing an ICU locale identifier (i.e. ** "en_AU", "tr_TR" etc.) and is the name of the ** collation sequence to create. */ static void icuLoadCollation( sqlite3_context *p, int nArg, sqlite3_value **apArg ){ sqlite3 *db = (sqlite3 *)sqlite3_user_data(p); UErrorCode status = U_ZERO_ERROR; const char *zLocale; /* Locale identifier - (eg. "jp_JP") */ const char *zName; /* SQL Collation sequence name (eg. "japanese") */ UCollator *pUCollator; /* ICU library collation object */ int rc; /* Return code from sqlite3_create_collation_x() */ assert(nArg==2); (void)nArg; /* Unused parameter */ zLocale = (const char *)sqlite3_value_text(apArg[0]); zName = (const char *)sqlite3_value_text(apArg[1]); if( !zLocale || !zName ){ return; } pUCollator = ucol_open(zLocale, &status); if( !U_SUCCESS(status) ){ icuFunctionError(p, "ucol_open", status); return; } assert(p); rc = sqlite3_create_collation_v2(db, zName, SQLITE_UTF16, (void *)pUCollator, icuCollationColl, icuCollationDel ); if( rc!=SQLITE_OK ){ ucol_close(pUCollator); sqlite3_result_error(p, "Error registering collation function", -1); } } /* ** Register the ICU extension functions with database db. */ int sqlite3IcuInit(sqlite3 *db){ struct IcuScalar { const char *zName; /* Function name */ int nArg; /* Number of arguments */ int enc; /* Optimal text encoding */ void *pContext; /* sqlite3_user_data() context */ void (*xFunc)(sqlite3_context*,int,sqlite3_value**); } scalars[] = { {"regexp", 2, SQLITE_ANY|SQLITE_DETERMINISTIC, 0, icuRegexpFunc}, {"lower", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, {"lower", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, {"upper", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16}, {"upper", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16}, {"lower", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, {"lower", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, {"upper", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16}, {"upper", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16}, {"like", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc}, {"like", 3, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc}, {"icu_load_collation", 2, SQLITE_UTF8, (void*)db, icuLoadCollation}, }; int rc = SQLITE_OK; int i; for(i=0; rc==SQLITE_OK && i<(int)(sizeof(scalars)/sizeof(scalars[0])); i++){ struct IcuScalar *p = &scalars[i]; rc = sqlite3_create_function( db, p->zName, p->nArg, p->enc, p->pContext, p->xFunc, 0, 0 ); } return rc; } #if !SQLITE_CORE #ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_icu_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ SQLITE_EXTENSION_INIT2(pApi) return sqlite3IcuInit(db); } #endif #endif ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/icu/sqliteicu.h ================================================ /* ** 2008 May 26 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This header file is used by programs that want to link against the ** ICU extension. All it does is declare the sqlite3IcuInit() interface. */ #include "sqlite3.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ int sqlite3IcuInit(sqlite3 *db); #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ ================================================ FILE: JetChat/Pods/WCDBOptimizedSQLCipher/ext/rbu/sqlite3rbu.c ================================================ /* ** 2014 August 30 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** ** ** OVERVIEW ** ** The RBU extension requires that the RBU update be packaged as an ** SQLite database. The tables it expects to find are described in ** sqlite3rbu.h. Essentially, for each table xyz in the target database ** that the user wishes to write to, a corresponding data_xyz table is ** created in the RBU database and populated with one row for each row to ** update, insert or delete from the target table. ** ** The update proceeds in three stages: ** ** 1) The database is updated. The modified database pages are written ** to a *-oal file. A *-oal file is just like a *-wal file, except ** that it is named "-oal" instead of "-wal". ** Because regular SQLite clients do not look for file named ** "-oal", they go on using the original database in ** rollback mode while the *-oal file is being generated. ** ** During this stage RBU does not update the database by writing ** directly to the target tables. Instead it creates "imposter" ** tables using the SQLITE_TESTCTRL_IMPOSTER interface that it uses ** to update each b-tree individually. All updates required by each ** b-tree are completed before moving on to the next, and all ** updates are done in sorted key order. ** ** 2) The "-oal" file is moved to the equivalent "-wal" ** location using a call to rename(2). Before doing this the RBU ** module takes an EXCLUSIVE lock on the database file, ensuring ** that there are no other active readers. ** ** Once the EXCLUSIVE lock is released, any other database readers ** detect the new *-wal file and read the database in wal mode. At ** this point they see the new version of the database - including ** the updates made as part of the RBU update. ** ** 3) The new *-wal file is checkpointed. This proceeds in the same way ** as a regular database checkpoint, except that a single frame is ** checkpointed each time sqlite3rbu_step() is called. If the RBU ** handle is closed before the entire *-wal file is checkpointed, ** the checkpoint progress is saved in the RBU database and the ** checkpoint can be resumed by another RBU client at some point in ** the future. ** ** POTENTIAL PROBLEMS ** ** The rename() call might not be portable. And RBU is not currently ** syncing the directory after renaming the file. ** ** When state is saved, any commit to the *-oal file and the commit to ** the RBU update database are not atomic. So if the power fails at the ** wrong moment they might get out of sync. As the main database will be ** committed before the RBU update database this will likely either just ** pass unnoticed, or result in SQLITE_CONSTRAINT errors (due to UNIQUE ** constraint violations). ** ** If some client does modify the target database mid RBU update, or some ** other error occurs, the RBU extension will keep throwing errors. It's ** not really clear how to get out of this state. The system could just ** by delete the RBU update database and *-oal file and have the device ** download the update again and start over. ** ** At present, for an UPDATE, both the new.* and old.* records are ** collected in the rbu_xyz table. And for both UPDATEs and DELETEs all ** fields are collected. This means we're probably writing a lot more ** data to disk when saving the state of an ongoing update to the RBU ** update database than is strictly necessary. ** */ #include #include #include #include "sqlite3.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RBU) #include "sqlite3rbu.h" #if defined(_WIN32_WCE) #include "windows.h" #endif /* Maximum number of prepared UPDATE statements held by this module */ #define SQLITE_RBU_UPDATE_CACHESIZE 16 /* ** Swap two objects of type TYPE. */ #if !defined(SQLITE_AMALGAMATION) # define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;} #endif /* ** The rbu_state table is used to save the state of a partially applied ** update so that it can be resumed later. The table consists of integer ** keys mapped to values as follows: ** ** RBU_STATE_STAGE: ** May be set to integer values 1, 2, 4 or 5. As follows: ** 1: the *-rbu file is currently under construction. ** 2: the *-rbu file has been constructed, but not yet moved ** to the *-wal path. ** 4: the checkpoint is underway. ** 5: the rbu update has been checkpointed. ** ** RBU_STATE_TBL: ** Only valid if STAGE==1. The target database name of the table ** currently being written. ** ** RBU_STATE_IDX: ** Only valid if STAGE==1. The target database name of the index ** currently being written, or NULL if the main table is currently being ** updated. ** ** RBU_STATE_ROW: ** Only valid if STAGE==1. Number of rows already processed for the current ** table/index. ** ** RBU_STATE_PROGRESS: ** Trbul number of sqlite3rbu_step() calls made so far as part of this ** rbu update. ** ** RBU_STATE_CKPT: ** Valid if STAGE==4. The 64-bit checksum associated with the wal-index ** header created by recovering the *-wal file. This is used to detect ** cases when another client appends frames to the *-wal file in the ** middle of an incremental checkpoint (an incremental checkpoint cannot ** be continued if this happens). ** ** RBU_STATE_COOKIE: ** Valid if STAGE==1. The current change-counter cookie value in the ** target db file. ** ** RBU_STATE_OALSZ: ** Valid if STAGE==1. The size in bytes of the *-oal file. */ #define RBU_STATE_STAGE 1 #define RBU_STATE_TBL 2 #define RBU_STATE_IDX 3 #define RBU_STATE_ROW 4 #define RBU_STATE_PROGRESS 5 #define RBU_STATE_CKPT 6 #define RBU_STATE_COOKIE 7 #define RBU_STATE_OALSZ 8 #define RBU_STATE_PHASEONESTEP 9 #define RBU_STAGE_OAL 1 #define RBU_STAGE_MOVE 2 #define RBU_STAGE_CAPTURE 3 #define RBU_STAGE_CKPT 4 #define RBU_STAGE_DONE 5 #define RBU_CREATE_STATE \ "CREATE TABLE IF NOT EXISTS %s.rbu_state(k INTEGER PRIMARY KEY, v)" typedef struct RbuFrame RbuFrame; typedef struct RbuObjIter RbuObjIter; typedef struct RbuState RbuState; typedef struct rbu_vfs rbu_vfs; typedef struct rbu_file rbu_file; typedef struct RbuUpdateStmt RbuUpdateStmt; #if !defined(SQLITE_AMALGAMATION) typedef unsigned int u32; typedef unsigned short u16; typedef unsigned char u8; typedef sqlite3_int64 i64; #endif /* ** These values must match the values defined in wal.c for the equivalent ** locks. These are not magic numbers as they are part of the SQLite file ** format. */ #define WAL_LOCK_WRITE 0 #define WAL_LOCK_CKPT 1 #define WAL_LOCK_READ0 3 #define SQLITE_FCNTL_RBUCNT 5149216 /* ** A structure to store values read from the rbu_state table in memory. */ struct RbuState { int eStage; char *zTbl; char *zIdx; i64 iWalCksum; int nRow; i64 nProgress; u32 iCookie; i64 iOalSz; i64 nPhaseOneStep; }; struct RbuUpdateStmt { char *zMask; /* Copy of update mask used with pUpdate */ sqlite3_stmt *pUpdate; /* Last update statement (or NULL) */ RbuUpdateStmt *pNext; }; /* ** An iterator of this type is used to iterate through all objects in ** the target database that require updating. For each such table, the ** iterator visits, in order: ** ** * the table itself, ** * each index of the table (zero or more points to visit), and ** * a special "cleanup table" state. ** ** abIndexed: ** If the table has no indexes on it, abIndexed is set to NULL. Otherwise, ** it points to an array of flags nTblCol elements in size. The flag is ** set for each column that is either a part of the PK or a part of an ** index. Or clear otherwise. ** */ struct RbuObjIter { sqlite3_stmt *pTblIter; /* Iterate through tables */ sqlite3_stmt *pIdxIter; /* Index iterator */ int nTblCol; /* Size of azTblCol[] array */ char **azTblCol; /* Array of unquoted target column names */ char **azTblType; /* Array of target column types */ int *aiSrcOrder; /* src table col -> target table col */ u8 *abTblPk; /* Array of flags, set on target PK columns */ u8 *abNotNull; /* Array of flags, set on NOT NULL columns */ u8 *abIndexed; /* Array of flags, set on indexed & PK cols */ int eType; /* Table type - an RBU_PK_XXX value */ /* Output variables. zTbl==0 implies EOF. */ int bCleanup; /* True in "cleanup" state */ const char *zTbl; /* Name of target db table */ const char *zDataTbl; /* Name of rbu db table (or null) */ const char *zIdx; /* Name of target db index (or null) */ int iTnum; /* Root page of current object */ int iPkTnum; /* If eType==EXTERNAL, root of PK index */ int bUnique; /* Current index is unique */ int nIndex; /* Number of aux. indexes on table zTbl */ /* Statements created by rbuObjIterPrepareAll() */ int nCol; /* Number of columns in current object */ sqlite3_stmt *pSelect; /* Source data */ sqlite3_stmt *pInsert; /* Statement for INSERT operations */ sqlite3_stmt *pDelete; /* Statement for DELETE ops */ sqlite3_stmt *pTmpInsert; /* Insert into rbu_tmp_$zDataTbl */ /* Last UPDATE used (for PK b-tree updates only), or NULL. */ RbuUpdateStmt *pRbuUpdate; }; /* ** Values for RbuObjIter.eType ** ** 0: Table does not exist (error) ** 1: Table has an implicit rowid. ** 2: Table has an explicit IPK column. ** 3: Table has an external PK index. ** 4: Table is WITHOUT ROWID. ** 5: Table is a virtual table. */ #define RBU_PK_NOTABLE 0 #define RBU_PK_NONE 1 #define RBU_PK_IPK 2 #define RBU_PK_EXTERNAL 3 #define RBU_PK_WITHOUT_ROWID 4 #define RBU_PK_VTAB 5 /* ** Within the RBU_STAGE_OAL stage, each call to sqlite3rbu_step() performs ** one of the following operations. */ #define RBU_INSERT 1 /* Insert on a main table b-tree */ #define RBU_DELETE 2 /* Delete a row from a main table b-tree */ #define RBU_REPLACE 3 /* Delete and then insert a row */ #define RBU_IDX_DELETE 4 /* Delete a row from an aux. index b-tree */ #define RBU_IDX_INSERT 5 /* Insert on an aux. index b-tree */ #define RBU_UPDATE 6 /* Update a row in a main table b-tree */ /* ** A single step of an incremental checkpoint - frame iWalFrame of the wal ** file should be copied to page iDbPage of the database file. */ struct RbuFrame { u32 iDbPage; u32 iWalFrame; }; /* ** RBU handle. ** ** nPhaseOneStep: ** If the RBU database contains an rbu_count table, this value is set to ** a running estimate of the number of b-tree operations required to ** finish populating the *-oal file. This allows the sqlite3_bp_progress() ** API to calculate the permyriadage progress of populating the *-oal file ** using the formula: ** ** permyriadage = (10000 * nProgress) / nPhaseOneStep ** ** nPhaseOneStep is initialized to the sum of: ** ** nRow * (nIndex + 1) ** ** for all source tables in the RBU database, where nRow is the number ** of rows in the source table and nIndex the number of indexes on the ** corresponding target database table. ** ** This estimate is accurate if the RBU update consists entirely of ** INSERT operations. However, it is inaccurate if: ** ** * the RBU update contains any UPDATE operations. If the PK specified ** for an UPDATE operation does not exist in the target table, then ** no b-tree operations are required on index b-trees. Or if the ** specified PK does exist, then (nIndex*2) such operations are ** required (one delete and one insert on each index b-tree). ** ** * the RBU update contains any DELETE operations for which the specified ** PK does not exist. In this case no operations are required on index ** b-trees. ** ** * the RBU update contains REPLACE operations. These are similar to ** UPDATE operations. ** ** nPhaseOneStep is updated to account for the conditions above during the ** first pass of each source table. The updated nPhaseOneStep value is ** stored in the rbu_state table if the RBU update is suspended. */ struct sqlite3rbu { int eStage; /* Value of RBU_STATE_STAGE field */ sqlite3 *dbMain; /* target database handle */ sqlite3 *dbRbu; /* rbu database handle */ char *zTarget; /* Path to target db */ char *zRbu; /* Path to rbu db */ char *zState; /* Path to state db (or NULL if zRbu) */ char zStateDb[5]; /* Db name for state ("stat" or "main") */ int rc; /* Value returned by last rbu_step() call */ char *zErrmsg; /* Error message if rc!=SQLITE_OK */ int nStep; /* Rows processed for current object */ int nProgress; /* Rows processed for all objects */ RbuObjIter objiter; /* Iterator for skipping through tbl/idx */ const char *zVfsName; /* Name of automatically created rbu vfs */ rbu_file *pTargetFd; /* File handle open on target db */ i64 iOalSz; i64 nPhaseOneStep; /* The following state variables are used as part of the incremental ** checkpoint stage (eStage==RBU_STAGE_CKPT). See comments surrounding ** function rbuSetupCheckpoint() for details. */ u32 iMaxFrame; /* Largest iWalFrame value in aFrame[] */ u32 mLock; int nFrame; /* Entries in aFrame[] array */ int nFrameAlloc; /* Allocated size of aFrame[] array */ RbuFrame *aFrame; int pgsz; u8 *aBuf; i64 iWalCksum; /* Used in RBU vacuum mode only */ int nRbu; /* Number of RBU VFS in the stack */ rbu_file *pRbuFd; /* Fd for main db of dbRbu */ }; /* ** An rbu VFS is implemented using an instance of this structure. */ struct rbu_vfs { sqlite3_vfs base; /* rbu VFS shim methods */ sqlite3_vfs *pRealVfs; /* Underlying VFS */ sqlite3_mutex *mutex; /* Mutex to protect pMain */ rbu_file *pMain; /* Linked list of main db files */ }; /* ** Each file opened by an rbu VFS is represented by an instance of ** the following structure. */ struct rbu_file { sqlite3_file base; /* sqlite3_file methods */ sqlite3_file *pReal; /* Underlying file handle */ rbu_vfs *pRbuVfs; /* Pointer to the rbu_vfs object */ sqlite3rbu *pRbu; /* Pointer to rbu object (rbu target only) */ int openFlags; /* Flags this file was opened with */ u32 iCookie; /* Cookie value for main db files */ u8 iWriteVer; /* "write-version" value for main db files */ u8 bNolock; /* True to fail EXCLUSIVE locks */ int nShm; /* Number of entries in apShm[] array */ char **apShm; /* Array of mmap'd *-shm regions */ char *zDel; /* Delete this when closing file */ const char *zWal; /* Wal filename for this main db file */ rbu_file *pWalFd; /* Wal file descriptor for this main db */ rbu_file *pMainNext; /* Next MAIN_DB file */ }; /* ** True for an RBU vacuum handle, or false otherwise. */ #define rbuIsVacuum(p) ((p)->zTarget==0) /************************************************************************* ** The following three functions, found below: ** ** rbuDeltaGetInt() ** rbuDeltaChecksum() ** rbuDeltaApply() ** ** are lifted from the fossil source code (http://fossil-scm.org). They ** are used to implement the scalar SQL function rbu_fossil_delta(). */ /* ** Read bytes from *pz and convert them into a positive integer. When ** finished, leave *pz pointing to the first character past the end of ** the integer. The *pLen parameter holds the length of the string ** in *pz and is decremented once for each character in the integer. */ static unsigned int rbuDeltaGetInt(const char **pz, int *pLen){ static const signed char zValue[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, 36, -1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, -1, -1, -1, 63, -1, }; unsigned int v = 0; int c; unsigned char *z = (unsigned char*)*pz; unsigned char *zStart = z; while( (c = zValue[0x7f&*(z++)])>=0 ){ v = (v<<6) + c; } z--; *pLen -= z - zStart; *pz = (char*)z; return v; } /* ** Compute a 32-bit checksum on the N-byte buffer. Return the result. */ static unsigned int rbuDeltaChecksum(const char *zIn, size_t N){ const unsigned char *z = (const unsigned char *)zIn; unsigned sum0 = 0; unsigned sum1 = 0; unsigned sum2 = 0; unsigned sum3 = 0; while(N >= 16){ sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]); sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]); sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]); sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]); z += 16; N -= 16; } while(N >= 4){ sum0 += z[0]; sum1 += z[1]; sum2 += z[2]; sum3 += z[3]; z += 4; N -= 4; } sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24); switch(N){ case 3: sum3 += (z[2] << 8); case 2: sum3 += (z[1] << 16); case 1: sum3 += (z[0] << 24); default: ; } return sum3; } /* ** Apply a delta. ** ** The output buffer should be big enough to hold the whole output ** file and a NUL terminator at the end. The delta_output_size() ** routine will determine this size for you. ** ** The delta string should be null-terminated. But the delta string ** may contain embedded NUL characters (if the input and output are ** binary files) so we also have to pass in the length of the delta in ** the lenDelta parameter. ** ** This function returns the size of the output file in bytes (excluding ** the final NUL terminator character). Except, if the delta string is ** malformed or intended for use with a source file other than zSrc, ** then this routine returns -1. ** ** Refer to the delta_create() documentation above for a description ** of the delta file format. */ static int rbuDeltaApply( const char *zSrc, /* The source or pattern file */ int lenSrc, /* Length of the source file */ const char *zDelta, /* Delta to apply to the pattern */ int lenDelta, /* Length of the delta */ char *zOut /* Write the output into this preallocated buffer */ ){ unsigned int limit; unsigned int total = 0; #ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST char *zOrigOut = zOut; #endif limit = rbuDeltaGetInt(&zDelta, &lenDelta); if( *zDelta!='\n' ){ /* ERROR: size integer not terminated by "\n" */ return -1; } zDelta++; lenDelta--; while( *zDelta && lenDelta>0 ){ unsigned int cnt, ofst; cnt = rbuDeltaGetInt(&zDelta, &lenDelta); switch( zDelta[0] ){ case '@': { zDelta++; lenDelta--; ofst = rbuDeltaGetInt(&zDelta, &lenDelta); if( lenDelta>0 && zDelta[0]!=',' ){ /* ERROR: copy command not terminated by ',' */ return -1; } zDelta++; lenDelta--; total += cnt; if( total>limit ){ /* ERROR: copy exceeds output file size */ return -1; } if( (int)(ofst+cnt) > lenSrc ){ /* ERROR: copy extends past end of input */ return -1; } memcpy(zOut, &zSrc[ofst], cnt); zOut += cnt; break; } case ':': { zDelta++; lenDelta--; total += cnt; if( total>limit ){ /* ERROR: insert command gives an output larger than predicted */ return -1; } if( (int)cnt>lenDelta ){ /* ERROR: insert count exceeds size of delta */ return -1; } memcpy(zOut, zDelta, cnt); zOut += cnt; zDelta += cnt; lenDelta -= cnt; break; } case ';': { zDelta++; lenDelta--; zOut[0] = 0; #ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST if( cnt!=rbuDeltaChecksum(zOrigOut, total) ){ /* ERROR: bad checksum */ return -1; } #endif if( total!=limit ){ /* ERROR: generated size does not match predicted size */ return -1; } return total; } default: { /* ERROR: unknown delta operator */ return -1; } } } /* ERROR: unterminated delta */ return -1; } static int rbuDeltaOutputSize(const char *zDelta, int lenDelta){ int size; size = rbuDeltaGetInt(&zDelta, &lenDelta); if( *zDelta!='\n' ){ /* ERROR: size integer not terminated by "\n" */ return -1; } return size; } /* ** End of code taken from fossil. *************************************************************************/ /* ** Implementation of SQL scalar function rbu_fossil_delta(). ** ** This function applies a fossil delta patch to a blob. Exactly two ** arguments must be passed to this function. The first is the blob to ** patch and the second the patch to apply. If no error occurs, this ** function returns the patched blob. */ static void rbuFossilDeltaFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *aDelta; int nDelta; const char *aOrig; int nOrig; int nOut; int nOut2; char *aOut; assert( argc==2 ); nOrig = sqlite3_value_bytes(argv[0]); aOrig = (const char*)sqlite3_value_blob(argv[0]); nDelta = sqlite3_value_bytes(argv[1]); aDelta = (const char*)sqlite3_value_blob(argv[1]); /* Figure out the size of the output */ nOut = rbuDeltaOutputSize(aDelta, nDelta); if( nOut<0 ){ sqlite3_result_error(context, "corrupt fossil delta", -1); return; } aOut = sqlite3_malloc(nOut+1); if( aOut==0 ){ sqlite3_result_error_nomem(context); }else{ nOut2 = rbuDeltaApply(aOrig, nOrig, aDelta, nDelta, aOut); if( nOut2!=nOut ){ sqlite3_result_error(context, "corrupt fossil delta", -1); }else{ sqlite3_result_blob(context, aOut, nOut, sqlite3_free); } } } /* ** Prepare the SQL statement in buffer zSql against database handle db. ** If successful, set *ppStmt to point to the new statement and return ** SQLITE_OK. ** ** Otherwise, if an error does occur, set *ppStmt to NULL and return ** an SQLite error code. Additionally, set output variable *pzErrmsg to ** point to a buffer containing an error message. It is the responsibility ** of the caller to (eventually) free this buffer using sqlite3_free(). */ static int prepareAndCollectError( sqlite3 *db, sqlite3_stmt **ppStmt, char **pzErrmsg, const char *zSql ){ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); if( rc!=SQLITE_OK ){ *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); *ppStmt = 0; } return rc; } /* ** Reset the SQL statement passed as the first argument. Return a copy ** of the value returned by sqlite3_reset(). ** ** If an error has occurred, then set *pzErrmsg to point to a buffer ** containing an error message. It is the responsibility of the caller ** to eventually free this buffer using sqlite3_free(). */ static int resetAndCollectError(sqlite3_stmt *pStmt, char **pzErrmsg){ int rc = sqlite3_reset(pStmt); if( rc!=SQLITE_OK ){ *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(sqlite3_db_handle(pStmt))); } return rc; } /* ** Unless it is NULL, argument zSql points to a buffer allocated using ** sqlite3_malloc containing an SQL statement. This function prepares the SQL ** statement against database db and frees the buffer. If statement ** compilation is successful, *ppStmt is set to point to the new statement ** handle and SQLITE_OK is returned. ** ** Otherwise, if an error occurs, *ppStmt is set to NULL and an error code ** returned. In this case, *pzErrmsg may also be set to point to an error ** message. It is the responsibility of the caller to free this error message ** buffer using sqlite3_free(). ** ** If argument zSql is NULL, this function assumes that an OOM has occurred. ** In this case SQLITE_NOMEM is returned and *ppStmt set to NULL. */ static int prepareFreeAndCollectError( sqlite3 *db, sqlite3_stmt **ppStmt, char **pzErrmsg, char *zSql ){ int rc; assert( *pzErrmsg==0 ); if( zSql==0 ){ rc = SQLITE_NOMEM; *ppStmt = 0; }else{ rc = prepareAndCollectError(db, ppStmt, pzErrmsg, zSql); sqlite3_free(zSql); } return rc; } /* ** Free the RbuObjIter.azTblCol[] and RbuObjIter.abTblPk[] arrays allocated ** by an earlier call to rbuObjIterCacheTableInfo(). */ static void rbuObjIterFreeCols(RbuObjIter *pIter){ int i; for(i=0; inTblCol; i++){ sqlite3_free(pIter->azTblCol[i]); sqlite3_free(pIter->azTblType[i]); } sqlite3_free(pIter->azTblCol); pIter->azTblCol = 0; pIter->azTblType = 0; pIter->aiSrcOrder = 0; pIter->abTblPk = 0; pIter->abNotNull = 0; pIter->nTblCol = 0; pIter->eType = 0; /* Invalid value */ } /* ** Finalize all statements and free all allocations that are specific to ** the current object (table/index pair). */ static void rbuObjIterClearStatements(RbuObjIter *pIter){ RbuUpdateStmt *pUp; sqlite3_finalize(pIter->pSelect); sqlite3_finalize(pIter->pInsert); sqlite3_finalize(pIter->pDelete); sqlite3_finalize(pIter->pTmpInsert); pUp = pIter->pRbuUpdate; while( pUp ){ RbuUpdateStmt *pTmp = pUp->pNext; sqlite3_finalize(pUp->pUpdate); sqlite3_free(pUp); pUp = pTmp; } pIter->pSelect = 0; pIter->pInsert = 0; pIter->pDelete = 0; pIter->pRbuUpdate = 0; pIter->pTmpInsert = 0; pIter->nCol = 0; } /* ** Clean up any resources allocated as part of the iterator object passed ** as the only argument. */ static void rbuObjIterFinalize(RbuObjIter *pIter){ rbuObjIterClearStatements(pIter); sqlite3_finalize(pIter->pTblIter); sqlite3_finalize(pIter->pIdxIter); rbuObjIterFreeCols(pIter); memset(pIter, 0, sizeof(RbuObjIter)); } /* ** Advance the iterator to the next position. ** ** If no error occurs, SQLITE_OK is returned and the iterator is left ** pointing to the next entry. Otherwise, an error code and message is ** left in the RBU handle passed as the first argument. A copy of the ** error code is returned. */ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){ int rc = p->rc; if( rc==SQLITE_OK ){ /* Free any SQLite statements used while processing the previous object */ rbuObjIterClearStatements(pIter); if( pIter->zIdx==0 ){ rc = sqlite3_exec(p->dbMain, "DROP TRIGGER IF EXISTS temp.rbu_insert_tr;" "DROP TRIGGER IF EXISTS temp.rbu_update1_tr;" "DROP TRIGGER IF EXISTS temp.rbu_update2_tr;" "DROP TRIGGER IF EXISTS temp.rbu_delete_tr;" , 0, 0, &p->zErrmsg ); } if( rc==SQLITE_OK ){ if( pIter->bCleanup ){ rbuObjIterFreeCols(pIter); pIter->bCleanup = 0; rc = sqlite3_step(pIter->pTblIter); if( rc!=SQLITE_ROW ){ rc = resetAndCollectError(pIter->pTblIter, &p->zErrmsg); pIter->zTbl = 0; }else{ pIter->zTbl = (const char*)sqlite3_column_text(pIter->pTblIter, 0); pIter->zDataTbl = (const char*)sqlite3_column_text(pIter->pTblIter,1); rc = (pIter->zDataTbl && pIter->zTbl) ? SQLITE_OK : SQLITE_NOMEM; } }else{ if( pIter->zIdx==0 ){ sqlite3_stmt *pIdx = pIter->pIdxIter; rc = sqlite3_bind_text(pIdx, 1, pIter->zTbl, -1, SQLITE_STATIC); } if( rc==SQLITE_OK ){ rc = sqlite3_step(pIter->pIdxIter); if( rc!=SQLITE_ROW ){ rc = resetAndCollectError(pIter->pIdxIter, &p->zErrmsg); pIter->bCleanup = 1; pIter->zIdx = 0; }else{ pIter->zIdx = (const char*)sqlite3_column_text(pIter->pIdxIter, 0); pIter->iTnum = sqlite3_column_int(pIter->pIdxIter, 1); pIter->bUnique = sqlite3_column_int(pIter->pIdxIter, 2); rc = pIter->zIdx ? SQLITE_OK : SQLITE_NOMEM; } } } } } if( rc!=SQLITE_OK ){ rbuObjIterFinalize(pIter); p->rc = rc; } return rc; } /* ** The implementation of the rbu_target_name() SQL function. This function ** accepts one or two arguments. The first argument is the name of a table - ** the name of a table in the RBU database. The second, if it is present, is 1 ** for a view or 0 for a table. ** ** For a non-vacuum RBU handle, if the table name matches the pattern: ** ** data[0-9]_ ** ** where is any sequence of 1 or more characters, is returned. ** Otherwise, if the only argument does not match the above pattern, an SQL ** NULL is returned. ** ** "data_t1" -> "t1" ** "data0123_t2" -> "t2" ** "dataAB_t3" -> NULL ** ** For an rbu vacuum handle, a copy of the first argument is returned if ** the second argument is either missing or 0 (not a view). */ static void rbuTargetNameFunc( sqlite3_context *pCtx, int argc, sqlite3_value **argv ){ sqlite3rbu *p = sqlite3_user_data(pCtx); const char *zIn; assert( argc==1 || argc==2 ); zIn = (const char*)sqlite3_value_text(argv[0]); if( zIn ){ if( rbuIsVacuum(p) ){ if( argc==1 || 0==sqlite3_value_int(argv[1]) ){ sqlite3_result_text(pCtx, zIn, -1, SQLITE_STATIC); } }else{ if( strlen(zIn)>4 && memcmp("data", zIn, 4)==0 ){ int i; for(i=4; zIn[i]>='0' && zIn[i]<='9'; i++); if( zIn[i]=='_' && zIn[i+1] ){ sqlite3_result_text(pCtx, &zIn[i+1], -1, SQLITE_STATIC); } } } } } /* ** Initialize the iterator structure passed as the second argument. ** ** If no error occurs, SQLITE_OK is returned and the iterator is left ** pointing to the first entry. Otherwise, an error code and message is ** left in the RBU handle passed as the first argument. A copy of the ** error code is returned. */ static int rbuObjIterFirst(sqlite3rbu *p, RbuObjIter *pIter){ int rc; memset(pIter, 0, sizeof(RbuObjIter)); rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pTblIter, &p->zErrmsg, sqlite3_mprintf( "SELECT rbu_target_name(name, type='view') AS target, name " "FROM sqlite_master " "WHERE type IN ('table', 'view') AND target IS NOT NULL " " %s " "ORDER BY name" , rbuIsVacuum(p) ? "AND rootpage!=0 AND rootpage IS NOT NULL" : "")); if( rc==SQLITE_OK ){ rc = prepareAndCollectError(p->dbMain, &pIter->pIdxIter, &p->zErrmsg, "SELECT name, rootpage, sql IS NULL OR substr(8, 6)=='UNIQUE' " " FROM main.sqlite_master " " WHERE type='index' AND tbl_name = ?" ); } pIter->bCleanup = 1; p->rc = rc; return rbuObjIterNext(p, pIter); } /* ** This is a wrapper around "sqlite3_mprintf(zFmt, ...)". If an OOM occurs, ** an error code is stored in the RBU handle passed as the first argument. ** ** If an error has already occurred (p->rc is already set to something other ** than SQLITE_OK), then this function returns NULL without modifying the ** stored error code. In this case it still calls sqlite3_free() on any ** printf() parameters associated with %z conversions. */ static char *rbuMPrintf(sqlite3rbu *p, const char *zFmt, ...){ char *zSql = 0; va_list ap; va_start(ap, zFmt); zSql = sqlite3_vmprintf(zFmt, ap); if( p->rc==SQLITE_OK ){ if( zSql==0 ) p->rc = SQLITE_NOMEM; }else{ sqlite3_free(zSql); zSql = 0; } va_end(ap); return zSql; } /* ** Argument zFmt is a sqlite3_mprintf() style format string. The trailing ** arguments are the usual subsitution values. This function performs ** the printf() style substitutions and executes the result as an SQL ** statement on the RBU handles database. ** ** If an error occurs, an error code and error message is stored in the ** RBU handle. If an error has already occurred when this function is ** called, it is a no-op. */ static int rbuMPrintfExec(sqlite3rbu *p, sqlite3 *db, const char *zFmt, ...){ va_list ap; char *zSql; va_start(ap, zFmt); zSql = sqlite3_vmprintf(zFmt, ap); if( p->rc==SQLITE_OK ){ if( zSql==0 ){ p->rc = SQLITE_NOMEM; }else{ p->rc = sqlite3_exec(db, zSql, 0, 0, &p->zErrmsg); } } sqlite3_free(zSql); va_end(ap); return p->rc; } /* ** Attempt to allocate and return a pointer to a zeroed block of nByte ** bytes. ** ** If an error (i.e. an OOM condition) occurs, return NULL and leave an ** error code in the rbu handle passed as the first argument. Or, if an ** error has already occurred when this function is called, return NULL ** immediately without attempting the allocation or modifying the stored ** error code. */ static void *rbuMalloc(sqlite3rbu *p, int nByte){ void *pRet = 0; if( p->rc==SQLITE_OK ){ assert( nByte>0 ); pRet = sqlite3_malloc64(nByte); if( pRet==0 ){ p->rc = SQLITE_NOMEM; }else{ memset(pRet, 0, nByte); } } return pRet; } /* ** Allocate and zero the pIter->azTblCol[] and abTblPk[] arrays so that ** there is room for at least nCol elements. If an OOM occurs, store an ** error code in the RBU handle passed as the first argument. */ static void rbuAllocateIterArrays(sqlite3rbu *p, RbuObjIter *pIter, int nCol){ int nByte = (2*sizeof(char*) + sizeof(int) + 3*sizeof(u8)) * nCol; char **azNew; azNew = (char**)rbuMalloc(p, nByte); if( azNew ){ pIter->azTblCol = azNew; pIter->azTblType = &azNew[nCol]; pIter->aiSrcOrder = (int*)&pIter->azTblType[nCol]; pIter->abTblPk = (u8*)&pIter->aiSrcOrder[nCol]; pIter->abNotNull = (u8*)&pIter->abTblPk[nCol]; pIter->abIndexed = (u8*)&pIter->abNotNull[nCol]; } } /* ** The first argument must be a nul-terminated string. This function ** returns a copy of the string in memory obtained from sqlite3_malloc(). ** It is the responsibility of the caller to eventually free this memory ** using sqlite3_free(). ** ** If an OOM condition is encountered when attempting to allocate memory, ** output variable (*pRc) is set to SQLITE_NOMEM before returning. Otherwise, ** if the allocation succeeds, (*pRc) is left unchanged. */ static char *rbuStrndup(const char *zStr, int *pRc){ char *zRet = 0; assert( *pRc==SQLITE_OK ); if( zStr ){ size_t nCopy = strlen(zStr) + 1; zRet = (char*)sqlite3_malloc64(nCopy); if( zRet ){ memcpy(zRet, zStr, nCopy); }else{ *pRc = SQLITE_NOMEM; } } return zRet; } /* ** Finalize the statement passed as the second argument. ** ** If the sqlite3_finalize() call indicates that an error occurs, and the ** rbu handle error code is not already set, set the error code and error ** message accordingly. */ static void rbuFinalize(sqlite3rbu *p, sqlite3_stmt *pStmt){ sqlite3 *db = sqlite3_db_handle(pStmt); int rc = sqlite3_finalize(pStmt); if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){ p->rc = rc; p->zErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); } } /* Determine the type of a table. ** ** peType is of type (int*), a pointer to an output parameter of type ** (int). This call sets the output parameter as follows, depending ** on the type of the table specified by parameters dbName and zTbl. ** ** RBU_PK_NOTABLE: No such table. ** RBU_PK_NONE: Table has an implicit rowid. ** RBU_PK_IPK: Table has an explicit IPK column. ** RBU_PK_EXTERNAL: Table has an external PK index. ** RBU_PK_WITHOUT_ROWID: Table is WITHOUT ROWID. ** RBU_PK_VTAB: Table is a virtual table. ** ** Argument *piPk is also of type (int*), and also points to an output ** parameter. Unless the table has an external primary key index ** (i.e. unless *peType is set to 3), then *piPk is set to zero. Or, ** if the table does have an external primary key index, then *piPk ** is set to the root page number of the primary key index before ** returning. ** ** ALGORITHM: ** ** if( no entry exists in sqlite_master ){ ** return RBU_PK_NOTABLE ** }else if( sql for the entry starts with "CREATE VIRTUAL" ){ ** return RBU_PK_VTAB ** }else if( "PRAGMA index_list()" for the table contains a "pk" index ){ ** if( the index that is the pk exists in sqlite_master ){ ** *piPK = rootpage of that index. ** return RBU_PK_EXTERNAL ** }else{ ** return RBU_PK_WITHOUT_ROWID ** } ** }else if( "PRAGMA table_info()" lists one or more "pk" columns ){ ** return RBU_PK_IPK ** }else{ ** return RBU_PK_NONE ** } */ static void rbuTableType( sqlite3rbu *p, const char *zTab, int *peType, int *piTnum, int *piPk ){ /* ** 0) SELECT count(*) FROM sqlite_master where name=%Q AND IsVirtual(%Q) ** 1) PRAGMA index_list = ? ** 2) SELECT count(*) FROM sqlite_master where name=%Q ** 3) PRAGMA table_info = ? */ sqlite3_stmt *aStmt[4] = {0, 0, 0, 0}; *peType = RBU_PK_NOTABLE; *piPk = 0; assert( p->rc==SQLITE_OK ); p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[0], &p->zErrmsg, sqlite3_mprintf( "SELECT (sql LIKE 'create virtual%%'), rootpage" " FROM sqlite_master" " WHERE name=%Q", zTab )); if( p->rc!=SQLITE_OK || sqlite3_step(aStmt[0])!=SQLITE_ROW ){ /* Either an error, or no such table. */ goto rbuTableType_end; } if( sqlite3_column_int(aStmt[0], 0) ){ *peType = RBU_PK_VTAB; /* virtual table */ goto rbuTableType_end; } *piTnum = sqlite3_column_int(aStmt[0], 1); p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[1], &p->zErrmsg, sqlite3_mprintf("PRAGMA index_list=%Q",zTab) ); if( p->rc ) goto rbuTableType_end; while( sqlite3_step(aStmt[1])==SQLITE_ROW ){ const u8 *zOrig = sqlite3_column_text(aStmt[1], 3); const u8 *zIdx = sqlite3_column_text(aStmt[1], 1); if( zOrig && zIdx && zOrig[0]=='p' ){ p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[2], &p->zErrmsg, sqlite3_mprintf( "SELECT rootpage FROM sqlite_master WHERE name = %Q", zIdx )); if( p->rc==SQLITE_OK ){ if( sqlite3_step(aStmt[2])==SQLITE_ROW ){ *piPk = sqlite3_column_int(aStmt[2], 0); *peType = RBU_PK_EXTERNAL; }else{ *peType = RBU_PK_WITHOUT_ROWID; } } goto rbuTableType_end; } } p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[3], &p->zErrmsg, sqlite3_mprintf("PRAGMA table_info=%Q",zTab) ); if( p->rc==SQLITE_OK ){ while( sqlite3_step(aStmt[3])==SQLITE_ROW ){ if( sqlite3_column_int(aStmt[3],5)>0 ){ *peType = RBU_PK_IPK; /* explicit IPK column */ goto rbuTableType_end; } } *peType = RBU_PK_NONE; } rbuTableType_end: { unsigned int i; for(i=0; iabIndexed[] array. */ static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){ sqlite3_stmt *pList = 0; int bIndex = 0; if( p->rc==SQLITE_OK ){ memcpy(pIter->abIndexed, pIter->abTblPk, sizeof(u8)*pIter->nTblCol); p->rc = prepareFreeAndCollectError(p->dbMain, &pList, &p->zErrmsg, sqlite3_mprintf("PRAGMA main.index_list = %Q", pIter->zTbl) ); } pIter->nIndex = 0; while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pList) ){ const char *zIdx = (const char*)sqlite3_column_text(pList, 1); sqlite3_stmt *pXInfo = 0; if( zIdx==0 ) break; p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx) ); while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ int iCid = sqlite3_column_int(pXInfo, 1); if( iCid>=0 ) pIter->abIndexed[iCid] = 1; } rbuFinalize(p, pXInfo); bIndex = 1; pIter->nIndex++; } if( pIter->eType==RBU_PK_WITHOUT_ROWID ){ /* "PRAGMA index_list" includes the main PK b-tree */ pIter->nIndex--; } rbuFinalize(p, pList); if( bIndex==0 ) pIter->abIndexed = 0; } /* ** If they are not already populated, populate the pIter->azTblCol[], ** pIter->abTblPk[], pIter->nTblCol and pIter->bRowid variables according to ** the table (not index) that the iterator currently points to. ** ** Return SQLITE_OK if successful, or an SQLite error code otherwise. If ** an error does occur, an error code and error message are also left in ** the RBU handle. */ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ if( pIter->azTblCol==0 ){ sqlite3_stmt *pStmt = 0; int nCol = 0; int i; /* for() loop iterator variable */ int bRbuRowid = 0; /* If input table has column "rbu_rowid" */ int iOrder = 0; int iTnum = 0; /* Figure out the type of table this step will deal with. */ assert( pIter->eType==0 ); rbuTableType(p, pIter->zTbl, &pIter->eType, &iTnum, &pIter->iPkTnum); if( p->rc==SQLITE_OK && pIter->eType==RBU_PK_NOTABLE ){ p->rc = SQLITE_ERROR; p->zErrmsg = sqlite3_mprintf("no such table: %s", pIter->zTbl); } if( p->rc ) return p->rc; if( pIter->zIdx==0 ) pIter->iTnum = iTnum; assert( pIter->eType==RBU_PK_NONE || pIter->eType==RBU_PK_IPK || pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_WITHOUT_ROWID || pIter->eType==RBU_PK_VTAB ); /* Populate the azTblCol[] and nTblCol variables based on the columns ** of the input table. Ignore any input table columns that begin with ** "rbu_". */ p->rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, sqlite3_mprintf("SELECT * FROM '%q'", pIter->zDataTbl) ); if( p->rc==SQLITE_OK ){ nCol = sqlite3_column_count(pStmt); rbuAllocateIterArrays(p, pIter, nCol); } for(i=0; p->rc==SQLITE_OK && irc); pIter->aiSrcOrder[pIter->nTblCol] = pIter->nTblCol; pIter->azTblCol[pIter->nTblCol++] = zCopy; } else if( 0==sqlite3_stricmp("rbu_rowid", zName) ){ bRbuRowid = 1; } } sqlite3_finalize(pStmt); pStmt = 0; if( p->rc==SQLITE_OK && rbuIsVacuum(p)==0 && bRbuRowid!=(pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) ){ p->rc = SQLITE_ERROR; p->zErrmsg = sqlite3_mprintf( "table %q %s rbu_rowid column", pIter->zDataTbl, (bRbuRowid ? "may not have" : "requires") ); } /* Check that all non-HIDDEN columns in the destination table are also ** present in the input table. Populate the abTblPk[], azTblType[] and ** aiTblOrder[] arrays at the same time. */ if( p->rc==SQLITE_OK ){ p->rc = prepareFreeAndCollectError(p->dbMain, &pStmt, &p->zErrmsg, sqlite3_mprintf("PRAGMA table_info(%Q)", pIter->zTbl) ); } while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ const char *zName = (const char*)sqlite3_column_text(pStmt, 1); if( zName==0 ) break; /* An OOM - finalize() below returns S_NOMEM */ for(i=iOrder; inTblCol; i++){ if( 0==strcmp(zName, pIter->azTblCol[i]) ) break; } if( i==pIter->nTblCol ){ p->rc = SQLITE_ERROR; p->zErrmsg = sqlite3_mprintf("column missing from %q: %s", pIter->zDataTbl, zName ); }else{ int iPk = sqlite3_column_int(pStmt, 5); int bNotNull = sqlite3_column_int(pStmt, 3); const char *zType = (const char*)sqlite3_column_text(pStmt, 2); if( i!=iOrder ){ SWAP(int, pIter->aiSrcOrder[i], pIter->aiSrcOrder[iOrder]); SWAP(char*, pIter->azTblCol[i], pIter->azTblCol[iOrder]); } pIter->azTblType[iOrder] = rbuStrndup(zType, &p->rc); pIter->abTblPk[iOrder] = (iPk!=0); pIter->abNotNull[iOrder] = (u8)bNotNull || (iPk!=0); iOrder++; } } rbuFinalize(p, pStmt); rbuObjIterCacheIndexedCols(p, pIter); assert( pIter->eType!=RBU_PK_VTAB || pIter->abIndexed==0 ); assert( pIter->eType!=RBU_PK_VTAB || pIter->nIndex==0 ); } return p->rc; } /* ** This function constructs and returns a pointer to a nul-terminated ** string containing some SQL clause or list based on one or more of the ** column names currently stored in the pIter->azTblCol[] array. */ static char *rbuObjIterGetCollist( sqlite3rbu *p, /* RBU object */ RbuObjIter *pIter /* Object iterator for column names */ ){ char *zList = 0; const char *zSep = ""; int i; for(i=0; inTblCol; i++){ const char *z = pIter->azTblCol[i]; zList = rbuMPrintf(p, "%z%s\"%w\"", zList, zSep, z); zSep = ", "; } return zList; } /* ** This function is used to create a SELECT list (the list of SQL ** expressions that follows a SELECT keyword) for a SELECT statement ** used to read from an data_xxx or rbu_tmp_xxx table while updating the ** index object currently indicated by the iterator object passed as the ** second argument. A "PRAGMA index_xinfo = " statement is used ** to obtain the required information. ** ** If the index is of the following form: ** ** CREATE INDEX i1 ON t1(c, b COLLATE nocase); ** ** and "t1" is a table with an explicit INTEGER PRIMARY KEY column ** "ipk", the returned string is: ** ** "`c` COLLATE 'BINARY', `b` COLLATE 'NOCASE', `ipk` COLLATE 'BINARY'" ** ** As well as the returned string, three other malloc'd strings are ** returned via output parameters. As follows: ** ** pzImposterCols: ... ** pzImposterPk: ... ** pzWhere: ... */ static char *rbuObjIterGetIndexCols( sqlite3rbu *p, /* RBU object */ RbuObjIter *pIter, /* Object iterator for column names */ char **pzImposterCols, /* OUT: Columns for imposter table */ char **pzImposterPk, /* OUT: Imposter PK clause */ char **pzWhere, /* OUT: WHERE clause */ int *pnBind /* OUT: Trbul number of columns */ ){ int rc = p->rc; /* Error code */ int rc2; /* sqlite3_finalize() return code */ char *zRet = 0; /* String to return */ char *zImpCols = 0; /* String to return via *pzImposterCols */ char *zImpPK = 0; /* String to return via *pzImposterPK */ char *zWhere = 0; /* String to return via *pzWhere */ int nBind = 0; /* Value to return via *pnBind */ const char *zCom = ""; /* Set to ", " later on */ const char *zAnd = ""; /* Set to " AND " later on */ sqlite3_stmt *pXInfo = 0; /* PRAGMA index_xinfo = ? */ if( rc==SQLITE_OK ){ assert( p->zErrmsg==0 ); rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", pIter->zIdx) ); } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ int iCid = sqlite3_column_int(pXInfo, 1); int bDesc = sqlite3_column_int(pXInfo, 3); const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4); const char *zCol; const char *zType; if( iCid<0 ){ /* An integer primary key. If the table has an explicit IPK, use ** its name. Otherwise, use "rbu_rowid". */ if( pIter->eType==RBU_PK_IPK ){ int i; for(i=0; pIter->abTblPk[i]==0; i++); assert( inTblCol ); zCol = pIter->azTblCol[i]; }else if( rbuIsVacuum(p) ){ zCol = "_rowid_"; }else{ zCol = "rbu_rowid"; } zType = "INTEGER"; }else{ zCol = pIter->azTblCol[iCid]; zType = pIter->azTblType[iCid]; } zRet = sqlite3_mprintf("%z%s\"%w\" COLLATE %Q", zRet, zCom, zCol, zCollate); if( pIter->bUnique==0 || sqlite3_column_int(pXInfo, 5) ){ const char *zOrder = (bDesc ? " DESC" : ""); zImpPK = sqlite3_mprintf("%z%s\"rbu_imp_%d%w\"%s", zImpPK, zCom, nBind, zCol, zOrder ); } zImpCols = sqlite3_mprintf("%z%s\"rbu_imp_%d%w\" %s COLLATE %Q", zImpCols, zCom, nBind, zCol, zType, zCollate ); zWhere = sqlite3_mprintf( "%z%s\"rbu_imp_%d%w\" IS ?", zWhere, zAnd, nBind, zCol ); if( zRet==0 || zImpPK==0 || zImpCols==0 || zWhere==0 ) rc = SQLITE_NOMEM; zCom = ", "; zAnd = " AND "; nBind++; } rc2 = sqlite3_finalize(pXInfo); if( rc==SQLITE_OK ) rc = rc2; if( rc!=SQLITE_OK ){ sqlite3_free(zRet); sqlite3_free(zImpCols); sqlite3_free(zImpPK); sqlite3_free(zWhere); zRet = 0; zImpCols = 0; zImpPK = 0; zWhere = 0; p->rc = rc; } *pzImposterCols = zImpCols; *pzImposterPk = zImpPK; *pzWhere = zWhere; *pnBind = nBind; return zRet; } /* ** Assuming the current table columns are "a", "b" and "c", and the zObj ** paramter is passed "old", return a string of the form: ** ** "old.a, old.b, old.b" ** ** With the column names escaped. ** ** For tables with implicit rowids - RBU_PK_EXTERNAL and RBU_PK_NONE, append ** the text ", old._rowid_" to the returned value. */ static char *rbuObjIterGetOldlist( sqlite3rbu *p, RbuObjIter *pIter, const char *zObj ){ char *zList = 0; if( p->rc==SQLITE_OK && pIter->abIndexed ){ const char *zS = ""; int i; for(i=0; inTblCol; i++){ if( pIter->abIndexed[i] ){ const char *zCol = pIter->azTblCol[i]; zList = sqlite3_mprintf("%z%s%s.\"%w\"", zList, zS, zObj, zCol); }else{ zList = sqlite3_mprintf("%z%sNULL", zList, zS); } zS = ", "; if( zList==0 ){ p->rc = SQLITE_NOMEM; break; } } /* For a table with implicit rowids, append "old._rowid_" to the list. */ if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ zList = rbuMPrintf(p, "%z, %s._rowid_", zList, zObj); } } return zList; } /* ** Return an expression that can be used in a WHERE clause to match the ** primary key of the current table. For example, if the table is: ** ** CREATE TABLE t1(a, b, c, PRIMARY KEY(b, c)); ** ** Return the string: ** ** "b = ?1 AND c = ?2" */ static char *rbuObjIterGetWhere( sqlite3rbu *p, RbuObjIter *pIter ){ char *zList = 0; if( pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE ){ zList = rbuMPrintf(p, "_rowid_ = ?%d", pIter->nTblCol+1); }else if( pIter->eType==RBU_PK_EXTERNAL ){ const char *zSep = ""; int i; for(i=0; inTblCol; i++){ if( pIter->abTblPk[i] ){ zList = rbuMPrintf(p, "%z%sc%d=?%d", zList, zSep, i, i+1); zSep = " AND "; } } zList = rbuMPrintf(p, "_rowid_ = (SELECT id FROM rbu_imposter2 WHERE %z)", zList ); }else{ const char *zSep = ""; int i; for(i=0; inTblCol; i++){ if( pIter->abTblPk[i] ){ const char *zCol = pIter->azTblCol[i]; zList = rbuMPrintf(p, "%z%s\"%w\"=?%d", zList, zSep, zCol, i+1); zSep = " AND "; } } } return zList; } /* ** The SELECT statement iterating through the keys for the current object ** (p->objiter.pSelect) currently points to a valid row. However, there ** is something wrong with the rbu_control value in the rbu_control value ** stored in the (p->nCol+1)'th column. Set the error code and error message ** of the RBU handle to something reflecting this. */ static void rbuBadControlError(sqlite3rbu *p){ p->rc = SQLITE_ERROR; p->zErrmsg = sqlite3_mprintf("invalid rbu_control value"); } /* ** Return a nul-terminated string containing the comma separated list of ** assignments that should be included following the "SET" keyword of ** an UPDATE statement used to update the table object that the iterator ** passed as the second argument currently points to if the rbu_control ** column of the data_xxx table entry is set to zMask. ** ** The memory for the returned string is obtained from sqlite3_malloc(). ** It is the responsibility of the caller to eventually free it using ** sqlite3_free(). ** ** If an OOM error is encountered when allocating space for the new ** string, an error code is left in the rbu handle passed as the first ** argument and NULL is returned. Or, if an error has already occurred ** when this function is called, NULL is returned immediately, without ** attempting the allocation or modifying the stored error code. */ static char *rbuObjIterGetSetlist( sqlite3rbu *p, RbuObjIter *pIter, const char *zMask ){ char *zList = 0; if( p->rc==SQLITE_OK ){ int i; if( (int)strlen(zMask)!=pIter->nTblCol ){ rbuBadControlError(p); }else{ const char *zSep = ""; for(i=0; inTblCol; i++){ char c = zMask[pIter->aiSrcOrder[i]]; if( c=='x' ){ zList = rbuMPrintf(p, "%z%s\"%w\"=?%d", zList, zSep, pIter->azTblCol[i], i+1 ); zSep = ", "; } else if( c=='d' ){ zList = rbuMPrintf(p, "%z%s\"%w\"=rbu_delta(\"%w\", ?%d)", zList, zSep, pIter->azTblCol[i], pIter->azTblCol[i], i+1 ); zSep = ", "; } else if( c=='f' ){ zList = rbuMPrintf(p, "%z%s\"%w\"=rbu_fossil_delta(\"%w\", ?%d)", zList, zSep, pIter->azTblCol[i], pIter->azTblCol[i], i+1 ); zSep = ", "; } } } } return zList; } /* ** Return a nul-terminated string consisting of nByte comma separated ** "?" expressions. For example, if nByte is 3, return a pointer to ** a buffer containing the string "?,?,?". ** ** The memory for the returned string is obtained from sqlite3_malloc(). ** It is the responsibility of the caller to eventually free it using ** sqlite3_free(). ** ** If an OOM error is encountered when allocating space for the new ** string, an error code is left in the rbu handle passed as the first ** argument and NULL is returned. Or, if an error has already occurred ** when this function is called, NULL is returned immediately, without ** attempting the allocation or modifying the stored error code. */ static char *rbuObjIterGetBindlist(sqlite3rbu *p, int nBind){ char *zRet = 0; int nByte = nBind*2 + 1; zRet = (char*)rbuMalloc(p, nByte); if( zRet ){ int i; for(i=0; izIdx==0 ); if( p->rc==SQLITE_OK ){ const char *zSep = "PRIMARY KEY("; sqlite3_stmt *pXList = 0; /* PRAGMA index_list = (pIter->zTbl) */ sqlite3_stmt *pXInfo = 0; /* PRAGMA index_xinfo = */ p->rc = prepareFreeAndCollectError(p->dbMain, &pXList, &p->zErrmsg, sqlite3_mprintf("PRAGMA main.index_list = %Q", pIter->zTbl) ); while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXList) ){ const char *zOrig = (const char*)sqlite3_column_text(pXList,3); if( zOrig && strcmp(zOrig, "pk")==0 ){ const char *zIdx = (const char*)sqlite3_column_text(pXList,1); if( zIdx ){ p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx) ); } break; } } rbuFinalize(p, pXList); while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ if( sqlite3_column_int(pXInfo, 5) ){ /* int iCid = sqlite3_column_int(pXInfo, 0); */ const char *zCol = (const char*)sqlite3_column_text(pXInfo, 2); const char *zDesc = sqlite3_column_int(pXInfo, 3) ? " DESC" : ""; z = rbuMPrintf(p, "%z%s\"%w\"%s", z, zSep, zCol, zDesc); zSep = ", "; } } z = rbuMPrintf(p, "%z)", z); rbuFinalize(p, pXInfo); } return z; } /* ** This function creates the second imposter table used when writing to ** a table b-tree where the table has an external primary key. If the ** iterator passed as the second argument does not currently point to ** a table (not index) with an external primary key, this function is a ** no-op. ** ** Assuming the iterator does point to a table with an external PK, this ** function creates a WITHOUT ROWID imposter table named "rbu_imposter2" ** used to access that PK index. For example, if the target table is ** declared as follows: ** ** CREATE TABLE t1(a, b TEXT, c REAL, PRIMARY KEY(b, c)); ** ** then the imposter table schema is: ** ** CREATE TABLE rbu_imposter2(c1 TEXT, c2 REAL, id INTEGER) WITHOUT ROWID; ** */ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){ if( p->rc==SQLITE_OK && pIter->eType==RBU_PK_EXTERNAL ){ int tnum = pIter->iPkTnum; /* Root page of PK index */ sqlite3_stmt *pQuery = 0; /* SELECT name ... WHERE rootpage = $tnum */ const char *zIdx = 0; /* Name of PK index */ sqlite3_stmt *pXInfo = 0; /* PRAGMA main.index_xinfo = $zIdx */ const char *zComma = ""; char *zCols = 0; /* Used to build up list of table cols */ char *zPk = 0; /* Used to build up table PK declaration */ /* Figure out the name of the primary key index for the current table. ** This is needed for the argument to "PRAGMA index_xinfo". Set ** zIdx to point to a nul-terminated string containing this name. */ p->rc = prepareAndCollectError(p->dbMain, &pQuery, &p->zErrmsg, "SELECT name FROM sqlite_master WHERE rootpage = ?" ); if( p->rc==SQLITE_OK ){ sqlite3_bind_int(pQuery, 1, tnum); if( SQLITE_ROW==sqlite3_step(pQuery) ){ zIdx = (const char*)sqlite3_column_text(pQuery, 0); } } if( zIdx ){ p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx) ); } rbuFinalize(p, pQuery); while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ int bKey = sqlite3_column_int(pXInfo, 5); if( bKey ){ int iCid = sqlite3_column_int(pXInfo, 1); int bDesc = sqlite3_column_int(pXInfo, 3); const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4); zCols = rbuMPrintf(p, "%z%sc%d %s COLLATE %s", zCols, zComma, iCid, pIter->azTblType[iCid], zCollate ); zPk = rbuMPrintf(p, "%z%sc%d%s", zPk, zComma, iCid, bDesc?" DESC":""); zComma = ", "; } } zCols = rbuMPrintf(p, "%z, id INTEGER", zCols); rbuFinalize(p, pXInfo); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1, tnum); rbuMPrintfExec(p, p->dbMain, "CREATE TABLE rbu_imposter2(%z, PRIMARY KEY(%z)) WITHOUT ROWID", zCols, zPk ); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0); } } /* ** If an error has already occurred when this function is called, it ** immediately returns zero (without doing any work). Or, if an error ** occurs during the execution of this function, it sets the error code ** in the sqlite3rbu object indicated by the first argument and returns ** zero. ** ** The iterator passed as the second argument is guaranteed to point to ** a table (not an index) when this function is called. This function ** attempts to create any imposter table required to write to the main ** table b-tree of the table before returning. Non-zero is returned if ** an imposter table are created, or zero otherwise. ** ** An imposter table is required in all cases except RBU_PK_VTAB. Only ** virtual tables are written to directly. The imposter table has the ** same schema as the actual target table (less any UNIQUE constraints). ** More precisely, the "same schema" means the same columns, types, ** collation sequences. For tables that do not have an external PRIMARY ** KEY, it also means the same PRIMARY KEY declaration. */ static void rbuCreateImposterTable(sqlite3rbu *p, RbuObjIter *pIter){ if( p->rc==SQLITE_OK && pIter->eType!=RBU_PK_VTAB ){ int tnum = pIter->iTnum; const char *zComma = ""; char *zSql = 0; int iCol; sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 1); for(iCol=0; p->rc==SQLITE_OK && iColnTblCol; iCol++){ const char *zPk = ""; const char *zCol = pIter->azTblCol[iCol]; const char *zColl = 0; p->rc = sqlite3_table_column_metadata( p->dbMain, "main", pIter->zTbl, zCol, 0, &zColl, 0, 0, 0 ); if( pIter->eType==RBU_PK_IPK && pIter->abTblPk[iCol] ){ /* If the target table column is an "INTEGER PRIMARY KEY", add ** "PRIMARY KEY" to the imposter table column declaration. */ zPk = "PRIMARY KEY "; } zSql = rbuMPrintf(p, "%z%s\"%w\" %s %sCOLLATE %s%s", zSql, zComma, zCol, pIter->azTblType[iCol], zPk, zColl, (pIter->abNotNull[iCol] ? " NOT NULL" : "") ); zComma = ", "; } if( pIter->eType==RBU_PK_WITHOUT_ROWID ){ char *zPk = rbuWithoutRowidPK(p, pIter); if( zPk ){ zSql = rbuMPrintf(p, "%z, %z", zSql, zPk); } } sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1, tnum); rbuMPrintfExec(p, p->dbMain, "CREATE TABLE \"rbu_imp_%w\"(%z)%s", pIter->zTbl, zSql, (pIter->eType==RBU_PK_WITHOUT_ROWID ? " WITHOUT ROWID" : "") ); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0); } } /* ** Prepare a statement used to insert rows into the "rbu_tmp_xxx" table. ** Specifically a statement of the form: ** ** INSERT INTO rbu_tmp_xxx VALUES(?, ?, ? ...); ** ** The number of bound variables is equal to the number of columns in ** the target table, plus one (for the rbu_control column), plus one more ** (for the rbu_rowid column) if the target table is an implicit IPK or ** virtual table. */ static void rbuObjIterPrepareTmpInsert( sqlite3rbu *p, RbuObjIter *pIter, const char *zCollist, const char *zRbuRowid ){ int bRbuRowid = (pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE); char *zBind = rbuObjIterGetBindlist(p, pIter->nTblCol + 1 + bRbuRowid); if( zBind ){ assert( pIter->pTmpInsert==0 ); p->rc = prepareFreeAndCollectError( p->dbRbu, &pIter->pTmpInsert, &p->zErrmsg, sqlite3_mprintf( "INSERT INTO %s.'rbu_tmp_%q'(rbu_control,%s%s) VALUES(%z)", p->zStateDb, pIter->zDataTbl, zCollist, zRbuRowid, zBind )); } } static void rbuTmpInsertFunc( sqlite3_context *pCtx, int nVal, sqlite3_value **apVal ){ sqlite3rbu *p = sqlite3_user_data(pCtx); int rc = SQLITE_OK; int i; assert( sqlite3_value_int(apVal[0])!=0 || p->objiter.eType==RBU_PK_EXTERNAL || p->objiter.eType==RBU_PK_NONE ); if( sqlite3_value_int(apVal[0])!=0 ){ p->nPhaseOneStep += p->objiter.nIndex; } for(i=0; rc==SQLITE_OK && iobjiter.pTmpInsert, i+1, apVal[i]); } if( rc==SQLITE_OK ){ sqlite3_step(p->objiter.pTmpInsert); rc = sqlite3_reset(p->objiter.pTmpInsert); } if( rc!=SQLITE_OK ){ sqlite3_result_error_code(pCtx, rc); } } /* ** Ensure that the SQLite statement handles required to update the ** target database object currently indicated by the iterator passed ** as the second argument are available. */ static int rbuObjIterPrepareAll( sqlite3rbu *p, RbuObjIter *pIter, int nOffset /* Add "LIMIT -1 OFFSET $nOffset" to SELECT */ ){ assert( pIter->bCleanup==0 ); if( pIter->pSelect==0 && rbuObjIterCacheTableInfo(p, pIter)==SQLITE_OK ){ const int tnum = pIter->iTnum; char *zCollist = 0; /* List of indexed columns */ char **pz = &p->zErrmsg; const char *zIdx = pIter->zIdx; char *zLimit = 0; if( nOffset ){ zLimit = sqlite3_mprintf(" LIMIT -1 OFFSET %d", nOffset); if( !zLimit ) p->rc = SQLITE_NOMEM; } if( zIdx ){ const char *zTbl = pIter->zTbl; char *zImposterCols = 0; /* Columns for imposter table */ char *zImposterPK = 0; /* Primary key declaration for imposter */ char *zWhere = 0; /* WHERE clause on PK columns */ char *zBind = 0; int nBind = 0; assert( pIter->eType!=RBU_PK_VTAB ); zCollist = rbuObjIterGetIndexCols( p, pIter, &zImposterCols, &zImposterPK, &zWhere, &nBind ); zBind = rbuObjIterGetBindlist(p, nBind); /* Create the imposter table used to write to this index. */ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 1); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1,tnum); rbuMPrintfExec(p, p->dbMain, "CREATE TABLE \"rbu_imp_%w\"( %s, PRIMARY KEY( %s ) ) WITHOUT ROWID", zTbl, zImposterCols, zImposterPK ); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0); /* Create the statement to insert index entries */ pIter->nCol = nBind; if( p->rc==SQLITE_OK ){ p->rc = prepareFreeAndCollectError( p->dbMain, &pIter->pInsert, &p->zErrmsg, sqlite3_mprintf("INSERT INTO \"rbu_imp_%w\" VALUES(%s)", zTbl, zBind) ); } /* And to delete index entries */ if( rbuIsVacuum(p)==0 && p->rc==SQLITE_OK ){ p->rc = prepareFreeAndCollectError( p->dbMain, &pIter->pDelete, &p->zErrmsg, sqlite3_mprintf("DELETE FROM \"rbu_imp_%w\" WHERE %s", zTbl, zWhere) ); } /* Create the SELECT statement to read keys in sorted order */ if( p->rc==SQLITE_OK ){ char *zSql; if( rbuIsVacuum(p) ){ zSql = sqlite3_mprintf( "SELECT %s, 0 AS rbu_control FROM '%q' ORDER BY %s%s", zCollist, pIter->zDataTbl, zCollist, zLimit ); }else if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ zSql = sqlite3_mprintf( "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' ORDER BY %s%s", zCollist, p->zStateDb, pIter->zDataTbl, zCollist, zLimit ); }else{ zSql = sqlite3_mprintf( "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' " "UNION ALL " "SELECT %s, rbu_control FROM '%q' " "WHERE typeof(rbu_control)='integer' AND rbu_control!=1 " "ORDER BY %s%s", zCollist, p->zStateDb, pIter->zDataTbl, zCollist, pIter->zDataTbl, zCollist, zLimit ); } p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, zSql); } sqlite3_free(zImposterCols); sqlite3_free(zImposterPK); sqlite3_free(zWhere); sqlite3_free(zBind); }else{ int bRbuRowid = (pIter->eType==RBU_PK_VTAB) ||(pIter->eType==RBU_PK_NONE) ||(pIter->eType==RBU_PK_EXTERNAL && rbuIsVacuum(p)); const char *zTbl = pIter->zTbl; /* Table this step applies to */ const char *zWrite; /* Imposter table name */ char *zBindings = rbuObjIterGetBindlist(p, pIter->nTblCol + bRbuRowid); char *zWhere = rbuObjIterGetWhere(p, pIter); char *zOldlist = rbuObjIterGetOldlist(p, pIter, "old"); char *zNewlist = rbuObjIterGetOldlist(p, pIter, "new"); zCollist = rbuObjIterGetCollist(p, pIter); pIter->nCol = pIter->nTblCol; /* Create the imposter table or tables (if required). */ rbuCreateImposterTable(p, pIter); rbuCreateImposterTable2(p, pIter); zWrite = (pIter->eType==RBU_PK_VTAB ? "" : "rbu_imp_"); /* Create the INSERT statement to write to the target PK b-tree */ if( p->rc==SQLITE_OK ){ p->rc = prepareFreeAndCollectError(p->dbMain, &pIter->pInsert, pz, sqlite3_mprintf( "INSERT INTO \"%s%w\"(%s%s) VALUES(%s)", zWrite, zTbl, zCollist, (bRbuRowid ? ", _rowid_" : ""), zBindings ) ); } /* Create the DELETE statement to write to the target PK b-tree. ** Because it only performs INSERT operations, this is not required for ** an rbu vacuum handle. */ if( rbuIsVacuum(p)==0 && p->rc==SQLITE_OK ){ p->rc = prepareFreeAndCollectError(p->dbMain, &pIter->pDelete, pz, sqlite3_mprintf( "DELETE FROM \"%s%w\" WHERE %s", zWrite, zTbl, zWhere ) ); } if( rbuIsVacuum(p)==0 && pIter->abIndexed ){ const char *zRbuRowid = ""; if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ zRbuRowid = ", rbu_rowid"; } /* Create the rbu_tmp_xxx table and the triggers to populate it. */ rbuMPrintfExec(p, p->dbRbu, "CREATE TABLE IF NOT EXISTS %s.'rbu_tmp_%q' AS " "SELECT *%s FROM '%q' WHERE 0;" , p->zStateDb, pIter->zDataTbl , (pIter->eType==RBU_PK_EXTERNAL ? ", 0 AS rbu_rowid" : "") , pIter->zDataTbl ); rbuMPrintfExec(p, p->dbMain, "CREATE TEMP TRIGGER rbu_delete_tr BEFORE DELETE ON \"%s%w\" " "BEGIN " " SELECT rbu_tmp_insert(3, %s);" "END;" "CREATE TEMP TRIGGER rbu_update1_tr BEFORE UPDATE ON \"%s%w\" " "BEGIN " " SELECT rbu_tmp_insert(3, %s);" "END;" "CREATE TEMP TRIGGER rbu_update2_tr AFTER UPDATE ON \"%s%w\" " "BEGIN " " SELECT rbu_tmp_insert(4, %s);" "END;", zWrite, zTbl, zOldlist, zWrite, zTbl, zOldlist, zWrite, zTbl, zNewlist ); if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ rbuMPrintfExec(p, p->dbMain, "CREATE TEMP TRIGGER rbu_insert_tr AFTER INSERT ON \"%s%w\" " "BEGIN " " SELECT rbu_tmp_insert(0, %s);" "END;", zWrite, zTbl, zNewlist ); } rbuObjIterPrepareTmpInsert(p, pIter, zCollist, zRbuRowid); } /* Create the SELECT statement to read keys from data_xxx */ if( p->rc==SQLITE_OK ){ const char *zRbuRowid = ""; if( bRbuRowid ){ zRbuRowid = rbuIsVacuum(p) ? ",_rowid_ " : ",rbu_rowid"; } p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, sqlite3_mprintf( "SELECT %s,%s rbu_control%s FROM '%q'%s", zCollist, (rbuIsVacuum(p) ? "0 AS " : ""), zRbuRowid, pIter->zDataTbl, zLimit ) ); } sqlite3_free(zWhere); sqlite3_free(zOldlist); sqlite3_free(zNewlist); sqlite3_free(zBindings); } sqlite3_free(zCollist); sqlite3_free(zLimit); } return p->rc; } /* ** Set output variable *ppStmt to point to an UPDATE statement that may ** be used to update the imposter table for the main table b-tree of the ** table object that pIter currently points to, assuming that the ** rbu_control column of the data_xyz table contains zMask. ** ** If the zMask string does not specify any columns to update, then this ** is not an error. Output variable *ppStmt is set to NULL in this case. */ static int rbuGetUpdateStmt( sqlite3rbu *p, /* RBU handle */ RbuObjIter *pIter, /* Object iterator */ const char *zMask, /* rbu_control value ('x.x.') */ sqlite3_stmt **ppStmt /* OUT: UPDATE statement handle */ ){ RbuUpdateStmt **pp; RbuUpdateStmt *pUp = 0; int nUp = 0; /* In case an error occurs */ *ppStmt = 0; /* Search for an existing statement. If one is found, shift it to the front ** of the LRU queue and return immediately. Otherwise, leave nUp pointing ** to the number of statements currently in the cache and pUp to the ** last object in the list. */ for(pp=&pIter->pRbuUpdate; *pp; pp=&((*pp)->pNext)){ pUp = *pp; if( strcmp(pUp->zMask, zMask)==0 ){ *pp = pUp->pNext; pUp->pNext = pIter->pRbuUpdate; pIter->pRbuUpdate = pUp; *ppStmt = pUp->pUpdate; return SQLITE_OK; } nUp++; } assert( pUp==0 || pUp->pNext==0 ); if( nUp>=SQLITE_RBU_UPDATE_CACHESIZE ){ for(pp=&pIter->pRbuUpdate; *pp!=pUp; pp=&((*pp)->pNext)); *pp = 0; sqlite3_finalize(pUp->pUpdate); pUp->pUpdate = 0; }else{ pUp = (RbuUpdateStmt*)rbuMalloc(p, sizeof(RbuUpdateStmt)+pIter->nTblCol+1); } if( pUp ){ char *zWhere = rbuObjIterGetWhere(p, pIter); char *zSet = rbuObjIterGetSetlist(p, pIter, zMask); char *zUpdate = 0; pUp->zMask = (char*)&pUp[1]; memcpy(pUp->zMask, zMask, pIter->nTblCol); pUp->pNext = pIter->pRbuUpdate; pIter->pRbuUpdate = pUp; if( zSet ){ const char *zPrefix = ""; if( pIter->eType!=RBU_PK_VTAB ) zPrefix = "rbu_imp_"; zUpdate = sqlite3_mprintf("UPDATE \"%s%w\" SET %s WHERE %s", zPrefix, pIter->zTbl, zSet, zWhere ); p->rc = prepareFreeAndCollectError( p->dbMain, &pUp->pUpdate, &p->zErrmsg, zUpdate ); *ppStmt = pUp->pUpdate; } sqlite3_free(zWhere); sqlite3_free(zSet); } return p->rc; } static sqlite3 *rbuOpenDbhandle( sqlite3rbu *p, const char *zName, int bUseVfs ){ sqlite3 *db = 0; if( p->rc==SQLITE_OK ){ const int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_URI; p->rc = sqlite3_open_v2(zName, &db, flags, bUseVfs ? p->zVfsName : 0); if( p->rc ){ p->zErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); sqlite3_close(db); db = 0; } } return db; } /* ** Free an RbuState object allocated by rbuLoadState(). */ static void rbuFreeState(RbuState *p){ if( p ){ sqlite3_free(p->zTbl); sqlite3_free(p->zIdx); sqlite3_free(p); } } /* ** Allocate an RbuState object and load the contents of the rbu_state ** table into it. Return a pointer to the new object. It is the ** responsibility of the caller to eventually free the object using ** sqlite3_free(). ** ** If an error occurs, leave an error code and message in the rbu handle ** and return NULL. */ static RbuState *rbuLoadState(sqlite3rbu *p){ RbuState *pRet = 0; sqlite3_stmt *pStmt = 0; int rc; int rc2; pRet = (RbuState*)rbuMalloc(p, sizeof(RbuState)); if( pRet==0 ) return 0; rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, sqlite3_mprintf("SELECT k, v FROM %s.rbu_state", p->zStateDb) ); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ switch( sqlite3_column_int(pStmt, 0) ){ case RBU_STATE_STAGE: pRet->eStage = sqlite3_column_int(pStmt, 1); if( pRet->eStage!=RBU_STAGE_OAL && pRet->eStage!=RBU_STAGE_MOVE && pRet->eStage!=RBU_STAGE_CKPT ){ p->rc = SQLITE_CORRUPT; } break; case RBU_STATE_TBL: pRet->zTbl = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); break; case RBU_STATE_IDX: pRet->zIdx = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc); break; case RBU_STATE_ROW: pRet->nRow = sqlite3_column_int(pStmt, 1); break; case RBU_STATE_PROGRESS: pRet->nProgress = sqlite3_column_int64(pStmt, 1); break; case RBU_STATE_CKPT: pRet->iWalCksum = sqlite3_column_int64(pStmt, 1); break; case RBU_STATE_COOKIE: pRet->iCookie = (u32)sqlite3_column_int64(pStmt, 1); break; case RBU_STATE_OALSZ: pRet->iOalSz = (u32)sqlite3_column_int64(pStmt, 1); break; case RBU_STATE_PHASEONESTEP: pRet->nPhaseOneStep = sqlite3_column_int64(pStmt, 1); break; default: rc = SQLITE_CORRUPT; break; } } rc2 = sqlite3_finalize(pStmt); if( rc==SQLITE_OK ) rc = rc2; p->rc = rc; return pRet; } /* ** Open the database handle and attach the RBU database as "rbu". If an ** error occurs, leave an error code and message in the RBU handle. */ static void rbuOpenDatabase(sqlite3rbu *p){ assert( p->rc || (p->dbMain==0 && p->dbRbu==0) ); assert( p->rc || rbuIsVacuum(p) || p->zTarget!=0 ); /* Open the RBU database */ p->dbRbu = rbuOpenDbhandle(p, p->zRbu, 1); if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); if( p->zState==0 ){ const char *zFile = sqlite3_db_filename(p->dbRbu, "main"); p->zState = rbuMPrintf(p, "file://%s-vacuum?modeof=%s", zFile, zFile); } } /* If using separate RBU and state databases, attach the state database to ** the RBU db handle now. */ if( p->zState ){ rbuMPrintfExec(p, p->dbRbu, "ATTACH %Q AS stat", p->zState); memcpy(p->zStateDb, "stat", 4); }else{ memcpy(p->zStateDb, "main", 4); } #if 0 if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ p->rc = sqlite3_exec(p->dbRbu, "BEGIN", 0, 0, 0); } #endif /* If it has not already been created, create the rbu_state table */ rbuMPrintfExec(p, p->dbRbu, RBU_CREATE_STATE, p->zStateDb); #if 0 if( rbuIsVacuum(p) ){ if( p->rc==SQLITE_OK ){ int rc2; int bOk = 0; sqlite3_stmt *pCnt = 0; p->rc = prepareAndCollectError(p->dbRbu, &pCnt, &p->zErrmsg, "SELECT count(*) FROM stat.sqlite_master" ); if( p->rc==SQLITE_OK && sqlite3_step(pCnt)==SQLITE_ROW && 1==sqlite3_column_int(pCnt, 0) ){ bOk = 1; } rc2 = sqlite3_finalize(pCnt); if( p->rc==SQLITE_OK ) p->rc = rc2; if( p->rc==SQLITE_OK && bOk==0 ){ p->rc = SQLITE_ERROR; p->zErrmsg = sqlite3_mprintf("invalid state database"); } if( p->rc==SQLITE_OK ){ p->rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0); } } } #endif if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ int bOpen = 0; int rc; p->nRbu = 0; p->pRbuFd = 0; rc = sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); if( rc!=SQLITE_NOTFOUND ) p->rc = rc; if( p->eStage>=RBU_STAGE_MOVE ){ bOpen = 1; }else{ RbuState *pState = rbuLoadState(p); if( pState ){ bOpen = (pState->eStage>RBU_STAGE_MOVE); rbuFreeState(pState); } } if( bOpen ) p->dbMain = rbuOpenDbhandle(p, p->zRbu, p->nRbu<=1); } p->eStage = 0; if( p->rc==SQLITE_OK && p->dbMain==0 ){ if( !rbuIsVacuum(p) ){ p->dbMain = rbuOpenDbhandle(p, p->zTarget, 1); }else if( p->pRbuFd->pWalFd ){ p->rc = SQLITE_ERROR; p->zErrmsg = sqlite3_mprintf("cannot vacuum wal mode database"); }else{ char *zTarget; char *zExtra = 0; if( strlen(p->zRbu)>=5 && 0==memcmp("file:", p->zRbu, 5) ){ zExtra = &p->zRbu[5]; while( *zExtra ){ if( *zExtra++=='?' ) break; } if( *zExtra=='\0' ) zExtra = 0; } zTarget = sqlite3_mprintf("file:%s-vacuum?rbu_memory=1%s%s", sqlite3_db_filename(p->dbRbu, "main"), (zExtra==0 ? "" : "&"), (zExtra==0 ? "" : zExtra) ); if( zTarget==0 ){ p->rc = SQLITE_NOMEM; return; } p->dbMain = rbuOpenDbhandle(p, zTarget, p->nRbu<=1); sqlite3_free(zTarget); } } if( p->rc==SQLITE_OK ){ p->rc = sqlite3_create_function(p->dbMain, "rbu_tmp_insert", -1, SQLITE_UTF8, (void*)p, rbuTmpInsertFunc, 0, 0 ); } if( p->rc==SQLITE_OK ){ p->rc = sqlite3_create_function(p->dbMain, "rbu_fossil_delta", 2, SQLITE_UTF8, 0, rbuFossilDeltaFunc, 0, 0 ); } if( p->rc==SQLITE_OK ){ p->rc = sqlite3_create_function(p->dbRbu, "rbu_target_name", -1, SQLITE_UTF8, (void*)p, rbuTargetNameFunc, 0, 0 ); } if( p->rc==SQLITE_OK ){ p->rc = sqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_RBU, (void*)p); } rbuMPrintfExec(p, p->dbMain, "SELECT * FROM sqlite_master"); /* Mark the database file just opened as an RBU target database. If ** this call returns SQLITE_NOTFOUND, then the RBU vfs is not in use. ** This is an error. */ if( p->rc==SQLITE_OK ){ p->rc = sqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_RBU, (void*)p); } if( p->rc==SQLITE_NOTFOUND ){ p->rc = SQLITE_ERROR; p->zErrmsg = sqlite3_mprintf("rbu vfs not found"); } } /* ** This routine is a copy of the sqlite3FileSuffix3() routine from the core. ** It is a no-op unless SQLITE_ENABLE_8_3_NAMES is defined. ** ** If SQLITE_ENABLE_8_3_NAMES is set at compile-time and if the database ** filename in zBaseFilename is a URI with the "8_3_names=1" parameter and ** if filename in z[] has a suffix (a.k.a. "extension") that is longer than ** three characters, then shorten the suffix on z[] to be the last three ** characters of the original suffix. ** ** If SQLITE_ENABLE_8_3_NAMES is set to 2 at compile-time, then always ** do the suffix shortening regardless of URI parameter. ** ** Examples: ** ** test.db-journal => test.nal ** test.db-wal => test.wal ** test.db-shm => test.shm ** test.db-mj7f3319fa => test.9fa */ static void rbuFileSuffix3(const char *zBase, char *z){ #ifdef SQLITE_ENABLE_8_3_NAMES #if SQLITE_ENABLE_8_3_NAMES<2 if( sqlite3_uri_boolean(zBase, "8_3_names", 0) ) #endif { int i, sz; sz = (int)strlen(z)&0xffffff; for(i=sz-1; i>0 && z[i]!='/' && z[i]!='.'; i--){} if( z[i]=='.' && sz>i+4 ) memmove(&z[i+1], &z[sz-3], 4); } #endif } /* ** Return the current wal-index header checksum for the target database ** as a 64-bit integer. ** ** The checksum is store in the first page of xShmMap memory as an 8-byte ** blob starting at byte offset 40. */ static i64 rbuShmChecksum(sqlite3rbu *p){ i64 iRet = 0; if( p->rc==SQLITE_OK ){ sqlite3_file *pDb = p->pTargetFd->pReal; u32 volatile *ptr; p->rc = pDb->pMethods->xShmMap(pDb, 0, 32*1024, 0, (void volatile**)&ptr); if( p->rc==SQLITE_OK ){ iRet = ((i64)ptr[10] << 32) + ptr[11]; } } return iRet; } /* ** This function is called as part of initializing or reinitializing an ** incremental checkpoint. ** ** It populates the sqlite3rbu.aFrame[] array with the set of ** (wal frame -> db page) copy operations required to checkpoint the ** current wal file, and obtains the set of shm locks required to safely ** perform the copy operations directly on the file-system. ** ** If argument pState is not NULL, then the incremental checkpoint is ** being resumed. In this case, if the checksum of the wal-index-header ** following recovery is not the same as the checksum saved in the RbuState ** object, then the rbu handle is set to DONE state. This occurs if some ** other client appends a transaction to the wal file in the middle of ** an incremental checkpoint. */ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){ /* If pState is NULL, then the wal file may not have been opened and ** recovered. Running a read-statement here to ensure that doing so ** does not interfere with the "capture" process below. */ if( pState==0 ){ p->eStage = 0; if( p->rc==SQLITE_OK ){ p->rc = sqlite3_exec(p->dbMain, "SELECT * FROM sqlite_master", 0, 0, 0); } } /* Assuming no error has occurred, run a "restart" checkpoint with the ** sqlite3rbu.eStage variable set to CAPTURE. This turns on the following ** special behaviour in the rbu VFS: ** ** * If the exclusive shm WRITER or READ0 lock cannot be obtained, ** the checkpoint fails with SQLITE_BUSY (normally SQLite would ** proceed with running a passive checkpoint instead of failing). ** ** * Attempts to read from the *-wal file or write to the database file ** do not perform any IO. Instead, the frame/page combinations that ** would be read/written are recorded in the sqlite3rbu.aFrame[] ** array. ** ** * Calls to xShmLock(UNLOCK) to release the exclusive shm WRITER, ** READ0 and CHECKPOINT locks taken as part of the checkpoint are ** no-ops. These locks will not be released until the connection ** is closed. ** ** * Attempting to xSync() the database file causes an SQLITE_INTERNAL ** error. ** ** As a result, unless an error (i.e. OOM or SQLITE_BUSY) occurs, the ** checkpoint below fails with SQLITE_INTERNAL, and leaves the aFrame[] ** array populated with a set of (frame -> page) mappings. Because the ** WRITER, CHECKPOINT and READ0 locks are still held, it is safe to copy ** data from the wal file into the database file according to the ** contents of aFrame[]. */ if( p->rc==SQLITE_OK ){ int rc2; p->eStage = RBU_STAGE_CAPTURE; rc2 = sqlite3_exec(p->dbMain, "PRAGMA main.wal_checkpoint=restart", 0, 0,0); if( rc2!=SQLITE_INTERNAL ) p->rc = rc2; } if( p->rc==SQLITE_OK ){ p->eStage = RBU_STAGE_CKPT; p->nStep = (pState ? pState->nRow : 0); p->aBuf = rbuMalloc(p, p->pgsz); p->iWalCksum = rbuShmChecksum(p); } if( p->rc==SQLITE_OK && pState && pState->iWalCksum!=p->iWalCksum ){ p->rc = SQLITE_DONE; p->eStage = RBU_STAGE_DONE; } } /* ** Called when iAmt bytes are read from offset iOff of the wal file while ** the rbu object is in capture mode. Record the frame number of the frame ** being read in the aFrame[] array. */ static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){ const u32 mReq = (1<mLock!=mReq ){ pRbu->rc = SQLITE_BUSY; return SQLITE_INTERNAL; } pRbu->pgsz = iAmt; if( pRbu->nFrame==pRbu->nFrameAlloc ){ int nNew = (pRbu->nFrameAlloc ? pRbu->nFrameAlloc : 64) * 2; RbuFrame *aNew; aNew = (RbuFrame*)sqlite3_realloc64(pRbu->aFrame, nNew * sizeof(RbuFrame)); if( aNew==0 ) return SQLITE_NOMEM; pRbu->aFrame = aNew; pRbu->nFrameAlloc = nNew; } iFrame = (u32)((iOff-32) / (i64)(iAmt+24)) + 1; if( pRbu->iMaxFrame